React mobx&redux

113 阅读3分钟

Redux

  • createStore只有一个函数,返回4个闭包

  • dispatch只做了一件事,调用reducer然后调用subscribelistener

    • Redux并不知道state有没有发生变化,更不知道state具体哪里发生了变化

    • view层需要知道哪一部分需要更新,只能通过脏检查

  • react-redux

    • 在store.subscribe上挂回调

    • 每次发生subscribe就调用connect传进去mapStateToPropsmapDispatchToProps,然后脏检测props的每一项

    • 如果有n个组件connect,每当dispatch一个action的时候,无论做了什么粒度的更新,都会发生O(n)时间复杂度的脏检测

    • 每次reducer执行完Redux就直接调用listener了

    • 在短时间内发生了多次修改(例如用户输入),不可变的开销,加上redux用字符串匹配action的开销,脏检测的开销,再加上view层的开销,时间复杂度就是O(n)

Mobx

  • 本身独立,不与任何view层框架互相依赖:可以随意选择合适的view层框架

  • 为每个组件创建一个Watcher,在数据的getter和setter上加钩子

  • 当组件渲染的时候(例如,调用render方法)会触发getter

  • 然后把这个组件对应的Watcher添加到getter相关的数据的依赖中(例如,一个Set)

  • 当setter被触发时,就能知道数据发生了变化,然后同时对应的Watcher去重绘组件

  • 当数据发生变化时,可以精确地知道哪些组件需要被重绘,数据变化时重绘的过程是O(1)的时间复杂度

  • Mobx:把数据声明为observable

选择Mobx的原因

  1. 学习成本少

    • 不需要额外异步处理库
  2. 面向对象编程,也支持函数式编程

    • @observable and @observer,以面向对象编程方式使得JavaScript对象具有响应式能力

    • Redux最推荐遵循函数式编程

  3. 模版代码少:相对于Redux的各种模版代码

    1. 不需要编写 ctionCreater,reducer,saga/thunk 模板代码

不选择Mobx的可能原因

  1. 过于自由

    • Mobx提供的约定及模版代码很少
  2. 大型项目需要特别注意可拓展,可维护性

架构

创建 store

  • Redux先配置

    • 创建store,用redux-thunkredux-saga中间件以支持异步action,用Provider将store注入应用
    // src/store.js
    import { applyMiddleware, createStore } from "redux";
    import createSagaMiddleware from 'redux-saga'
    import React from 'react';
    import { Provider } from 'react-redux';
    import { BrowserRouter } from 'react-router-dom';
    import { composeWithDevTools } from 'redux-devtools-extension';
    import rootReducer from "./reducers";
    import App from './containers/App/';
    
    const sagaMiddleware = createSagaMiddleware()
    const middleware = composeWithDevTools(applyMiddleware(sagaMiddleware));
    
    export default createStore(rootReducer, middleware);
    
    // src/index.jsReactDOM.render(
      <BrowserRouter>
        <Provider store={store}>
          <App />
        </Provider>
      </BrowserRouter>,
      document.getElementById('app')
    );
    
  • Mobx 直接将所有store注入应用

    import React from 'react';
    import { render } from 'react-dom';
    import { Provider } from 'mobx-react';
    import { BrowserRouter } from 'react-router-dom';
    import { useStrict } from 'mobx';
    import App from './containers/App/';
    import * as stores from './flux/index';
    
    // set strict mode for mobx
    // must change store through action
    useStrict(true);
    
    render(
      <Provider {...stores}>
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </Provider>,
      document.getElementById('app')
    );
    

注入Props

  • Redux

    // src/containers/Company.js
    class CompanyContainer extends Component {
      componentDidMount () {
        this.props.loadData({});
      }
      render () {
        return <Company
          infos={this.props.infos}
          loading={this.props.loading}
        />
      }
    }
    
    // function for injecting state into props
    const mapStateToProps = (state) => {
      return {
        infos: state.companyStore.infos,
        loading: state.companyStore.loading
      }
    }
    
    const mapDispatchToProps = dispatch => {
      return bindActionCreators({
          loadData: loadData
      }, dispatch);
    }
    
    // injecting both state and actions into props
    export default connect(mapStateToProps, { loadData })(CompanyContainer);
    
  • Mobx

    @inject('companyStore')
    @observer
    class CompanyContainer extends Component {
      componentDidMount () {
        this.props.companyStore.loadData({});
      }
      render () {
        const { infos, loading } = this.props.companyStore;
        return <Company
          infos={infos}
          loading={loading}
        />
      }
    }
    

定义Action/Reducer等

  • Redux

    // src/flux/Company/action.js
    export function fetchContacts(){
      return dispatch => {
        dispatch({
          type: 'FREQUEST_COMPANY_INFO',
          payload: {}
        })
      }
    }
    
    // src/flux/Company/reducer.js
    const initialState = {};
    function reducer (state = initialState, action) {
      switch (action.type) {
        case 'FREQUEST_COMPANY_INFO': {
          return {
            ...state,
            contacts: action.payload.data.data || action.payload.data,
            loading: false
          }
        }
        default:
          return state;
      }
    }
    
  • Mobx

    // src/flux/Company/store.js
    import { observable, action } from 'mobx';
    
    class CompanyStore {
      constructor () {
        @observable infos = observable.map(infosModel);
      }
    
      @action
      loadData = async(params) => {
        this.loading = true;
        this.errors = {};
        return this.$fetchBasicInfo(params).then(action(({ data }) => {
          let basicInfo = data.data;
          const preCompanyInfo = this.infos.get('companyInfo');
          this.infos.set('companyInfo', Object.assign(preCompanyInfo, basicInfo));
          return basicInfo;
        }));
      }
    
      $fetchBasicInfo (params) {
        return fetch({
          ...API.getBasicInfo,
          data: params
        });
      }
    }
    export default new CompanyStore();
    

异步Action

  • Redux:另外添加redux-thunkredux-saga以支持异步action,Mobx并不需要额外配置

    • redux-saga

      // src/flux/Company/saga.js
      // Sagas
      // ------------------------------------
      const $fetchBasicInfo = (params) => {
        return fetch({
          ...API.getBasicInfo,
          data: params
        });
      }
      
      export function *fetchCompanyInfoSaga (type, body) {
        while (true) {
          const { payload } = yield take(REQUEST_COMPANY_INFO)
          console.log('payload:', payload)
          const data = yield call($fetchBasicInfo, payload)
          yield put(receiveCompanyInfo(data))
        }
      }
      export const sagas = [
        fetchCompanyInfoSaga
      ];
      

链接:juejin.cn/post/684490…