状态管理
关于react 的状态管理
Provider + Context(useContext)
一个完整的例子
import React, {
createContext,
Dispatch,
SetStateAction,
useState,
} from 'react';
export interface IState {
theme: string;
color: string;
}
interface Props {
count: number;
setCount: Dispatch<SetStateAction<number>>;
}
const Context = createContext<Props>({ count: 0, setCount: () => {} });
const Example = () => {
console.log('Example');
const [count, setCount] = useState(0);
return (
<Context.Provider value={{ count, setCount }}>
<button
onClick={() => {
setCount(v => v + 1);
}}
>
add
</button>
<Component2 />
</Context.Provider>
);
};
function Component2() {
console.log('Component2');
return (
<>
<h1>Component 2</h1>
<Component3 />
</>
);
}
function Component3() {
console.log('Component3');
return (
<>
<h1>Component 3</h1>
<Component4 />
</>
);
}
function Component4() {
console.log('Component4');
const context = React.useContext(Context);
return (
<>
<h1>{context.count}</h1>
<button
onClick={() => {
context.setCount(v => v + 1);
}}
>
add
</button>
</>
);
}
export default Example;
context 可以实现基础的状态共享。步骤:
- 通过createContext创建一个Context,并设置默认值。
- Context 中的值都在 Provider 的作用域下有效。所以在最外层包一个 Provider,value中设置传递值。
- 如果子组件需要时,则通过 React.useContext拿到Context值。
缺点:
- 重复渲染,性能差
当 Context 中的某个属性发生变化时,所有使用该 Context 的子组件都会被重新渲染,即使某些组件可能并未使用到这个属性。
这种问题有两种解决方法:
React.memo 和 shouldComponentUpdate 这两种方式来解决。
将一个大型的 Context 拆分成多个小的 Context,可以精细地控制每个 Context 的更新,从而减少整个应用程序的重新渲染次数,提高性能和用户体验。
- 数据变更方法难以维护
为了保证数据变更方法的可维护性与 action 的不变性,有两种方法但各有问题:使用自定义 hooks,但为了不会重复渲染,每个修改方法需要使用useCallback,需要声明依赖,会造成极大的心智负担。使用useReducer,不支持异步函数、不支持内部的 reducer 互相调用,不支持和其他 state 联动(比如要当参数传进去才可用)。
- 使用范围限制
context的store只能在react中使用,无法在外部函数使用,例如请求函数中需要store中的属性,不能直接在请求函数中调用,就只能通过react中作为参数传递。
- 无法处理异步请求。
对于异步的逻辑,Context API并没有提供任何API,需要自己做封装。
- 无法处理数据间的联动
Context API并没有提供API来生成派生状态,同样也需要自行去封装一些方法来实现。
Zustand
优点:
状态共享
context需要在最外层包一个 Provider。 Context 中的值都在 Provider 的作用域下有效。而 zustand默认是单例模式, 不需要 Provider。直接声明一个 hooks 式的 useStore 后就可以在不同组件中进行调用。
如果需要多实例的话,zustand 也提供了对应的 Provider 的书写方式,
状态变更
-
函数可以直接写,完全不用区分同步或者异步。
-
zustand 会默认将所有的函数保持同一引用。用 zustand 写的方法,默认都不会造成额外的重复渲染。
-
更好的状态内聚性,可以互相调用,不用再重新声明函数。
状态派生
- zustand 用了类似 redux selector 的方法,实现相应的状态派生。
- 可以实现只有监听的属性变化,才会重新渲染
// zustand 的 selector 用法
// 写法1
const App = () => {
const url = useStore( s => URL_HITU_DS_BASE(s.name || ''));
// ...
}
// 写法2 将 selector 单独抽为函数
export const dsUrlSelector = (s) => URL_HITU_DS_BASE(s.name || '');
const App = () => {
const url = useStore(dsUrlSelector);
// ...
}
状态组合
可以将相关的状态进行组合,结合中间件实现对单个状态组合进行一些操作,例如对状态组合实现持久化,缓存到localStorage
多环境集成
可以不在 react 环境内直接获取状态数据
// 官方示例
// 1. 创建Store
const useDogStore = create(() => ({ paw: true, snout: true, fur: true }))
// 2. react 环境外直接拿值
const paw = useDogStore.getState().paw
// 3. 提供外部事件订阅
const unsub1 = useDogStore.subscribe(console.log)
// 4. react 世界外更新值
useDogStore.setState({ paw: false })
const Component = () => {
// 5. 在 react 环境内使用
const paw = useDogStore((state) => state.paw)
引用: