简单又强大的Zustand,为啥不自己手写一个呢
谈起react的状态管理库,大家肯定会想到redux,用过redux的人肯定会发现,它使用起来是非常的繁琐,受人诟病。
后起之秀的react的管理库就有zustand jotai
等,其中最为简单且流行的就是 zustand
,它可以说是操作简单,功能强大,redux的任何功能他都可以实现,并且可以更简单的实现。
我们先来简单看一下 zustand
使用方法。
- 安装
npm install zustand
import { create } from 'zustand'type BearState = {bears: numberincreasePopulation: () => 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) {//获取到最新的stateconst nextState = typeof partial === 'function' ? partial(state) : partial;// 数据有变化if (!Object.is(nextState, state)) {//记录旧的stateconst previousState = state;//更新stateif (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) {//获取到最新的stateconst nextState = typeof partial === 'function' ? partial(state) : partial;// 数据有变化if (!Object.is(nextState, state)) {//记录旧的stateconst previousState = state;//更新stateif (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;
完全没问题,源码也是这个思路。
看完这篇你文章是不是发现原来高高在上的库也不过如此。