组件性能优化

PureRender

Pure局势组件满足纯函数的条件。react-addons-pure-render-mixin.其原理为重新实现了shouldComponentUpdate生命周期方法。

Immutable

react-addons-perf

官方提供的插件,通过Perf.start()和Perf.stop()两个API设置开始和结束的状态来作分析。

动画

react-smooth

three.js

react-motion

自动化测试

Jest

react-addons-test-utils

常用方法如下:
Simulate.{eventName} (DomElement element, [object eventData])模拟触发事件

renderIntoDocument(ReactElement instance)渲染React组件到文档中,这里的文档节点由JSDOM提供

findRenderedDomComponentWithClass(ReactComponent tree, string className)从渲染的DOM树中查找含有class的节点
findRenderedDOMComponentWithTag(ReactComponent tree, function componentClass)从渲染的DOM树中找到指定的节点
##Enzyme

生命周期

为了查看组件生命周期的执行顺序,推荐使用react-lifecycle mixin。将此mixin添加到需要观察的组件中,当任何生命周期的方法被调用时,就能在控制台观察到对应的生命周期的调用时状态。

  • 当首次挂载组件时,按顺序执行getDefaultProps,getInitialState,componentWillMount,render和componentDidMount
  • 当卸载组件时,执行componentWillUnmount
  • 当重新挂载组件时,此时按顺序执行getInitialState,componentWillMount,render和componentDidMount,但并不执行getDefaultProps
  • 当再次渲染组件时,组件接收到更新状态,此时按顺序执行componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate,render,componentDidUpdate

Redux应用实例

mkdir redux-blog && cd redux-blog
npm install --save react react-dom redux react-router react-redux react-router-redux whatwg-fetch

高阶reducer

reducer的复用

当A模块调用loadData来分发相应的action时,A和B的reducer都会处理这个action,然后A和B的内容就完全一致了。
在一个应用中,不同模块间的actionType必须时全局唯一的。
使用高阶reducer来添加前缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function generateReducer(prefix, state) {
const LOAD_DATA = prefix + "LOAD_DATA";
const initialState = { ...state, ...};

return function reducer(state = initialState, action) {
switch (action.type) {
case LOAD_DATA:
return {
...state,
data: action.payload,
};
default:
return state;
}
}
}

redux-undo 实现撤销重做

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
function undoable(reducer) {
const initialState = {
//记录过去的state
past: [],
//以一个空的action调用reducer来产生当前值的初始值
prensent: reducer(undefined, {}),
//记录后续的state
future: []
};

return function (state = initialState, action) {
const { past, present, future } = state;

switch (action.type) {
case '@@redux-undo/UNDO':
const previous = past[past.length - 1];
const newPast = past.slice(0, past.length - 1);

return {
past: newPast,
present: previous,
future: [ present, ...future]
};
case '@@redux-undo/REDO':
const next = future[0];
const newFuture = future.slice(1);

return {
past: [ ...past, present ],
present: next,
future: newFuture
};
default:
// 将其他 action 委托给原始的 reducer 处理
const newPresent = reducer(present, action);

if(present === newPresent) {
return state;
}

return {
past: [...past, present],
present: newPresent,
future: []
};
}
}
}

对任意 reducer 封装
import { createStore } from "redux";

function todos(state = [], action) {
switch (action.type) {
case "ADD_TODO":
// ...
}
}

const undoableTodos = undoable(todos);
const store = createStore(undoableTodos);

store.dispatch({
type: 'ADD_TODO',
text: 'Use Redux'
});

store.dispatch({
type: 'ADD_TODO',
text: 'Implement Undo'
})

store.dispatch({
type: '@@redux-undo/UNDO'
})

redux-form-utils减少创建表单的冗余代码

redux-form-utils提供了两个方便的工具函数,createForm(config)和bindRedux(config),前者可以当作decorate使用,传入表单的配置,自动为被装饰的组件添加表单相关的props;而后者则生成与Redux应用相关的reducer、initialState、和actionCreator等。

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
41
42
43
44
45
46
47
48
// components/MyForm.js
import React, { Component } from "react";
import { createForm } from 'redux-form-utils';

@createForm({
form: 'my-form',
fields: ['name', 'address', 'gender'],
})
class Form extends Component {
render() {
const { name, address, gender } = this.props.fields;
return (
<form className="form">
<input name="name" {...name} />
<input name="address" {...address} />
<select {...gender}>
<option value="male" />
<option value="female" />
</select>
</form>
)
}
}

// reducer/MyForm.js
import { bindRedux } from 'redux-form-utils';

const { state: formState, reducer: formReducer } = bindRedux({
form: 'my-form',
fields: ['name', 'address', 'gender'],
});

const initialState = {
foo: 1,
bar: 2,
...formState
};

function myReducer(state = initialState, action) {
switch (action.type) {
case 'MY_ACTION': {
// ...
}

default:
return formReducer(state, action);
}
}

redux-form 表单的异步验证

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
41
42
43
44
45
46
import { createStore, combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';

const reducers = {
// 其他的 reducer ...
// 所有表单相关的 reducer 挂载在 form 下
form: formReducer
}

const reducer = combineReducers(reducers);
const store = createStore(reducer);
// 完成了基本的配置后,现在看看 reudx-form 如何帮我们完成表单验证功能

import React, { Component } from 'react';
import { reduxForm } from 'redux-form';

function validate(values) {
if (values.name == null || vaules.name === '') {
reutrn {
name: '请填写名称',
}
}
}

@reduxForm({
form: 'my-form',
fields: ['name', 'address', 'gender'],
validate
})
class Form extends Component {
render() {
const { name, address, gender } = this.props.fields;
return (
<form className="form">
<input name="name" {...name} />
{ name.error && <span>{name.error}</span> }
<input name="address" {...address} />
<select {...gender}>
<option value="male" />
<option value="female" />
</select>
<button type="submit">提交</button>
</form>
)
}
}

为现有模块引入表单功能

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
// 假设 reducer 已经写好了如下逻辑:
const initialState = {
firstName: '',
lastName: '',
fullName: ''
};

function myReducer(state = initialState, action) {
switch (actions.type) {
case 'GET_FULL_NAME':
return {
...state,
fullName: `${state.firstName} ${state.lastName}`,
};
default:
return state;
}
}

// 引入一个高阶reducer----modeled
import { modeled } from 'react-redux-form';

const intialState = /* ... */
function myReducer(...) {
/* ... */
}

// 为reducer提供处理表单的能力
const myModeledReducer = modeled(myReducer, 'my');

export default myModelReducer;

// 当想要修改定义在这个 reducer 里的状态,则需要用到 react-redux-form 的actions.change 方法:
import { actions } from 'react-redux-form';
let state = { firstName: 'Daniel', lastName: 'Walker'};

let newState = myModeledReducer(state, actions.change('my.firstName', 'Johnnie'));
// => { firstName: 'Johnnie', lastName: 'Walker' }

Reselect

store中的数据成为源数据,要求在reducer中保持简单,不做处理,会在connect的mapStateToProps中把state中的数据做一些转换和合并,生成的衍生数据通过props提供给组件。把这个产生衍生数据的方法,称为selector。

这里引入了reselect库,在connect中对radioGroup做如下转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { creatorSelector } from 'selector';
const getRadioGroup = state => state.radioGroup;
const transformRadioGroup = createSelector(
getRadioGroup,
(radioGroup) => ({
radioGroupCompute: Object.keys(radioGroup.entities)
.map(key => ({
...radioGroup[key],
selected: key === String(radioGroup.result)
}))
})
)
@connect(transformRadioGroup,mapDispatchToProps)

服务器端渲染