知识点
纯函数
- 如果函数的调用参数相同,则永远返回相同的结果。
- 它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
- 该函数不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变(mutation)。
Reduce
const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer)); // 没有初始值
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5)); //初始值为5
// expected output: 15
Reducer
- 概念:reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。
(previousState, action) => newState
- 之所以将这样的函数称之为 reducer,是因为这种函数与被传入 Array.prototype.reduce(reducer, ?initialValue) 里的回调函数属 于相同的类型。
- 保持 reducer 纯净非常重要,不要在 reducer修改传入参数,不要执行有副作用的操作,如api请求和跳转路由,不要调用非纯函数,如 Date.now() 或 Math.random()。
redux使用
简单的同步数据流动的场景
安装redux
yarn add redux
使用
- 需要一个store来存储数据
- store里的reducer初始化state并定义state修改规则
- 通过dispatch一个action来提交对数据的修改
- action提交到reducer函数里,根据传入的action的type,返回新的state。
创建store---src/store/index.js
import {createStore} from "redux";
function countReducer(state = 0, action) {
switch (action.type) {
case "ADD":
return state + 1;
case "MINUS":
return state - 1;
default:
return state;
}
}
const store = createStore(countReducer);
export default store;
创建ReduxPage
import React, {Component} from "react";
import store from "../store/";
export default class ReduxPage extends Component {
componentDidMount() {
store.subscribe(() => {
this.forceUpdate();
});
}
add = () => {
store.dispatch({type: "ADD"});
};
minus = () => {
store.dispatch({type: "MINUS"});
};
render() {
console.log("store", store); //sy-log
return (
<div>
<h3>ReduxPage</h3>
<p>{store.getState()}</p>
<button onClick={this.add}>add</button>
<button onClick={this.minus}>minus</button>
</div>
);
}
}
注意:如果点击按钮不能更新,查看是否订阅(subscribe)状态变更。
还可以在src/index.js的render里订阅状态变更
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import store from './store/'
const render = () => ReactDOM.render(<App />, document.getElementById("root"));
render()
store.subscribe(render)
检查点
- createStore创建store
- reducer初始化、修改状态函数
- getState获取状态值
- dispatch提交更新
- subscribe变更订阅
redux源码
核心实现
- 存储状态state
- 获取状态getState
- 更新状态dispatch
- 变更订阅subscribe
核心代码
export default function createStore(reducer, enhancer) {
if (enhancer) {
// 原版dispatch只能接受普通对象,加强之后变强大,可以处理多种形式,如callback、promise等
return enhancer(createStore)(reducer);
}
let currentState;
let currentListeners = [];
function getState() {
return currentState;
}
function dispatch(action) {
currentState = reducer(currentState, action);
currentListeners.forEach(listener => listener());
}
function subscribe(listener) {
currentListeners.push(listener);
return () => {
// 自己实现过滤
currentListeners = [];
};
}
// 主动调一次dispatch是为了返回初始值
dispatch({type: "REDUX/ZXB"});
return {
getState, //获取状态
dispatch, // 触发改变state
subscribe //订阅
};
}
异步
Redux只是个纯粹的状态管理器,默认只支持同步,实现异步任务 比如延 迟,网络请求,需要中间件的支持,,比如我们使用最简单的redux-thunk和 redux-logger,redux-promise等
中间件
- 中间件就是一个函数,对store.dispatch方法进行改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。
- redux 的 middleware 是为了增强 dispatch 而出现的
应用中间件
yarn add redux-thunk redux-logger
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import counterReducer from './counterReducer'
const store = createStore(counterReducer,applyMiddleware(thunk, logger));
- 实现applyMiddleware
// 第一步:函数式编程思想设计 middleware,
// middleware 的设计有点特殊,是一个层层包裹的匿名函数,
// 这其实是函数式编程中的柯里化 curry,
// 一种使用匿名单参数函数来实现多参数函数的方法。
// applyMiddleware 会对 logger 这个 middleware 进行层层调用,
// 动态地对 store 和 next 参数赋值。
export default function applyMiddleware(...middlewares) {
return createStore => reducer => {
const store = createStore(reducer);
let dispatch = store.dispatch;
const midApi = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
};
// 第二步:map让每一个middleware携带 midApi 这个参数分别执行一遍,获得 chain 数组,[f1, f2, ... , fx, ...,fn],
// 因为闭包,每个匿名函数都可以访问相同的 store,即 middlewareAPI。
const middlewareChain = middlewares.map(middleware => middleware(midApi));
// 第三部:组合串联 middlewares
// compose 是函数式编程中的组合
// compose 将 chain 中的所有匿名函数,[f1, f2, ... , fx, ..., fn],组装成一个新的函数,即新的 dispatch
// 当新 dispatch 执行时,[f1, f2, ... , fx, ..., fn],从左到右依次执行( 所以顺序很重要)
// dispatch被加强了
dispatch = compose(...middlewareChain)(store.dispatch);
return {
...store,
// 返回加强之后的dispatch
dispatch
};
};
}
// compose(...chain) 返回的是一个匿名函数,函数里的 funcs 就是 chain 数组
// 当调用 reduce 时,依次从 funcs 数组的右端取一个函数 fx 拿来执行
// fx 的参数 composed 就是前一次 fx+1 执行的结果
// 而第一次执行的fn(n代表chain的长度)的参数 arg 就是 store.dispatch。
// 所以当 compose 执行完后,我们得到的 dispatch 是这样的,假设 n = 3,,会得到:dispatch = f1(f2(f3(store.dispatch))))
// 这个时候调用新 dispatch,每个 middleware 的代码就依次执行了嘛
function compose(...funcs) {
if (funcs.length === 0) {
return args => args;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((composed, func) => (...args) => composed(func(...args)));
}
总结: applyMiddleware 机制的核心在于组合 compose,将不同的 middlewares 一层一层包裹到原生的 dispatch 之上,而为了方便进行 compose,需对 middleware 的设计采用柯里化 curry 的方式,达到动态产生 next 方法以及保持 store 的一致性。由于在 middleware 中,可以像在外部一样轻松访问到 store, 因此可以利用当前 store 的 state 来进行条件判断,用 dispatch 方法拦截老的 action 或发送新的 action。
- 实现redux-thunk
// !next就是聚合函数
function thunk({dispatch, getState}) {
return next => action => {
if (typeof action === "function") {
return action(dispatch, getState);
}
return next(action);
};
}
- 实现redux-logger
function logger({getState}) {
return next => action => {
console.log(action.type + "执行了!"); //sy-log
let prevState = getState();
console.log("prev state", prevState); //sy-log
// 执行next()之后,拿到新的state
const returnValue = next(action);
let nextState = getState();
console.log("next state", nextState); //sy-log
return returnValue;
};
}
- redux-promise
import isPromise from "is-promise";
function promise({dispatch}) {
return next => action => {
return isPromise(action) ? action.then(dispatch) : next(action);
};
}