React小结

我们项目目前使用的react脚手架是create-react-app,官方脚手架,基本配置依赖已经完成,只需要下载安装我们接下来项目所需要的别的依赖包就可以了,create构建项目过程:

安装

  • 安装nodejs npm 基础环境
  • 运用npm 安装create-react-app
  • 利用create-react-app构建项目
  • 启动项目

  • npm install -g create-react-app

  • create-react-app my-app
  • cd my-app
  • my-app start

我本地现在用的是webstorm来启动工程,直接在webstrom的终端输入
npm start 就可以启动项目,启动成功之后浏览器会自动打开,如果端口占用,会提示切换到端口+1的端口,如,3001;每一次修改工程中的代码,都会自动刷新页面。

项目启动后,进入工程,查看src文件夹中的index.js,index.js为工程的入口文件,组件命名全部以.js为后缀。所用到的依赖包,都在package.json中有版本信息,新增依赖包,通过npm install –save xxx 来添加

代码书写方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React,{Component} from 'react';
import {Layout,BackTop} from 'antd'
import './index.css';
import Footer from '../common/bottom'
import Head from "./header";
const { Sider,Content} = Layout;
class Container extends Component{
render() {
return (
<Layout className="containAll">
<Head/>
<Footer/>
<BackTop/>
</Layout>
);
}
}
export default Container;

在编写组件时,必须引入react依赖。其余的是我们需要什么才引入什么,如,Head,Footer这都是我们在页面上需要展示的组件,通过ES6的语法糖import引入,来按需加载。
一个组件的js文件中,必须有的是​

1
2
3
4
5
6
7
8
9
10
11
import React,{Component} from 'react';
class Container extends Component{
render() {
return (
<Layout className="containAll">
<Head/>
</Layout>
);
}
}
export default Container;

这是基本结构。也可以是这样的形式

1
2
3
4
5
6
7
8
9
10
import React,{Component} from 'react';
export default class Bottom extends React.Component{
render() {
return (
<Layout className="containAll">
<Head/>
</Layout>
);
}
}

我们一般编写代码是在开发环境,代码部署到服务器时都是运用的自动构建工具,将代码自动打包,转化之后的代码,我们叫产品环境。

用npm run build 命令打包

需要先修改package.json文件,添加上这个,来解决路径问题,之后再执行run build 命令

1
"homepage": ".",

打包之后的文件,在工程中的build文件夹中,进入build文件夹,双击index.html就可以看到我们之前编辑的页面。

用npm run eject 实现弹射,自定义我们自己的需求

run eject 命令是一个不可逆的操作,但是eject只是将webpack的一些配置文件弹射出来,让我们来改变配置文件,实现我们自己需要的功能配置,eject之后,工程中就会多出来两个文件就夹,一个是scripts,还有一个是config文件夹,我们在这次的项目中只是修改了三个地方。

1.paths.js 文件

1
2
3
4
5
6
function getServedPath(appPackageJson) {
const publicUrl = getPublicUrl(appPackageJson);
const servedUrl =
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : './');
return ensureSlash(servedUrl, true);
}

这个地方修改的是将pathname:’/‘,修改为’./‘;

1
appBuild: resolveApp('dashboard'),

这个地方修改的是将原来的resolveApp(‘build’)替换成了dashboard,目的是执行完build命令之后将build文件夹的名字换为dashboard;

2.webpack.config.dev.js 文件

修改这个文件的目的是让其能够将antd框架中的组件实现按需加载,避免全部加载过大,影响渲染速度,修改之后,需要重启才能看到生效,未修改之前项目页面的debug会有一个错误提示信息。

1
2
3
4
5
6
7
loader: require.resolve('babel-loader'),
options: {
plugins: [
['import', [{ libraryName: 'antd', style: 'css' }]],
],
cacheDirectory: true,
},

做的修改是在options中直接加上

1
2
3
plugins: [
['import', [{ libraryName: 'antd', style: 'css' }]],
],

3.webpack.config.prod.js 文件

这个文件的修改跟dev文件的修改是一样的,也是在bable-loader直接添加上

1
2
3
plugins: [
['import', [{ libraryName: 'antd', style: 'css' }]],
],

以上基本项目安装及配置

基础语法

初始化及周期函数

1
2
3
4
5
6
7
8
componentDidUpdate //界面刷新调用,钩子函数
componentDidMount //刚进入界面时候调用,钩子函数
constructor(props) {
super(props);//初始化state
}
componentWillUnmount() {
}//界面渲染结束后,一般我们在这个函数中来清除定时器,钩子函数

我们一般是通过this.state来进行初始化赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
constructor(props){
super(props)
this.state = {
theme: 'light',
current: 'home',
mode: 'horizontal',
username:'Logon',
flags:true,
linkData:[],
ITCODEtips:''
}
}
static defaultProps = {
className: "layout",
isDraggable: true,
isResizable: true,
items: 3,
rowHeight: 115,
margin:[15,30],
onLayoutChange: function() {},
cols: {lg: 12, md: 10, sm: 6, xs: 4, xxs: 2},
initialLayout: function () {}
};

样式

  • 可以通过引入样式文件

    1
    import './index.css';
  • 可以在组件内写样式,不过这里一切皆对象,在样式表文件中的border-right写法要转换成驼峰是写法borderRight,多个样式以逗号隔开, 在react中class要写成className

    1
    2
    3
    4
    <div className="text">
    <div style={{width:"50%"}}>***</div>
    <div style={{borderRight:'0px'}}>***</div>
    <div style={{float:'left',width:'16.667%',padding:'0 15px 0 0'}}>
  • 可以通过在上面定义样式,引入到行内

    1
    2
    3
    4
    5
    6
    let backAndTextColor = {
    backgroundColor:'red',
    color:'white',
    fontSize:40
    };
    <div style={backAndTextColor}>***</div>

图片引入

1
2
import logo from '../imgs/logo_red.png'
<img src={logo} alt=""/>

背景图片

1
<div style={{background:`url(http://myludp.lenovo.com${menulist.img}) center 10px no-repeat`}}></div>

图标引入

我们项目中送到的图标有两种,一种是直接在antd框架中的图标,一种是我们自定义的图标,由iconfont.com生成的字体图标
1.antd框架中的图标引用方法

1
2
3
4
5
6
import {Menu,Icon,Switch} from 'antd'
<Menu.Item key={subMenu.url}>
<Link to={`/${subMenu.url}`}>
<Icon type={subMenu.icon} /><span className="nav-text">{subMenu.name}</span>
</Link>
</Menu.Item>

type中写的就是我们需要的图标类型

2.我们自定义的图标引用方法

1
2
3
import '../fonts/iconfont.css'
<SubMenu title={<span><i className="iconfont icon-lianjie" style={{fontSize:'1rem'}}>

只需在 i 标签中添加上两个类,iconfont(图标基本样式) 和 icon-lianjie (图标类型)

修改值

我们不能直接修改State的值,要通过修改setState的值来实现改变值
如:

1
2
3
4
5
6
7
8
9
constructor(props) {
super(props)
this.state = {
timer: 0
}
}
tick = () => {
this.setState({ timer:this.state.timer + 1 });
}

我们经常在代码中使用三元运算来进行简单的状态切换

1
2
3
4
5
changeTheme = (value) => {
this.setState({
theme: value ? 'dark' : 'light',
});
}

传值:

1
<CardsLayout cardsmenu={cardsmenu}/>

{cardmenu}这里的cardmenu 是一个数组
cardmenu是个属性(就跟我们在原来html中设置 data-id差不多),在CardsLayout中调用改数组的值通过props来调用

1
2
3
4
5
6
constructor(props){
super(props)
this.state={
cardsmenu:props.cardsmenu,
}
}

就是这样的用法,props.cardsmenu来调用之前我们赋予他的数组

函数

我们在项目中写函数方法的方式有

1. 箭头函数
1
2
3
4
5
6
7
handleClick = (e) => {
if(e.key==="logOut"){
this.setState({
current: 'home',
});
}
}

2.普通函数

1
2
3
4
handleClick(){
loginState(this)
LinkData(this)
}

3.写在render外面的函数

function loginState(){}

遍历取值

我们项目中主要使用的遍历方法是通过map来遍历

1
2
3
4
5
6
7
{linkItem.list.map(menulist =>(
<Menu.Item key={menulist.index} className='entry-item'>
<a href={menulist.url}>
<p style={{background:`url(http://myludp.lenovo.com${menulist.img}) center 10px no-repeat`}}>{menulist.name}</p>
</a>
</Menu.Item>
))}

要注意的是,每个遍历出来的对象都要有一个你能够唯一识别的key,从而实现哪个值变化了根据key来加载改变的值,避免重复渲染,提高渲染速度。

fetch数据

我们之前都是用ajax来获取服务端的数据,进行数据交互,现在我们用的是fecth来获取数据
首先要做的就是在install fetch依赖包,我们项目上使用的是 fetch和whatwg-fetch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function LinkData(e){
fetch('http://ludp.lenovo.com/ludp/api/quicklinks/')
.then(function(response) {
return response.json();
})
.then(function(json) {
e.setState({
linkData:json
})
})
.catch(function(error) {
console.log('Request failed', error)
});
}

项目框架

我们本次的项目框架是antd,首先要做的就是现在安装antd依赖包

1
2
npm install antd --save
npm install babel-plugin-import --save

安装好之后就可以按需加载antd了(前提是完成上面webpack的配置)

antd的用法

参考:https://ant.design/docs/react/introduce-cn

路由 react-router

我们在项目中现在使用的react-touter版本为v4,最新的,
在最新版本中我们是这样来设置我们的路由的

1
2
3
4
5
6
7
8
9
10
11
import {HashRouter} from 'react-router-dom'
import { Route } from 'react-router'
export default class Routes extends Component{
render(){
return(
<HashRouter history={customHistory}>
<Route path='/' component={index}/>
</HashRouter>
)
}
}

我们运用的是hashRouter,这个是带有锚点的路由 http://localhost:3000/#/ ,
在我们本地环境中就可以直接通过路由跳转,如果我们使用browserRouter的话就需要配置服务器环境才能正常访问页面,所以我们暂时先采用的是hashRouter,另外我们所用到的路由方法一般都是从react-router-dom里面引用的。

1
2
3
4
5
6
7
8
9
10
11
import {Route} from 'react-router-dom'
export default class Contents extends React.Component {
render() {
return (
<Content className="content">
<Route exact path='/' component={Container}/>
<Route exact path='/home' component={Container}/>
</Content>
);
}
}

exact属性是严格匹配才会跳转,path就是我们的跳转路径,也就是链接后面的路径,component是跳转时要加载的组件,可以同时使用多个route

1
2
3
4
import {Link} from 'react-router-dom'
<Link to={`/${subMenu.url}`}>
<Icon type={subMenu.icon} /><span className="nav-text">{subMenu.name} </span>
</Link>

我们在项目中的跳转都是通过Link组件来代替我们之前的a标签,to来制定跳转路径,这个只是需要我们用到路由跳转的是时候才会用,一般的跳转链接还是可以用 a 标签的。

主要依赖库 react-grid-layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<ResponsiveReactGridLayout
breakpoints={{ lg: 1000, md: 830, sm: 640, xs: 400, xxs: 0 }}
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
onBreakpointChange={this.onBreakpointChange}
onLayoutChange={this.onLayoutChange}
measureBeforeMount={true}
useCSSTransforms={this.state.mounted}
{...this.props}>
{
_.map(this.state.layouts, function (l) {
var i = l.adds ? '+' : l.i;
return (
<div key={i} data-grid={l}>
{l.data.data!==undefined?<l.components data={l.data.data}/>
:<l.components data={l.data}/>}
<delete className="settingStyle"
onClick={that.onRemoveItem.bind(that,i,l.data)} >X</delete>
</div>)
})
}
</ResponsiveReactGridLayout>

在这个里面控制布局的区域大小的是breakpoints属性,里面的数值设置就是我们的区域大小
我们要做的就是将每一个子组件的位置设定好

1
2
3
4
5
6
7
8
9
10
11
12
13
layouts: _.values(props.cardsmenu).map(function (item) {
return {
data:item,
x: item.x,
y: item.y,
w: item.w,
h: item.h,
maxH:item.maxH,
i: item.i.toString(),
static: item.static,
components:item.components
};
}),

x,y,w,h是必须的,如果没有设置则都会在0,0的位置。
在组件中,通过下面的方式来根据屏幕节点的改变,实现自适应布局

1
2
3
4
5
6
7
8
9
onBreakpointChange = (breakpoint,cols) => {
this.setState({
currentBreakpoint: breakpoint,
cols: cols
});
};
onLayoutChange = (layout,layouts) => {
this.props.onLayoutChange(layout,layouts);
};

删除

1
2
3
4
5
6
7
onRemoveItem(i,data){
this.setState({
layouts:_.reject(this.state.layouts, {i: i}),
cardsmenu:this.state.cardsmenu.concat(data)
})
}
<delete className="settingStyle" onClick={that.onRemoveItem.bind(that,i,l.data)} >X</delete>

新增

1
2
3
4
5
6
7
8
onAddItem(newLayout) {
this.setState({
layouts: this.state.layouts.concat(newLayout),
newCounter: this.state.newCounter + 1,
adds:true
});
}
<AddList data={this.state.cardsmenu} onadd={this.onAddItem.bind(this)}/>

在AddList组件中的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ClickADD(data,i){
var Dragprama = document.getElementsByClassName('addListItem');
Dragprama[i].classList.add('ClikedItem');
var newLayout = {};
newLayout.data = data;
newLayout.x = (0+i)*3;
newLayout.y=1;
newLayout.w=3;
newLayout.h=1;
newLayout.static=false;
newLayout.maxH = 1;
newLayout.i = (4+i).toString();
newLayout.components=CardsListII;
this.props.onadd(newLayout);
}
<div key={i} className="addListItem" draggable="true" onDragEnd={that.ClickADD.bind(that,item,i)}>
<i className="iconfont">{item.icon}</i>
<h5 style={{margin:"0px",lineHeight:"2"}}>{item.text}</h5>
</div>

遗留问题

目前我只是实现了一个组件的两种样式的显示,可以增加和删除,现在删除之后可以在新增的列表中添加,但不还未实现去重。新增可以新增,但是在重新切换新增按钮之后,之前已经拖拽过的项状态没有保存下来.