react 进阶之路
eslint
- 本地安装 eslint
npm install eslint --save-dev
npm install –save-dev babel-eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-react eslint-plugin-jsx-a11y
- 创建基础的 .eslintrc.js
eslint --fix
1 | //.eslintrc.js |
- package.json
1 | "lint": "eslint --ext .js --ext .jsx src", |
- pre-commit
如果项目使用了 git,可以通过使用 pre-commit 钩子在每次提交前检测,如果检测失败则禁止提交。可以在很大一定程度上保证代码质量。
npm install pre-commit –save-dev`
1 | //package.json |
属性校验和默认属性
1 | PostItem.propTypes = { |
事件处理
- 在constructor中绑定this.handleClick=this.handleClick.bind(this)
- 可传入参数onClick={this.handleClick.bin(this, item)}
- 使用ES7属性初始化语法onClick={this.handleClick}
1
2
3
4
5
6handleClick = (event) => {
const number = ++this.state.number;
this.setState({
number:number
})
}
表单
- 文本框
1
2
3
4
5handleChange = (event) => {
const target = event.target;
this.setState({[target.name]: target.value})
}
<input type="text" name="name" value={this.state.name} onChange={this.handleChange}>
ref非受控组件
- 在Dom元素上使用ref,回调会接收当前DOM元素作为参数
<input defaultValue="something" type="text" ref={(input) => this.input = input} />
- 在组件上使用ref,回调会接收当前组件的实例(类组件可以,函数组件不行)
- 父组件要获取子组件的DOM元素而不是实例
1
2
3
4
5
6//Parent 使用自定义属性inputRef
<Children
inputRef={el => this.inputElement = el}
/>
//子组件使用父子间传递的inputRef,为input的ref赋值
<input ref={props.inputRef} />
state修改
注意,不要使用push,pop,shift,unshift,splice等方法,因为这些方法会修改原数组,而concat,slice,filter会返回一个新的数组。
数组
- 数组类型状态books,新增一本书
1
2
3
4
5
6
7
8//方法一 concat 创建新数组
this.setState(preState => {
books: preState.books.concat(['React Guide']);
})
//方法二 ES6 spread syntax
this.setState(preState => {
books: [...preState.books, 'React Guide'];
}) - 从books中截取部分元素作为新状态时,可以使用数组的slice方法:
1
2
3this.setState(preState => ({
books: preState.books.slice(1,3);
})) - 从books中过滤部分元素后,作为新状态时,可以使用数组的filter方法:
1
2
3
4
5this.setState(preState => ({
books: preState.books.filter(item => {
return item !=='React';
});
}))状态类型是普通对象
- 使用es6 Object.assgin方法:
1
2
3this.setState(preState => ({
owner: Object.assign({}, preState.owner, {name: 'Jason'});
})) - 使用对象扩展语法
1
2
3this.setState(preState => ({
owner: {...preState.owner, name: 'Jason'};
}))组件通信
- 组件挂载通信
componentDidMount
- 组件更新阶段通信
1
2
3
4
5
6
7
8
9
10componentWillReceiveProps(nextProps) {
var that = this;
if(nextProps.category !== this.props.category) {
fetch('...').then(function(response) {
response.json().then(function(data) {
that.setState({user: data})
})
})
}
} - context 多层级自动传递
让任意层级的子组件都可以获取父组件中的状态和方法1
2
3
4
5
6
7
8
9
10
11
12//父 UserListContainer
getChildContext() {
return {onAddUser: this.handleAddUser};
}
UserListContainer.childContextTypes = {
onAddUser: PropTypes.func
}
//子 UserAdd
UserAdd.contextTypes {
onAddUser: PropTypes.func
}
使用this.context.onAddUser(this.state.newUser)
性能优化
避免不必要的组件渲染
- shouldComponentUpdate
1
2
3
4
5
6shouldComponentUpdate(nextProps, nextState) {
if(nextProps.item === this.props.item) {
return false;
}
return true;
} - class xxx extends React.PureComponent 代替手写shouldComponentUpdate
使用key
高阶组件
const EnhancedComponent = higherOrderComponent(WrappedComponent);
主要分为以下四种情况:
- 操纵props,进行拦截props并进行修改
1
2
3
4
5
6
7
8
9
10
11
12funciton withPersistentData(WrappedComponent) {
return class extends Component {
componentWillMount() {
let data = localStorage.getItem("data");
this.setState({data});
}
render() {
//{...this.props}把传递给当前组件的属性继续传递给包装的组件
return <WrappedComponent data={this.state.data} {...this.props} />
}
}
} - 通过ref访问组件实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function withRef(wrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.someMethod = this.someMethod.bind(this);
}
}
someMethod() {
this.wrappedInstance.someMethodInWrappedComponent();
}
render() {
//为包装组件添加ref属性,从而获取该组件实例并赋值给this.wrappedInstance
return <WrappedComponent ref={(instance) => {this.wrappedInstance = instance}} {...this.props}>
}
} - 组件状态提升
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26function withControlledState(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
this.handleValueChange = this.handleValueChange.bind(this);
}
handleValueChange(event) {
this.setState({
value: event.target.value
});
}
render() {
//newProps保存受控组件需要使用的属性和事件处理函数
const newProps = {
controlledProps: {
value: this.state.value,
onChange: this.handleValueChange
}
};
return <WrappedComponent {...this.props}{...newProps}>
}
}
} - 用其他元素包装组件
1
2
3
4
5
6
7
8
9
10
11function withRedBackground(WrappedComponent) {
return class extends React.Component {
render() {
return (
<div style={{backgroundColor: 'red'}}>
<WrappedComponent {...this.props}/>
</div>
)
}
}
}参数传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import React, {Component} from 'react'
function withPersistentData(WrappedComponent, key) {
return class extends Component {
componentWillMount() {
let data = localStorage.getItem(key);
this.setState({data});
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />
}
}
}
const MyComponentWithPersistentData = withPersistentData(MyComponent, 'data');
//新版本的参数传递为HOC(...params)(WrappedComponent)
//可改写为
function withPersistentData = (key) => (WrappedComponent) => {
......
}
const MyComponentWithPersistentData = withPersistentData('data')(MyComponent)
react-router
npm install react-router-dom
Redux
- 唯一数据源
- 保持应用状态只读,但需要修改应用状态时,必须发送一个action
- 引用状态的改变通过纯函数完成,actio表明修改应用状态的意图,真正对应用状态做修改的是reducer
action
ation是Redux中信息的载体,是store唯一的信息来源。把action发送给store必须通过store的dispatch方法。action是普通的JavaScrpit对象,但每个action必须有一个type属性描述action的类型,type一般被定义为字符串常量。除type外,action的结构完全由自己决定,但应该确保action的结构能清晰地描述实际业务场景。一般通过action creator创建action, action creator是返回action的函数。reducer
reducer根据action做出响应,决定如何修改应用的状态state。reducer是一个纯函数,接受两个参数,当前的state和action。但应用变得复杂,这个reducer可以拆分出多个reducer,每个reducer处理state中的部分状态。redux还提供了combineReducers函数,用于合并多个reducer。store
store是action和reducer之间的桥梁,主要负责以下几个工作: - 保存应用状态
- 通过方法getState()访问应用状态
- 通过方法dispatch(action)发送更新状态的意图
- 通过方法subscribe(listener)注册监听函数、监听应用状态的改变
一个Redux应用只有一个store,store保存了唯一数据源。store通过createStore()函数创建,创建时需要传递reducer作为参数。
性能优化
React Router和Redux一起使用会造成重复渲染,可以使用高阶组件
1 | export default function connectRoute(WrappedComponent) { |
Immutable.JS
- 保证数据的不可变
- 丰富的API,如Map,List,Set,Record
- 优化的性能
npm install immutable
引入1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40(1)使用Immuta.JS创建模块使用的初始state:
import Immutable from "immutable";
const initialState = Immutable.fromJS({
allIds: [],
byId: {}
});
posts模块中的每一个reducer设置state默认值的方式也需要修改:
const allIds = (state = initialState.get("allIds), action) => {
...
}
const byId = (state = initialState.get("byId"), action) => {
...
}
(2)当reducer接收到action时,reducer内部也需要通过Immutable.JS的API来修改state,代码如下
const allIds = (state = initialState.get("allIds"), action) => {
switch (action.type) {
case types.FETCH_ALL_POSTS:
// Immutable.List创建一个List类型的不可变对象
return Immutable.List(action.postIds);
case types.CREATE_POST:
// 使用unshift向List类型的state增加元素
return state.unshift(action.post.id);
default:
return state;
}
};
const byId = (state = initialState.get("byId"), action) => {
switch (action.type) {
case types.FETCH_ALL_POSTS:
// 使用merge合并获取到的post列表数据
return state.merge(action.posts);
case types.FETCH_POST:
case types.CREATE_POST:
case types.UPDATE_POST:
// 使用merge修改对应post的数据
return state.merge({ [action.post.id]: action.post});
default:
return state;
}
}npm install redux-immutable
Mobx
state
computed value
1 | // computed 一般用于接收一个函数 |
reaction
autorun
autorun会返回一个清楚函数disposer,但不再需要观察相关state变化时,可以调用disposer函数清楚副作用
1 | var numbers = observable([1,2,3]); |
reaction
rection(() => data, data => { sideEffect }, options?)
第一个函数返回被观测的state,这个返回值同时时第二个函数的输入值,只有第一个函数的返回值发生变化时,第二个函数才会被执行。
when
when(() => condition, () => { sideEffect })
condition会自动响应它使用的任何state的变化,但condition返回true时,函数sideEffect会执行,且执行一次。
action
action是用来修改state的函数,Mobx提供了API来包装action函数
action(fn)
@action classMethod
react中使用Mobx
API
observer/@observer
Provider 利用React的context机制把引用所需的state传递给子组件
injext 是一个高阶组件,和Provier结合使用,用于从Provider提供的state中选取所需数据,作为props传递给目标组件。inject("store1","store2")(observer(MyComponent))
@inject("store1","store2") @observer MyComponent
1 | import React, { Component } from "react"; |