likes
comments
collection
share

自己写了一个 React 原子化状态管理工具 Clodx,有什么不同?

作者站长头像
站长
· 阅读数 51

Why Clodx?

现如今 React 生态中已经存在着诸多状态管理工具,随着 React 版本的更新,hooks 机制的出现,状态管理工具也从古早的 redux,到后来的 atom,再到现在风靡的 jotai 或者 zustand。这些状态工具我都使用过,但是他们也多多少少有一些不是令我很满意的地方。

  • 比如当你想注册全局事件来更新状态或者是从某些第三方工具的回调里更新状态时,需要先到 React 上下文中用 useEffect 之类的注册,并且还要通过 useAtom 之类的才能调用 set 方法。一来对于 useEffect 来说,你需要考虑各种依赖,注册和销毁等,二来是这觉得很绕,为什么更新一个状态的值非得要在 React 上下文才能更新。
  • 又比如当你想要用状态管理来开发组件时,总是很难做到这点,因为几乎所有状态管理工具都是从全局上下文派发,组件复用意味着组件需要自己的状态管理上下文,这就导致组件开发里很难用上状态管理的通信机制。绝大多数通用组件开发仍然在使用 Provider + useSelectContext(社区的一个 hook) 形式进行组件跨级通信。
  • 这里有的工具你不得不使用他们提供的 hooks 来获取某个状态值,这非常不友好。

Clodx 解决了这些问题,使用 Clodx,

  • 可以在任意位置获取状态的当前值。
  • 可以在你任意想更新状态的上下文中直接调用函数更新某个 clod 状态,同时也能更新使用该状态的 React 组件。
  • 支持在组件开发中使用状态管理,仅需要在组件最外层包裹一层没有任何传参的 Earth 组件即可,这个组件也不会影响内容更新,它负责在组件上下文中自动派发全新的 clod 状态

React 原子化状态管理工具 Clodx 介绍

目前它很小,打包后也只有 500b 左右。源码实现其实也非常直观,在文章末尾我会带上。 (比起 jotai 里一大堆面条代码真的是好受很多)

具有的特性——

  • 基本的原子化状态管理使用
  • 可以在脱离 React 上下文的情况下更新某个状态,也会更新使用这个状态的组件
  • 实现了状态隔离机制,使状态管理可以赋能于通用组件开发
  • Typescript 类型全自动推导

安装

npm install clodx

功能 Feature

clodx 拥有一些原子化管理工具类似的使用方式,同时也实现了诸多不同的特性。

import {
    clod,
    pickClod,
    makeClod,
    setClodValue,
    updatePickClod,
    exportClod,
    useClod,
    usePickClod,
    useMakeClod,
    Earth,
    updatePickClodByEarthName,
    setClodValueByEarthName,
    exportClodByEarthName,
} from 'clodx'

基本用法

和某些知名原子化状态管理工具一样, clodx 支持原子化式的基本使用和派生。

const state = clod(0)
const stateGetter = pickClod((get) => get(state))
const stateSetter = makeClod((value, _get, set) => set(state, value))

也支持异步方式的派生调用

const state = clod(0)
const syncStateGetter = pickClod(async (get) => {
    await new Promise((res) => setTimeout(res, 1000, true))
    return get(state)
})
const asyncStateSetter = makeClod(async (value, _get, set) => {
    await new Promise((res) => setTimeout(res, 1000, true))
    set(state, value)
})

在 React 程序内,提供了对应的 hooks 可以使用,分别是

  • useClod
  • useClodValue
  • useSetClod
  • usePickClod
  • useMakeClod

如果你是用过其他的状态管理工具的开发者,估计一眼就看懂怎么使用了。

const state = clod(0)
const stateGetter = pickClod((get) => get(state))
const stateSetter = makeClod((value, _get, set) => set(state, value))

function Getter () {
    const state = usePickClod(stateGetter)
    return ( <span>{state}</span> )
}

function Setter () {
    const setState = useMakeClod(stateSetter)
    return ( <button onClick={() => setState(123)}>click</button> )
}

function App () {
    const [state, setState] = useClod(state)
    const plus = () => setState(_ => _ + 1)
    
    return (
        <div>
            <span>count: {state}</span>
            <button onClick={plus}>plus</button>
        </div>
    )
}

useSetClod 即是 useClod 中返回的 set 方法,和 useMakeClod 不一样的是,useSetClod 可以像 useState 的 set 方法一样既可以直接传入值,也可以传入一个方法获取到当前的 clod 值,并将新值返回。

支持自定义更新的 equal 判定

对于 clodpickClod,可以声明他们的 equal 判定,当满足判定时,才会视为状态更新,从而去更新对应的组件。

const state = clod({ id: 0, value: 1 }, (prev, cur) => cur.id !== prev.id)
const stateGetter = pickClod(
    (get) => get(state), 
    (prev, cur) => cur.id !== prev.id
)

一些可以在代码任意位置调用更新状态的函数方法

在大家熟悉的使用方式基础上,我实现了以下可以使你能在 React 程序外部使用状态的函数式方法。

  • setClodValue 设置某个 clod 的值,当然只能设置 clod 和 makeClod
import { clod, makeClod, setClodValue } from "clodx";

const a = clod(123);
const b = makeClod((value: number, _get, set) => set(a, value));

function example() {
	setClodValue(a, 456);
	setClodValue(b, 789);
}
  • updatePickClod 重新更新某个 pickClod 的值(即调用传入的 pick 方法)
import { clod, pickClod, updatePickClod, setClodValue, exportClod } from "clodx";

const a = clod(123);
const b = pickClod((get) => get(a));

function example() {
	updatePickClod(b);
}
  • exportClod 获取 clod 的值,当然仅限于 clod 和 pickClod
import { clod, setClodValue, updatePickClod, exportClod } from "clodx";

const a = clod(123);
const b = pickClod((get) => get(a));
const c = makeClod((value: number, _get, set) => set(a, value));

function example() {
	exportClod(a); // 123
	exportClod(b); // undefined
	updatePickClod(b);
	exportClod(b); // 123
	setClodValue(c, 456);
	exportClod(a); // 456
	exportClod(b); // 456
}

此处 b 在初始化时,不会调用内部的 pick 方法,所以 exportClod(b) 的值是 undefined,通过 updatePickClod 的方法进行首次调用 pick。在使用 usePickClod 时,也会执行 pick 进行值的初始化。

这些方法在对外部调用更新时,也会更新使用该状态的 React 组件喔~

注意,在 Earth 包裹的上下文中使用这些方法是无法正确更新 Earth 上下文中的 clod 值的。

状态隔离之 Earth 组件

众所周知,众多状态管理工具无论是 Vue 还是 React 都具有一大痛点,那就是难以支持通用组件开发,因为状态管理工具通常都是由全局上下文保持状态,这就导致通用组件在被多次使用的时候都会指向同一个状态管理中的状态,而不能支持在组件上下文中独立管理。所以状态管理往往在通用组件的实现上难以派上用场,大部分通用组件仍然在使用原生 Provider 的方式进行跨组件通信。

为了解决这个问题,我在 clodx 里实现了 Earth 组件,来看这样几个例子——

const countState = clod(0);
const Recorder = (props: { name: string }) => {
        const count = useClodValue(countState);
        return (
                <div>
                    {props.name} - count: {count}
                </div>
        );
};
const PlusButton = () => {
        const setCount = useSetClod(countState);
        const plus = () => {
                setCount((count) => count + 1);
        };
        return <button onClick={plus}>plus</button>;
};
function MyComponent(props: { name: string }) {
    return (
        <Earth>
                <Recorder name={props.name} />
                <PlusButton />
        </Earth>
    );
}
function App () {
    return (
        <StrictMode>
                <MyComponent name="A" />
                <MyComponent name="B" />
                <MyComponent name="C" />
        </StrictMode>
    )
}

在这个程序执行时,Earth 组件会天然的把每个 MyComponent 的内部使用的状态上下文分开。你在每个 MyComponent 组件里调用的 setCount 方法只会对这个组件内的 count 值生效。

同时也支持直接指定 Earth 组件的 name,这样相同 name 下的 clod 就不会被分开。

const countState = clod(0);
const Recorder = (props: { name: string }) => {
        const count = useClodValue(countState);
        return (
                <div>
                        {props.name} - count: {count}
                </div>
        );
};
const PlusButton = () => {
        const setCount = useSetClod(countState);
        const plus = () => {
                setCount((count) => count + 1);
        };
        return <button onClick={plus}>plus</button>;
};
function MyComponent(props: { name: string }) {
        return (
                {/** 指定相同的 name **/}
                <Earth name="holy">
                        <Recorder name={props.name} />
                        <PlusButton />
                </Earth>
        );
}
function App () {
    return (
        <StrictMode>
           <Recorder name="other" />
            <MyComponent name="A" />
            <MyComponent name="B" />
            <MyComponent name="C" />
        </StrictMode>
    )
}

这里执行的结果就是,三个 MyComponent 里的 clod 值是相等的,而不在 Earth 组件包裹下的 Recorder 组件里的 clod 值是独立的。

这个机制完美的契合了通用组件开发,使组件开发者可以轻松地在通用组件内实现状态统一管理。

同样也提供了几个函数式方法可以让你通过 name 值在外部获取这些被区分的上下文中的 clod 值。

  • updatePickClodByEarthName
  • setClodValueByEarthName
  • exportClodByEarthName

Clodx TODO

  • 支持 Classic Component
  • 支持 clod 插件化开发
  • 其他工具开发
  • clodx for Vue

最后

这是 clodx 代码仓库,希望各位老板能够提提意见咯。

这里是 Xekin(/zi:kin/),以上这就是本篇文章分享的全部内容了,喜欢的掘友们可以点赞关注点个收藏~

最近摸鱼时间比较多,写了一些奇奇怪怪有用但又不是特别有用的工具,不过还是非常有意思的,之后会一一写文章分享出来,感谢各位支持。

我还是喜欢写没人写过的东西~

转载自:https://juejin.cn/post/7380283122489098240
评论
请登录