本文从 redux 原理出发,一步步实现一个自己的 mini-redux,主要目的是了解其内部之间的各种关系,所以本篇不会讲解太多关于 redux 的用法
redux 是一种可预测的状态管理库,在 react 中,它解决的是多个组件之间的通信问题
在没有使用 redux 的情况下,如果两个组件(非父子关系)之间需要通信的话,可能需要多个中间组件来为他们进行消息传递,这样既浪费了资源,代码也会变得更复杂
redux 提出了单一数据源 store 来存储状态数据,所有的组件都可以通过 action 来修改 store,也可以从 store 中获取最新状态。使用了 redux 就可以完美解决组件之间的通信问题
意思是整个 react 项目里的 state 都存放在一起,单一数据源主要是为了解决状态一致性的问题
在传统的 MVC 架构中,需要创建无数个 Model,而 Model 之间可以互相监听、触发事件甚至循环或嵌套触发事件,这些在 redux 中都是不允许的
在 redux 的思想里,一个应用永远只有唯一的数据源,这个设计也是有一些好处的,对于开发者来说,它可以更容易调试和观察状态的变化
也不用担心数据源对象过于庞大的问题,redux 提供的 combineReducers 函数可以解决这个问题
这里说的状态,指的是上面说的存放在 store 中的状态数据,你不能直接对其中的状态数据进行改动,只能间接的通过发送 action 来改动状态。间接的改动状态,这是一个很关键的设计,也是单向数据流的重点之一,对于每个动作的发生,最终会影响到什么状态上的改动,一个接一个的执行顺序等等,都是可预测的
纯函数的概念:函数的返回结果只依赖其参数,并且执行过程中不会产生副作用
在 redux 中,我们通过定义 reducer 来更改状态,每个 reducer 都是纯函数,这意味着它没有副作用,相同的输入必定有相同的输出
ps:修改外部的变量、调用 DOM API 修改页面,发送 Ajax 请求,调用 window.reload 刷新浏览器甚至是console.log 打印数据,都是副作用
就问你纯不纯
store 是存储数据的地方,它是一个对象,有这么几个方法
action 可以理解为操作数据的行为
action 一般的写法如下:
const add = (val) => {
return {
type: 'ADD',
value: val
}
}
通过 type 去定义这个 action 是干嘛的,在 reducer 中要进行什么操作
dispatch 的作用就是派发一个 action,让 reducer 进行数据的处理
一般写法:
dispatch({
type: 'ADD',
value: 1
})
reducer 里是真正更改数据的地方,dispatch 派发的 action 最终由 reducer 来进行数据的处理,并且每次的更改都是返回新的 state,这样做的目的是为了让 state 变的可预测
在创建 store 的时候 createStore 可以传入三个参数,第三个参数就是中间件,使用 redux 提供的 applyMiddleware 方法来调用,applyMiddleware 等于是对 dispatch 进行了增强,这样的话,在 dispatch 的过程中可以做一些其他的事情,比如记录 state 的变化、异步请求等等
redux 的核心,就是 createStore 这个函数,store、getState、dispatch 都是这个函数返回的
redux 的大致原理就是发布订阅模式:通过 dispatch 派发 action 更改 store,通过 subscribe 订阅 store 的变化,去更新对应的 view
用过 createStore 方法的都知道,创建一个 store 需要三个参数
/**
* 创建 store
* @param {*} reducer
* @param {*} initState 初始 state
* @param {*} enhancer 中间件
*/
const createStore = (reducer, initState, enhancer) => {
}
这个函数会返回几个功能函数
/**
* 创建 store
* @param {*} reducer
* @param {*} initialState 初始 state
* @param {*} enhancer 中间件
*/
const createStore = (reducer, initialState, enhancer) => {
return {
getState,
dispatch,
subscribe,
replaceReducer
}
}
下面来实现这几个方法
getState 方法的作用就是返回当前的 state
let currentState; // 当前 state
/**
* 返回最新的 state
*/
const getState = () => {
return currentState;
};
subscribe 的作用是订阅 state 的变化,使用者通过这个方法订阅,当 state 变化后,执行监听函数subscribe 是一个高阶函数,它的返回值一个函数,执行该函数可以移除当前的监听函数
let subQueue = []; // 创建一个监听队列
/**
* 监听 state 的变动
* @param {*} listener 数据变化时要执行的函数
*/
const subscribe = (listener) => {
// 把监听函数放入监听队列里
subQueue.push(listener);
// 移除监听事件
return () => {
subQueue = subQueue.filter((l) => l !== listener);
};
};
dispatch 方法接收一个 action 参数,来执行 reducer,执行完成后会执行所有的监听函数
let currentReducer = reducer;
let isDispatch = false;
/**
* 派发 action 并执行所有 监听函数
* @param {*} action
*/
const dispatch = (action) => {
// 这里使用 isDispatch 做标识,上一个处理完成后才能处理下一个
if(isDispatch) {
throw new Error('dispatching')
}
try {
currentState = currentReducer(currentState, action)
isDispatch = true;
} finally {
isDispatch = false;
}
// 执行所有监听函数
subQueue.forEach((listener) => listener())
return action
}
replaceReducer 的作用就是替换当前 reducer,执行 createStore 的时候,会接收一个默认的 reducer,如果后面想要重新换一个,就需要用到 replaceReducer 了
/**
* 替换 reducer
* @param {*} reducer
*/
const replaceReducer = (reducer) => {
// 直接把新的 reducer 覆盖掉旧的就行了
currentReducer = reducer;
// 替换之后派发一次 dispatch
dispatch({ type: 'MINI_REDUX_REPLACE' });
};
替换之后派发一次 dispatch 的目的是初始化一下新的 reducer
要想理解并实现中间件,内容还是蛮多的,所以本篇先不写中间件相关的内容
/**
* 创建 store
* @param {*} reducer
* @param {*} initialState 初始 state
* @param {*} enhancer 中间件
*/
const createStore = (reducer, initialState, enhancer) => {
let currentState; // 当前 state
let subQueue = []; // 创建一个监听队列
let currentReducer = reducer;
let isDispatch = false;
if (initialState) {
currentState = initialState;
}
/**
* 返回最新的 state
*/
const getState = () => {
return currentState;
};
/**
* 监听 state 的变动
* @param {*} listener 数据变化时要执行的函数
*/
const subscribe = (listener) => {
// 把监听函数放入监听队列里
subQueue.push(listener);
// 移除监听事件
return () => {
subQueue = subQueue.filter((l) => l !== listener);
};
};
/**
* 派发 action 并执行所有 监听函数
* @param {*} action
*/
const dispatch = (action) => {
// 这里使用 isDispatch 做标识,上一个处理完成后才能处理下一个
if (isDispatch) {
throw new Error('dispatching');
}
try {
currentState = currentReducer(currentState, action);
isDispatch = true;
} finally {
isDispatch = false;
}
// 执行所有监听函数
subQueue.forEach((listener) => listener());
return action;
};
/**
* 替换 reducer
* @param {*} reducer
*/
const replaceReducer = (reducer) => {
if (reducer) {
// 直接把新的 reducer 覆盖掉旧的就行了
currentReducer = reducer;
}
// 替换之后派发一次 dispatch
dispatch({ type: 'MINI_REDUX_REPLACE' });
};
return {
getState,
dispatch,
subscribe,
replaceReducer,
};
};
export default createStore;
要想在项目中跑起来,光实现一个 createStore 是不够的
createStore 建造了一个仓库,还需要配送点(Provider)和送货员(connect)才能到用户(组件)手里
首先,我们需要清楚他们三者之间的职责:
createStore:生成 store,返回一系列功能函数
Provider:把 createStore 返回的一系列函数传递到每个子组件里
connect:把 store 里的数据关联到组件上
Provider 的主要作用就是把 store 里的数据传递下去
// Provider.jsx
import React, { createContext } from 'react';
export const StoreContext = createContext(null);
const Provider = (props = {}) => {
const { store, children } = props;
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
};
export default Provider;
connect 是一个高阶组件,第二个参数为需要关联数据的组件,返回一个新组件
connect 的作用就是把 store 的数据关联到对应组件里,并监听 store 的变化,数据变化后更新对应组件
// connect.jsx
import React, { useContext, useEffect, useState } from 'react';
import { StoreContext } from './Provider';
const connect = (mapStateToProps, mapDispatchToProps) => (WrapComponent) => {
const ConnectComponent = () => {
const { getState, dispatch, subscribe } = useContext(StoreContext);
const [props, setProps] = useState({
getState,
dispatch,
});
let stateToProps;
let dispatchToProps;
const update = () => {
if (mapStateToProps) {
stateToProps = mapStateToProps(getState());
}
if (mapDispatchToProps) {
dispatchToProps = mapDispatchToProps(dispatch);
}
setProps({
...props,
...stateToProps,
...dispatchToProps,
});
};
useEffect(() => {
update();
subscribe(() => update());
}, []);
return <WrapComponent {...props} />;
};
return ConnectComponent;
};
export default connect;
一个基础版的 mini redux 就实现完了,有空了把中间件相关的东西输出一下
这是我学习完相关内容之后输出的一个笔记,有写的不对的地方,还请各位铁汁指正 抱拳了老铁
体验在线 demo:点我点我点我
github:https://github.com/isxiaoxin/mini_redux
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。