集atom、signal、依赖追踪为一体的helux发布了,为react带来细粒度响应式更新能力

简介

helux 是一个集atomsignal依赖追踪为一体,支持细粒度响应式更新的状态引擎,兼容所有类 react 库,包括 react18。

btw:helux是目前唯一一个将细粒度响应式更新特性带到react开发者面前的框架

架构

helux包含了core层和适配层,core层基于最快的不可变数据操作库limu构建,包含了状态,动作和副作用3大模块,我们可以把core层理解为状态引擎的核心驱动包。

基于core层我们继续向上构建了适配reacthelux包,该包对接了react基础钩子,实现了atomsignal依赖追踪双向绑定细粒度响应式更新观察派生等常用功能或特性。

注意架构里的红色区域里是react-like,强调helux整体架构并非与react强绑定,只要满足提供了图示中几个api的类react库,core就可以秒适配并导出所有功能。

helux是我们默认适配好react而发布的包体

所以除了react自身,我们为此还适配了preact,同时也支持和现阶段各个生态的其他框架集成使用,例如nextjs,可查看下来各个链接体验。

如果想在其他类react库中使用helux,也可以参考 helux-preact-starter 示例去自行适配。

优势

综合上面的架构图,不难看出,helux相比现阶段开源社区较出名的状态管理库(reduxrecoiljotaizustandmobx等)的优势较为显著:

  • 内置依赖追踪特性,基于最快的不可变 js 库limu开发,拥有超强性能
  • atom 支持任意数据结构且自带依赖收集功能, 无需拆分很细,天然对 DDD 领域驱动设计友好
  • 内置 signal 响应机制,实现 0 hook 编码 dom 粒度或块粒度的更新
  • 内置 loading 模块,可管理所有异步任务的运行状态、并捕捉错误抛给组件、插件
  • 内置 sync 系列 api,支持双向绑定,轻松应对表单处理
  • 内置 reactive 响应式对象,支持数据变更直接驱动关联 ui 渲染
  • 内置 define 系列 api,方便对状态模块化抽象,轻松驾驭大型前端应用架构
  • 内置事件系统
  • 支持可变派生mutate derive,适用于当共享对象 a 部分节点变化需引起其他节点自动变化的场景,数据更新粒度更小
  • 支持全量派生full derive,适用于不需要对数据做细粒度更新的场景
  • 全量派生、可变派生均支持异步任务
  • 全量派生、可变派生除数据变更驱动执行外,还支持人工重新触发运行
  • 支持中间件、插件系统,可无缝对接 redux 生态相关工具库
  • 100% ts 编码,类型安全

落地场景

腾讯新闻web

腾讯新闻web是一个迭代了很多年的老项目,在7年前就引入了react技术栈,采用了csr+ssr混合渲染架构,在实际开发过程中,很多老组件在尽可能不动代码的情况下需要共享状态,即同一个组件的多个实例状态是通用的,例如这样一个运行多年的关注按钮。

旧代码类似

js 复制代码
function FlowButton(){
	const [state, setState] = useState({...});
	const clickButton = ()=>{
		// 逻辑略
		// setState({ isFollowed: true})
	};
}

这样一个按钮刚开始只显示一个,随着需求变化,按钮需要底部显示,或者其他排版显示时出现了一屏2个关注按钮同时存在,这时候旧代码面临着需要状态提升的问题,在改造老代码时尤为慎重,故如何已最小的代价完成状态共享,早点下班回家才是我们想要达成的目标。

为了不动原有代码,我们以useState作为切入点,接入heluxuseShared将其替换掉,就完成了我们需要最小代价共享状态的目的。

注:useShared 是v2版本提供的接口,v3已命名为 useAtom

diff 复制代码
import React from 'react';
+ import { createShared, useShared } from 'helux';
+ 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变更才触发重渲染
}

腾讯新闻运营平台

C端对包体大小敏感,故使用了的是裁剪了大量功能,只关注状态共享的v2版本(gzip后2kb),在对内使用的运营平台上,则可以放开手脚,尽一切可能提高开发体验和运行效率,故在>=v3版本后我们基于limu继续构建完全颠覆了传统开发模式的新版本。

在构建新版本helux的同时,还引入了工具链无关的微模块技术hel-micro搭建了一套全新开发模式的react 微前端架构的运营平台。

基于 helux + hel-micro 构建的基于微模块的 react 微前端元框架 helra 我们也将在上半年开源出去,敬请期待。

在这个模式下,我们可以精选化的管理动态模块资源,做到面向不同场景灵活组合出定制的应用(例如灰度、按地域放量、按分支提供某个子应用的测试链接等)。

继续结合公司的ci&cd体系可做到全生命周期的模块管控流程闭环(开发、部署、上线、运维)。

其他

其他内外部其他小伙伴也在使用中的项目,这里就不再一一提及,感谢大家的信任与支持,同时也感谢一些使用者积极共建生态,贡献了其他面向特定场景的封装库,例如面向表单的speed-form 等。

特性一览

此章节我们先了解如何快速开始,然后简单介绍各个重磅特性,包含atomsignal依赖追踪双向绑定细粒度响应式更新观察派生 等特性,同时建议访问官网文档了解更多并体验,每一个api我们都提供了保姆级的配套demo代码和渲染好的可演示组件。

快速开始

阅读此章节可简单了解helux常用接口并快速学会使用它们。

定义 atom

支持定义任意数据结构 atom 对象,被包装为{val:T}结构

ts 复制代码
import { atom } from 'helux';

// 原始类型 atom
const [numAtom] = atom(1);
// 字典对象类型 atom
const [objAtom] = atom({ a: 1, b: { b1: 1 } });

修改 atom

原始值修改

ts 复制代码
const [numAtom, setAtom] = atom(1);
setAtom(100);

字典对象修改,基于setAtom接口回调里的草稿对象直接修改即可

ts 复制代码
const [numAtom, setAtom] = atom({ a: 1, b: { b1: 1 } });
setAtom((draft) => {
  // draft 已拆箱 { val: T } 为 T
  draft.b.b1 += 1;
});

或基于reactive响应式对象修改,数据变更在下一次事件循环微任务开始前被提交。

ts 复制代码
const [numAtom, setAtom, {reactive}] = atom({ a: 1, b: { b1: 1 } });
function change(){
  reactive.b.b1 += 1;
}

或定义action修改

ts 复制代码
const [numAtom, setAtom, { action, defineActions }] = atom({ a: 1, b: { b1: 1 } });
// 方式1:裸写 action
const change = action()(({draft})=>{
 draft.b.b1 += 1;
}, 'change');
change(); // 触发变更

// 方式2:调用可读性更友好的 defineActions
const { actions } = defineActions()({
  change({draft}){
    draft.b.b1 += 1;
  },
  // 可以继续定义其他 action
});
actions.change(); // 触发变更

通过 action 函数调用有 2 大好处

  • 接入 devltool 后状态修改历史可详细追溯
  • 异步函数可自动享受下文提到的loading管理能力

观察 atom

可观察整个根对象变化,也可以观察部分节点变化

ts 复制代码
import { atom, watch, getSnap } from 'helux';

watch(
  () => {
    console.log(`change from ${getSnap(numAtom).val} to ${numAtom.val}`);
  },
  () => [atom],
);

watch(
  () => {
    console.log(
      `change from ${getSnap(numAtom).val.b.b1} to ${numAtom.val.b.b1}`,
    );
  },
  () => [objAtom.val.b.b1],
);

派生 atom

  • 1 全量派生

derive 接口接受一个派生函数实现,返回一个全新的派生值对象,该对象是一个只可读的稳定引用,全局使用可总是读取到最新值。

ts 复制代码
import { atom, derive } from 'helux';

const [numAtom, setAtom] = atom(1);
const plus100 = derive(() => atom.val + 100);

setAtom(100);
console.log(plus100); // { val: 200 }

setAtom(100); // 设置相同结果,派生函数不会再次执行

使用已派生结果继续派生新的结果

ts 复制代码
const plus100 = derive(() => atom.val + 100);
const plus200 = derive(() => plus100.val + 200);
  • 2 可变派生

当共享对象 a 的发生变化后需要自动引起共享状态 b 的某些节点变化时,可定义 mutate 函数来完成这种变化的连锁反应关系,对数据做最小粒度的更新

ts 复制代码
import { atom, derive } from 'helux';

const  [ objAtom1, setAtom ] = atom({a:1,b:{b1:1}});

const [objAtom2] = atom(
  { plusA100: 0 }
  {
    // 当 objAtom1.val.a 变化时,重计算 plusA100 节点的值
    mutate: {
      changePlusA100: (draft) => draft.plusA100 = objAtom1.val.a + 100,
    }
  },
);

setAtom(draft=>{ draft.a=100 });
console.log(objAtom2.val.plusA100); // 200

使用 atom

react 组件通过useAtom 钩子可使用 atom 共享对象,该钩子返回一个元组,使用方式和 react.useState 类似,区别在于对于非原始对象,回调提供草稿供用户直接修改,内部会生成结构化共享的新状态

tsx 复制代码
import { atom, useAtom } from 'helux';
const [numAtom] = atom(1);

export default function Demo() {
  // 返回结果自动拆箱
  const [num, setAtom] = useAtom(numAtom);
  return <h1 onClick={() => setAtom(Math.random())}>{num}</h1>;
}

atom 对象天然是全局共享的,可将 atom 对象提供给多个组件实例使用

tsx 复制代码
import { atom, useAtom } from 'helux';
const [objAtom, setAtom] = atom({ name: 'hello helux', info: { age: 1 } });

function Demo() {
  const [obj, setAtom] = useAtom(objAtom);
  const changeName = () =>
    setAtom((draft) => {
      draft.info.age += 1;
    });

  return (
    <h1 onClick={() => setAtom(Math.random())}>
      {obj.name} {obj.info.age}
      <button onClick={changeName}>changeName</button>
    </h1>
  );
}

export default () => (
  <>
    <Demo />
    <Demo />
  </>
);

Signal

signal响应机制允许用户跳过useAtom直接将数据绑定到视图,实现 0 hook 编码、dom 粒度块粒度更新。

dom 粒度更新

使用$符号绑定单个原始值创建信号响应块,实现 dom 粒度更新

tsx 复制代码
import { $ } from 'helux';

// 数据变更仅触发 $符号区域内重渲染
<h1>{$(numAtom)}</h1> // 包含原始值的atom可安全绑定
<h1>{$(shared.b.b1)}</h1>// 对象型需自己取到原始值绑定

块粒度更新

使用block绑定多个原始值创建局部响应块,实现块粒度更新

ts 复制代码
// UserBlock 已被 memo
const UserBlock = block(() => (
  <div>
    name: {user.name}
    desc: {user.detail.desc}
  </div>
));

// 其他地方使用 UserBlock
<UserBlock />;

依赖追踪

除了对$block这些静态节点建立起视图对数据变化的依赖关系,使用useAtom方式的组件渲染期间将实时收集到数据依赖

依赖收集

组件时读取数据节点值时就产生了依赖,这些依赖被收集到helux内部为每个组件创建的实例上下文里暂存着,作为更新凭据来使用。

helux 内部默认的收集深度为6,可自己按需调节。

tsx 复制代码
const { state, setDraft, useState } = atomx({ a: 1, b: { b1: 1 } });

// 修改草稿,生成具有数据结构共享的新状态,当前修改只会触发 Demo1 组件渲染
const changeObj = () => setDraft((draft) => (draft.a = Math.random()));

function Demo1() {
  const [obj] = useState();
  // 仅当 obj.a 发生变化时才触发重渲染
  return <h1>{obj.a}</h1>;
}

function Demo2() {
  const [obj] = useState();
  // 仅当 obj.b.b1 发生变化时才触发重渲染
  return <h1>{obj.b.b1}</h1>;
}

依赖变更

存在 if 条件时,每一轮渲染期间收集的依赖将实时发生变化

tsx 复制代码
import { atomx } from 'helux';

const { state, setDraft, useState } = atomx({ a: 1, b: { b1: 1 } });
const changeA = () => setDraft((draft) => (draft.a += 1));
const changeB = () => setDraft((draft) => (draft.a.b1 += 1));

function Demo1() {
  const [obj] = useState();
  // 大于 3 时,依赖为 a, b.b1
  if (obj.a > 3) {
    return (
      <h1>
        {obj.a} - {obj.b.b1}
      </h1>
    );
  }

  return <h1>{obj.a}</h1>;
}

依赖比较

得益于limu产生的结构共享数据,helux内部可以高效的比较快照变更部分,当用户重复设置相同的值组件将不被渲染

tsx 复制代码
import { atomx } from 'helux';

const { state, setDraft, useState } = atomx({
  a: 1,
  b: { b1: { b2: 1, ok: true } },
});
const changeB1 = () => setDraft((draft) => (draft.b.b1 = { ...draft.b.b1 }));
const changeB1_Ok_oldValue = () =>
  setDraft((draft) => (draft.b.b1.ok = draft.b.b1.ok));
const changeB1_Ok_newValue = () =>
  setDraft((draft) => (draft.b.b1.ok = !draft.b.b1.ok));

// 调用 changeB1_Ok_oldValue changeB1 Demo1 不会被重渲染
// 调用 changeB1_Ok_newValue ,Demo1 被重渲染
function Demo1() {
  const [obj] = useState();
  return <h1>obj.b.b1.ok {`${obj.b.b1.ok}`}</h1>;
}

响应式

atom 返回的 state 是只可读数据,变更必须配合setState,同时 atom 也提供响应式对象,可直接操作修改,变化部分数据会在下一次事件循环微任务开始前执行

直接修改

ts 复制代码
import { atom } from 'helux';
import { delay } from '@helux/demo-utils';

// reactive 已自动拆箱
const { state, reactive } = atomx({ a: 1, b: { b1: { b2: 1, ok: true } } });

async function change() {
  reactive.a = 100;
  console.log(state.val.a); // 1
  await delay(1);
  console.log(state.val.a); // 100
}

组件中使用

组件中可使用useReactive钩子来获得响应式对象

tsx 复制代码
import { sharex } from 'helux';

const { reactive, useReactive } = sharex({
  a: 1,
  b: { b1: { b2: 1, ok: true } },
});

// 定时修改 a b2
setTimeout(() => {
  reactive.a += 1;
  reactive.b.b1.b2 += 1;
}, 2000);

// 组件外部修改 ok
function toogleOkOut() {
  reactive.b.b1.ok = !reactive.b.b1.ok;
}

function Demo() {
  const [reactive] = useReactive();
  return <h1>{reactive.a}</h1>;
}
function Demo2() {
  const [reactive] = useReactive();
  return <h1>{reactive.b.b1.b2}</h1>;
}
function Demo3() {
  const [reactive] = useReactive();
  // 组件内部切换 ok
  const toogle = () => (reactive.b.b1.ok = !reactive.b.b1.ok);
  return <h1>{`${reactive.b.b1.ok}`}</h1>;
}

signal 中使用

可直接将 reactive 值传给 $原始值响应或block块响应

tsx 复制代码
import { $, block, sharex } from 'helux';

const { reactive } = sharex({
  a: 1,
  b: { b1: { b2: 1, ok: true } },
});

function InSignalZone() {
  return <h1>{$(reactive.a)}</h1>;
}

const InBlockZone = block(() => {
  return (
    <div>
      <h3>{reactive.a}</h3>
      <h3>{reactive.b.b1.b2}</h3>
    </div>
  );
});

主动flush

input组件实时输入过程中,需主动调用flush接口刷新状态,避免中文输入法出现中文无法提示的问题。

tsx 复制代码
import { sharex } from 'helux';
const { reactive, useState, flush } = sharex({ str: '' });
function change(e) {
  reactive.str = e.target.value;
  // 去掉 flush 调用,中文输入法无法录入汉字
  flush();
}

双向绑定

提供syncersync函数生成数据同步器,可直接绑定到表达相关onChange事件,同步器会自动提取事件值并修改共享状态,达到双向绑定的效果!

浅层数据绑定

只有一层 json path 的对象,可以使用 syncer 生成数据同步器来绑定

tsx 复制代码
const { syncer, state } = sharex({ a: 1, b: { b1: 1 }, c: true });

<input value={state.a} onChange={syncer.a} />;
<input type="checkbox" checked={state.c} onChange={syncer.c} />;

syncer会自动分析是否是事件对象,是就提取值不是就直接传值,所以也可以很方便的绑定 ui 组件库

tsx 复制代码
import { Select } from 'antd';

<Select value={state.a} onChange={syncer.a} />;

原始值 atom 绑定时,传递syncer自身即可

tsx 复制代码
const { syncer, useState } = atomx('');

function Demo1() {
  const [state] = useState();
  return <input value={state} onChange={syncer} />;
}

深层数据绑定

多层 json path 的对象,使用 sync 生成数据同步器来绑定,可通过回调设定绑定节点

tsx 复制代码
// 数据自动同步到 to.b.b1 下
<input value={state.b.b1} onChange={sync((to) => to.b.b1)} />

可传递路径字符串数组定义绑定目标节点

tsx 复制代码
<input value={state.b.b1} onChange={sync(['b', 'b1'])} />

拦截修改

sync 函数提供 before 回调给用户,支持数据提交前做二次修改

tsx 复制代码
<input
  value={num}
  onChange={sync(
    (to) => to.b.b1,
    (val) => {
      return val === '888' ? 'boom' : val;
    },
  )}
/>

支持before回调里修改其他值

tsx 复制代码
<input
  value={num}
  onChange={sync(
    (to) => to.b.b1,
    (val, params) => {
      if (val === '888') {
        params.draft.b2 = 'b2 changed';
        return 'boom';
      }
    },
  )}
/>

派生

支持全量派生可变派生,两种派生都支持定义异步计算任务,执行时间除了观察数据变化自执行以外,均可人工触发。

全量派生

derive 接口该接受一个派生函数实现,返回一个全新的派生值对象,该对象是一个只可读的稳定引用,全局使用可总是读取到最新值。

ts 复制代码
import { atom, derive } from 'helux';

const [numAtom] = atom(5);
const [info] = share({
  a: 50,
  c: { c1: 100, c2: 1000 },
  list: [{ name: 'one', age: 1 }],
});

// 仅在 numAtom.val 或 info.c.c1 发生变化后才会重运行计算出新的 result
const result = derive(() => {
  return numAtom.val + info.c.c1;
});

// 定义异步全量派生
const resultAsync = derive({
  fn: ()=>0, // 初始值
  deps: ()=>[numAtom.val, info.c.c1], // 依赖函数,返回值会透传给 input
  task: async({ input }){
  	await delay(1000);
	return input[0] + input[1];
  },
});

可变派生

由于 atomshare 返回的对象天生自带依赖追踪特性,当共享对象 a 的发生变化后需要自动引起共享状态 b 的某些节点变化时,可定义 mutate 函数来完成这种变化的连锁反应关系,对数据做最小粒度的更新

ts 复制代码
import { atom, share, mutate } from 'helux';

const [baseAtom] = atom(1);
const [numAtom] = atom(3000);

// baseAtom 变化计算 numAtom
mutate(numAtom)({
  fn: (draft) => (draft.val = baseAtom.val + 100),
  desc: 'mutateNumAtomVal',
});

// 定义异步可变派生
mutate(numAtom)({
  deps: ()=>[baseAtom.val], // 依赖函数,返回值会透传给 input
  task: async({draftRoot, input}){
     await delay(1000);
     draftRoot.val = input[0] + 100; // 直接修改 draft
  },
  desc: 'mutateNumAtomVal',
});

观察

helux在内部为实现更智能的自动观察变化做了大量优化工作,同时也暴露了相关接口支持用户在一些特殊场景做人工的观察变化。

watch

使用watch可观察 atom 对象自身变化或任意多个子节点的变化。

观察函数立即执行,首次执行时收集到相关依赖

ts 复制代码
import { share, watch, getSnap } from 'helux';

const [priceState, setPrice] = share({ a: 1 });

watch(
  () => {
    // 首次执行日志如下
    // price change from 1 to 1
    //
    // 反复调用 changePrice,日志变化如下
    // price change from 1 to 101
    // price change from 101 to 201
    console.log(
      `price change from ${getSnap(priceState).a} to ${priceState.a}`,
    );
  },
  { immediate: true },
);

const changePrice = () =>
  setPrice((draft) => {
    draft.a += 100;
  });

观察函数不立即执行,通过 deps 函数定义需要观察的数据,观察的粒度可以任意定制

ts 复制代码
const [priceState, setPrice] = share({ a: 1 });
const [numAtom, setNum] = atom(3000);

// 观察 priceState.a 的变化
watch(
  () => {
    console.log(`found price.a changed: () => [priceState.a]`);
  },
  () => [priceState.a],
  // 或写为
  // { deps: () => [priceState.a] }
);

// 观察整个 priceState 的变化
watch(
  () => {
    console.log(`found price changed: [ priceState ]`);
  },
  () => [priceState],
);

// 观察整个 priceState 和 numAtom 的变化
watch(
  () => {
    console.log(`found price or numAtom changed: ()=>[ priceState, numAtom ]`);
  },
  () => [priceState, numAtom],
);

即设置依赖函数也设置立即执行,此时的依赖由 depswatch 共同收集到并合并而得。

ts 复制代码
watch(
  () => {
    const { a } = priceState;
    console.log(`found one of them changed: [ priceState.a, numAtom ]`);
  },
  { deps: () => [numAtom], immediate: true },
);

watchEffect

watchEffect回调会立即执行,自动对首次运行时函数内读取到的值完成变化监听

ts 复制代码
import { watchEffect, getSnap } from ' helux ';
const [priceState, setPrice] = share({ a: 1 });

// 观察 priceState.a 的变化
watchEffect(() => {
  console.log(`found price.a changed from ${getSnap(priceState).a} to ${priceState.a}`);
});

useWatch

提供useWatch让开发者在组件内部观察变化

tsx 复制代码
import { getSnap, share, useWatch } from 'helux';
const [priceState, setPrice] = share({ a: 1 });

function changeA() {
  setPrice((draft) => void (draft.a += 1));
}

function Comp(props: any) {
  const [tip, setTip] = React.useState('');
  // watch 回调随组件销毁会自动取消监听
  useWatch(
    () => {
      setTip(
        `priceState.a changed from ${getSnap(priceState).a} to ${priceState.a}`,
      );
    },
    () => [priceState.a],
  );

  return <h1>watch tip: {tip}</h1>;
}

useWatch 无闭包陷阱问题,总能感知闭包外的最新值

tsx 复制代码
import { $, share, useObject, useWatch } from 'helux';
const [priceState, setPrice] = share({ a: 1 });
function changeA() {
  setPrice((draft) => void (draft.a += 1));
}

function Comp(props: any) {
  const [obj, setObj] = useObject({ num: 1 });
  const [tip, setTip] = React.useState('');

  useWatch(
    () => {
      // priceState.a changed, here can read the latest num
      setTip(`num in watch cb is ${obj.num}`);
    },
    () => [priceState.a],
  );
}

useWatchEffect

在组件中使用 useWatchEffect 来完成状态变化监听,会在组件销毁时自动取消监听。

useWatchEffect 功能同 watchEffect``一样,区别在于 useWatchEffect` 会立即执行回调,自动对首次运行时函数内读取到的值完成变化监听。

tsx 复制代码
import { share, useMutable, useWatchEffect, getSnap } from 'helux';

const [priceState, setState, ctx] = share({ a: 1, b: { b1: { b2: 200 } } });

function changeA() {
  setState((draft) => {
    draft.a += 1;
  });
}

export default function Comp(props: any) {
  const [ state, setState] = useMutable({tip:'1'})
  useWatchEffect(() => {
    // 自动收集到 priceState.a 依赖
    setState(draft=>{
      draft.tip = `priceState.a changed from ${getSnap(priceState).a} to ${priceState.a}`;
    });
  });
}

模块化

尽管atom共享上下文提供了actionderivemutateuserStateuserActionLoadinguserMutateLoading等一系列 api 方便用户使用各项功能,但这些 api 比较零碎,处理大型前端应用时用户更希望面向领域模型对状态的statederiveaction建模,故共享上下文还提供define系列 api 来轻松驾驭此类场景。

为了开发者工具能够查看模块化相关变更动作记录,配置moduleName即可

defineActions

批量定义状态对应的修改函数,返回 { actions, eActions, getLoading, useLoading, useLoadingInfo }, 组件中可通过 useLoading 读取异步函数的执行中状态 loading、是否正常执行结束 ok、以及执行出现的错误 err, 其他地方可通过 getLoading 获取

ts 复制代码
// 【可选】约束各个函数入参 payload 类型
type Payloads = {
  changeA1: number;
  foo: boolean | undefined;
  // 不强制要求为每一个action key 都定义 payload 类型约束,但为了可维护性建议都补上
};

// 不约束 payloads 类型时写为 ctx.defineActions()({ ... });
const { actions, eActions, useLoading, getLoading } =
  ctx.defineActions<Payloads>()({
    // 同步 action,直接修改草稿
    changeA1({ draft, payload }) {
      draft.a.b.c += payload;
    },
    // 同步 action,返回结果
    changeA2({ draft, payload }) {
      draft.a.b.c += payload;
      return true;
    },
    // 同步 action,直接修改草稿深节点数据,使用 merge 修改浅节点数据
    changeA3({ draft, payload, merge }) {
      draft.a.b.c += payload;
      merge({ c: 'new desc' }); // 等效于 draft.c = 'new desc';
      return true;
    },
    // 异步 action,直接修改草稿
    async foo1({ draft, payload }) {
      await delay(3000);
      draft.a.b.c += 1000;
    },
    // 异步 action,多次直接修改草稿,合并修改多个状态,同时返回一个结果
    async foo2({ draft, payload, merge }) {
      draft.a.b.c += 1000;
      await delay(3000); // 进入下一次事件循环触发草稿提交
      draft.a.b.c += 1000;
      await delay(3000); // 再次进入下一次事件循环触发草稿提交
      const { list, total } = await fetchList();
      merge({ list, total }); // 等价于 draft.list = list, draft.tatal = total
      return true;
    },
  });

多个 action 组合为一个新的 action

ts 复制代码
const { actions, eActions, useLoading, getLoading } =
  ctx.defineActions<Payloads>()({
    foo() {},
    bar() {},
    baz() {
      actions.foo();
      actions.bar();
    },
  });

调用 actions.xxx 执行修改动作,actions 方法调用只返回结果,如出现异常则抛出,同时也会发送给插件和伴生 loading 状态

defineFullDerive

批量定义状态对应的全量派生函数,返回结果形如
{ result, helper: { [key]: runDeriveFn, runDeriveTask, useDerived, useDerivedInfo } }

ts 复制代码
type DR = {
  a: { result: number };
  c: { deps: [number, string]; result: number };
  // 不强制要求为每一个 result key 都定义 deps 返回类型约束和 result 类型约束,但为了可维护性建议都补上
};

const df = ctx.defineFullDerive<DR>()({
  a: () => priceState.a.b.c + 10000,
  b: () => priceState.a.b.c + 20000,
  c: {
    // DR['c']['result'] 将约束此处的 deps 返回类型
    deps: () => [priceState.a.b1.c1, priceState.info.name],
    fn: () => 1,
    async task(params) {
      const [c1, name] = params.input; // 获得类型提示
      await delay(2000);
      return 1 + c1;
    },
  },
});

结语

一直以来,支持细粒度响应式更新成为react开发者梦寐以求的特性,而支持此特性,就需要singal原语和依赖收集特性,本质来说这和react追求不可变是相互矛盾的,而helux则跳出常规思维,保持react不可变的精髓,把可变放置到另一个空间去操作,每次生成一份全新的具有结构共享特性的数据快照后,再传递给react即可。

注意这样去做的不只是helux,采取开辟新空间做可变修改,再生成快照给react策略还有mobx-reactvaltio,采取atom路线的有recoidjotai

而同时做到 atom + signal + 依赖追踪,并支持细粒度响应式只有helux,所以helux的目标是期望重定义react开发范式,并全面提升react应用的 DXUX (开发体验、用户体验),为了这个目标我们耕耘近3年(包含了初代仅支持浅层依赖追踪的 concent,到打磨不可变数据操作库 limu,再到构建 helux 整个历程),现全面开源提供给广大react开发者使用,让我们一起来体验全新的开发模式吧。

友链:

❤️ 你的小星星是我们开源最大的精神动力,欢迎关注以下项目:

helux 集atom、signal、依赖追踪为一体,支持细粒度响应更新的状态引擎

limu 最快的不可变数据js操作库.

hel-micro 工具链无关的运行时模块联邦sdk.

相关推荐
weixin_382395238 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__8 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.9 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~10 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华11 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子13 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅13 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni13 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学15 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端