突破Zustand的局限性:与React ContentAPI搭配使用

Zustand在状态管理中是非常适手的工具,在很多场景中我们都可以用它来解决复杂问题.

但是由于Zustand的设计理念,它仍然有一些限制,在这里用官网中的小demo举一个很简单的例子:

tsx 复制代码
import { create } from 'zustand'
type CountStore = {
  count: number
  inc: () => void
}
export const useCountStore = create<CountStore>((set) => ({
  count: 1,
  inc: () => set((state) => ({ count: state.count + 1 })),
}))

如上所示,我们在其他地方可以很灵活的使用count并且简易的控制inc函数count的值做修改。但是如果我们想要为此函数中的count设定灵活初始值的话,或者说是想要依靠React组件中传递来的一些值对其进行初始化的话,我们该怎么做呢?

事实上我们可以使用useEffect来对初值进行修改:

tsx 复制代码
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(更新我们所需的状态),然后组件会触发重新渲染,它在组件中将显示正确的值。这相当于我们用两次渲染来实现同样的事情,这在大型应用中是会极大的影响性能的


这篇文章中要强调的就是另外一种更为优雅的方式:

ZustandReactContextAPI结合使用

仍然是承接上文举一个小demo:

tsx 复制代码
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初始化了Zustandstore,使用传入的initialCount作为初始值。我们利用ContentAPI不会重新渲染的机制,确保在store状态发生改变的时候,CountProvider不会重新渲染,这样很有效的避免了性能上的问题,也解决了我们所谓初始store的"痛点"

最后我们可以按照如下方法进行初始化以及使用

tsx 复制代码
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

相关推荐
赵大仁24 分钟前
深入理解 Vue 3 中的具名插槽
前端·javascript·vue.js·react.js·前端框架·ecmascript·html5
一雨方知深秋28 分钟前
v-bind 操作 class(对象,数组),v-bind 操作 style
前端·css·vue.js·html·style·class·v-bind
安晴晚风2 小时前
从0开始在linux服务器上部署SpringBoot和Vue
linux·运维·前端·数据库·后端·运维开发
前端小小王3 小时前
pnpm、Yarn 和 npm 的区别?
前端·npm·node.js
supermapsupport3 小时前
使用npm包的工程如何引入mapboxgl-enhance/maplibre-gl-enhance扩展包
前端·webpack·npm·supermap·mapboxgl
牛奔3 小时前
windows nvm 切换node版本后,npm找不到
前端·windows·npm·node.js
鱼大大博客3 小时前
Edge SCDN酷盾安全重塑高效安全内容分发新生态
前端·安全·edge
鸭梨山大。3 小时前
NPM组件包 vant部分版本内嵌挖矿代码
前端·安全·npm·node.js·vue
蟾宫曲8 小时前
在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)
前端·npm·vue3·vite·element-plus·计时器
秋雨凉人心8 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js