React系列之 Redux 架构模式

前端之家收集整理的这篇文章主要介绍了React系列之 Redux 架构模式前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

原文地址:https://gmiam.com/post/react-...

没想到这篇文章这么晚才出,最近发生了太多的事情,已致于心态全无,最终也离开了现在的公司,没想到是这么的狼狈

一个人的光芒可以放到很大也可以小到微乎其微,如果不能好好的规划最终也只能默默的承受

世上没有相同的感同身受,感受真实才能真正的认清一切

好了,下面步入正题

前面看到 Flux 架构相对来说还是比较繁琐,同时社区也涌现了很多第三方的框架模式,而 Redux 则脱颖而出

React 以组件的形式维护了一颗 UI 树,但是对状态数据没有做更多的处理,Redux 则把状态数据也抽象成了一棵树来维护

它本身与 React 没有直接关系,可以与其他框架配合使用,也可以很好的与 React 配合使用

Redux 的代码量非常短小,核心只提供了 5 个 API

  • createStore

  • combineReducers

  • bindActionCreators

  • applyMiddleware

  • compose

下面先来直观的感受下 Redux

  1. import { createStore } from 'redux';
  2.  
  3. function counter(state = 0,action) {
  4. switch (action.type) {
  5. case 'INCREMENT':
  6. return state + 1;
  7. case 'DECREMENT':
  8. return state - 1;
  9. default:
  10. return state;
  11. }
  12. }
  13.  
  14. let store = createStore(counter);
  15.  
  16. store.subscribe(() =>
  17. console.log(store.getState())
  18. );
  19.  
  20. store.dispatch({ type: 'INCREMENT' });
  21. // 1
  22. store.dispatch({ type: 'INCREMENT' });
  23. // 2
  24. store.dispatch({ type: 'DECREMENT' });
  25. // 1

表象可以看出入口是 createStore,接收一个函数(这里叫做 reducer),这个函数接收 state 与 action 俩个参数,然后 dispatch 一个对象(这里叫 action,要包含一个 type 属性标明行为),reducer 函数就会被触发执行来操作状态,同时也会触发 subscribe 订阅的回调,回调可以通过 store.getState() 获取当前状态数据

到这里都很简单,那么如果我们需要处理的数据和状态越来越多 reducer 函数就会越来越大导致难以维护,所以 Redux 提供了 combineReducers 来处理这种情况,它把这个大的 reducer 分解成一个个小的 reducer ,每个小 reducer 维护自己的状态数据,这样就分解出了一个状态树

做下变种

reducers/todos.js

  1. export default function todos(state = [],action) {
  2. switch (action.type) {
  3. case 'ADD_TODO':
  4. return state.concat([action.text])
  5. default:
  6. return state
  7. }
  8. }

reducers/counter.js

  1. export default function counter(state = 0,action) {
  2. switch (action.type) {
  3. case 'INCREMENT':
  4. return state + 1
  5. case 'DECREMENT':
  6. return state - 1
  7. default:
  8. return state
  9. }
  10. }

reducers/index.js

  1. import { combineReducers } from 'redux'
  2. import todos from './todos'
  3. import counter from './counter'
  4.  
  5. export default combineReducers({
  6. todos,counter
  7. })

App.js

  1. import { createStore } from 'redux'
  2. import reducer from './reducers/index'
  3.  
  4. let store = createStore(reducer)
  5. console.log(store.getState())
  6. // {
  7. // counter: 0,// todos: []
  8. // }
  9.  
  10. store.dispatch({
  11. type: 'ADD_TODO',text: 'Use Redux'
  12. })
  13. console.log(store.getState())
  14. // {
  15. // counter: 0,// todos: [ 'Use Redux' ]
  16. // }

可以看到我们利用 combineReducers 把 reducer 做了拆分,combineReducers 部分精简源码

  1. export default function combineReducers(reducers) {
  2. var reducerKeys = Object.keys(reducers)
  3. var finalReducers = {}
  4. for (var i = 0; i < reducerKeys.length; i++) {
  5. var key = reducerKeys[i]
  6. if (typeof reducers[key] === 'function') {
  7. finalReducers[key] = reducers[key]
  8. }
  9. }
  10. var finalReducerKeys = Object.keys(finalReducers)
  11.  
  12. return function combination(state = {},action) {
  13.  
  14. var hasChanged = false
  15. var nextState = {}
  16. for (var i = 0; i < finalReducerKeys.length; i++) {
  17. var key = finalReducerKeys[i]
  18. var reducer = finalReducers[key]
  19. var prevIoUsStateForKey = state[key]
  20. var nextStateForKey = reducer(prevIoUsStateForKey,action)
  21. nextState[key] = nextStateForKey
  22. hasChanged = hasChanged || nextStateForKey !== prevIoUsStateForKey
  23. }
  24. return hasChanged ? nextState : state
  25. }
  26. }

可以看到就是把对象中的 reducer 全部执行一遍,把上次的状态传入进去,最新的状态返回回来,当然你也可以提供自己的

combineReducers 方法

前面我们注意到 store.dispatch 都是一个纯对象,也就是说我们的触发都是同步的,如何支持异步?

下面我们来引入 Redux 中间件来增强下

  1. import { createStore,applyMiddleware } from 'redux';
  2. import thunk from 'redux-thunk';
  3. import rootReducer from './reducers/index';
  4.  
  5. function increment() {
  6. return {
  7. type: 'INCREMENT_COUNTER'
  8. }
  9. }
  10.  
  11. function incrementAsync() {
  12. return dispatch => {
  13. setTimeout(() => {
  14. dispatch(increment());
  15. },1000)
  16. }
  17. }
  18.  
  19. const store = createStore(
  20. rootReducer,applyMiddleware(thunk)
  21. )
  22.  
  23. store.dispatch(increment()) // 同步
  24.  
  25. store.dispatch(incrementAsync()) // 异步

同步方式的触发跟以前是一样的,这里的异步支持就是靠 Redux 的 applyMiddleware 中间件模式与 thunk 中间件做增强支持

来看下 applyMiddleware 与部分 createStore 源码

  1. export default function applyMiddleware(...middlewares) {
  2. return (createStore) => (reducer,preloadedState,enhancer) => {
  3. var store = createStore(reducer,enhancer)
  4. var dispatch = store.dispatch
  5. var chain = []
  6.  
  7. var middlewareAPI = {
  8. getState: store.getState,dispatch: (action) => dispatch(action)
  9. }
  10. chain = middlewares.map(middleware => middleware(middlewareAPI))
  11. dispatch = compose(...chain)(store.dispatch)
  12.  
  13. return {
  14. ...store,dispatch
  15. }
  16. }
  17. }
  1. export default function createStore(reducer,enhancer) {
  2. if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  3. enhancer = preloadedState
  4. preloadedState = undefined
  5. }
  6.  
  7. if (typeof enhancer !== 'undefined') {
  8. return enhancer(createStore)(reducer,preloadedState)
  9. }
  10. ....
  11.  
  12. return {
  13. dispatch,subscribe,getState,replaceReducer,[$$observable]: observable
  14. }
  15. }

createStore 里所谓的增强就是 applyMiddleware 一些中间件,

  1. const store = createStore( rootReducer,applyMiddleware(thunk) )

与下面写法是等效的

  1. const store = applyMiddleware(thunk)(createStore)(rootReducer)

看上面 applyMiddleware 的源码可以知道会先用 createStore 创建原始 store,然后把 getState 与 dispatch 传给中间件,中间件处理完后返回扩展后的 store

看下 thunk 中间件源码

  1. function createThunkMiddleware(extraArgument) {
  2. return ({ dispatch,getState }) => next => action => {
  3. if (typeof action === 'function') {
  4. return action(dispatch,extraArgument);
  5. }
  6.  
  7. return next(action);
  8. };
  9. }
  10.  
  11. const thunk = createThunkMiddleware();
  12. thunk.withExtraArgument = createThunkMiddleware;
  13.  
  14. export default thunk;

很简单传入 dispatch,getState 后返回 next => action = > {...},然后传入 store.dispatch 返回 action => {...} 即扩展后的 dispatch

这个新的 dispatch 也是接受 action,如果是对象用原始 store.dispatch 直接触发,如果是函数则把 dispatch 传进函数体,把控制权交给函数内部

注意后面执行用到的 dispatch 已是扩展后的能处理函数的 dispatch

回过头来在说下 compose API,applyMiddleware 可以接受一系列中间件,内部调用 compose 来做处理

compose(...chain) 等同于 (...args) => f(g(h(...args)))

也就是说传入一组函数,它会倒序执行,把前一个的执行结果传给下一个,达到渐进增强效果

说到这里 Redux 和 它的 API 终于介绍差不多了,至于 bindActionCreators 后面介绍

说了这么多可以看到 Redux 自己就可以跑,那如何与 React 结合起来?那就需要 react-redux 这个中间桥梁了

react-redux 提供了俩个 API

  • Provider store

  • connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])

Provider 就是一个 React 组件,它接收一个 store 属性,把 store 挂在 React 的 Context 上,这样它的子组件不需要显示的传递 store 就可以获取

看个例子

  1. import { Provider } from 'react-redux'
  2.  
  3. const store = createStore(reducer)
  4.  
  5. render(
  6. <Provider store={store}>
  7. <App />
  8. </Provider>,document.getElementById('root')
  9. )

那么问题来了,可以获取到 store 后呢,如何做交互以及 React 与 Redux 的沟通,这时候 connect API 就派上用场了

还是继续看个例子

  1. import { bindActionCreators } from 'redux'
  2.  
  3. const App = ({todos,actions}) => (
  4. <div>
  5. <Header addTodo={actions.addTodo} />
  6. <MainSection todos={todos} actions={actions} />
  7. </div>
  8. )
  9.  
  10. const mapStateToProps = state => ({
  11. todos: state.todos
  12. })
  13.  
  14. const mapDispatchToProps = dispatch => ({
  15. actions: bindActionCreators(TodoActions,dispatch)
  16. })
  17.  
  18. export default connect(
  19. mapStateToProps,mapDispatchToProps
  20. )(App)

connect 的源码执行大概是这样

  1. export default function connect(mapStateToProps,mapDispatchToProps,mergeProps,options = {}) {
  2.  
  3. return function wrapWithConnect(WrappedComponent) {
  4.  
  5. class Connect extends Component {
  6.  
  7. constructor(props,context) {
  8. this.store = props.store || context.store
  9. }
  10.  
  11. render() {
  12.  
  13. const mappedState = mapStateToProps(store.getState(),this.props)
  14. const mappedDispatch = mapDispatchToProps(store.dispatch,this.props)
  15.  
  16. const mergedProps = {
  17. mappedState,mappedDispatch
  18. }
  19.  
  20. this.renderedElement = createElement(WrappedComponent,mergedProps)
  21. return this.renderedElement
  22. }
  23. }
  24. }
  25. }

这里做了适当的简化,从这可以看出 connect 返回了一个 Connect 组件获取到 store,然后把 store.getState() 与 store.dispatch

传递给我们的 mapStateToProps 与 mapDispatchToProps 函数,返回相应的数据与方法通过 props 传递给 React 组件,这样 React 组件就可以获取到相应数据展示,同时也可以通过 dispatch 触发 Redux store 的数据变动,Connect 组件在根据数据对比看是否需要重新渲染~

connect 实际的代码比这复杂的多,内部做了细致的浅数据对比以提升性能

对于 react-redux 这里还有一个潜规则,那就是展示组件与容器组件相分离,就是说只有容器组件处理数据与状态与 Redux 沟通,

展示组件只做正常的 UI 渲染,可以从这里了解更多 http://redux.js.org/docs/basi...

再看下上面的

  1. const mapDispatchToProps = dispatch => ({
  2. actions: bindActionCreators(TodoActions,dispatch)
  3. })

会把传入的函数或对象的每一个方法做下面的变形

  1. function bindActionCreator(actionCreator,dispatch) {
  2. return (...args) => dispatch(actionCreator(...args))
  3. }

这样 React 组件调用对应的 action 时就可以 dispatch 这个 actionCreator 产生的数据

最终不管有没有明白都可以看下 https://github.com/reactjs/re...

这个例子来加深下理解,以及目录结构的分工,当然有兴趣多了解一些例子就更好了

呼,这篇到这里终于算是写完了,最后大家都加油吧!

猜你在找的React相关文章