简单又强大的Zustand,为啥不自己手写一个呢
谈起react的状态管理库,大家肯定会想到redux,用过redux的人肯定会发现,它使用起来是非常的繁琐,受人诟病。
后起之秀的react的管理库就有zustand jotai
等,其中最为简单且流行的就是 zustand
,它可以说是操作简单,功能强大,redux的任何功能他都可以实现,并且可以更简单的实现。
我们先来简单看一下 zustand
使用方法。
- 安装
npm install zustand
import { create } from 'zustand'
type BearState = {
bears: number
increasePopulation: () => void
}
const useBearStore = create<BearState>((set, get, api) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
}))
export default useBearStore;
我们可以看到三个参数,set get api
他们分别是后去设置state,获取state,api是有关state方法的数据。
其中的getState和setState就是上述的中的get set
在App.tsx中使用
import useBearStore from "./zustand/baseStore";
function App() {
const bears = useBearStore(state => state.bears)
const increasePopulation = useBearStore(state => state.increasePopulation)
return (
<div>
{bears}
<button onClick={increasePopulation}>+ 1</button>
</div>
);
}
export default App;
使用方式很简单,只需要创建一个store,使用的时候用 useBearStore
传入一个回调,返回需要的数据即可。
在zustand中另一个实用的凡是subscribe,添加一个监听器,在state变化的时候会执行传入的回调函数。
import useBearStore from "./zustand/baseStore";
import { useEffect } from "react";
function App() {
const bears = useBearStore(state => state.bears)
const increasePopulation = useBearStore(state => state.increasePopulation)
useEffect(() => {
//监听state的变化
useBearStore.subscribe((state, prevState) => {
console.log(state, prevState);
})
}, []);
return (
<div>
{bears}
<button onClick={increasePopulation}> + 1</button>
</div>
);
}
export default App;
使用起来是不是很简单,redux中需要自己定义action,reducer ,然后在把action导出,要繁杂的不少。
那我们如何写一个zustand,其实zustand的原理很简单,包括源码都没有多少。
那我们如何自己写一个呢!
在这里插入图片描述
首先就是create方法接受一个回调函数,回调函数中接口set get api
这么几个参数,并返回初始数据
所以写一个create方法
function create(initialState) {
let state;
//replace 是否替换
function setState(partial, replace) {
if(replace) {
//直接替换
state = partial;
} else {
//合并老数据
state = { ...state, ...partial };
}
}
function getState() {
return state;
}
const api = { setState, getState };
state = initialState(setState, getState);
}
之后返回的值要能通过回调返回用户想要的数据。
继续补全代码
function create(initialState) {
let state;
//replace 是否替换
function setState(partial, replace) {
if(replace) {
//直接替换
state = partial;
} else {
//合并老数据
state = { ...state, ...partial };
}
}
function getState() {
return state;
}
const api = { setState, getState };
state = initialState(setState, getState);
return (func) => {
//执行函数将state当作参数传入
const result = func(api.getState());
return result;
}
}
现在我们在继续完善subscribe
订阅操作
订阅操作就是在state变化的时候,调用相应的回调
只要又setState的时候state会变化,所以我们只需要在封装setState方法即可,在setState的时候去对比一下前后数据,如果数据变化了则执行回调函数。
代码如下
function create(initialState) {
let state;
//监听器(存放回调函数)
const listeners = new Set();
//replace 是否替换
function setState(partial, replace) {
//获取到最新的state
const nextState = typeof partial === 'function' ? partial(state) : partial;
// 数据有变化
if (!Object.is(nextState, state)) {
//记录旧的state
const previousState = state;
//更新state
if (replace) {
state = nextState;
} else {
state = { ...state, ...nextState };
}
//执行记录的回调函数
listeners.forEach((listener: any) => listener(state, previousState));
}
}
//增加订阅方法
function subscribe(listener) {
listeners.add(listener);
}
//增加移除调用的方法,如果组件卸载了,那么他的相关订阅也许删除
function unsubscribe(listener) {
listeners.delete(listener);
}
function getState() {
return state;
}
const api = { setState, getState , subscribe};
state = initialState(setState, getState);
const useBoundStore = (func) => {
const result = func(state);
return result;
}
//我们知道返回值是一个函数,但是它又可以调用subscribe这种api所以我们需要将api挂载到返回的函数中
//函数是一个对象,使用Object.assign将api挂载到返回的函数中
return Object.assign(useBoundStore, api);
}
我们都知道如果组件中使用的state的数据变化肯定是要刷新的,谁又能监听到state的变化呢,这不正好是我们刚写的subscribe吗
代码如下
import { useEffect } from "react";
import { useState } from "react";
function create(initialState) {
let state;
//监听器(存放回调函数)
const listeners = new Set();
//replace 是否替换
function setState(partial, replace) {
//获取到最新的state
const nextState = typeof partial === 'function' ? partial(state) : partial;
// 数据有变化
if (!Object.is(nextState, state)) {
//记录旧的state
const previousState = state;
//更新state
if (replace) {
state = nextState;
} else {
state = { ...state, ...nextState };
}
//执行记录的回调函数
listeners.forEach((listener: any) => listener(state, previousState));
}
}
//增加订阅方法
function subscribe(listener) {
listeners.add(listener);
}
//取消订阅
function unsubscribe(listener) {
listeners.delete(listener);
}
function getState() {
return state;
}
const api = { setState, getState , subscribe, unsubscribe};
state = initialState(setState, getState);
const useBoundStore = (func) => {
//增加一个useState,这样调用forceUpdate就可以自己控制组件是否刷新
const [_, forceUpdate] = useState(0);
useEffect(() => {
//只要增加一个订阅,在state变化的时候看一下func返回值是否变化即可
const listener = subscribe((state, previousState) => {
//变化了则强制刷新
if (func(state) !== func(previousState)) forceUpdate(Math.random());
});
return () => {
//组件卸载的时候取消订阅
unsubscribe(listener);
}
}, []);
const result = func(state);
return result;
}
//我们知道返回值是一个函数,但是它又可以调用subscribe这种api所以我们需要将api挂载到返回的函数中
//函数是一个对象,使用Object.assign将api挂载到返回的函数中
return Object.assign(useBoundStore, api);
}
我们只需要在useBearStore的时候增加一个监听,在state变化的时候调用回调函数,回调函数中判断属性是否变化即可。
这样就能实习一个zustand了,是不是很简单。
然后替换成自己的手写的zustand试试
import useBearStore from "./zustand/baseStore";
import { useEffect } from "react";
function App() {
const bears = useBearStore(state => state.bears)
const increasePopulation = useBearStore(state => state.increasePopulation)
useEffect(() => {
useBearStore.subscribe((state, prevState) => {
console.log(state, prevState);
})
}, []);
return (
<div>
{bears}
<button onClick={increasePopulation}> + 1</button>
</div>
);
}
export default App;
完全没问题,源码也是这个思路。
看完这篇你文章是不是发现原来高高在上的库也不过如此。