Zustand在状态管理中是非常适手的工具,在很多场景中我们都可以用它来解决复杂问题.
但是由于Zustand的设计理念,它仍然有一些限制,在这里用官网中的小demo举一个很简单的例子:
import { create } from 'zustand'
type CountStore = {count: numberinc: () => void
}
export const useCountStore = create<CountStore>((set) => ({count: 1,inc: () => set((state) => ({ count: state.count + 1 })),
}))
如上所示,我们在其他地方可以很灵活的使用count
并且简易的控制inc函数
对count
的值做修改。但是如果我们想要为此函数中的count设定灵活初始值的话,或者说是想要依靠React组件中传递来的一些值对其进行初始化的话,我们该怎么做呢?
事实上我们可以使用useEffect
来对初值进行修改:
import { useCountStore } from "@/store/countStore"
type AppProps = {initialCount: number
}
export default function App ({initialCount}: AppProps) {count { count } = useCountStore()React.useEffect(() => {useCountStore.setState((prev) => ({...prev, count: initialCount}))}, [])return (<div>{ count }</div>)
}
在这个例子中,useEffect
会在组件挂载后调用,并修改 store 中的count
值。注意到初次渲染时,count
会是初始值1
,然后通过useEffect
更新为传入的 initialCount
。这种方式虽然可以解决问题,但它的缺点是需要两次渲染:第一次渲染时显示默认值,第二次渲染时显示传入的initialCount
。
这是遇到这个问题的很普遍或是说很简单的解决方法,不过很明显,它并不是一个很好的方法。
这样的处理逻辑是在组件中首先渲染了count
的值为1
,然后我们在useEffect
中调用函数将count
的值修改为我们所需的initialCount
(更新我们所需的状态),然后组件会触发重新渲染,它在组件中将显示正确的值。这相当于我们用两次渲染来实现同样的事情,这在大型应用中是会极大的影响性能的
这篇文章中要强调的就是另外一种更为优雅的方式:
将
Zustand
与React
的ContextAPI
结合使用
仍然是承接上文举一个小demo:
import React, { PropsWithChildren, useState } from "react";
import { StoreApi, createStore } from "zustand";
import { useStore } from "zustand/react";type CountStore = {count: number;inc: () => void;
};const CountContext = React.createContext<StoreApi<CountStore> | undefined>(undefined);type CountProviderProps = React.PropsWithChildren & {initialCount: number; // 设定初始值给 count
};export default function CountProvider({ children, initialCount }: CountProviderProps) {// 创建 store 对象const [store] = useState(() =>createStore<CountStore>((set) => ({count: initialCount, // 使用传入的 initialCount 作为初始值inc: () => set((state) => ({ count: state.count + 1 })),})));return <CountContext.Provider value={store}>{children}</CountContext.Provider>;
}export function useCountStore<T>(selector: (state: CountStore) => T) {const context = React.useContext(CountContext);if (!context) {throw new Error("CountContext.Provider is missing!");}return useStore(context, selector);
}
在上述代码中,在渲染时我们使用useState
初始化了Zustand
的store
,使用传入的initialCount
作为初始值。我们利用ContentAPI
不会重新渲染的机制,确保在store状态发生改变的时候,CountProvider
不会重新渲染,这样很有效的避免了性能上的问题,也解决了我们所谓初始store
的“痛点”
最后我们可以按照如下方法进行初始化
以及使用
:
import CountProvider, { useCountStore } from './CountProvider';function App() {return (<CountProvider initialCount={10}><ChildComponent /></CountProvider>);
}function ChildComponent() {const count = useCountStore((state) => state.count);return <div>Count: {count}</div>;
}
虽然通过React Context
提供 store 是一种较好的方法,但它依然引入了一定的复杂性,尤其是在大规模应用中。如果有很多状态需要管理或者是组件数非常非常深的时候,不得不承认Context
确实会带来一些额外的开销,这样我们就需要根据具体的场景制定特殊的方案或者使用其他相关工具来解决问题
总而言之言而总之,在我们需要状态管理的时候,还是要根据应用的复杂度和性能需求进行权衡,确保我们选取的工具较为合理
灵感来源:TkDodo’s blog / Zustand and React Context