最近在网上看了很多关于Redux的文章,感觉很奇怪哈,为什么这么简单的东西会被很多人讲的那么复杂。官方对Redux的描述有以下几个特点:可预测
、可调试
、集中管理
、灵活
、单一数据流
等等。这都能理解,毕竟营销嘛,看着就是一乐,谁开发了一个库,都会去有的没的说一些伪优点。在经历了长期的正反思考后,我得出了以下几个结论,希望能够对你有用:
- 如果一个博主不能将复杂的理论简单化,那他一定没懂这个理论。因为再复杂的理论也是由单个知识点串起来的,所以一定不存在永远都搞不懂的理论,这只是 精力问题 + 学习方式问题。
- 前端框架层出不穷,像redux这种第三方库就更不用提了,更新的频率只会越来越快。越是这种时候,就越考验一个人的洞察能力。就比如 状态管理,你觉着有必要使用第三方库吗?第三方库的衍生品你觉着有必要使用吗?所以在这种大环境下,对技术leader的要求就比较高,合格的技术leader更是一票难求。
- 多读源码,多写code。眼高不手低了,洞察问题的能力自然就上去了。
接下来,我们来看看如何写出一个状态管理。
需求
一想到状态管理,其实需求就是 有一个地方,可以保存我们的状态,在有能力改变状态的同时,我们还要感知到它的变化
。
这不就是所有的状态管理的库都会去做的事情嘛,剩下的就只是实现的方式不一样,扩展的能力不一样而已。
如何存储状态
2个思路,分别如下:
- 维护全局变量。
- 闭包。
在这里我们使用闭包的形式来实现:
javascript
function CustomCreateStore(){
let state = undefined;
let getState = () => {
return state;
}
return {
getState
}
}
export CustomCreateStore;
有了上面的方法,我们就可以使用下面的代码来访问state了。
javascript
CustomCreateStore().getState();
规范state
个人认为这一步是很有必要,不能用户放进来啥我们就存啥,想要用我的库,就必须要遵守我的原则。我们希望用户可以用object的形式去保存它想要用到的变量。最简单的方式就是让用户把状态传进来。
所以改造如下:
javascript
function CustomCreateStore(obj){
if( Object.prototype.toString.call(obj) !== '[object Object]' ){
throw new Error('CustomCreateStore的参数必须是个对象');
}
let state = obj;
let getState = () => {
return state;
}
return {
getState
}
}
我们现在可以随心所欲的定义initState了。
javascript
CustomCreateStore({count: 1}).getState();
连接state与reducer
最开始讲需求的时候,我们就说过,一个状态管理库要包含2个能力,分别是改变状态的能力
、监听状态改变的能力
。
想要实现改变状态的能力,我们可以做一个桥梁,这个桥梁的作用是连接着用户的initState、保存用户改变state的方法
。具体思路如下:
如何实现用户派发一个通知,然后状态管理库就知道了用户派发通知了呢?
其实很简单,我们只需要给用户暴露一个方法,只要用户调用了这个方法,状态管理库就认为你要改变状态了。
紧接着,我们如何根据通知的类型,去执行相应的更新呢?
我觉着用户实现这一步就好了。也就是库收到了这个通知,然后去执行这个通知对应的方法
,至于通知是什么,通知对应的方法是什么,统统不管,我只负责收到和触发。如下:
jsx
let store = CustomCreateStore({count: 1});
// 通知对应的方法
function executeClickAdd(){
return ...
}
// 点击按钮触发
const clickAddButton = () => {
// 派发一个通知
store.dispatch({
type: 'click-add'
})
}
<button onclick={ clickAddButton }> +1 </button>
那问题来了,状态管理库如何将 通知click-add
与 executeClickAdd函数
联系起来?我们可以再随意一点,我哪知道你是哪个通知对应哪个方法,用户去自己想办法吧。
此时用户愤怒到了极点,但又觉得它说的有道理,所以用户维护了一个配置:
javascript
function ActionToExecuteOfConfig(state, action){
switch(action.type){
case 'click-add':
return state + 1;
default:
return state;
}
}
于是用户将上述方案交给了状态管理,并且对狠狠的说了一句,你必须要将当前state返回给我,这样我才能依据当前的state去得到下一步的state。状态管理库看到这样的配置就乐了,正符合我意,于是连夜改了createStore方法。
javascript
function CustomCreateStore(obj, fn){
if( Object.prototype.toString.call(obj) !== '[object Object]' ){
throw new Error('CustomCreateStore的参数必须是个对象');
}
let state = obj;
let getState = () => {
return state;
}
// 新增++++++
let ActionToExecuteOfConfig = fn;
let dispatch = (action) => {
state = ActionToExecuteOfConfig(state, action);
}
return {
getState,
dispatch
}
}
用户看到这样的方案后,也连夜修改了业务代码如下:
jsx
function ActionToExecuteOfConfig(state, action){
switch(action.type){
case 'click-add':
return state + 1;
default:
return state;
}
}
let store = CustomCreateStore({count: 1}, ActionToExecuteOfConfig);
let curData = store.getState();
// 点击按钮触发
const clickAddButton = () => {
// 派发一个通知
store.dispatch({
type: 'click-add'
})
curData = store.getState();
}
<button onclick={ clickAddButton }> +1 </button>
<div>当前的值是:{ curData.count }</div>
点击button按钮后,页面上的数据也跟着+1了,用户露出了慈祥的笑容。
监听状态的改变
有了上面的基础,相信这个肯定难不住你们,subscribe的时候将callback push到listener队列里,最后在dispatch的末尾将listener里的callback全部依次执行即可。
jsx
function CustomCreateStore(obj, fn){
if( Object.prototype.toString.call(obj) !== '[object Object]' ){
throw new Error('CustomCreateStore的参数必须是个对象');
}
let state = obj;
let listener = [];
let getState = () => {
return state;
}
// 新增++++++
let ActionToExecuteOfConfig = fn;
let dispatch = (action) => {
state = ActionToExecuteOfConfig(state, action);
// 执行监听函数
for (let index = 0; index < listener.length; index++){
listener[index](state);
}
}
// 新增++++++
let subscribe = (callback) => {
listener.push(callback);
return function unsubscribe(){
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
return {
getState,
dispatch,
subscribe
}
}
用户的代码修改如下:
jsx
// 其余代码不变
let store = CustomCreateStore({count: 1}, ActionToExecuteOfConfig);
store.subscribe(
() => {
console.log('store发生变化了');
}
);
// 其余代码不变
此时用户再点击button +1时,发现我们注册的listener生效了。
改造state
随着应用的越来越大,做这个项目的人越来越多,因为所有的case都写在了一个ActionToExecuteOfConfig函数里,这就避免不里冲突了。你影响我,我影响你的。所以用户又给状态管理提了一个需求,你要给我想办法,我们不想所有的case都写在一个函数里,限你下班之前给我这个需求写出来,要不然我就不用你了。
于是状态管理就想了一个招,我给每个initState都动态分配一个名称name吧,在createStore的时候,我支持你传入一个对象,但是这个对象我有一个条件,value必须是用户处理action的函数。如果你想要访问自己注册的initState,就必须通过store.getState().name来访问,而这个name的值就是你的处理action得函数名。具体做法如下:
javascript
// 新增combineReducers函数 ++++++++++++++++++++++
function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i];
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers);
return function combination(state = {}, action) {
let hasChanged = false;
const nextState = {};
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i];
const reducer = finalReducers[key];
const previousStateForKey = state[key];
const nextStateForKey = reducer(previousStateForKey, action);
nextState[key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
}
}
// CustomCreateStore函数修改如下:
function CustomCreateStore(fn){
if( Object.prototype.toString.call(obj) !== '[object Object]' ){
throw new Error('CustomCreateStore的参数必须是个对象');
}
let state = obj;
let listener = [];
let ActionToExecuteOfConfig = fn;
// 获取initState
let getState = () => {
return state;
}
// 派发通知
let dispatch = (action) => {
state = ActionToExecuteOfConfig(state, action);
// 执行监听函数
for (let index = 0; index < listener.length; index++){
listener[index](state);
}
}
// store的监听函数
let subscribe = (callback) => {
listener.push(callback);
return function unsubscribe(){
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
// 初始化状态
// 此时会执行combination函数,这个函数主要就是将所有的key对应的函数都执行一遍,最后再将key,key对应的value全部都拼到一个新的对象里
dispatch({ type: 'initState-996' })
return {
getState,
dispatch,
subscribe
}
}
用户修改业务代码如下:
javascript
let store = CustomCreateStore(
combineReducers({
count: () => {},
number: () => {}
})
);
// 访问count下的initState
let count = store.getState().count.xxx;
// 访问number下的initState
let number = store.getState().number.xxx;
最后,经过这么一顿折腾,用户露出了满意的笑容。
最后
好啦,本篇文章到这里也就结束啦,希望我的文章对您有帮助,2023年12月16日,23点39分,我们下期再见啦,拜拜~~