手写傻瓜式 React 全家桶之 Reduxjavascript
手写傻瓜式 React 全家桶之 React-Reduxjava
本文代码react
A Predictable State Container for JS Apps(JS应用的可预测状态容器)git
可预测:实际上指的是纯函数(每一个相同的输入,都会有固定输出,而且没有反作用)这个是由 reducer 来保证的,同时方便了测试github
状态容器: 在 web 页面中,每一个 DOM 元素都有本身的状态,好比弹出框有显示与隐藏两种状态,列表当前处于第几页,每页显示多少条就是这个列表的状态,存储这些状态的对象就是状态容器。web
虽然说 Redux 是 React 全家桶的一员,但实际上 Redux 与 React 没有必然联系,它能够应用在任何其余库上,只是跟 React 搭配比较火。 redux
接下来分别讲解下:设计模式
就是 React 组件,也就是 UI 层markdown
管理数据的仓库,对外暴露些 APIapp
let store = createStore(reducers);
复制代码
有如下职责:
getState()
方法获取 state;dispatch(action)
方法更新 state;subscribe(listener)
注册监听器;subscribe(listener)
返回的函数注销监听器。action 就是个动做,在组件里经过 dispatch(action)
来触发 store 里的数据更改
action 只是个指令,告诉 store 要进行怎样的更改,真正更改数据的是 reducer。
默认 React 传递数据只能自上而下传递,而下层组件要向上层组件传递数据时,须要上层组件传递修改数据的方法到下层组件,当项目愈来愈大时,这种传递方式会很杂乱。
而引用了 Redux,因为 Store 是独立于组件,使得数据管理独立于组件,解决了组件间传递数据困难的问题
定义个 store 容器文件,根据 reducer 生成 store
import { createStore } from "redux";
const counterReducer = (state = 0, { type, payload = 1 }) => {
switch (type) {
case "ADD":
return state + payload;
case "MINUS":
return state - payload;
default:
return state;
}
};
export default createStore(counterReducer);
复制代码
在组件中
import React, { Component } from "react";
import store from "../store";
export default class Redux extends Component {
componentDidMount() {
this.unsubscribe = store.subscribe(() => {
this.forceUpdate();
});
}
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
add = () => {
store.dispatch({ type: "ADD", payload: 1 });
};
minus = () => {
store.dispatch({ type: "MINUS", payload: 1 });
};
render() {
return (
<div className="border"> <h3>加减器</h3> <button onClick={this.add}>add</button> <span style={{ marginLeft: "10px", marginRight: "10px" }}> {store.getState()} </span> <button onClick={this.minus}>minus</button> </div>
);
}
}
复制代码
经过上面的讲解也能够看到,其实主要的就是 createStore 函数,该函数会暴露 getState,dispatch,subScribe 三个函数 因此先搭下架子,建立 createStore.js
文件
export default function createStore(reducer) {
let currentState;
// 获取 store 的 state
function getState() {}
// 更改 store
function dispatch() {}
// 订阅 store 更改
function subscribe() {}
return {
getState,
dispatch,
subscribe,
};
}
复制代码
接着完善下各方法
返回当前的 state
function getState() {
return currentState
}
复制代码
接收 action,并更新 store,经过谁更新的呢: reducer
// 更改 store
function dispatch(action) {
// 将当前的 state 以及 action 传入 reducer 函数
// 返回新的 state 存储在 currentState
currentState = reducer(currentState, action);
}
复制代码
做用: 订阅 state 的更改
如何作: 采用观察者模式,组件方监听 subscribe ,并传入回调函数,在 subscribe 里注册回调,并在 dispatch 方法里触发回调
let curerntListeners = [];
// 订阅 state 更改
function subscribe(listener) {
curerntListeners.push(listener);
return () => {
const index = curerntListeners.indexOf(listener);
curerntListeners.splice(index, 1);
};
}
复制代码
dispatch 方法在更新数据以后,要执行订阅事件。
// 更改store
function dispatch(action) {
// store里面数据就更新了
currentState = reducer(currentState, action);
// 执行订阅事件
curerntListeners.forEach(listener => listener());
}
复制代码
将上面计数器里的 redux 改为引用手写的 redux,会发现页面没有最初值
因此在 createStore 里加上 dispatch({ type: "kkk" });
给 state 赋初始值,要注意传入的这个 type 要进入 reducer 函数的 default 条件
完整代码以下:
export default function createStore(reducer) {
let currentState;
let curerntListeners = [];
// 获取 store 的 state
function getState() {
return currentState;
}
// 更改 store
function dispatch(action) {
// 将当前的 state 以及 action 传入 reducer 函数
// 返回新的 state 存储在 currentState
currentState = reducer(currentState, action);
// 执行订阅事件
curerntListeners.forEach((listener) => listener());
}
// 订阅 state 更改
function subscribe(listener) {
curerntListeners.push(listener);
return () => {
const index = curerntListeners.indexOf(listener);
curerntListeners.splice(index, 1);
};
}
dispatch({ type: "kkk" });
return {
getState,
dispatch,
subscribe,
};
}
复制代码
你们也能够自行查看下 Redux 里的 createStore 源码
说到 Redux,那就不得不提中间件,由于自己 Redux 能作的东西颇有限,好比须要 redux-thunk 来达到异步调用,redux-logger 记录日志等。 中间件就是个函数,在组件发出 Action 和执行 Reducer 这两步之间,添加其余功能,至关于增强 dispatch。
开发中间件是有模板代码的
export default store => next => action => {}
复制代码
好比模拟写个 logger 中间件
function logger(store) {
return (next) => {
return (action) => {
console.log("====================================");
console.log(action.type + "执行了!");
console.log("prev state", store.getState());
next(action);
console.log("next state", store.getState());
console.log("====================================");
};
};
}
export default logger;
复制代码
// 在createStore的时候将applyMiddleware做为第二个参数传进去
const store = createStore(
reducer,
applyMiddleware(logger)
)
复制代码
能够看出是经过 createStore 的第二个参数来实现的,这个参数官方称为 enhancer。 enhancer 是个参数为 createStore 的函数,并返回个新的 createStore 函数
function enhancer (createStore) {
return function (reducer) {
var store = createStore(reducer);
var dispatch = store.dispatch;
function _dispatch (action) {
if (typeof action === 'function') {
return action(dispatch)
}
dispatch(action);
}
return {
...store,
dispatch: _dispatch
}
}
}
复制代码
实现上 createStore 总共是有三个参数,除了第一个 reducer 参数是必传的以外,第二个 state 初始值,以及第三个 enhancer 都是可选的
下面咱们在手写的 createStore 里加入 enhancer 参数, 以及手写下 applyMiddleware 函数
function createStore(reducer,enhancer) {
// 判断是否存在 enhancer
// 若是存在而且是个函数, 则将 createStore 传递给它, 不是函数则抛出错误
// 它会返回个新的 createStore
// 传入 reducer ,执行新的 createStore,返回 store
// 返回该 store
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('enhancer必须是函数')
}
return enhancer(createStore)(reducer)
}
// 没有 enhancer 走原先的逻辑
// 省略
}
复制代码
按上面的分析,applyMiddleware 函数,会接收中间件函数,并返回个 enhancer,因此基本结构为
export default function applyMiddleware(...middlewares) {
// applyMiddleware 的返回值应该是一个 enhancer
// enhancer 是个接收 createStore 为参数的函数
return function (createStore) {
// enhancer 要返回一个新的 createStore
return function newCreateStore(reducer) {};
};
}
复制代码
看下 logger 中间件的结构,
function logger(store) {
return (next) => {
return (action) => {
console.log("====================================");
console.log(action.type + "执行了!");
console.log("prev state", store.getState());
next(action);
console.log("next state", store.getState());
console.log("====================================");
};
};
}
export default logger;
复制代码
完善下 applyMiddleware
export default function applyMiddleware(middleware) {
// applyMiddleware 的返回值应该是一个 enhancer
// enhancer 是个接收 createStore 为参数的函数
return function (createStore) {
// enhancer 要返回一个新的 createStore
return function newCreateStore(reducer) {
// 建立 store
let store = createStore(reducer);
let dispatch = store.dispatch;
// dispatch 属性必定要写成这种形式,不能直接将 store.dispatch 传入
// 由于有多个中间件时, dispatch 的值是要获取上一个中间件增强后的 dispatch
// 这种传递方式有效,是因为 dispatch 是引用类型
const midApi = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args),
};
// 传入 store 执行中间件的第一层函数
const chain = middleware(midApi);
// 将原始的 dispatch 函数做为 next 参数传给 chain,调用中间件的第二层函数
// 返回增强的 dispatch 覆盖原先的 dispatch
dispatch = chain(dispatch);
return {
...store,
dispatch,
};
};
};
}
复制代码
测试: 是能够正常打印出日志的
上面已经在 applyMiddleware 函数里处理只有一个中间件的状况,那多个的场景呢?
首先咱们再模拟写个 redux-thunk 中间件
默认 redux 只支持同步,而且参数只能是对象形式,redux-thunk 要实现的是你传入一个函数时,我就直接执行该函数,异步操做代码写在传递过来的函数里,若是传递过来的是个对象,则调用下一个中间件
function thunk({ getState, dispatch }) {
return next => {
return action => {
// 若是是个函数,则直接执行,并传入 dispatch 与 getState
if (typeof action == 'function') {
return action(dispatch, getState)
}
next(action)
}
}
}
复制代码
如今要去依次执行各个中间件,要如何依次执行呢?就得采用柯里化,首先写个 compose 函数
function compose(...funs) {
// 没有传递函数时,则返回参数透传函数
if (funs.length === 0) {
return (arg) => arg
}
// 传递一个函数时,则直接返回该函数,省去了遍历
if (funs.length === 1) {
return funs[0]
}
// 传递多个时,则采用 reduce,进行合并
// 好比执行 compose(f1,f2,f3) 则会返回 (...args) => f1(f2(f3(...args)))
return funs.reduce((a, b) => {
return (...args) => {
return a(b(...args))
}
})
}
复制代码
applyMiddleware 函数支持多个中间件:
export default function applyMiddleware(...middlewares) {
// applyMiddleware 的返回值应该是一个 enhancer
// enhancer 是个接收 createStore 为参数的函数
return function (createStore) {
// enhancer 要返回一个新的 createStore
return function newCreateStore(reducer) {
// 建立 store
let store = createStore(reducer);
let dispatch = store.dispatch;
// dispatch 属性必定要写成这种形式,不能直接将 store.dispatch 传入
// 由于有多个中间件时, dispatch 的值是要获取上一个中间件增强后的 dispatch
// 这种传递方式有效,是因为 dispatch 是引用类型
const midApi = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args),
};
// 调用中间件的第一层函数 传递阉割版的 store 对象
const middlewareChain = middlewares.map((middle) => middle(midApi));
// 用 compose 获得一个组合了全部中间件的函数
const middleCompose = compose(...middlewareChain);
// 将原始的 dispatch 函数做为参数逐个调用中间件的第二层函数
// 返回增强的 dispatch 覆盖原先的 dispatch
dispatch = middleCompose(dispatch);
return {
...store,
dispatch,
};
};
};
}
复制代码
验证:
import { createStore } from "../kredux";
import logger from "../kredux/middlewares/logger";
import thunk from "../kredux/middlewares/thunk";
import applyMiddleware from "../kredux/applyMiddleware";
const counterReducer = (state = 0, { type, payload = 1 }) => {
switch (type) {
case "ADD":
return state + payload;
case "MINUS":
return state - payload;
default:
return state;
}
};
export default createStore(counterReducer, applyMiddleware(thunk, logger));
复制代码
并将 add 函数更改为异步触发 dispatch
add = () => {
// store.dispatch({ type: "ADD", payload: 1 });
store.dispatch(function (dispatch) {
setTimeout(() => {
dispatch({ type: "ADD", payload: 2 });
}, 1000);
});
};
复制代码
当业务逻辑复杂时,不可能都写在一个 reducer 里,这时就得使用 combineReducers 将几个 reducer 组合起来。
再添加个 userReducer:
const userReducer = (state = { ...initialUser }, { type, payload }) => {
switch (type) {
case "SET":
return { ...state, ...payload };
default:
return state;
}
};
复制代码
引入 combineReducers ,该函数接收个对象,key 为标识,value 为每一个 reducer
export default createStore(
combineReducers({ count: counterReducer, user: userReducer }),
applyMiddleware(thunk, logger)
);
复制代码
根据上面的分析,手写个 combineReducers ,它要返回个 reducer 函数,reducer 函数天然是要接收 state 跟 action 并返回新的 state
export default function combineReducers(reducers) {
return function reducer(state = {}, action) {
let nextState = {};
// 遍历全部的 reducers,并依次触发返回新的 state
for (let key in reducers) {
nextState[key] = reducers[key](state[key], action);
}
return nextState;
};
}
复制代码
export default store => next => action => {}
的函数