1. 首页
  2. React

Vite6+React18+Ts项目-04.使用zustand状态管理

1.简单使用

安装zustand

npm install zustand

创建用来保存状态的 store.ts 文件

import { create } from 'zustand'

type State = {
  count: number
}

type Actions = {
  increment: (qty: number) => void
}

const useCountStore = create<State & Actions>((set) => ({
  count: 0,
  increment: (qty: number) => set((state) => ({ count: state.count + qty })),
}))

在app.ts组件中使用

function App() {
  const count = useCountStore((state) => state.count)
  const increment= useCountStore((state) => state.increment)
  return (
    <>
      <div className="card">
        <button onClick={() => increment(1)}>增加</button>
        <p>当前值:{count}</p>
      </div>
    </>
  )
}

2.一个保存用户语言的例子

store.ts

下面的代码使用了两个中间件

immer:更加方便的操作对象,改变对象的值时,可直接操作对象

persist:持久化存储,将对象存储到 localStorage

import { create } from 'zustand'
import { createJSONStorage, persist, subscribeWithSelector } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

// 定义类型
interface Lang  {
  code: string;
  name: string;
}

// 定义初始值
const init: Lang = {
  code: 'zh-CN',
  name: '中文简体',
}

// 存储store的key
const storeKey = 'store-lang';

// 创建一个store
const useLangStore = create<Lang>()(
  immer(
    persist(
      () => ({
        ...init
      }), 
      {
        // 持久化时存储到localStorage的key
        name: storeKey,
        // 存储方式,默认localStorage
        storage: createJSONStorage(() => localStorage),
      },
    )
  )
)

export default useLangStore;

// 设置语言
export const setLang = (state: Lang) => {
  useLangStore.setState(state);
}

// 重置语言
export const resetLang = () => {
  useLangStore.setState(init);
}

// 获取语言编码,通过这种方法获取,在组件里不会同步刷新状态,即当code改变时,不会触发组件渲染
export const getLangCodee = (): string => {
  return useLangStore.getState().code;
}

app.ts

import { useState } from 'react'
import './app.css'
import useLangStore, { setLang } from './stores/lang/lang.store'

function App() {
  const [inputLang, setInputLang] = useState('en-Es')
  
  // 如果同时获取多个值可使用 useShallow
  const langCode = useLangStore((state) => state.code)

  const setLangData = () => {
    setLang({
      code: inputLang,
      name: inputLang
    });
  }

  return (
    <>
      <div className="card">
        <button onClick={setLangData}>设置语言</button>
        <p>
          <input type="text" value={inputLang} onChange={(e) => setInputLang(e.target.value)} />
          当前语言编码:{langCode}
        </p>
      </div>
    </>
  )
}

export default App

3.监听 localStorage 变化

通过监听 localStorage 的变化,可以使用当一个页签的内容发送改变时,其它页签的store也会发生改变

// 监听 localStorage 变化
window.addEventListener('storage', (event) => {
  if (event.key === storeKey && event.newValue !== null) {
    const val = JSON.parse(event.newValue);
    // 这里判断用户状态,如果用户变化了,则进入登录页面或进入首页,根据具体业务自行处理
    if (JSON.stringify(useLangStore.getState()) != JSON.stringify(val.state)) {
      useLangStore.setState(val.state);
    }
  }
});

4.使用 useShallow 的例子

可以更加方便的同时获取多个值,而不会因为其它未使用到值的改变触发组件渲染

使用前

const langCode = useLangStore((state) => state.code)
const langName = useLangStore((state) => state.name)

使用后

const {code, name} = useLangStore(useShallow((state) => ({
  code: state.code,
  name: state.name
})))

5.使用订阅的例子

可优化组件渲染次数,如下示例,只有达到一定条件才会触发渲染,如果是在外层直接取出state的值,则state的值每次改变都会触发渲染

useEffect(() => {
  return useCountStore.subscribe((state, prevState) => {
    if (state.count >= 7 && prevState.count < 7) {
      setText('超出')
    } else if (state.count >= 7 && prevState.count < 7) {
      setText('太少')
    }
  });
}, []);

6.更细粒度的订阅 subscribeWithSelector

上面的例子,在state每次改变都会触发逻辑判断,如果只想count改变的时候触发逻辑判断,可使用subscribeWithSelector,需要在 create 的时候嵌套中间件 subscribeWithSelector

这里说明一下中间件顺序: immer -> devtools -> subscribeWithSelector-> persist

本文未使用到 devtools,devtools中间件可以帮助你在开发过程中调试和检查应用程序的状态变化,因为我们将状态存储到了localStorage,可直接通过浏览器控制台查看,故无需使用该中间件

示例

const useCountStore = create<Count>()(
  immer(
    subscribeWithSelector(
      persist(
        () => ({
          ...init
        }), 
        {
          // 持久化时存储到localStorage的key
          name: storeKey,
          // 存储方式,默认localStorage
          storage: createJSONStorage(() => localStorage),
        },
      )
    )
  )
)

用法

useEffect(() => {
  return useCountStore.subscribe(
    state => state.count,
    (count, prevCount) => {
    if (count >= 7 && prevCount < 7) {
      setText('超出')
    } else if (count >= 7 && prevCount < 7) {
      setText('太少')
    }
  });
}, []);

这样,就只有state中的count的值发生改变的时候,才会进入逻辑判断流程。


TOP