基於最近的兩個項目對 redux、 react-redux 和 react context 作總結html
redux 中有三大核心,分別是 store
、 reducer
、action
。其中 store 全局惟一,用於保存 app 裏的 state,reducer 控制 state 狀態,action 用於描述如何改變 state,compose 用於多個函數參數調用,middleware 中間件,以下所示:react
import {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
// __DO_NOT_USE__ActionTypes
} from "redux";
複製代碼
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 用於指定應用狀態的變化如何響應 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
...
在咱們建立 store 時候,store 會提供 dispath 函數,dispatch 會傳入一個帶 type 的 action,執行一個 listeners,並返回一個 action,dispatch 是惟一一個能夠改變 state 的方式。固然 redux 還支持 dispatch 個 promise, observable 等,你須要使用第三方中間件包裝你的 store。例如redux-thunk
react-router
function dispath(action) {
//check is plain object
//check action type
//check is dispatching
listener();
//call listener
return action;
}
dispatch({ type: ActionTypes.INIT });
複製代碼
既然咱們知道 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
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 就介紹完畢,然而咱們的項目通常都會比較複雜,這樣簡單的並不適用,故此咱們作些改造
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;
複製代碼
一樣與 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;
複製代碼
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;
}
複製代碼
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);
複製代碼
異步 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-promise
和reddid API
context
是由 react 原生的跨組件數據傳輸方案,其 API 包括 React.creatContext, Context.Provider, Context.Consumer
,
建立 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>
);
複製代碼