redux
Redux介绍
单一数据源
整个单页应用的 state 都被储存在store的内部,可以通过store.getState()获取,再作为props传给对应的组件。
state应该尽量少嵌套扁平化,通过id相互引用数据。
import { createStore } from 'redux'; const store = createStore(reducer); //stroe需要reducer计算新的state //createStore函数的简单实现 const createStore = (reducer) => { let state; let listeners = []; //返回store中的state tree的API const getState = () => state; //store.dispatch()会自动调用reducer生成新的state,并执行注册的回调函数 const dispatch = (action) => { //调用reducer更新state state = reducer(state, action); listeners.forEach(listener => listener()); }; //注册回调函数,通常是render或setState const subscribe = (listener) => { listeners.push(listener); return () => { //注销回调函数 listeners = listeners.filter(l => l !== listener); } }; dispatch({}); return { getState, dispatch, subscribe }; };
State 为只读
为防止出现race condition和deep equal的遍历,state应为只读,不能直接修改state,每个state都是新对象,而改变 state 的方法只有dispatch action。
使用纯函数reducer来修改state
reducer 是纯函数,接收prestate 和 action,返回新的 state。reducer必须保证同样的参数输入其输出一样,所以redux规定在Reducer函数里面不能直接改变 State,必须返回新的对象。在reducer中,不能改写参数,不能调用系统 I/O 的API,不能调用Date.now()或者Math.random()等不纯的方法,只是单纯的执行计算。例如 API 调用或路由跳转等有副作用的操作应该在 dispatch action 前发生。
function reducer(state, action) { switch (type) { case ADD_CHAT: //若state 是一个对象,不能直接修改state,而应使用如下方式返回新建的state return Object.assign({}, state, { newState }); // 或者return { ...state, ...newState }; default: return state; //如果action不属于该reducer负责,需要返回原state } } // 若State 是一个数组 function reducer(state, action) { return [...state, newItem]; //或者 return […state].concat(newItem); }
也可以使用immutable.js把 State 对象设成只读,这样改变State时只能通过生成一个新对象。
因为reducer函数的作用只是接收一个state计算后返回一个新的state,所以在应用中可以将多个分布在不同目录的reducer(函数)合成一个rootReducer,每个小的reducer只负责管理全局store中对应的一部分state:
export default function todoAppReducer(state = {}, action) { return { //只负责store中visibilityFilter和todos字段的state,别的state不可访问 visibilityFilter: visibilityFilter(state.visibilityFilter, action), todos: todosReducer(state.todos, action) //state.todos是大state中todosReducer负责的state } }
//等价于
import { combineReducers } from 'redux'; const todoAppReducer = combineReducers({ visibilityFilter, //store的state树中的节点名和reducer函数名相同 todos:todosReducer // 设置不同的对应名字 }) export default todoApp;
//combineReducer将子reducer合成一个大reducer。每个 reducer 根据 key 来筛选出 state 中的一部分数据并处理。最后这个生成的函数再将所有 reducer 的结果合并成一个大的对象,从而更新store。 const combineReducers = reducers => { return (state = {}, action) => { return Object.keys(reducers).reduce( (nextState, key) => { nextState[key] = reducers[key](state[key], action); return nextState; }, {} ); }; };
array.reduce(function(accumulator, currentValue, currentIndex, array), initialValue)
注意:若无initialValue,reduce 会跳过索引0从索引1开始执行 callback,若提供 initialValue,则从索引0开始。
如果需要在一个 Reducer 中访问另外一个 Reducer 负责的 state,这时需要自己写root Reducer或用reduce-reducers来控制。
state可以嵌套,对应的reducer亦可以嵌套,最终由combineReducer方法生成finalReducers,对应的state结构和reducer的嵌套结构相同。
action 是一个用于抽象描述已发生事件的普通对象,可以被序列化,是 store 数据的唯一来源。应尽量减少在 action 中传递的数据。
action Creator
View要发送多少种消息,就会有多少种 Action。可以定义一个Action Creato函数来生成 Action,不用每次都写样板代码。
const ADD_TODO = '添加 TODO'; function addTodo(text) { return { type: ADD_TODO, text } } store.dispatch( addTodo('Learn Redux') ); //store会自动调用reducer得到新的state store.dispatch( addTodo('Learn React) );
可以直接通过 store.dispatch() 调用 dispatch() 方法,也可以用 react-redux 包提供的 connect()方法调用,更方便。
@connect() es7的类修饰器
React-Redux
Redux 和 React 之间没有直接关系,可以在react中直接使用store的方法,也可以通过react-redux模块进行绑定。
容器组件 |
展示组件 |
|
Location |
最顶层,路由处理 |
中间和子组件,可复用 |
Aware of Redux |
是 |
否 |
读取数据 |
从 Redux 获取 state |
从 props 获取数据 |
修改数据 |
向 Redux 派发 actions |
从 props 调用回调函数 |
容器组件Container Components:使用redux的API进行数据管理,带有内部状态。
展示组件Presentational Components:不使用任何 Redux 的 API,通过props传递数据,负责UI呈现,没有状态(不使用this.state这个变量)。
connect方法
从 UI 组件生成容器组件(UI component => container container),将react (ui)和redux (store)绑定起来,可以在嵌套的不同层次使用。
/*container component *容器组件负责管理数据和业务逻辑,UI组件只显示视图 */ import { connect } from 'react-redux' import { setVisibilityFilter } from '../actions' import Link from '../components/Link' //mapStateToProps将state映射到UI组件的参数(props),返回一个对象,其属性代表 UI 组件的同名参数。 //mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,最后触发 UI 组件的重新渲染。如果不传入mapStateToProps参数,store的更新不会引起UI组件的更新。 const mapStateToProps = (state, ownProps) =>{ //state指reduce负责的那部分state,ownProps指容器父组件传入的所有属性 //active是传给子组件的prop属性,父组件传入的其他props也会在子组件中 return{ active: ownProps.filter === state.visibilityFilter } } //如果mapDispatchToProps为函数,则应返回一个对象,其每个键值对都是一个映射,定义了用户对 UI 组件的操作怎样分发出 Action。 //若mapDispatchToProps是一个对象,则属性名对应UI组件的同名参数,属性值是一个当作Action creator的函数,其返回的action会由Redux自动发出给store。 const mapDispatchToProps = (dispatch, ownProps) => ({ //onClick是传给子组件调用的方法,在view触发action时调用 onClick: () => { dispatch(setVisibilityFilter(ownProps.filter)) } }) //如果不传入mapStateToProps/mapStateToProps,将默认只传入dispatch和父组件传入的props,不传入store const FilterLink = connect( // FilterLink是容器组件 mapStateToProps, // 将全局state对象映射到 UI 组件 mapDispatchToProps // UI 组件将传出action对象的方法 )(Link) //Link为UI组件 export default FilterLink
/*UI 组件,只负责UI的呈现*/ import React from 'react' //active和onClick是父容器组件通过connect方法传入的props属性 const Link = ({ active, children, onClick }) => { if (active) { return <span>{children}</span> } return ( <a href="#" onClick={e => { e.preventDefault() onClick() }} > {children} </a> ) } export default Link
<Provider> 组件
使用Provider组件包裹根组件,将store传入容器组件,并通过组件的context属性使嵌套的所有子组件都能获得根state而不用一级一级传递下去。
//在任意组件引用全局store,尽量不要直接操作store的实例 Link.contextTypes = { store: React.PropTypes.object }