封裝一個在react上更易用的redux框架

Colin_Mindset發表於2019-03-24

redux是一個用於管理狀態的前端框架,不僅可以用於react,還可以用於vue。它的原始碼和設計思想,我們在《顫抖吧!一起手寫一個redux框架!》已經講過,沒有看過的小夥伴們可以去看一下。

redux的核心是發明了store,通過dispatch來更改store裡的值。

可redux要想在react上使用,需要把兩者連線起來,這個時候react-redux就出現了。它的主要作用是:

  • componentDidMountstore.subscribe,這樣在store.dispatch的時候就能回撥subscribelistener
    具體看Provider.js中的原始碼實現:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { ReactReduxContext } from './Context'

class Provider extends Component {
  //...

  componentDidMount() {
    this._isMounted = true
    this.subscribe()
  }

  subscribe() {
    const { store } = this.props

    this.unsubscribe = store.subscribe(() => {
      const newStoreState = store.getState()

      if (!this._isMounted) {
        return
      }

      this.setState(providerState => {
      	//小tip:return null代表不更新state
        if (providerState.storeState === newStoreState) {
          return null
        }

        return { storeState: newStoreState }
      })
    })

    // ...
  }

}

export default Provider

  • 提供了context,可以實現跨元件通訊,而不用去狀態提升。
    這其實用到了react自帶的api:context
    具體實現是在Provider.js中宣告context,並把store.getState()賦值給它。這樣其子元件中就可以拿到context值,只不過要寫一些模板程式碼,這些程式碼都封裝在了connect.js中。這也就是為什麼我們只要把我們的元件傳給高階元件connect中就可以獲取到reduxstate
  • mapStateToProp用來把store中的state傳給connect元件,所以在元件中可以通過this.props獲取store中state的值。
  • mapDispatchToProps用來把用dispatch包裹的actionCreator傳給元件(bindActionCreators中把dispatch給bind到actionCreator上),所以,在元件中通過props可以直接dispatch一個action。

以上大概就是要在react中使用redux要寫的程式碼。
其實,react-redux封裝的功能並不一定適合我們的業務場景,相反我們還多寫了很多與業務無關的模板程式碼!
因此,我們可以嘗試著自己去封裝一個更易用的redux框架。它有如下幾個特點:

  • 可以成功地連線react和redux。
  • 不需要在業務程式碼中手動bindActionCreator,也不需要寫mapDispatchToProps和mapStateToProps。
  • 不用寫完reducer,再去寫action了。使用過react-redux的小夥伴們應該都發現了reducer和action實際上都是為了去更新資料層,卻要寫兩次非常類似的程式碼。
    它的使用方法也很簡單,只需要讓你的頁面繼承這個basePage就可以了。
import React from "react";
import { createStore, applyMiddleware } from "redux";

export default class BasePage {
  constructor(props) {
    super(props);
    this.$actions = null;
    this.$store = null;
    this.state = {};
  }

  render() {
    let { View } = this;

    if (!View) return null;

    let pageContext = {
      page: this,
      state: this.state
    };
    return <View state={this.state} page={this} />;
  }

  get store() {
    if (this.$store) return this.$store;
    this.$store = this.createStore();
    return this.$store;
  }

  get actions() {
    if (this.$actions) return this.$actions;
    this.store; // trigger createStore
    this.$actions = {};
    if (!util.isObject(this.model)) return this.$actions;

    Object.keys(this.model).forEach(type => {
      this.$actions[type] = payload => this.store.dispatch({ type, payload });
    });
    return this.$actions;
  }

  createReducer() {
    return (state, action) => {
      let model = this.model;
      let nextState = state;
      if (util.isObject(model)) {
        let handler = reducer[action.type];
        if (util.isFunction(handler)) {
            nextState = handler(nextState, action.payload);
        }
      } else if (util.isFunction(model)) {
        nextState = reducer(nextState, action);
      }
      return nextState;
    };
  }

  createStore() {
    const middlewares = [];
    if (process.env.NODE_ENV === "development") {
      const reduxLogger = require("redux-logger");
      const logger = reduxLogger.createLogger();
      middlewares.push(logger);
    }
    const createStoreWithMiddleware = applyMiddleware(...middlewares)(
      createStore
    );
    let store = createStoreWithMiddleware(this.createReducer(), this.state);
    store.unsubscribe = store.subscribe(() => {
      let nextState = store.getState();
      this.setState(nextState);
    });
    Object.defineProperty(store, "actions", {
      get: () => this.actions
    });
    return store;
  }

可以看到,如上框架程式碼行數並不多,下面我來為大家來講解一下。
首先,先看render(),在render中給每個view傳入了page

<View state={this.state} page={this} />

這主要是用來在元件中呼叫action來更新state。
例如,我們在CounterView中呼叫INCRE_BY

export default function CounterView({ state, page }) {
  const { count } = state;

  return (
    <View>
      <TouchableOpacity onPress={()=> {
        page.actions.INCRE_BY
      }}>
        <Text style={{ backgroundColor: "red" }}>increase</Text>
      </TouchableOpacity>
    </View>
  );
}

此時頁面才開始createStore。。或者說,只有呼叫頁面第一個action來開始createStore。因為,在呼叫page.actions.INCRE_BY時,實際上會先呼叫

  get actions() {
    if (this.$actions) return this.$actions;
    this.store; // trigger createStore
    this.$actions = {};
    if (!util.isObject(this.reducer)) return this.$actions;

    Object.keys(this.reducer).forEach(type => {
      this.$actions[type] = payload => this.store.dispatch({ type, payload });
    });
    return this.$actions;
  }

此時,this.$actions實際上是空的,所以這個時候就會用this.model來構建action。這裡的model不光可以構建action,還可以構建出reducer
我們來看下mode.js是怎樣的:

export const INCRE = state => {
  let count = state.count + 1;
  return {
    ...state,
    count
  };
};

export const DECRE = state => {
  let count = state.count - 1;
  return {
    ...state,
    count
  };
};

export const INCRE_BY = (state, aaa = 2) => {
  let count = state.count + aaa;
  return {
    ...state,
    count
  };
};

看到這個model我相信你大致就能猜到我是如何通過model來構建reduceraction的了——構建action時,取出modelkey作為action名,並把每個functiondispatch包裹一下;構建reducer時,就直接呼叫model中相應keyfunction,注意這裡每一個functionreturn一個新的state

而上面在呼叫action時,當發現store為空時,會接著去createStore

createStore() {
    const middlewares = [];
    if (process.env.NODE_ENV === "development") {
      const reduxLogger = require("redux-logger");
      const logger = reduxLogger.createLogger();
      middlewares.push(logger);
    }
    const createStoreWithMiddleware = applyMiddleware(...middlewares)(
      createStore
    );
    let store = createStoreWithMiddleware(this.createReducer(), this.state);
    store.unsubscribe = store.subscribe(() => {
      let nextState = store.getState();
      this.setState(nextState);
    });
    Object.defineProperty(store, "actions", {
      get: () => this.actions
    });
    return store;
  }

以上我們就完成了一個比react-redux更輕量級的redux框架,該框架使得我們能儘可能少地在業務程式碼中寫無腦的模板程式碼。

最後,該框架是參考自@工業聚,他對於技術的態度,給了我很多前進的動力!

相關文章