突破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

相关推荐
爱吃羊的老虎11 分钟前
【WEB开发.js】getElementById :通过元素id属性获取HTML元素
前端·javascript·html
妙哉73628 分钟前
零基础学安全--HTML
前端·安全·html
咔叽布吉35 分钟前
【前端学习笔记】AJAX、axios、fetch、跨域
前端·笔记·学习
GISer_Jing1 小时前
Vue3常见Composition API详解(适用Vue2学习进入Vue3学习)
前端·javascript·vue.js
Dragon Wu1 小时前
TailwindCss 总结
前端·css·前端框架
bpmf_fff1 小时前
十、事件类型(鼠标事件、焦点.. 、键盘.. 、文本.. 、滚动..)、事件对象、事件流(事件捕获、事件冒泡、阻止冒泡和默认行为、事件委托)
前端·javascript
泰山小张只吃荷园1 小时前
期末Python复习-输入输出
java·前端·spring boot·python·spring cloud·docker·容器
悦涵仙子2 小时前
vueuse中的useTemplateRefsList
前端·javascript·vue.js
萧萧玉树2 小时前
分布式在线评测系统
前端·c++·后端·负载均衡
haima952 小时前
ubuntu安装chrome无法打开问题
前端·chrome