helux-mini
v1.0.1
Published
A React state library that encourages service injection and supports reactive updates
Downloads
9
Readme
helux-mini
helux-mini 是一个鼓励服务注入,并支持响应式变更 react 的全新数据流方案,为了更符合现在流行的 DDD 围绕业务构建领域模型而生。
它的前身是concent,经过抛弃面向 class 组件的 api 处理并做大量裁剪后,诞生了
helux
,现降2.*
版本独立为helux-mini
来发布
它拥有以下优势:
- 轻量,压缩后 2kb
- 简单,仅暴露 6 个 api
- 高性能,自带依赖收集
- 响应式,支持创建响应式对象,在视图之外变更对象将同步更新视图
- 服务注入,配合
useService
接口轻松控制复杂业务逻辑,返回稳定的方法引用 - 状态提升 0 改动,所以地方仅需将
useObject
换为useSharedObject
即可提升状态共享到其他组件 - 避免 forwordRef 地狱,内置的
exposeService
模式将轻松解决父调子时的ref
转发晦涩理解问题和传染性(隔代组件需要层层转发) - ts 友好,100% ts 编写,为你提供全方位类型提示
30s 上手
使用 npm 命令npm i helux-mini
安装helux-mini
,然后调用createShared
创建共享状态,调用useShared
使用共享状态,that's all,你已接入helux-mini
来提升局部状态为共享状态. ✨
import React from 'react';
+ import { createShared, useShared } from 'helux-mini';
+ const { state: sharedObj } = createShared({a:100, b:2});
function HelloHelux(props: any) {
- const [state, setState] = React.useState({ a: 100, b: 2 });
+ const [state, setState] = useShared(sharedObj);
return <div>{state.a}</div>; // 当前组件仅依赖a变更才触发重渲染
}
创建响应式对象
const { state: sharedObj, setState } = createShared({ a: 100, b: 2 }, true);
// or
const { state: sharedObj, setState } = createShared({ a: 100, b: 2 }, { enableReactive: true });
// 以下两种写法均可以更新所有使用 `sharedObj.a` 值的组件实例
sharedObj.a++;
setState({ a: sharedObj.a + 1 });
api 详解
极致的简单是 helux 最大的优势,了解以下 6 个 api 后,你可以轻松应付任何复杂场景,最大的魅力在于useSharedObject
和useService
两个接口,且看如下 api 介绍
useObject
使用 useObject 有两个好处
- 1 方便定义多个状态值时,少写很多 useState
- 2 内部做了 unmount 判断,让异步函数也可以安全的调用 setState,避免 react 出现警告 : "Called SetState() on an Unmounted Component" Errors
// 基于对象初始化一个视图状态
const [state, setState] = useObject({ a: 1 });
// 基于函数初始化一个视图状态
const [state, setState] = useObject(() => ({ a: 1 }));
useForceUpdate
强制更新当前组件视图,某些特殊的场景可以使用它来做视图重刷新
const forUpdate = useForceUpdate();
createSharedObject
创建一个共享对象,可透传给 useSharedObject
,具体使用见 useSharedObject
// 初始化一个共享对象
const sharedObj = createSharedObject({ a: 1, b: 2 });
// 基于函数初始化一个共享对象
const sharedObj = createSharedObject(() => ({ a: 1, b: 2 }));
createReactiveSharedObject
创建一个响应式的共享对象,可透传给 useSharedObject
// 初始化一个共享对象
const [reactiveObj, setState] = createReactiveSharedObject({ a: 1, b: 2 });
sharedObj.a = 111; // 任意地方修改 a 属性,触发视图渲染
setSharedObj({ a: 111 }); // 使用此方法修改 a 属性,同样也能触发视图渲染,深层次的数据修改可使用此方法
createShared
函数签名
function createShared<T extends Dict = Dict>(
rawState: T | (() => T),
strBoolOrCreateOptions?: ICreateOptionsType,
): {
state: SharedObject<T>;
call: <A extends any[] = any[]>(
srvFn: (ctx: { args: A; state: T; setState: (partialState: Partial<T>) => void }) => Promise<Partial<T>> | Partial<T> | void,
...args: A
) => void;
setState: (partialState: Partial<T>) => void;
};
创建一个响应式的共享对象,可透传给 useSharedObject,它是createReactiveSharedObject
和createSharedObject
的结合体,当需要调用脱离函数上下文的服务函数时(即不需要感知组件 props 时),可使用该接口,第二位参数为是否创建响应式状态,为 true 时效果同 createReactiveSharedObject
返回的 sharedObj
const ret = createShared({ a: 100, b: 2 });
const ret2 = createShared({ a: 100, b: 2 }, true); // 创建响应式状态
// ret.state 可透传给 useSharedObject
// ret.setState 可以直接修改状态
// ret.call 可以调用服务函数,并透传上下文
以下将举例两种具体的定义服务函数的方式,之后用户便可在其他其他地方任意调用这些服务函数修改共享状态了,如需感知组件上下文(例如 props),则需要用到下面介绍的useService
接口去定义服务函数。
// 调用服务函数第一种方式,直接调用定义的函数,配合 ret.setState 修改状态
function changeAv2(a: number, b: number) {
ret.setState({ a, b });
}
*
// 第二种方式,使用 ret.call(srvFn, ...args) 调用定义在call函数参数第一位的服务函数
function changeA(a: number, b: number) {
ret.call(async function (ctx) { // ctx 即是透传的调用上下文,
// args:使用 call 调用函数时透传的参数列表,state:状态,setState:更新状态句柄
// 此处可全部感知到具体的类型
// const { args, state, setState } = ctx;
return { a, b };
}, a, b);
}
useShared
函数签名
function useShared<T extends Dict = Dict>(sharedObject: T, enableReactive?: boolean): [SharedObject<T>, (partialState: Partial<T>) => void];
接收一个共享对象,多个视图里将共享此对象,内部有依赖收集机制,不依赖到的数据变更将不会影响当前组件更新
useSharedObject
和useShared
是同一个函数
const [obj, setObj] = useShared(sharedObj);
useShared
默认返回非响应式状态,如需要使用响应式状态,透传第二位参数为 true 即可
const [obj, setObj] = useShared(sharedObj);
// now obj is reactive
setInterval(() => {
state.a = Date.now(); // 触发视图更新
}, 2000);
useService
函数签名
/**
* 使用用服务模式开发 react 组件:
* @param compCtx
* @param serviceImpl
*/
function useService<P extends Dict = Dict, S extends Dict = Dict, T extends Dict = Dict>(
compCtx: {
props: P;
state: S;
setState: (partialState: Partial<S>) => void;
},
serviceImpl: T,
): T & {
ctx: {
setState: (partialState: Partial<S>) => void;
getState: () => S;
getProps: () => P;
};
};
它可搭配useObject
和useSharedObject
一起使用,会创建服务对象并返回,该服务对象是一个稳定的引用,且它包含的所有方法也是稳定的引用,可安全方法交给其它组件且不会破会组件的 pros 比较规则,避免烦恼的useMemo
和useCallback
遗漏相关依赖
搭配useObject
时
function DemoUseService(props: any) {
const [state, setState] = useObject({ a: 100, b: 2 );
// srv本身和它包含的方法是一个稳定的引用,
// 可安全的将 srv.change 方法交给其它组件且不会破会组件的pros比较规则
const srv = useService({ props, state, setState }, {
change(a: number) {
srv.ctx.setState({ a });
},
});
return <div>
DemoUseService:
<button onClick={() => srv.change(Date.now())}>change a</button>
</div>;
}
搭配useSharedObject
时,只需替换useObject
即可,其他代码不用做任何改变
+ const sharedObj = createSharedObject({a:100, b:2})
function DemoUseService(props: any) {
- const [state, setState] = useObject({ a: 100, b: 2 );
+ const [state, setState] = useSharedObject(sharedObj);
getState 和 getProps
因 state
和 props
是不稳定的,所以服务内部函数取的时候需从srv.ctx.getState
或srv.ctx.getProps
// 抽象服务函数
export function useChildService(compCtx: { props: IProps; state: S; setState: (partialState: Partial<S>) => void }) {
const srv = useService<IProps, S>(compCtx, {
change(label: string) {
// !!! do not use compCtx.state or compCtx.state due to closure trap
// console.log("expired state:", compCtx.state.label);
// get latest state
const state = srv.ctx.getState();
console.log('the latest label in state:', state.label);
// get latest props
const props = srv.ctx.getProps();
console.log('the latest props when calling change', props);
// your logic
compCtx.setState({ label });
},
});
return srv;
}
export function ChildComp(props: IProps) {
const [state, setState] = useObject(initFn);
const srv = useChildService({ props, state, setState });
}
return (
<div>
i am child <br />
<button onClick={() => srv.change(`self:${Date.now()}`)}>change by myself</button>
<h1>{state.label}</h1>;
</div>
);
exposeService
当孩子组件 props 上透传了exposeService
函数时,useService
将自动透传服务对象给父亲组件,是一种比较方便的逃离forwardRef
完成父调子的模式
import { ChildSrv, Child } from './Child';
function App() {
// 保存孩子的服务
const childSrv = React.useRef<{ srv?: ChildSrv }>({});
const seeState = () => {
console.log('seeState', childSrv.current.srv?.ctx.getState());
};
return (
<div>
<button onClick={() => childSrv.current.srv?.change(`${Date.now()}`)}>call child logic</button>
<Child unstableProp={`${Date.now()}`} exposeService={(srv) => (childSrv.current.srv = srv)} />
</div>
);
}