@zjxpcyc/react-tiny-store
Version:
简易版 React hook store
196 lines (165 loc) • 5.17 kB
JavaScript
/*
* Copyright (c) [2020] Zhang Yansen.All rights reserved.
*
* react-tiny-store is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import React from 'react';
import observe from '@zjxpcyc/observe-ext';
/**
* 创建一个 store
* 使用场景:需要将部分动做脱离 react 组件,在外部进行。使用 context 返回值可以任意操作
*
* @param {*|function} initialState - 初始 state 或者 定义 action 的函数
* @param {function} enhancer - 插件或者用于扩展的函数
* @returns {Object} 返回 context
*/
export function createStore(initialState, enhancer) {
const initialEnhancer = initEnhancerCreator(initialState);
return observe(compose(enhancer, hookEnhancer, initialEnhancer, stateEnhancer));
}
/**
* 创建一个 store
* 使用场景:需要在 react 组件内进行各种 动作执行
*
* @param {*|function} initialState - 初始 state 或者 定义 action 的函数
* @param {function} enhancer - 插件或者用于扩展的函数
* @returns {hook} 返回 store hook
*/
export function create(initialState, enhancer) {
return createStore(initialState, enhancer).useStore;
}
/**
* 初始化函数 Enhancer 的创建者,该 Enhancer 用于初始化状态或提供一个自定义的动作集合。
* @param {*|Function} initialState - 初始状态对象或一个函数,函数接收context作为参数。
* @returns {Function} 返回一个 Enhancer 。
*/
function initEnhancerCreator(initialState) {
return function(context) {
let actions = context.setState;
if (typeof initialState == 'function') {
actions = initialState(context) || context.setState;
} else {
context.setState(initialState);
}
return {
...context,
actions,
};
}
}
/**
* 创建并管理状态的 Enhancer
*
* @param {Object} context - 上下文对象,至少包含一个publish函数
* @returns {Object} 返回一个 Enhancer
*/
function stateEnhancer(context) {
const { publish } = context;
// 状态
let state = undefined;
/**
* 更新状态函数
*
* @param {*} newState
*/
const getState = () => state;
/**
* 更新状态函数
*
* @param {*} newState
*/
const setState = (newState) => publish(state = typeof newState == 'function' ? newState(state) : newState);
return {
...context,
getState,
setState,
}
}
/**
* hookEnhancer 用来生成 react hook
*
* @param {Object} context
* @returns {Function} 返回 store hook
*/
function hookEnhancer(context) {
// 小于 React 18 的版本,使用一个简易的 shim
const useSyncExternalStore = React.useSyncExternalStore || useSyncExternalStoreShim;
/**
* 最终返回的 hook
*/
function useStore(selector, filterActions) {
// 构造 actions
const actions = React.useMemo(() => {
return typeof filterActions == 'function' ? filterActions(context.actions) : context.actions;
}, []);
// 构造 getSnapshot 函数
const getSnapshot = React.useCallback(() => {
const snapshot = context.getState();
return typeof selector == 'function' ? selector(snapshot) : snapshot;
}, []);
const state = useSyncExternalStore(context.subscribe, getSnapshot);
const result = React.useMemo(() => ([
state,
actions,
{...context},
]), [state]);
return result;
}
return {
...context,
useStore,
}
}
/**
* Composes multiple functions from right to left.
*
* @param {...Function} funcs - Functions to be composed.
* @returns {Function} The composed function.
*/
export function compose(...funcs) {
const fs = funcs.filter(Boolean);
if (fs.length === 0) {
return (arg) => arg; // Return identity function if no functions provided
}
if (fs.length === 1) {
return fs[0]; // Return the single function if only one is provided
}
return fs.reduce((acc, curr) => {
return (...args) => acc(curr(...args));
});
}
/**
* useSyncExternalStore 的简易实现
*
* @param {Function} subscribe
* @param {Function} getSnapshot
* @returns useSyncExternalStore
*/
function useSyncExternalStoreShim(subscribe, getSnapshot) {
const initialState = React.useMemo(getSnapshot, []);
const [value, setValue] = React.useState(initialState);
const valueRef = React.useRef();
valueRef.current = value;
React.useEffect(() => {
const listener = () => {
const newVal = getSnapshot();
if (!Object.is(valueRef.current, newVal)) {
setValue(newVal);
}
}
// 防止 从初始化 value 到 useEffect 这个时间段内,有 state 更新
// 所以此处执行一次 listener
listener();
return subscribe(listener);
}, []);
return value;
}