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

相关推荐
奋斗的小羊羊1 小时前
HTML5关键知识点之多种视频编码工具的使用方法
前端·音视频·html5
前端呆猿1 小时前
深入解析HTML5中的object-fit属性
前端·css·html5
再学一点就睡1 小时前
实现大文件上传全流程详解(补偿版本)
前端·javascript·面试
你的人类朋友2 小时前
【Node&Vue】什么是ECMAScript?
前端·javascript·后端
路灯下的光3 小时前
用scss设计一下系统主题有什么方案吗
前端·css·scss
l_tian_tian_3 小时前
SpringClound——网关、服务保护和分布式事务
linux·服务器·前端
一只小风华~3 小时前
CSS @media 媒体查询
前端·css·媒体
shix .4 小时前
最近 | 黄淮教务 | 小工具合集
前端·javascript
John_ToDebug4 小时前
Chrome 内置扩展 vs WebUI:浏览器内核开发中的选择与实践
前端·c++·chrome
烛阴4 小时前
解锁动态键:TypeScript 索引签名完全指南
前端·javascript·typescript