# Redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
# Redux 三大原则
# 单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
这让同构应用开发变得非常容易
# State 是只读的
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
# 使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers。
# Redux 基础 API
# Action
Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
# Reducer
Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。
# 纯函数
- 如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间 函数 外部任何状态或数据的变化,必须只依赖于其输入参数。
- 该 函数 不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变(mutation)
所以 永远不要在 reducer 里做这些操作:
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如 Date.now() 或 Math.random() 。
# 如何使用:
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
default:
return state
}
}
export default todos
# store
Store 就是把它们联系到一起的对象。Store 有以下职责:
- 维持应用的 state;
- 提供
getState()
方法获取 state; - 提供
dispatch(action)
方法更新 state; - 通过
subscribe(listener)
注册监听器; - 通过
subscribe(listener)
返回的函数注销监听器。
Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 (opens new window)而不是创建多个 store。
举个例子:
export default class ReduxPage extends Component {
componentDidMount() {
this.unsubscribe = store.subscribe(() => {
// store state 改变
this.forceUpdate();
});
}
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
add = () => {
store.dispatch({ type: "ADD" });
};
asyAdd = () => {
store.dispatch((dispatch, getState) => {
setTimeout(() => {
dispatch({ type: "ADD" });
console.log("getState", getState());
}, 1000);
});
};
promiseMinus = () => {
store.dispatch(
Promise.resolve({
type: "MINUS",
payload: 100,
})
);
};
render() {
return (
<div>
<h3>ReduxPage</h3>
<p>{store.getState()}</p>
{/* <p>{store.getState().home}</p> */}
<button onClick={this.add}>add</button>
<button onClick={this.asyAdd}>asyAdd</button>
<button onClick={this.promiseMinus}>promise minus</button>
</div>
);
}
}
# API 分解实现
# createStore
const store = createStore(
countReducer,
applyMiddleware(thunk, logger, promise)
);
从使用实例可以搭出函数框架
export default function createStore(reducer, enhancer) {
let currentState; // 选中的状态值,记录下方便获取
let currentListeners = []; // 选中的监听器,方便订阅和取消订阅
function getState() {}
function dispatch() {}
function subscribe() {}
return {
getState,
dispatch,
subscribe,
};
}
然后依次实现
getState 比较简单 直接返回选中值即可
function getState() {
return currentState;
}
dispatch 函数只是在 reducer 中找到对应的函数执行以后,进行对视图的通知更新
// add = () => {
// store.dispatch({ type: "ADD" });
// };
function dispatch(actions) {
//将reducer执行一遍,获取变化后的值
currentState = reducer(currentState, actions);
// 发布订阅模式 都是通过一个数组进行遍历通知视图进行更新
currentListeners.forEach((listener) => listener());
}
订阅函数 subscribe 则是标准的发布订阅函数,记得返回一个取消订阅的函数
//订阅函数
function subscribe(fn) {
currentListeners.push(fn);
// 返回一个取消订阅函数
return () => (currentListeners = []);
}
最后执行一次 dispatch 进行默认值的设定
dispatch({ type: "随机生成一段Type进行初始值设置" });
# applyMiddleware
顾名思义中间件,把 createStore 通过一轮 Middleware 增强一遍
if (enhancer) {
enhancer(createStore)(reducer);
}
这样我们就可以搭出如下框架
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer) => {
let store = createStore(reducer);
// 这是原版的dispatch,这个dispatch只能接受plain object,不能处理异步、promise
let dispatch = store.dispatch;
return {
...store,
// 加强版的dispatch
dispatch,
};
};
}
这里需要注意 ,我们传入了多个增强函数,需要一次执行增强同一个参数,like this
将参数向下传递,进行加强后,继续向下传递
function f1(arg) {
console.log("f1", arg);
return arg;
}
function f2(arg) {
console.log("f2", arg);
return arg;
}
function f3(arg) {
console.log("f3", arg);
return arg;
}
const res = f1(f2(f3("omg")));
console.log("res", res); //sy-log
// f3 omg
// f2 omg
// f1 omg
// res omg
但是这么写不免繁琐,所以就有了
# compose
也叫函数合成,执行顺序是越后面越内层,也就是越早执行
function compose(...funcs) {
if (!funcs.length) {
return (arg) => arg;
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
compose(f1, f2, f3)("omg");
// f3 omg
// f2 omg
// f1 omg
这样我们就可以开始使用中间件进行函数增强了
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer) => {
.....
const API = {
state: store.getState(),
dispatch: (actions, ...args) => store.dispatch(actions, ...args),
};
//将middlewares转化成 参数为 API 的函数数组
const middlewaresChain = middlewares.map((middleware) => middleware(API));
// 对 dispatch 进行增强
dispatch = compose(...middlewaresChain)(store.dispatch);
.....
};
}
# combineReducers
# 用法改变
<p>{store.getState()}</p>
=>
<p>{store.getState().home}</p>
const store = createStore(
// combineReducers用法
combineReducers({home: countReducer}),
applyMiddleware(thunk, logger, promise)
);
combineReducers 辅助函数的作用是
- 把一个由多个不同 reducer 函数作为 value 的 object,
- 合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore。
# 使用规则:
每个传入 combineReducers 的 reducer 都需满足以下规则:
所有未匹配到的 action,必须把它接收到的第一个参数也就是那个 state 原封不动返回。
永远不能返回 undefined。当过早 return 时非常容易犯这个错误,为了避免错误扩散,遇到这种情况时 combineReducers 会抛异常。
如果传入的 state 就是 undefined,一定要返回对应 reducer 的初始 state。根据上一条规则,初始 state 禁止使用 undefined。
使用 ES6 的默认参数值语法来设置初始 state 很容易,但你也可以手动检查第一个参数是否为 undefined。
# 实现:
export default function combineReducers(reducers) {
return function combination(state = {}, action) {
var hasChanged = false; // 做缓存的标记
var nextState = {};
// 循环reducer,如果是函数就进行执行
// 将执行完成的值,赋值给对象保存
for (let key in reducers) {
let reducer = reducers[key];
if (typeof reducer !== "function") {
break;
}
var previousStateForKey = state[key];
var nextStateForKey = reducer(previousStateForKey, action);
nextState[key] = nextStateForKey;
//判断值有没变化
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
//判断有没新增state
hasChanged =
hasChanged || Object.keys(nextState).length !== Object.keys(state).length;
return hasChanged ? nextState : state;
};
}
redux 内部的 API 我们已经全部实现了。
接下来 我们再通过实现几个中间件,加强下对 redux 中间件的理解。
# Middleware
首先我们从上面 applyMiddleware 实现中可以获得一些参数
function xxx( store ) {
return (next) => (action) => {
console.log(next);
/* dispatch(actions) {
currentState = reducer(currentState, actions);
currentListeners.forEach(listener => listener());
} */
console.log(action);
// {type: "MINUS", payload: 100}
return next(action);
};
}
那么就可以开始写功能了
# thunk
thunk 支持传入一个函数,只是把dispatch
, getState
向下传递
function thunk({ dispatch, getState }) {
return (next) => (action) => {
if (typeof action === "function") {
return action(dispatch, getState);
}
return next(action);
};
}
# Promise
支持 dispatch
传入一个 Promise
function promise({ dispatch }) {
return (next) => (action) => {
return isPromise(action) ? action.then(dispatch) : next(action);
};
}
# logger
每次值改变的时候进行一次控制台输出
function logger({ getState }) {
return (next) => (action) => {
console.log(next);
console.log("*******************************");
console.log(action.type + "执行了!");
let prevState = getState();
console.log("prev state", prevState);
const returnValue = next(action);
let nextState = getState();
console.log("next state", nextState);
console.log("*******************************");
return returnValue;
};
}
# React-redex
Redux (opens new window) 官方提供的 React 绑定库。 具有高效且灵活的特性。
# 如何使用
@connect(
// mapStateToProps
({count}) => ({count}),
// mapDispatchToProps object | function
{
add: () => ({type: "ADD"})
}
// dispatch => {
// let creators = {
// add: () => ({type: "ADD"}),
// minus: () => ({type: "MINUS"})
// };
// creators = bindActionCreators(creators, dispatch);
// return {
// dispatch,
// ...creators
// };
// }
)
class ReactReduxPage extends Component {
render() {
const {count, dispatch, add} = this.props;
console.log("pr", this.props);
return (
<div>
<h3>ReactReduxPage</h3>
<p>{count}</p>
<button onClick={() => dispatch({type: "ADD"})}>dispatch add</button>
<button onClick={add}>add</button>
</div>
);
}
}
@connect 接受两个参数 mapStateToProps
和 mapDispatchToProps
分别是 state
和 dispatch
的映射
← 红宝书第四版 React 更新策略 →