redux & react-redux & react context

基於最近的兩個項目對 redux、 react-redux 和 react context 作總結html

react-redux

redux 中有三大核心,分別是 storereduceraction。其中 store 全局惟一,用於保存 app 裏的 state,reducer 控制 state 狀態,action 用於描述如何改變 state,compose 用於多個函數參數調用,middleware 中間件,以下所示:react

import {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
  // __DO_NOT_USE__ActionTypes
} from "redux";
複製代碼

建立 store

redux 中createStore函數用於建立 store,store 爲一對象,其功能包括維持應用的 state,獲取 state(getState),更新 state(dispatch),添加 listener(subscribe),第二個參數爲初始值,第三個參數用於加強服務,多用於中間件,須要說明的是整個應用應該就只有一個 store,當須要拆分數據處理時候須要拆分 reducer 而不是建立多個 store。git

//@param{Func} reducer 爲一個返回下一個state的函數
//@param{any} preloadedState 初始state
//@param{Func} enhancer 可用於第三方的
const store = createStore(reducer, preloadedState, enhancer);
//store 返回四個方法
//dispatch, 更新state
//subscribe,
//getState, 獲取state
//replaceReducer
複製代碼

建立 reducer

reducer 用於指定應用狀態的變化如何響應 actions 併發送到 store,其爲一個純函數,接收舊的 state 和 action,返回新的 state,關於純函數能夠參考react pure functiongithub

const todoReducer = (state, action) => {
  return state;
};
複製代碼

固然對於初始化的頁面,咱們通常會給 state 個默認值或給空,redux

const initState = {
  page: 10
};
const initState = null;
const todoReducer = (state = initState, action) => {
  const type = action.type
  if(type === 'a') {
      return state + 'a';
  }
  if(type === 'b'){
      return {...state, {limit:2}}
  }
  return
};
複製代碼

redux 還提供了個 combineReducers 方法,調用沒個子 reducer,合併他們的結果到一個 state 中,用對象字面量來寫以下,api

import { combineReducers } from "redux";
const todoApp = combineReducers({
  reducer1,
  reducer2
});
export default todoApp;
複製代碼

注意:promise

  • 不要直接修改 state,使用 object.assign() 或...
  • 可能 action 未知,需傳入 default

redux dispatch

在咱們建立 store 時候,store 會提供 dispath 函數,dispatch 會傳入一個帶 type 的 action,執行一個 listeners,並返回一個 action,dispatch 是惟一一個能夠改變 state 的方式。固然 redux 還支持 dispatch 個 promise, observable 等,你須要使用第三方中間件包裝你的 store。例如redux-thunkreact-router

function dispath(action) {
  //check is plain object
  //check action type
  //check is dispatching
  listener();
  //call listener
  return action;
}
dispatch({ type: ActionTypes.INIT });
複製代碼

actions

既然咱們知道 action 是一個帶 type 的對象,那麼咱們能夠把 action 抽象出來併發

export const UPDATE_ALERT_RES = "UPDATE_ALERT_RES";
export function todoAction(alertRes, ...rest) {
  return {
    type: UPDATE_ALERT_RES,
    payload: alertRes,
    ...rest
  };
}
複製代碼

至此 redux 相關的就建立成功了,可是爲了和 react 結合,咱們須要引入react-redux,react-redux 提供多個方法可供咱們使用app

react-redux

import {
  Provider,
  connectAdvanced,
  ReactReduxContext,
  connect,
  batch,
  useDispatch,
  useSelector,
  useStore,
  shallowEqual
} from "react-redux";
複製代碼

其中provider能讓組件層級中的 connet()方法都能得到到 redux store,咱們通常把這個置於根組件中,

import React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import todoApp from "./reducers";
import App from "./components/App";
let store = createStore(todoApp);
render(
  <Provider store={store}> <App /> </Provider>,
  document.getElementById("root")
);
複製代碼

另外一個咱們經常使用的函數connect可以鏈接 react 組件與 redux store,並返回一個與 store 鏈接的新組件

connect(
  [mapStateToProps],
  [mapDispatchToProps],
  [mergeProps],
  [options]
);
複製代碼

根據以上這些,咱們就能夠建立一個簡單的基於 redux 的 app

index.js

import * as React from "react";
import { render } from "react-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import Counter from "./counter";
const initStatus = {
  count: 0
};
function reducer(state = initStatus, action) {
  switch (action.type) {
    case "INCREMENT_COUNT":
      return { count: state.count + 5 };
    case "DECREMENT_COUNT":
      return { count: state.count - 1 };
    case "RESET_COUNT":
      return { count: 0 };
    default:
      return state;
  }
}
class App extends React.Component {
  render() {
    const store = createStore(reducer);
    return (
      <Provider store={store}> <Counter /> </Provider>
    );
  }
}
const rootElement = document.getElementById("root");
render(<App />, rootElement); 複製代碼

counter.js

import * as React from "react";
import { connect } from "react-redux";
import { incrementCount } from "./action";
function mapStateToProps(state) {
  return {
    count: state.count
  };
}
class Counter extends React.Component {
  decreCount = () => {
    this.props.dispatch({ type: "DECREMENT_COUNT" });
  };
  increCount = () => {
    this.props.dispatch(incrementCount());
  };
  resetCount = () => {
    this.props.dispatch({ type: "RESET_COUNT" });
  };

  render() {
    return (
      <div className="App"> <h2>Count</h2> <div> <div> <button onClick={this.decreCount}>-</button> </div> <div> <span className="count">{this.props.count}</span> </div> <div> <button onClick={this.increCount}>+</button> </div> <div> <button onClick={this.resetCount}>重置</button> </div> </div> </div>
    );
  }
}
export default connect(mapStateToProps)(Counter);
複製代碼

action.js

const INCREMENT_COUNT = "INCREMENT_COUNT";
export function incrementCount() {
  return { type: INCREMENT_COUNT };
}
複製代碼

一個簡單版的 react-redux 就介紹完畢,然而咱們的項目通常都會比較複雜,這樣簡單的並不適用,故此咱們作些改造

合併多個 reducer combineReducers

考慮到多個 reducer 不易操做,咱們把多個 reducer 合併成一個 reduer 來方便管理(其中APIReducer爲與 API 操做有關的 reducer,咱們把與 API 相關的也抽象成一個 reducer,稍後介紹) rootReducer.js

import { routerReducer as routing } from "react-router-redux";
import { combineReducers } from "redux";
import reducer1 from "./reducer1";
import reducer2 from "./reducer2";
import reducer3 from "./reducer3";
import {
  reducer as formReducer,
  actionTypes as formActionTypes
} from "redux-form";
import { reducer as uiReducer } from "redux-ui";
import { reducers as APIReducer } from "~/API";
const rootReducer = combineReducers({
  APIReducer,
  reducer1,
  reducer2,
  reducer3,
  form: formReducer.plugin({
    HostForm: (state, action) => {
      if (!state || lodash.get(action, "XX") !== "XX") return state;
      //TODO SOMETHIN
      return state;
    }
  }),
  ui: uiReducer
});

export default rootReducer;
複製代碼

合併多個 action

一樣與 API 相關的也抽象成 Actions 方便管理,以防 action 錯誤,咱們使用個過濾器過濾未定義的 action rootActions.js

import lodash from "lodash";
import * as action1 from "./action1";
import * as action2 from "./action2";
import * as action3 from "./action3";
import { actions as APIActions } from "~/API";
const actions = Object.assign({}, APIActions, action1, action2, action3);
export function filterDispatchers(...args) {
  args.forEach(v => {
    if (!actions.hasOwnProperty(v)) {
      throw new Error(`filterDispatchers: No dispatcher named: ${v}`);
    }
  });
  return lodash.pick(actions, args);
}
export default actions;
複製代碼

配置 store

import { createStore } from "redux";
import rootReducer from "../reducers";
import rootEnhancer from "./enhancer"; //處理token license等中間件
export default function configureStore(preloadedState) {
  const store = createStore(rootReducer, preloadedState, rootEnhancer);
  if (module.hot) {
    module.hot.accept("../reducers", () => {
      const nextRootReducer = require("../reducers").default;
      store.replaceReducer(nextRootReducer);
    });
  }
  return store;
}
複製代碼

配置 selectors

reselect能夠用於建立可記憶的、可組合的 selector 函數,高效計算衍生數據

componets.js

export const selector1 = state => state.selector1;
export const selector2 = state => state.selector2;
export const selector3 = state => state.selector3;
複製代碼

selectors.js

import lodash from "lodash";
import { createSelector } from "reselect";
import * as componentSelectors from "./components";
import { selectors as APISelectors } from "~/API";
const selectors = Object.assign(componentSelectors, APISelectors);
export function filterSelectors(...args) {
  return function mapStateToProps(state) {
    const inputSelectors = args.map(v => {
      const selector = `${v}Selector`;
      if (!selectors.hasOwnProperty(selector)) {
        throw new Error(`filterSelectors: No selector named: ${selector}`);
      }
      return selectors[selector];
    });
    return createSelector(
      inputSelectors,
      (...selected) => lodash.zipObject(args, selected)
    )(state);
  };
}

export default selectors;
複製代碼

常見頁面結構以下,connect 多個 state、action, 再用 compose 組合,其餘 HOC,socket,page 等 page.js

import { compose } from "redux";
import { connect } from "react-redux";
import { filterSelectors } from "~/selectors";
import { filterDispatchers } from "~/actions";
const connector = compose(
  connect(
    filterSelectors("state"),
    filterDispatchers("action")
  ),
  Hoc()
);
class Page extends React.Componet {}
export default connector(Page);
複製代碼

API redux

異步 redux 相似於同步,只需添加中間件處理 fetch 數據,reducer 爲 fetching(state, action),action 狀態爲 RESTFul API 加上返回狀態 例如

const resultType = ['REQUEST','SUCCESS', 'FAILURE'];
  const methodd = ['PATCH','GET','POST','PUT']
  const actionType = PATCH_SOMEAPI_SUCCESS;

複製代碼

callAPI 可參考官方示例redux-promisereddid API

react context

context是由 react 原生的跨組件數據傳輸方案,其 API 包括 React.creatContext, Context.Provider, Context.Consumer,

creatContext

建立 context,能夠指定默認值,也可在初始頁面 fetch,咱們選擇基本的 createContext 建立方式,指定一個共享的對象 something 和一個更新改對象的方法,updateSomething()

context.js

import React from "react";
export const GlobalContext = React.createContext({
  user: {},
  updateUser() {},
  something: {},
  updateSomething: {}
});
複製代碼

在要共享的頁面提供該 context,把要共享的值傳出去, provider props,通常用於 dashsboard 頁面,讓子組件都能共享 provider.js

import {GlobalContext} from '/context'
    class page extends React.componet{
        updateUser() {
            return update
        }
        render() {
            const response  = this.fetch();
            return (
            <GlobalContext.Provider
                value={
                    user:this.response.user,
                    updateUser: this.updateUser
                }
            >
            <GlobalContext.Provider>
            )
        }
    }

複製代碼

子組件做爲消費者拿到 context consumer.js

import { GlobalContext } from "/context";
class page extends React.componet {
  render() {
    <div>111</div>;
  }
}
export default props => (
  <GlobalContext.Consumer>
    {({ user, updateUser }) => (
      <Resources {...props} user={user} updateUser={updateUser} />
    )}
  </GlobalContext.Consumer>
);
複製代碼
相關文章
相關標籤/搜索