🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # 官网 [Redux](https://redux.js.org) # 需要状态管理? ![props层层传递](https://img.kancloud.cn/73/01/73017faaf0dac4b0b65e2a1f69b00617_294x376.png) ![使用Redux图示](https://img.kancloud.cn/20/87/2087027513d4e66da2175b9f6904a205_801x469.png) 这种需要在每个组件都设置和管理`props`的情况,会随着应用的发展而变得繁琐,那么我们就会考虑每个组件都需要层次传递`props`吗? 其实 `祖父 --> 父 --> 子` 这种情况,React 不使用`props`层层传递也是能拿到数据的,使用[Context](https://reactjs.org/docs/context.html)即可。后面要讲到的 react-redux 就是通过 `Context` 让各个子组件拿到`store` 中的数据的。 # [Flux](https://facebook.github.io/flux/) ![Flux 的单向数据流](https://img.kancloud.cn/50/d3/50d315b98f1c7ffe0d0cdab99b87aa67_1273x669.png) 由于原始的 Flux 架构在实现上有些部分可以精简和改善,在实际项目上我们通常会使用开发者社群开发的 Flux-like 相关的架构实现(例如:Redux、Alt、Reflux 等)。 Flux 架构不是一个库或者框架,它是一种解决问题的思路,它认为 MVC 框架存在很大的问题,它推翻了MVC框架,并用一个新的思维来管理数据流转。 每种软件架构都有它特定的适用场景,Flux也是。如果你的项目足够简单,全都是静态组件,组件之间没有共享数据,那么使用Flux 只会徒增烦恼。 ## Flux 架构示例 在 Flux中,我们把应用关注点分为4种逻辑模块: * DIspatcher:处理动作分发,维持Store直接的依赖关系;(相当于MVC的 Controller) ```js import {Dispatcher} from 'flux'; //创建flux中的dispatcher,由flux框架控制为单例模式的; export default new Dispatcher(); ``` * Store:负责存储数据和处理数据相关逻辑; (相当于MVC的Model) ```js // CounterStore 相当于一类组件的数据中心,它是单例的共享的; // Store中包含了组件是无状态model,其中包含了组件的初始状态值,以普通对象的形式存在; // 一般在store中应该提供一个get方法,用于组件获取初始状态的值; // store中应该为Dispatcher注册状态计算函数,用于更新计算出新的数据,当组件同步这些数据后,组件的状态会更新,渲染被驱动进行; import AppDispatcher from '../dispatchers/Appdispatcher.js'; import * as ActionTypes from '../actions/ActionTypes'; import {EventEmitter} from 'events'; // 定义监听事件; const CHANGE_EVENT = 'changed'; // 定义组件的初始值,当组件加载的时候会在声明周期函数中同步这些值到组件的状态或者属性上去; const counterValues = { 'First': 0, 'Second': 10, 'Third': 30 }; // 定义组件的store对象; const CounterStore = Object.assign({},EventEmitter.prototype,{ //定义getter函数,方便数据的同步; getCounterValues:function(){ return counterValues; }, //定义事件发射函数,store中的数据发生改变的时候通知对应的组件更新数据[是否会造成所有监听到该事件的组件都去调用getter函数同步数据呢? //会不会造成应用运行效率的降低呢]; emitChange:function(){ this.emit(CHANGE_EVENT); }, //定义事件监听器添加函数,当组件挂载成功的时候添加监听函数; //callbacek函数由调用者传入,用于指明监听到事件后的行为, //通常callback函数的作用是更新组件的内部状态; addChangeListener:function(callback){ this.on(CHANGE_EVENT,callback); }, //监听函数移除函数; removeChangeListener:function(callback){ this.removeListener(CHANGE_EVENT,callback); } }); //用来登记各种Action的回调函数 CounterStore.dispatchToken = AppDispatcher.register( (action)=>{ if(action.type===ActionTypes.INCREMENT){ counterValues[action.counterCaption]++; CounterStore.emitChange(); }else if(action.type===ActionTypes.DECREMENT){ counterValues[action.counterCaption]--; CounterStore.emitChange(); } } ); //定义导出接口; export default CounterStore; ``` * Action:驱动Dispatcher的 JavaScript 对象;(多出来的,可以理解为对应的MVC框架的用户请求) ```js //action描述了组件发出的请求,包含了请求的类型和请求传递的数据; //action是一个纯函数,其产出只依赖与输入; //在实际的操作中,counterCasption相当于组件的ID,当组件产生动作的同时如果携带了数据,那么还可以添加对应的数据项; import * as ActionTypes from './ActionTypes.js'; import AppDispatcher from '../dispatchers/Appdispatcher.js'; //定义action构造器; export const increment = (counterCaption)=>{ AppDispatcher.dispatch( { type:ActionTypes.INCREMENT, counterCaption:counterCaption } ); }; export const decrement = (counterCaption)=>{ AppDispatcher.dispatch( { type:ActionTypes.DECREMENT, counterCaption:counterCaption } ); } ``` * View:视图部分,负责显示用户界面。(相当于MVC的 View) components: ```js //定义组件; import React, {Component, PropTypes} from 'react'; import * as Actions from '../actions/Actions'; import CounterStore from '../stores/CounterStore'; const buttonStyle = { margin: '10px' }; //定义组件的主体部分; class Counter extends Component { constructor(props) { super(props); //绑定this到成员函数; this.onChange = this.onChange.bind(this); this.onClickIncrementButton = this.onClickIncrementButton.bind(this); this.onClickDecrementButton = this.onClickDecrementButton.bind(this); //获取组件的初始状态; this.state = { count: CounterStore.getCounterValues()[props.caption] } } // 使用 shouldComponentUpdate 函数增加组件的渲染速度; // 因为当事件发射器发射事件后,所有监听该事件的组件中的主句或者状态将被更新,this.setState函数将被调用,采用 shouldComponentUpdate // 函数进行判断可以提高组件的渲染效率; shouldComponentUpdate(nextProps, nextState) { return (nextProps.caption !== this.props.caption) || (nextState.count !== this.state.count); } //组件成功挂载到dom上之后添加事件的监听; componentDidMount() { CounterStore.addChangeListener(this.onChange); } //组件被移除后对应的监听也应该被移除; componentWillUnmount(callback) { CounterStore.removeChangeListener(this.onChange); } //更新组件的状态; onChange() { //同步store中的数据到组件中; const newCount = CounterStore.getCounterValues()[this.props.caption]; this.setState({count: newCount}); } onClickIncrementButton() { Actions.increment(this.props.caption); } onClickDecrementButton() { Actions.decrement(this.props.caption); } render() { const {caption} = this.props; return (<div> <span>JunJun的计算器</span> <button style={buttonStyle} onClick={this.onClickIncrementButton}>+</button> <button style={buttonStyle} onClick={this.onClickDecrementButton}>-</button> <span>{caption}count:{this.state.count}</span> </div>); } } //约束组件的属性类型; Counter.propTypes = { caption: PropTypes.string.isRequired }; //声明组件的引用接口; export default Counter; ``` ## 示例 [react-flux-demo](https://github.com/jiji262/react-flux-demo) [自己对react中flux框架部分底层原理的理解](https://blog.csdn.net/zj20142213/article/details/78885928) # [Redux](https://redux.js.org/) 众多 Flux-like 相关的架构中,Redux有很多其他框架无法比拟的优势。在客户端,服务器端,原生应用都可以使用 Redux。 Redux 并不是和React 有特别关系,其他JS 框架也可以使用Redux 作为状态管理器。 Flux 可以有多个 state 来处理不同类型的数据,而**Redux 整个应用的 state 都在一个 单独的 Object中**,通过 `reducer`来定义整个应用state 的该如何响应。 原生的 Flux 会有许多分散的 store 储存各个不同的状态,但在 redux 中,只会有唯一一个 store 将所有的state 用 对象树 (object tree)的方式包起来。 ```js //原生 Flux 的 store const userStore = { name: '' } const todoStore = { text: '' } // Redux 的单一 store const state = { userState: { name: '' }, todoState: { text: '' } } ``` Redux 拥有许多方便好用的辅助测试工具(例如:[redux-devtools](https://github.com/gaearon/redux-devtools)、[react-transform-boilerplate](https://github.com/gaearon/react-transform-boilerplate)),方便测试和使用 `Hot Module Reload`。 ## Redux核心概念 ![redux-flowchart](https://img.kancloud.cn/37/bf/37bf231af15dff58990df4ddbb9dcc78_873x708.png) Redux 数据流的模型大致上可以简化成: `View -> Action -> (Middleware) -> Reducer` reducer 示例:`(state, action) => newState` ## Redux App 的数据流程图 (使用者与 `View` 互动 => `dispatch` 出 `Action` => `Reducers` 依据 `action type` 分配到对应处理方式,回传新的 state => 透过 `React-Redux` 传送给 React,React 重新绘制 View): ![React Redux App 的数据流程图](https://box.kancloud.cn/26ad5d78ee29ebf2183f51fbc990c04c_800x534.png) ## Redux 示例 ``` import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import registerServiceWorker from './registerServiceWorker'; import { createStore } from 'redux' const tiger = 10000; // 定义 action const increase = { type:'涨工资' } const decrease = { type:'扣工资' } // 定义 reducer const reducer = (state = tiger, action) => { switch (action.type){ case '涨工资': return state += 100; case '扣工资': return state -= 100; default: return state; } } // 创建 store 并绑定 reducer ,此处可以添加中间件 const store = createStore(reducer); // 订阅事件 const unsubscribe = store.subscribe(() => { console.log(store.getState()) }); // 派发事件 console.log(`dispatch action: INCREMENT`); store.dispatch(increase) ReactDOM.render(<App />, document.getElementById('root')); registerServiceWorker(); ``` ## [React-Redux](https://github.com/reactjs/react-redux) React-Redux 将所有组件分成两大类: 展示组件(presentational component)和容器组件(container component)。 * 展示组件只负责 UI 的呈现,通过 `props` 层层传递所需数据; * 容器组件只负责维护内部状态,进行数据分发和处理派发 action 。 ## React-Redux 示例: ``` import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { createStore } from 'redux'; import { Provider, connect } from 'react-redux'; class App extends Component { render() { // 来自 mapDispatchToProps const { PayIncrease, PayDecrease } = this.props; return ( <div className="App"> <div className="App"> <h2>当月工资为{this.props.tiger}</h2> <button onClick={PayIncrease}>升职加薪</button> <button onClick={PayDecrease}>迟到罚款</button> </div> </div> ); } } const tiger = 10000; // 这是 action const increase = { type: '涨工资' } const decrease = { type: '扣工资' } // 这是 reducer const reducer = (state = tiger, action) => { switch (action.type) { case '涨工资': return state += 100; case '扣工资': return state -= 100; default: return state; } } // 创建 store const store = createStore(reducer); // 需要渲染什么数据 function mapStateToProps(state) { return { tiger: state } } // 需要触发什么行为 function mapDispatchToProps(dispatch) { return { PayIncrease: () => dispatch({ type: '涨工资' }), PayDecrease: () => dispatch({ type: '扣工资' }) } } // 连接组件 App = connect(mapStateToProps, mapDispatchToProps)(App) ReactDOM.render( <Provider store={store}> // 融合 <App /> </Provider>, document.getElementById('root') ) ``` ## 生成 容器组件 ```js connect (mapStateToProps, rnapDispatchToProps) (presentationalCornponent) ; ``` `connect` 函数的返回值是一个 WrappedComponent 组件。 `connect` 是典型的柯里化函数,它执行两次,第一次是设置参数;第二次是接收一个正常的 presentationalComponent 组件,并在该组件的基础上返回一个容器组件 WrappedComponent。 **这其实是一种高阶组件(HOC)的用法。** ## `connect` 是如何获取到 Redux store 中的内容的? 这就要借助于 `Provider` 组件来实现了。react-redux 提供了`Provider` 组件,一般用法是需要将 `Provider` 作为整个应用的根组件,并获取 `store` 为其 `prop`,以便后续进行下发处理。 在开发中,借助于react-redux的基本模式便是: ```js const WrappedComponent = connect (mapStateToProps, mapDispatchToProps) (presentationalCornponent); ReactDOM.render( <Provider store={store}> <WrappedComponent /> </Provider>, rootEl ) ``` [示例](https://codesandbox.io/s/oj874qk28y) ## [中间件(middleware)](http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html) 在 Express/Koa 这样的服务器框架中,中间件扮演着对 request/response 统一进行特定处理行为的角色,它们可以接触到 request、response 以及触发下一个 middleware 继续处理的 next 方法。 Redux 的 Middleware 是对`store.dispatch()`进行了封装之后的方法,可以使 `dispatch` 传递 action 以外的函数或者 promise ;通过`applyMiddleware`方法应用中间件。(**middleware 链中的最后一个 middleware 开始 dispatch action 时,这个 action 必须是一个普通对象**) 常用的中间件:[redux-logger](https://github.com/LogRocket/redux-logger) redux-actions ``` const store = createStore( reducer, // 依次执行 applyMiddleware(thunk, promise, logger) ) ``` ### 异步操作怎么办? Action 发出以后,Reducer 立即算出 State,这叫做同步; Action 发出以后,过一段时间再执行 Reducer,这就是异步。 如果你使用了 asynchronous(异步)的行为的话,需要使用其中一种 middleware: [redux-thunk](https://github.com/gaearon/redux-thunk)、[redux-promise](https://github.com/acdlite/redux-promise) 或 [redux-promise-middleware](https://github.com/pburtchaell/redux-promise-middleware) ,这样可以让你在 actions 中 dispatch Promises 而非 function。 asynchronous(异步)运作方式: ![asynchronous](https://box.kancloud.cn/82554daa0202220192fa908e3ede49fc_789x409.png) # redux-saga VS redux-thunk [redux-thunk](https://github.com/gaearon/redux-thunk) 、[redux-saga](https://redux-saga-in-chinese.js.org/) 是 redux 应用中最常用的两种异步流处理方式。 ## redux-saga [redux-saga](https://redux-saga-in-chinese.js.org/docs/ExternalResources.html) 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。 redux-saga 启动的任务可以**在任何时候通过手动来取消**,也可以把任务和其他的 Effects **放到 race 方法里以自动取消** ### 执行流程 ui组件触发 action 创建函数 ---> action 创建函数返回一个 action ------> action 被传入 redux 中间件(被 saga 等中间件处理) ,产生新的 action,传入 reducer-------> reducer 把数据传给ui组件显示 -----> mapStateToProps ------> ui组件自渲染显示 ### 常见 effect 的用法 1. call 异步阻塞调用 2. fork 异步非阻塞调用,无阻塞的执行fn,执行fn时,不会暂停Generator 3. put 相当于dispatch,分发一个action 4. select 相当于getState,用于获取 store 中相应部分的state 5. take 监听 action,暂停 Generator,匹配的 action 被发起时,恢复执行。take 结合 fork,可以实现 takeEvery 和 takeLatest 的效果 6. takeEvery 监听 action,每监听到一个 action,就执行一次操作 7. takeLatest 监听 action,监听到多个 action,只执行最近的一次 8. cancel 指示 middleware 取消之前的 fork 任务,cancel 是一个无阻塞 Effect。也就是说,Generator 将在取消异常被抛出后立即恢复 9. race 竞速执行多个任务 10. throttle 节流 ### 参考 [Redux-saga](https://www.jianshu.com/p/6f96bdaaea22) ## redux-thunk redux-thunk 的主要思想是扩展 action,使得 action 从一个对象变成一个函数。 从 UI 组件直接触发任务。 ```js // redux-thunk 示例 import {applyMiddleware, createStore} from 'redux'; import axios from 'axios'; import thunk from 'redux-thunk'; const initialState = { fetching: false, fetched: false, users: [], error: null } const reducer = (state = initialState, action) => { switch(action.type) { case 'FETCH_USERS_START': { return {...state, fetching: true} break; } case 'FETCH_USERS_ERROR': { return {...state, fetching: false, error: action.payload} break; } case 'RECEIVE_USERS': { return {...state, fetching: false, fetched: true, users: action.payload} break; } } return state; } const middleware = applyMiddleware(thunk); //应用中间件 // 默认action对象形式:store.dispatch({type: 'FOO'}); // redux-thunk 的作用即是将 action 从一个对象变成一个函数 store.dispatch((dispatch) => { dispatch({type: 'FETCH_USERS_START'}); // 下面是一些异步操作 axios.get('http://rest.learncode.academy/api/wstern/users') .then((response) => { dispatch({type: 'RECEIVE_USERS', payload: response.data}) }) .catch((err) => { dispatch({type: 'FECTH_USERS_ERROR', payload: err}) }) }); ``` # 其他状态管理库 我们平时在开发 React 项目中,深深的感受到了 Redux 的“长得丑,用得烦”,有的人去改造它,如 `dva`、`rematch`,对 Redux 包装语法糖,也有如 `smox` ,直接重熔再生,完全摆脱 Redux 的局限的同时,还能拥抱“新特性”。其他状态管理库:[alt](https://github.com/goatslacker/alt)、[dob](https://github.com/dobjs/dob) ## [nofux](https://github.com/jayphelps/nofux) # 参考 [Mobx使用详解](https://www.jianshu.com/p/505d9d9fe36a) [详解react、redux、react-redux之间的关系](https://www.jb51.net/article/138084.htm) [Redux 和 Mobx的选择问题:让你不再困惑!](https://www.jb51.net/article/123964.htm) https://www.bookstack.cn/read/reactjs101/Ch07-react-flux-introduction.md [React 技术栈系列教程](http://www.ruanyifeng.com/blog/2016/09/react-technology-stack.html) # 相关知识 ## getInitialState `object getInitialState() ` ``` getInitialState: function () { return { items: ListStore.getAll() }; }, ``` 在组件挂载之前调用一次。返回值将会作为 this.state 的初始值。 ## 为什么页面刷新后 store 被重置了? 所谓单页应用,就是在不刷新浏览器的情况下可以在整个网页应用实时共享数据。 store 是内存机制,不是缓存机制,页面刷新和关闭都会导致 store 初始化,store 里面一般保存什么数据呢? 1. 组件的初始状态; 2. 后端数据的初始状态; 如果你需要存储是数据是要实时存储并且读取显示出来,那么存在本地缓存(但是在PC端,是由存在刷新操作的,可以判断 `sessionStorage` 中是否有值,有值的话将`sessionStorage` 中的数据直接付给defaultState。)或者服务端,这样每次刷新页面都需要读取本地缓存或者服务端的API,然后保存到 store,再从 store 去读到组件上。 ## state本地持久化 1. H5 本地存储方式 2. redux-persist redux-persist 会将 redux 的 store 中的数据缓存到浏览器的 localStorage 中。 # 参考 [Tips to learn React + Redux in 2018](https://www.robinwieruch.de/tips-to-learn-react-redux/)