Skip to main content

状态库 unstated-next

是什么

基于 React 的 createContext(创建上下文) + useContext(使用上下文)实现的

怎么做

使用方法:只需要将状态存储在 StoreContext 中,Provider 下的任意子组件都可以通过 useContext 获取到上下文中的状态。

提供 createContainer 将自定义 Hook 封装为一个数据对象,提供"Provider"注入与"useContainer"获取 Store 这两个方法。 "Provider":就是对"value"进行了约束,固化了 Hooks 返回的 value 直接作为 value 传递给"Context.Provider"这个规范 "useContainer":就是对"React.useContext(Context)"的封装

原理

  1. 一个创建容器的方法:"createContainer"
    • "createContainer"该方法接收一个自定义 hooks 作为参数,先使用原生 React.createContext 创建 Context 对象;
    • 存在一个"Provider"方法,在其内部执行传递给 createContainer 的 Hooks 得到一个 value 值,返回内部 Context.Provider 的封装。
    • 存在一个"useContainer"方法,在其内部使用"useContext"获取当前 Context 的值,并返回。
  2. 一个使用容器的方法:"useContainer"
    • "useContainer"该方法内部其实就是返回我们创建的 Container 实例所对应的值。

源码

import React from "react"

const EMPTY: unique symbol = Symbol()

/* 类型 */
interface ContainerProviderProps<State = void> {
initialState?: State
children: React.ReactNode
}
interface Container<Value, State = void> {
Provider: React.ComponentType<ContainerProviderProps<State>>
useContainer: () => Value
}

// 1. 创建内容管理者
export function createContainer<Value, State = void>(
useHook: (initialState?: State) => Value,
): Container<Value, State> {
let Context = React.createContext<Value | typeof EMPTY>(EMPTY)

function Provider(props: ContainerProviderProps<State>) {
let value = useHook(props.initialState)
return <Context.Provider value={value}>{props.children}</Context.Provider>
}

// 内容
function useContainer(): Value {
let value = React.useContext(Context)
if (value === EMPTY) {
throw new Error("Component must be wrapped with <Container.Provider>")
}
return value
}

return { Provider, useContainer }
}

// 内容
function useContainer<Value, State = void>(
container: Container<Value, State>,
): Value {
return container.useContainer()
}

Example

store.js

import { useState } from 'react';
import { createContainer } from 'unstated-next';

const useListStore = (
initialState = {
count:0
},
) => {
const [listStates, setListStates] = useState(initialState);

const changeListStates = newListStates => {
setListStates(prevStates => Object.assign({}, prevStates, newListStates));
};

return { listStates, changeListStates };
};

export const ListStore = createContainer(useListStore);

index.js

import {  ListStore } from './store.js';

const Layout = ({ content }) => {

return (
<ListStore.Provider>
<Latest />
</ListStore.Provider>
);
};

export default Layout;

Latest.js

import { ListStore } from '../store';

const Latest = () => {
const listStore = ListStore.useContainer();
const { listStates, changeListStates } = listStore;
const { count } = listStates;

const useCounter = () => {
changeListStates({
count: count + 1,
});
}

return (
<div className={styles.wrap}>
<span>{count}</span>
<button onClick={useCounter}>+</button>
</div>
);
};

export default Latest;

解决了什么问题

在不使用 unstated-next 库时,我们针对函数式组件,想使用 Context 的话必然会在子组件中显式的使用"useContext"和"React.createContext.Provider"。

因此,unstated-next 要做的就是针对"Provider 作固定封装"及针对"useContext”api 进行隐藏不显式提供使用而封装的一个库,全代码就 40 行。

缺点

当不同子组件引用 Container 里面的不同的数据时,当更新某个属性时,另一个未使用到组件也会被更新,这个原因是 useContainer 提供的数据流是一个引用整体,部分子节点的更新会引起整个 Hook 重新执行,因而所有引用它组件也会 re-render,不同实现"按需更新"

参考文章