基于最近的两个项目对 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>
);
复制代码