Redux
Redux 由 Flux 演变而来,并且引入了大量函数式编程的理念,提供可预测化的状态管理。
单向数据流
不同于Flux架构,Redux中没有dispatcher这个概念,并且Redux不允许直接操作数据,只能在reducer中返回新的对象来作为应用的新状态。但是它们都可以用(state, action) => newState来表述其核心思想,所以Redux可以被看成是Flux思想的一种实现,但是在细节上会有一些差异。
流程图
核心概念
View 即 React 组件。它们负责渲染界面,捕获用户事件,从 Store 获取数据。
Store 用于管理数据。需要特别注意 Redux 应用只有一个单一的 Store。
Action 是传递给 Store.dispatch() 方法的对象,包含 Payload 和 Action Type。
Action Type 指定了可以创建哪些 Action,Store 只会更新特定 Action Type 的 Action 触发的数据。
Action Creator 是 Actions 的创建者,在 Redux 中只是简单的返回一个 action 对象。
Reducer 用于指明应用如何更新 state 。Reducer 是一个纯函数,接收旧的 state 和 action,返回新的 state 。
概念解析
Action:包含Action Type以及应用送往store(存储)的信息载荷(payload,也可称为有效信息)
Action具有固定格式,即FSA, 全称为Flux Standard Action,格式如下:
{ type: 'ADD_TODO', payload: { text: 'Do something' } }
Action Type : Action中的type , 如下:
{ type: 'COMPLETE_TODO', //action type payload: { text: 'Do something' } }
Action Creator :是一种辅助创建Action的函数,类似工厂模式,传入参数生成对应的Action ,如下所示:
function actionCreateor(text) { return { type: ADD_TODO, text } }
Store : 用来维持应用所有的 state 树的一个对象。 改变 Store 内 state 的惟一途径是对它 dispatch 一个 action。有如下方法:
Store.getState()
返回应用当前的 state 树
Store.dispatch(action)
分发 action。这是触发 state 变化的惟一途径
Store.subscribe(listener)
添加一个变化监听器。每当 dispatch action 的时候就会执行,state 树中的一部分可能已经变化。你可以在回调函数里调用 getState() 来拿到当前 state。如果需要解绑这个变化监听器,执行 subscribe 返回的函数即可。
Store.replaceReducer(nextReducer)
替换 Store 当前用来计算 state 的 reducer。只有在你需要实现代码分隔,而且需要立即加载一些 reducer 的时候才可能会用到它。在实现 Redux 热加载机制的时候也可能会用到。
import { createStore } from 'redux' import todoApp from './reducers' //创建初始状态,并利用服务端数据初始化 let store = createStore(todoApp, window.STATE_FROM_SERVER) // 打印初始状态 console.log(store.getState()) // 每次 state 更新时,打印日志 // 注意 subscribe() 返回一个函数用来注销监听器 let unsubscribe = store.subscribe(() => console.log(store.getState()) ) //分发action store.dispatch(addTodo('Learn about Store')) // 停止监听 state 更新 unsubscribe();
Reducer : 一个纯函数,接收旧的 state 和 action,返回新的 state ,即 (state, action) => newState 。需要谨记 Reducer 一定要保持纯净。只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。
//简单的reducer,处理单一action function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: //谨记不要直接修改state return Object.assign({}, state, { visibilityFilter: action.filter }) default: return state } } //处理多个action,合并reducers import { combineReducers } from 'redux' import * as reducers from './reducers' const todoApp = combineReducers(reducers)
View : 在 Redux 中,组件分为smart component(容器类组件) 和 dumb component(展示型组件)。
容器类组件: 与redux或router进行连接,起到了维护状态,分发action的作用
展示类组件: 不维护状态,所有的状态由容器组件通过props传递,所有操作通过回调完成
//容器类组件 import React,{Component} from 'react'; class Class extends Component { ... render() { return ( <div> <Title value={this.state.value} /> </div> ); } } //展示类组件 const Title = ({value}) => (<h1>{value}</h1>);
深入理解
- Middleware : 中间件。 在 Express 或者 Koa 服务端框架中,middleware 是指可以被嵌入在框架接收请求到产生响应过程之中的代码。而在 Redux 中,middleware 是指 action 被发起之后,到达 reducer 之前的代码,确切的说是dispatch之前的代码。middleware 最优秀的特性就是可以被链式组合,可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等操作。如下所示:
//中间件的导入
const store = createStore(reducers, initState, applyMiddleware(...middlewares))
//applyMiddleware源码
import compose from './compose'
export default function applyMiddleware(...middlewares) {
//enhancer是增强器的意思,这里即为应用中间件
//这里涉及到函数柯里化
//函数柯里化ES6表现:
// const logger = store => next => action => {
// console.log('dispatching', action)
// let result = next(action)
// console.log('next state', store.getState())
// return result
// }
//
// 等价于下面高阶函数:
// function logger(store) {
// return function wrapDispatchToAddLogging(next) {
// return function dispatchAndLog(action) {
// console.log('dispatching', action)
// let result = next(action)
// console.log('next state', store.getState())
// return result
// }
// }
// }
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
// 原先store.dispatch方法
let dispatch = store.dispatch
// 链数组,用于存放middleware
let chain = []
//暴露middlewareAPI给第三方中间件使用
const middlewareAPI = {
getState: store.getState,
//applyMiddleware 执行完后,dispatch 会发生变化
//匿名函数是为了只要 dispatch 更新了,middlewareAPI 中的 dispatch 应用也会发生变化
dispatch: (...args) => dispatch(...args)
}
//通过map方法使中间件可以获取middlewareAPI
chain = middlewares.map(middleware => middleware(middlewareAPI))
//compose是FP(函数式编程)中常用方法,用于从右至左来组合函数
//扩展dispatch方法,类似dispatch=f1(f2(f3(store.dispatch)))的效果
//middleware内部dispatch方法改变,但不会影响原来store.dispatch方法
//即dispatch为store.dispatch的高阶函数
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
Redux常用的middleware主要为: 异步处理中间件和路由跳转中间件。异步处理中间件以redux-saga为代表,路由跳转中间件以react-router-redux为代表。
- redux-saga : 主要用来处理异步行为。saga 把副作用 (Side effect,异步行为就是典型的副作用) 看成”线程”,可以通过普通的action去触发它,当副作用完成时也会触发action作为输出。如下图所示,可以很直观得去理解saga :
// redux-saga 启动 const sagaMiddleware = createSagaMiddleware(); const store = createStore(rootReducer, [], compose( applyMiddleware(sagaMiddleware) ); // 将 Saga 连接至外部的输入和 输 出,返回一个 Task 对象,类似 fork Effect 返回的 sagaMiddleware.run(rootSaga); // redux-saga API/effect //put的作用和 redux 中的 dispatch 相同。 yield put({ type: SET_HOME_FEED, payload: homeFeed }) //select获取 State 下数据。 let homeFeed = yield select(state => state.homeFeed) //等待 Store 上指定的 action。 const { payload: id } = yield take(FETCH_DETAIL_FEED) //redux-saga 可以用 fork 来调用子 saga ,其中 fork 是无阻塞型调用 function* countSaga () { while (true) { const { payload: number } = yield take(BEGIN_COUNT); const countTaskId = yield fork(count, number); yield take(STOP_TASK); yield cancel(countTaskId); } } //redux-saga 也可以用 call 来调用子 saga ,其中 call 是阻塞型调用 const homeFeed = (yield call(homeService.fetchHomeFeed)).data const replyList = (yield call(detailService.fetchReplyList, id)).data
- react-router-redux : 用来处理路由跳转的中间件。
//创建一个 Redux 中间件,将 router 与 Redux Store 建立连接 import createHistory from 'history/createBrowserHistory' import {routerMiddleware} from 'react-router-redux' const history = createHistory(); const historyMiddleware = routerMiddleware(history); const store = createStore( createRootReducer(), initState, compose( historyMiddleware ) ) //路由映射 const routeconfig =( <ConnectedRouter history={history}> <Switch> <Route exact path="/" component={Home} /> <Route path="/detail/:id" component={Detail}></Route> <Route component={NotFound}></Route> </Switch> </ConnectedRouter> ); //注入组件 class App extends Component { render() { return ( <Container> {routeconfig} </Container> ); } }
总结 : Redux中间件的实质是对store.dispatch 方法的再包装,以达到 dispatch 一些除了 action 以外的其他内容,例如:函数或者 Promise。
React与Redux的结合
Redux 仅仅是一个用于管理状态的库,你可以与 Vue 、Angluar 等框架配合使用,当然契合度最佳的莫过于 React。react-redux提供两个关键模块:Provider和connect。
Provider : 使容器组件获取到state。
import { Provider } from 'react-redux' import { createStore } from 'redux' import todoApp from './reducers' import App from './components/App' let store = createStore(todoApp) ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
connect : 连接 React 组件与 Redux store。操作不会改变原来的组件,而是返回一个新的已与 Redux store 连接的组件类。
API : connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
[mapStateToProps(state, [ownProps]): stateProps] (Function): 如果定义该参数,组件将会监听 Redux store 的变化。任何时候,只要 Redux store 发生改变,mapStateToProps 函数就会被调用。该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。如果你省略了这个参数,你的组件将不会监听 Redux store。
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): 如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,而且这个对象会与 Redux store 绑定在一起,其中所定义的方法名将作为属性名,合并到组件的 props 中。如果传递的是一个函数,该函数将接收一个 dispatch 函数,然后由你来决定如何返回一个对象,这个对象通过 dispatch 函数与 action creator 以某种方式绑定在一起(提示:你也许会用到 Redux 的辅助函数 bindActionCreators())
//容器类组件 class Home extends React.Component{ ... render(){ return( ... ); } } //actionCreator模块 const fetchDetailFeed = (id) => ({ type: FETCH_DETAIL_FEED, payload: id }) //必须返回一个对象,这样connect时fetchDetailFeed便为Redux action creator export default { fetchDetailFeed } //连接组件 export default connect( //{ homeFeed, platform } = state 解构赋值 //箭头函数返回对象需要括号({ homeFeed, platform }) //这里的actionCreator为一对象,内部存在函数 ({ homeFeed, platform }) => ({ homeFeed, platform }),actionCreator )(Home)
在通常情况下,表现为connect(state,{actionCreator})
React与Redux相结合的流程图
参考链接
Redux:
https://github.com/reactjs/redux/tree/master/src
https://github.com/jasonslyvia/a-cartoon-intro-to-redux-cn
https://code-cartoons.com/a-cartoon-intro-to-redux-3afb775501a6
https://medium.com/@meagle/understanding-87566abcfb7a
https://zhuanlan.zhihu.com/p/24337401
https://zhuanlan.zhihu.com/p/20597452
https://segmentfault.com/a/1190000005766289
http://cn.redux.js.org/
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html
https://www.zhihu.com/question/41312576
Redux-saga:
https://zhuanlan.zhihu.com/p/25024255
https://zhuanlan.zhihu.com/p/23012870
http://leonshi.com/redux-saga-in-chinese/index.html
React-Redux:
https://github.com/reactjs/react-redux
React-Router-Redux:
https://github.com/ReactTraining/react-router/tree/master/packages/react-router-redux
API of the Object.assign()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
FP(function program):
https://github.com/llh911001/mostly-adequate-guide-chinese
https://zhuanlan.zhihu.com/p/21714695