我用过或了解过前端业界大部分流行的状况办理库。他们有的很杂乱,有的很简略。有的用了一些深度改造的手法来优化细节,有的则是平淡无奇的告知所有运用者发生了变化。在技能方案怪异多变与层出不穷的当下,只有一个状况办理库让我深深着迷,她极度精简到让我觉得不能再简略了,可是她也满足完备到应对任何场景。而我就一直在追究这样的一个宛如艺术品一样的状况办理库,经过一段时间的运用,我很确认她便是我的梦中情库。
她的名字叫 zustand
Github: github.com/pmndrs/zust…
极简界说
咱们先看看其他业界的状况办理库的运用方法:
以比较主流的redux
和mobx
为例, 这儿直接仿制了官网的最小示例。
redux(@reduxjs/toolkit)
import { createSlice, configureStore } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: {
value: 0,
},
reducers: {
incremented: (state) => {
state.value += 1;
},
decremented: (state) => {
state.value -= 1;
},
},
});
export const { incremented, decremented } = counterSlice.actions;
const store = configureStore({
reducer: counterSlice.reducer,
});
store.subscribe(() => console.log(store.getState()));
store.dispatch(incremented());
store.dispatch(incremented());
store.dispatch(decremented());
mobx
import React from "react";
import ReactDOM from "react-dom";
import { makeAutoObservable } from "mobx";
import { observer } from "mobx-react";
class Timer {
secondsPassed = 0;
constructor() {
makeAutoObservable(this);
}
increase() {
this.secondsPassed += 1;
}
reset() {
this.secondsPassed = 0;
}
}
const myTimer = new Timer();
// Build a "user interface" that uses the observable state.
const TimerView = observer(({ timer }) => (
<button onClick={() => timer.reset()}>
Seconds passed: {timer.secondsPassed}
</button>
));
ReactDOM.render(<TimerView timer={myTimer} />, document.body);
// Update the 'Seconds passed: X' text every second.
setInterval(() => {
myTimer.increase();
}, 1000);
在 redux
中,咱们需求先构建 reducer
来记录如何处理状况,然后根据reducer
构建一个store
。取值是经过store
来获取,修正则需求经过 根据reducer
一一对应的action
来修正。
这样就保证了数据流永远是单向活动的: 即 UI -> action -> reducer -> state -> UI
这样的进程。其呼应式的完成便是在执行action -> reducer
的进程中搜集到了变化,然后通知所有订阅这个store
的所有人。然后订阅者再经过名为selector
的函数来比对变更决议本身是否要更新。
如:
const Foo = () => {
useSelector(state => state.count.value)
}
咱们再来看看另一派的完成: mobx
界说了一个 class
作为存储数据的store
, 而对于数据的任何修正都是用一种类似原生的方法 —— 直接赋值来完成的。即既能够直接访问store
中修正里面的值也能够经过调用store
暴露出的方法来修正数据。而数据的取值也是直接经过最简略的数据访问来完成的。
看上去十分美好,可是这是经过一些”黑魔法”来完成的,当执行makeAutoObservable(this)
的那一刻,本来的成员变量已经不是本来的数据了,已经变成了由mobx
包裹了一层完成的 可观察目标, 即这些目标的赋值与取值都不是本来的含义了。这也便是为什么mobx
能够完成reactive
呼应式的原因。
这时分咱们再来看看zustand
是怎样做的:
import create from 'zustand'
const useBearStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))
function BearCounter() {
const bears = useBearStore((state) => state.bears)
return <h1>{bears} around here ...</h1>
}
function Controls() {
const increasePopulation = useBearStore((state) => state.increasePopulation)
return <button onClick={increasePopulation}>one up</button>
}
是的,只需求简略的一个目标就界说了一个store
,不需求特意去区分是state
仍是action
, 也不需求特意去结构一个 class
来做的十分臃肿。zustand
便是用最简略的规划去做一些工作,乃至其中心代码只有500
行不到。
redux本身最中心的代码只有200行左右,可是假如要在react中运用需求加上
redux-react
和@reduxjs/toolkit
就远远超过了
别的能够注意到的是,zustand
天然生成规划了一种场景便是react
环境。其他”有野心”的状况办理库往往是从 vanilla
环境(纯js环境)开始规划,然后增加了对react
的支撑,或许后续还会增加其他框架的支撑。可是zustand
则不是,天然生成支撑了react
环境,然后根据react
环境再衍生出vanilla
环境的支撑。
那么很多人就会好奇,已然都支撑vanilla
和react
,那么从哪个环境开始规划有什么区别么?
答案是有的,从不同的环境开始会从底层规划上就带来很大的误差,最后落地到运用方来说便是根本运用需求调用的代码、运行时以及杂乱度的差异。在我过去的开发经验告知我这样是正确的,我几乎没有看见过哪个库能同时在多个框架中都能如鱼得水的。不同的框架会有不同的生态,而哪些特有的生态则是最贴合的,如redux
之于react
,pinia
之于vue
, rxjs
之于Angular
。很少有哪个库能够在多个环境中”巴结”的。因此zustand
就一种十分聪明的做法,专心于一点十分重要。
那么回到zustand
的根本运用,咱们能够看到zustand
经过create
导出的是一个 react hook
, 经过这个hook
咱们能够直接拿到store里面的state
和action
,十分类似于redux
的useSelector
。不同的是不需求dispatch
来推送action
, 也没有任何模板代码,数据类型天然生成区分了state
和action
, 只需求最简略的调用即可。
比较于mobx
, 也没有什么”黑魔法”, 简略而不容易出错。而且也不像mobx
会由于依靠class
完成的store
而引进天然的问题(比方作为数据store不该该有生命周期,而class
的constructor
天然生成就成为了生命周期的一种)
人的惊骇往往来自未知,
mobx
的目标便是这样的一个黑盒。这便是我不怎样喜爱mobx
的原因
那么,怎样应用到所有场景呢
zustand
是一种十分简略的完成,简略到让人觉得是不是总有一些场合是无法掩盖到的。而这便是我觉得zustand
是一件艺术品的原因。由于他总有巧妙的方法来不失高雅的适配任何我想要的场景。
在纯js中调用? 能够
useBearStore.getState()
经过getState
方法就能够获取最新的状况,在运用的进程中需求注意这是一个函数,意图是在运行时中获取到最新的值。里面的数据不是reactive
的。
想要有效果域? 能够
import { createContext, useContext } from 'react'
import { createStore, useStore } from 'zustand'
const store = createStore(...) // vanilla store without hooks
const StoreContext = createContext()
const App = () => (
<StoreContext.Provider value={store}>
...
</StoreContext.Provider>
)
const Component = () => {
const store = useContext(StoreContext)
const slice = useStore(store, selector)
...
与原生 react
的 Context
结合就能完成效果域的效果。并且进一步将store
的创建合并到组件中,也能取得组件相关的生命周期。
想要在不同的store中彼此调用?能够
经过useBearStore.getState()
就能完成store
间彼此调用。当然需求注意办理好store
间的依靠联系。
想要中间件? 没问题
zustand
的库自带了一些中间件,其完成也十分简略。参考zustand/middleware
的完成能够学习如何制作zustand
的中间件。
想要处理异步action?没问题
在redux
前期,想要做异步action
是十分头疼的工作,而rtk
出来后会稍微好一点,可是也很费事。而在zustand
,能够十分简略
const useFishStore = create((set) => ({
fishies: {},
fetch: async (pond) => {
const response = await fetch(pond)
set({ fishies: await response.json() })
},
}))
缺乏与思考
再好的规划假如不加约束也会呈现 shit code
。想要把 zustand
这样细巧而精美的库用好而不是用坏需求必定的技能办理能力。盲意图去运用新的技能并不必定能给技能团队带来一些收益,可是能够带来新的思考。
另一方面,zustand
是一种大局store
的规划,不能说这种规划欠好,可是也意味着带来了一种比较经典的技能难题,即依靠办理。当项目中呈现彼此依靠的时分,如何办理,怎样保证在后续的保护中不构成污染,在调试时不会引进噪音。这是我认为所有的大局store
都会面对的问题。