组件性能优化
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提供了两个方便的工具函数,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); } }
|
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)
|
服务器端渲染