状态管理:本质对一个[全局唯一、具有响应式]变量的管理
因为是全局的,那为了流转/使用上的不混乱/冲突等,所以会对其制定流转规则,让变化变得可预测。
Redux 基本原理(手撸简版)
createStore
/**
* 创建 Redux store
*
* @param reducers 用于处理 action 的 reducer 函数
* @param initialValue 初始状态值
* @returns 返回一个包含 getState、dispatch 和 subscribe 方法的对象
*/
export function createStore(reducers, initialValue) {
// 初始化状态为初始值
let state = initialValue;
// 存储监听器的数组
const listeners = [];
// 获取当前状态的方法
const getState = () => state;
// 订阅监听器的函数
const subscribe = (fn) => {
// 将监听器添加到监听器数组中
listeners.push(fn);
};
// 派发动作的函数
const dispatch = (action) => {
// 使用reducers函数计算出新的状态
const nextState = reducers(state, action);
// 更新状态为新的状态
state = nextState;
// 遍历监听器数组,并调用每个监听器函数
listeners.forEach((fn) => fn());
};
// 返回一个包含getState、dispatch和subscribe的对象
return { getState, dispatch, subscribe };
}
combineReduces
/**
* 组合多个 reducer 函数
*
* @param reducers 一个包含多个 reducer 函数的对象
* @returns 返回一个 reducer 函数,将多个 reducer 函数组合在一起
*/
export function combineReducers(reducers) {
// 获取所有reducer的键名
const keys = Object.keys(reducers);
// 返回一个新的reducer函数
return (state, action) => {
// 创建一个新的状态对象
const nextState = {};
// 遍历所有reducer的键名
keys.forEach((key) => {
// 获取对应键名的reducer函数
const reducer = reducers[key];
// 获取当前状态中对应键名的值
const prve = state[key];
// 调用reducer函数,传入当前值和action,得到下一个状态的值
const next = reducer(prve, action);
// 将下一个状态的值添加到新的状态对象中
nextState[key] = next;
});
// 返回新的状态对象
return nextState;
};
}
connect
/**
* 连接函数,用于将组件与 Redux store 连接起来
*
* @param mapStateToProps 将 Redux store 中的 state 映射到组件的 props
* @param mapDispatchToProps 将 Redux store 中的 dispatch 方法映射到组件的 props
* @returns 返回一个高阶组件,该高阶组件将传入的组件与 Redux store 连接起来
*/
export function connect(mapStateToProps, mapDispatchToProps) {
// 返回一个函数,该函数接收一个组件作为参数
return function (Component) {
// 返回一个函数,该函数接收props作为参数
return function (props) {
// 使用useContext钩子获取Redux的store
const store = useContext(ReduxContext);
// 使用useState钩子创建一个状态变量,并初始化为false
const [, setBool] = useState(false);
// 定义一个forceUpdate函数,用于强制更新组件
const forceUpdate = () => setBool((val) => !val);
// 在组件挂载后,将forceUpdate函数作为监听器订阅store的更新
useEffect(() => store.subscribe(forceUpdate), []);
// 返回一个JSX元素,该元素渲染传入的组件,并传入props和绑定的state和dispatch
return (
<Component
{...props}
{...mapStateToProps(store.getState())}
{...mapDispatchToProps(store.dispatch)}
/>
);
};
};
}
完整手写(可运行的)
src/store/redux.js
import { useEffect } from "react";
import { createContext, useContext, useState } from "react";
/**
* 创建 Redux store
*
* @param reducers 用于处理 action 的 reducer 函数
* @param initialValue 初始状态值
* @returns 返回一个包含 getState、dispatch 和 subscribe 方法的对象
*/
export function createStore(reducers, initialValue) {
// 初始化状态为初始值
let state = initialValue;
// 存储监听器的数组
const listeners = [];
// 获取当前状态的方法
const getState = () => state;
// 订阅监听器的函数
const subscribe = (fn) => {
// 将监听器添加到监听器数组中
listeners.push(fn);
};
// 派发动作的函数
const dispatch = (action) => {
// 使用reducers函数计算出新的状态
const nextState = reducers(state, action);
// 更新状态为新的状态
state = nextState;
// 遍历监听器数组,并调用每个监听器函数
listeners.forEach((fn) => fn());
};
// 返回一个包含getState、dispatch和subscribe的对象
return { getState, dispatch, subscribe };
}
/**
* 组合多个 reducer 函数
*
* @param reducers 一个包含多个 reducer 函数的对象
* @returns 返回一个 reducer 函数,将多个 reducer 函数组合在一起
*/
export function combineReducers(reducers) {
// 获取所有reducer的键名
const keys = Object.keys(reducers);
// 返回一个新的reducer函数
return (state, action) => {
// 创建一个新的状态对象
const nextState = {};
// 遍历所有reducer的键名
keys.forEach((key) => {
// 获取对应键名的reducer函数
const reducer = reducers[key];
// 获取当前状态中对应键名的值
const prve = state[key];
// 调用reducer函数,传入当前值和action,得到下一个状态的值
const next = reducer(prve, action);
// 将下一个状态的值添加到新的状态对象中
nextState[key] = next;
});
// 返回新的状态对象
return nextState;
};
}
export const ReduxContext = createContext();
/**
* 连接函数,用于将组件与 Redux store 连接起来
*
* @param mapStateToProps 将 Redux store 中的 state 映射到组件的 props
* @param mapDispatchToProps 将 Redux store 中的 dispatch 方法映射到组件的 props
* @returns 返回一个高阶组件,该高阶组件将传入的组件与 Redux store 连接起来
*/
export function connect(mapStateToProps, mapDispatchToProps) {
// 返回一个函数,该函数接收一个组件作为参数
return function (Component) {
// 返回一个函数,该函数接收props作为参数
return function (props) {
// 使用useContext钩子获取Redux的store
const store = useContext(ReduxContext);
// 使用useState钩子创建一个状态变量,并初始化为false
const [, setBool] = useState(false);
// 定义一个forceUpdate函数,用于强制更新组件
const forceUpdate = () => setBool((val) => !val);
// 在组件挂载后,将forceUpdate函数作为监听器订阅store的更新
useEffect(() => store.subscribe(forceUpdate), []);
// 返回一个JSX元素,该元素渲染传入的组件,并传入props和绑定的state和dispatch
return (
<Component
{...props}
{...mapStateToProps(store.getState())}
{...mapDispatchToProps(store.dispatch)}
/>
);
};
};
}
src/store/index.js
import { createStore, combineReducers, connect } from "./redux";
function countReducers(count, action) {
switch (action.type) {
case "addCount":
return count + 1;
default:
return count;
}
}
function infoReducers(info, action) {
switch (action.type) {
case "addAge":
return { ...info, age: info.age + 1 };
default:
return info;
}
}
const initialValue = {
count: 23,
info: {
name: "张三",
age: 27,
},
};
const reducers = combineReducers({ count: countReducers, info: infoReducers });
export const store = createStore(reducers, initialValue);
function mapStateToProps(state) {
return { count: state.count, info: state.info };
}
function mapDispatchToProps(dispatch) {
return {
addCount() {
dispatch({ type: "addCount" });
},
addAge() {
dispatch({ type: "addAge" });
},
};
}
export const connected = connect(mapStateToProps, mapDispatchToProps);
某个 .jsx 文件内
import React from "react";
import { store, connected } from "../../store";
import { ReduxContext } from "../../store/redux";
const Child = connected(({ count, info, addCount, addAge }) => {
return (
<div>
I am Child
<div>
<span>count:{count}</span>
<button onClick={addCount}>addCount</button>
</div>
<div>
<span>
info:{info.name},{info.age}
<button onClick={addAge}>addAge</button>
</span>
</div>
</div>
);
});
const Parent = () => (
<div>
I am Parent
<Child />
</div>
);
export default function Store() {
return (
<ReduxContext.Provider value={store}>
<Parent />
</ReduxContext.Provider>
);
}
Mobx 基本原理(手撸简版)
可以发现跟Vue
的响应式有点相似
// 具体实现:
let effect = null;
const deps = [];
function handle() {
// 返回一个对象,包含两个方法:get 和 set
return {
// get 方法用于获取目标对象的属性值
get(target, key, desc) {
// 如果存在 effect,则将其添加到 deps 数组中
if (effect) deps.push(effect);
// 调用 Reflect.get 方法获取目标对象的属性值
return Reflect.get(target, key, desc);
},
// set 方法用于设置目标对象的属性值
set(target, value, key, desc) {
// 调用 Reflect.set 方法设置目标对象的属性值
Reflect.set(target, value, key, desc);
// 遍历 deps 数组,依次执行其中的函数
deps.forEach((dep) => dep());
},
};
}
/**
* 遍历数据,递归处理对象和数组中的值
*
* @param data 要遍历的数据
* @returns 返回处理后的数据
*/
function walk(data) {
// 如果数据为空或者数据类型不是对象,则直接返回数据
if (data === null || typeof data !== "object") return data;
// 遍历对象的所有键值对
Object.entries(data).forEach(([key, value]) => {
// 递归调用 walk 函数,处理每个键值对的值,并将处理后的值重新赋值给对应的键
data[key] = walk(value);
});
// 使用 Proxy 对象包装数据,并调用 handle 函数处理代理对象
return new Proxy(data, handle());
}
/**
* 将给定的数据转换为可观察对象
*
* @param data 要转换的数据
* @returns 返回可观察对象
*/
function observable(data) {
// 调用 walk 函数处理 data 参数,并返回处理结果
return walk(data);
}
/**
* 自动运行函数
*
* @param _effect 需要运行的函数
*/
function autorun(_effect) {
// 将传入的参数_effect赋值给全局变量effect
effect = _effect;
// 调用全局变量effect对应的函数
effect();
// 将全局变量effect置为null
effect = null;
}
// 具体使用:
const data = { count: 1 };
const store = observable(data);
autorun(() => {
console.log("autorun store.count:", store.count);
});
store.count = 2; // 自动执行一次 autorun
store.count = 3; // 自动执行一次 autorun