// 相关中间件等应用顺序(固定写法):create(immer(devtools(subscribeWithSelector(persist((set, get) => ({}))))))
1. 初步使用介绍
// 1. 安装zustand
npm install zustand
// 2. 创建store使用
import { create } from "zustand";
export const useBearStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}));
// 3. 组件内使用
import {useBearStore} from './store/index.js';
function App() {
// 不建议的写法:(除非状态都要使用到,否则建议使用下方写法,理由是会一直触发渲染导致性能差),如果一定要使用这种写法,参照下方标题selector和shallow去使用提高性能,何时使用selector和shallow下方将会提到
// const {bears, increasePopulation} = useBearStore();
// 支持写法,如果有很多状态则使用下方标题selector和shallow,何时使用selector和shallow下方将会提到
const bears = useBearStore(state => state.bears);
const increasePopulation = useBearStore(state => state.increasePopulation);
const handleClick = () => {
increasePopulation();
}
return (
<div className="App">
{bears}
<button onClick={handleClick}>按钮</button>
</div>
);
}
export default App;
2. 作者建议store的写法
1. store
import { create } from "zustand";
const initialState = {
bears: 0,
count: 0,
}
export const useBearStore = create(() => initialState)
export const increaseBear = () => useBearStore.setState((state) => ({ bears: state.bears + 1 }))
export const increaseCount = () => useBearStore.setState((state) => ({ count: state.count + 1 }))
export const decreaseCount = () => useBearStore.setState((state) => ({ count: state.count - 1 }))
2. 组件
import { useBearStore, increaseBear, increaseCount, decreaseCount } from './store/index.js';
import { shallow } from 'zustand/shallow';
// 子组件
function Child() {
const { bears, count } = useBearStore(state => ({
bears: state.bears,
count: state.count,
}), shallow);
return (
<div>
<div>{bears}</div>
<div>{count}</div>
<button onClick={increaseBear}>bears+1</button>
<button onClick={increaseCount}>count+1</button>
<button onClick={decreaseCount}>count-1</button>
</div>
)
}
// 父组件, 一定要把页面的每个部位都封装成一个子组件,否则会导致失效性能一样差
function App() {
return (
<div className="App">
<Child />
</div>
);
}
export default App;
3. zustand内get与set使用
// 1. store
import { create } from "zustand";
export const useBearStore = create((set, get) => ({
bears: 0,
count: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
increaseCount: () => set((state) => ({ count: state.count + 1 })),
removeAllBears: () => set({ bears: 0 }),
total: () => {
// get()可以获取到state状态里的数据
return get().bears + get().count
}
}));
// 2. 组件使用
import { useBearStore } from './store/index.js';
function App() {
const bears = useBearStore(state => state.bears);
const count = useBearStore(state => state.count);
// 使用set使bears+1
const increasePopulation = useBearStore(state => state.increasePopulation);
// 使用set使count+1
const increaseCount = useBearStore(state => state.increaseCount);
// 使用get获取bears+count
const total = useBearStore(state => state.total);
const handleClickBears = () => {
increasePopulation();
}
const handleClickCount = () => {
increaseCount();
}
return (
<div className="App">
<div>{bears}</div>
<div>{count}</div>
<button onClick={handleClickBears}>按钮bears</button>
<button onClick={handleClickCount}>按钮count</button>
{/* 汇总 */}
<div>{total()}</div>
</div>
);
}
export default App;
4. selector使用(简单或单状态可以使用这个,多状态建议使用第四个shallow写法)
// 1. 创建一个ts文件
import { StoreApi, UseBoundStore } from 'zustand'
type WithSelectors<S> = S extends { getState: () => infer T }
? S & { use: { [K in keyof T]: () => T[K] } }
: never
const createSelectors = <S extends UseBoundStore<StoreApi<object>>>(
_store: S
) => {
let store = _store as WithSelectors<typeof _store>
store.use = {}
for (let k of Object.keys(store.getState())) {
;(store.use as any)[k] = () => store((s) => s[k as keyof typeof s])
}
return store
}
export default createSelectors;
// 2. store使用
import { create } from "zustand";
import createSelectors from './index.ts';
// 在store内使用
export const useBearStore = createSelectors(create((set, get) => ({
bears: 0,
count: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
increaseCount: () => set((state) => ({ count: state.count + 1 })),
removeAllBears: () => set({ bears: 0 }),
total: () => {
// get()可以获取到state状态里的数据
return get().bears + get().count
}
})));
// 3. 组件内使用,提高性能
import { useBearStore } from './store/index.js';
// 子组件
function Child() {
const increasePopulation = useBearStore.use.increasePopulation();
const increaseCount = useBearStore.use.increaseCount();
return (
<div>
<div>{Math.random()}</div>
<button onClick={increasePopulation}>按钮bears</button>
<button onClick={increaseCount}>按钮count</button>
</div>
)
}
// 子组件
function Some() {
const { count, bears } = useBearStore();
return (
<div className="App">
<div>{Math.random()}</div>
<div>{bears}</div>
<div>{count}</div>
</div>
);
}
// 父组件, 一定要把页面的每个部位都封装成一个子组件,否则会导致失效性能一样差
function App() {
return (
<div className="App">
<Some />
<hr />
<Child />
</div>
);
}
export default App;
5. shallow使用(zustand 最新的5版本废弃下面这种写法,如果是5的可以直接看下方第六条去使用)
import { useBearStore } from './store/index.js';
// shallow判断两次对象是否相等, 相等则不会重新渲染
import { shallow } from 'zustand/shallow'
// 子组件
function Child() {
// 对象写法
// const { increasePopulation, increaseCount } = useBearStore(state => ({
// increasePopulation: state.increasePopulation,
// increaseCount: state.increaseCount
// }), shallow)
// 数组写法
const [increasePopulation, increaseCount] = useBearStore(state => ([
state.increasePopulation,
state.increaseCount
]), shallow)
return (
<div>
<div>{Math.random()}</div>
<button onClick={increasePopulation}>按钮bears</button>
<button onClick={increaseCount}>按钮count</button>
</div>
)
}
// 子组件
function Some() {
const { count, bears } = useBearStore();
return (
<div className="App">
<div>{Math.random()}</div>
<div>{bears}</div>
<div>{count}</div>
</div>
);
}
// 父组件, 一定要把页面的每个部位都封装成一个子组件,否则会导致失效性能一样差
function App() {
return (
<div className="App">
<Some />
<Child />
</div>
);
}
export default App;
6. createWithEqualityFn(还在学习中~)
7. persist保存状态使用(相当于浏览器缓存)
1. store
import { create } from "zustand";
import { persist, createJSONStorage } from 'zustand/middleware'
export const useBearStore = create(persist(
(set, get) => ({
bears: 0,
count: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
increaseCount: () => set((state) => ({ count: state.count + 1 })),
removeAllBears: () => set({ bears: 0 })
}),
{
// 必须传配置项,name代表名称一定要唯一
name: 'bear store',
// 默认localStorage, 可以是sessionStorage,IndexDB或移动端asyncStorage,写法如下
storage: createJSONStorage(() => localStorage),
// 控制缓存你想要的状态
partialize: state => ({ bears: state.bears, count: state.count }),
// 过滤掉你不想要的状态:这个例子是除了count不要以外,其他状态都要在缓存内
// partialize: state => Object.fromEntries(Object.entries(state).filter(([key]) => !['count'].includes(key)))
}
))
2. 组件
import { useBearStore } from './store/index.js';
import { shallow } from 'zustand/shallow';
// 父组件, 一定要把页面的每个部位都封装成一个子组件,否则会导致失效性能一样差
function App() {
const { count, bears, increasePopulation, increaseCount } = useBearStore(state => ({
count: state.count,
bears: state.bears,
increasePopulation: state.increasePopulation,
increaseCount: state.increaseCount,
}), shallow);
return (
<div className="App">
<div>{bears}</div>
<div>{count}</div>
<button onClick={increasePopulation}>按钮bears</button>
<button onClick={increaseCount}>按钮count</button>
{/* 清除缓存后再点击按钮会存在原来的数字加1,因为没清除状态 */}
<button onClick={useBearStore.persist.clearStorage}>清除缓存</button>
</div>
);
}
export default App;
8. subscribe使用(发布与订阅,优点是在点击时如果此页面组件状态未改变不会触发重新渲染,用来提升性能),应用于状态少,状态多只关心其中一部分则使用第7个subscribeWithSelector
1.
// store
import { create } from "zustand";
export const useBearStore = create((set, get) => ({
bears: 0,
count: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
increaseCount: () => set((state) => ({ count: state.count + 1 })),
decreaseCount: () => set((state) => ({ count: state.count - 1 }))
}))
// 组件
import { useBearStore } from './store/index.js';
import { useEffect, useState } from 'react'
import { shallow } from 'zustand/shallow';
// 子组件
function Child() {
const { increasePopulation, increaseCount, decreaseCount } = useBearStore(state => ({
increasePopulation: state.increasePopulation,
increaseCount: state.increaseCount,
decreaseCount: state.decreaseCount,
}), shallow);
const [bg, setBg] = useState('white');
useEffect(() => {
// 添加订阅模式,监听到满足条件时才触发以及渲染组件, 否则不重新渲染组件
const unsub = useBearStore.subscribe((state, prevState) => {
// 如果count大于2则背景变红
if (state.count > 2) {
setBg('red')
} else {
setBg('white')
}
});
// unsub返回的是 () => listeners.delete(listener);
return unsub
}, [])
return (
<div style={{ backgroundColor: bg }}>
<div>{Math.random()}</div>
<button onClick={increasePopulation}>按钮bears</button>
<button onClick={increaseCount}>按钮count + 1</button>
<button onClick={decreaseCount}>按钮count - 1</button>
</div>
)
}
// 子组件
function Some() {
const { count, bears } = useBearStore();
return (
<div className="App">
<div>{Math.random()}</div>
<div>{bears}</div>
<div>{count}</div>
</div>
);
}
// 父组件, 一定要把页面的每个部位都封装成一个子组件,否则会导致失效性能一样差
function App() {
return (
<div className="App">
<Some />
<Child />
</div>
);
}
export default App;
9. subscribeWithSelector使用(假如你有很多状态,但你只关心其中一部分,可使用subscribeWithSelector)
// store
import { create } from "zustand";
import { subscribeWithSelector } from 'zustand/middleware'
export const useBearStore = create(subscribeWithSelector(
(set, get) => ({
bears: 0,
count: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
increaseCount: () => set((state) => ({ count: state.count + 1 })),
decreaseCount: () => set((state) => ({ count: state.count - 1 }))
})
))
// 组件
import { useBearStore } from './store/index.js';
import { useEffect, useState } from 'react'
import { shallow } from 'zustand/shallow';
// 子组件
function Child() {
const { increasePopulation, increaseCount, decreaseCount } = useBearStore(state => ({
increasePopulation: state.increasePopulation,
increaseCount: state.increaseCount,
decreaseCount: state.decreaseCount,
}), shallow);
const [bg, setBg] = useState('white');
useEffect(() => {
// // 添加订阅模式,监听到满足条件时才触发以及渲染组件, 否则不重新渲染组件
// const unsub = useBearStore.subscribe((state, prevState) => {
// // 如果count大于2则背景变红
// if (state.count > 2) {
// setBg('red')
// } else {
// setBg('white')
// }
// });
// 使用中间件subscribeWithSelector写法
const unsub = useBearStore.subscribe(state => state.count, (count, prevCount) => {
if (count > 2) {
setBg('red')
} else {
setBg('white')
}
}, {
// 与先前的对象做比较是否相关,提升性能
equalityFn: shallow,
// subscribe是否立即执行
fireImmediately: true,
});
return unsub
}, [])
return (
<div style={{ backgroundColor: bg }}>
<div>{Math.random()}</div>
<button onClick={increasePopulation}>按钮bears</button>
<button onClick={increaseCount}>按钮count + 1</button>
<button onClick={decreaseCount}>按钮count - 1</button>
</div>
)
}
// 子组件
function Some() {
const { count, bears } = useBearStore();
return (
<div className="App">
<div>{Math.random()}</div>
<div>{bears}</div>
<div>{count}</div>
</div>
);
}
// 父组件, 一定要把页面的每个部位都封装成一个子组件,否则会导致失效性能一样差
function App() {
return (
<div className="App">
<Some />
<Child />
</div>
);
}
export default App;
10. 如何在组件内使用zustand的setState和getState直接更新或获取状态(性能也很好,不会导致不必要的渲染)
1. store
import { create } from "zustand";
// import { subscribeWithSelector } from 'zustand/middleware'
export const useBearStore = create((set, get) => ({
bears: 0,
count: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
increaseCount: () => set((state) => ({ count: state.count + 1 })),
decreaseCount: () => set((state) => ({ count: state.count - 1 }))
}))
2. 组件
import { useBearStore } from './store/index.js';
import { useEffect, useState } from 'react'
import { shallow } from 'zustand/shallow';
// 子组件
function Child() {
// 获取useBearStore的初始值bears,属于non-reactive
console.log(useBearStore.getState().bears);
const { bears } = useBearStore(state => ({
bears: state.bears,
}), shallow);
// 直接在组件调用setState去更新而不需要在store内写
const handleAddBearfive = () => {
useBearStore.setState(state => ({ bears: state.bears + 5 }))
}
return (
<div>
<div>{bears}</div>
<button onClick={handleAddBearfive}>按钮bears直接 + 5</button>
</div>
)
}
// 父组件, 一定要把页面的每个部位都封装成一个子组件,否则会导致失效性能一样差
function App() {
return (
<div className="App">
<Child />
</div>
);
}
export default App;
11. zustand - 切片模式(单个store比较大的时候可以采用切片模式进行模块拆分组合,类似模块化)
import { create } from 'zustand';
const createCounterStore = (set) => {
return {
count: 0,
inc: () => {
set(state => ({ count: state.count + 1 }))
}
}
}
const createChannelStore = (set) => {
return {
channelList: [],
fetchChannelList: async () => {
const res = await fetch(URL);
const jsonRes = res.json();
set({
channelList: jsonRes.data.channels
})
}
}
}
const useStore = create((...params) => {
return {
...createCounterStore(...params),
...createChannelStore(...params)
}
})
function Layout() {
const count = useStore(state => state.count);
const inc = useStore(state => state.inc);
return (
<div>
<button onClick={inc}>{count}</button>
</div >
)
}
export default Layout;