🧿 将 React 变为 SolidJS,按需更新,再无 re-render

2022-04-07 22:30:14 +08:00
 nanxiaobei

1. SolidJS 是啥?

我们都知道 SolidJS ,如果不知道,那你为啥要看这篇文章 ( ̄▽ ̄)"

我们都知道 SolidJS ,这是它的文档: https://www.solidjs.com/

简单来说,SolidJS 就是真正 "react" 版的 React ,完全按需更新(哪里不懂点哪里),哪里数据变化更新哪里。

比如一个组件:

function App() {
  const [value, setValue] = useState(0);
  return <div>{value}</div>;
}

React 是把 App 整个函数死去活来的调用(即 re-render ),而 SolidJS 则只更新 value 那一小块。

当然,SolidJS 中是这么写的:

function App() {
  const [value, setValue] = createSignal(0);
  return <div>{value()}</div>;
}

在 SolidJS 中, App 只在初始化时被调用一次,之后就不再执行。

所以 SolidJS 里的 JSX 相当于 "静态模板",只用来描述 UI 而不会再次调用,更没有 diff 。

也就是说,随便在 App 里执行函数,随便在 JSX 里执行函数,都只触发一次。

2. 先声明一下

如何把 React 变为 SolidJS ?

当然不是把 solid-js 重命名 react,也不是手动操作 DOM 脱离 React 的逻辑去更新。

这里必须声明一下:

以下实现,完全是基于 React 的 API 做的,而不是用 DOM API 或 jQuery 来 hack 实现的,那样的话就失去了意义。

3. 如何实现呢?

1. 怎么只更新 value() 一小块?

这里是实现思路的核心所在,直接说了吧 —— 就是把 value() 变成一个组件。

是的,它展示的是数据,但它其实是一个组件。它是一个只返回数据的组件。

2. 为什么是 value() 而不是 value

因为需要知道这里有一个数据,后续得更新,怎么知道呢?

根据 JS 语法,除了 state.value(监听 getter)或 value()(调用函数),别无他法。

这也是为什么 SolidJS 必须写成 value(),如果写成 value,神仙也不知道怎么去更新这个数据,因为在 "静态模板" 的实现方式下,函数不会再次运行。

3. 实现一个类似 createSignaluseSignal

我们希望实现一个 useSignal,类似 SolidJS 的 createSignal,返回 getter 和 setter 两个函数。

同时,getter 的返回得是一个组件。

function useSignal(val) {
  const valRef = useRef(val);
  const update = useRef();

  const Render = () => {
    const [value, setValue] = useState(valRef.current);
    update.current = setValue;
    return value;
  };

  const getter = () => {
    try {
      useState(); // 通过此 hack 获知数据是在 JSX 中,还是其它位置正常读取
      return <Render />;
    } catch (e) {
      return valRef.current;
    }
  };

  const setter = (newVal) => {
    valRef.current = newVal;
    update.current(newVal);
  };

  return [getter, setter];
}

上面是一个极简实现,但有问题,因为数据可能用在多处,而上文只能更新最后一处的数据。

4. 数据同步更新版 useSignal

用一个 listeners 数组将更新函数们收集起来,这样就行了。其实这也是 React 状态管理器们的实现思路。

function useSignal(val) {
  const valRef = useRef(val);
  const listeners = useRef([]);

  const Render = () => {
    const [value, setValue] = useState(valRef.current);

    useEffect(() => {
      listeners.current.push(setValue);
      return () => {
        listeners.current.splice(listeners.current.indexOf(setValue), 1);
      };
    }, []);

    return value;
  };

  return [
    () => {
      try {
        useState();
        return <Render />;
      } catch (e) {
        return valRef.current;
      }
    },
    (payload) => {
      listeners.current.forEach((listener) => {
        listener((prev) => {
          valRef.current =
            typeof payload === 'function' ? payload(prev) : payload;
          return valRef.current;
        });
      });
    },
  ];
}

上面已经是一个可用的实现。

写到这里,其实故事的核心已经讲完了。

但如果要真用于开发需求,还有很多未尽的事业。

4. 还要做什么?

如果要真的 "可用",最起码还应该实现:

5. solid-react 登场

上面写了一堆问题,自然是准备好了答案 ... 这个答案就叫 solid-react

上文提到的所有问题都解决了,若深入了解的话可以看源码。

☞ GitHub: https://github.com/nanxiaobei/solid-react

下面是 solid-react 的 API:

请注意 API 的命名,这也是有说法的:尽量不与已有 API 冲突(比如不直接命名为 useState useMemo,那样的话会让代码一团迷惑),同时还得保持足够的简洁(写起来不费事)和直观(理解起来不费事)。

具体 API 介绍请查看 README: https://github.com/nanxiaobei/solid-react

如此,已经可以覆盖大多数常见的开发场景,也就是可用于 "生产" 了。

6. 试试 solid-react

Demo: https://codesandbox.io/s/solid-react-rymhr6?fontsize=14&hidenavigation=1&theme=dark&file=/src/App.js

这里是一个 demo ,可以打开 console ,点击按钮试试,然后你就会发现:

组件再也不 re-render 了,React 完全变成了 SolidJS 式的按需更新!

useUpdate useAuto 也不需要 deps 之类的玩意,对其依赖都是自动获知。而且只有在依赖变化时,它们才再次执行。

是的,也就是可以摆脱 Hooks 了,什么 useCallback useMemo deps memo,会不会触发 re-render ,通通不需要了。

函数就是函数,对象就是对象,写在那里就不会再次新建了,完全不需要再包一层,随便给子组件 props 传递吧。

7. 还有什么?

solid-react 是一个实验项目,只是为了实现一个想法,而事实上实现的还不错。

solid-react 尽力做成了 "能力完备",无论是发请求,还是监听数据,麻雀虽小(但很好吃),五脏俱全。

solid-react 是一个小东西,它可能会有缺陷,当然不能跟直接用 React 开发的成熟度对比,也不可能比。

solid-react 用于小 demo 项目肯定没问题,但未在大项目中实践过,它适合先用来玩一下,如果感兴趣的话。

solid-react 更像是一种理念,React 官方不可能走上这条路,但感谢开源,大可以自己在这条路上实验一下。

solid-react 为 "饱受 Hooks 写法困扰" 这一好几年也没消失的业界普遍困惑而努力(虽然我自己感觉 Hooks 还好)

solid-react 欢迎感兴趣的人一起尝试,创造出更多可能。

将 React 变成 SolidJS ,告别 Hooks ,告别 re-render ↓↓↓

https://github.com/nanxiaobei/solid-react

肯定有人想说,上面 solid-react 的 API 不就是 Hooks 吗?怎么告别 Hooks !其实上面是为了兼容 React 和 solid-react 混用的情况 ... 是的,我连这种情况都考虑到了 🙈

3902 次点击
所在节点    React
9 条回复
wonderblank
2022-04-08 07:31:14 +08:00
东西蛮好的,op 是怎么理解 “ React 是把 App 整个函数死去活来的调用(即 re-render )” ?我反而觉得这个是个好事情。
KuroNekoFan
2022-04-08 15:20:17 +08:00
要把一个 expression 变成一个 call....这么核心的改动怎么可以说“完全是基于 React 的 API 做的”....
alexsunxl
2022-04-08 17:51:39 +08:00
没啥意义啊, 你要自己怼一个生态出来吗?
复杂的场景搞不定。。。
玩具都不好玩的感觉。
nanxiaobei
2022-04-08 18:10:28 +08:00
@KuroNekoFan #2
@alexsunxl #3
知道我为啥写 "7. 还有什么" 吗?
就是为了防止你俩这样的回复  ̄□ ̄||
IvanLi127
2022-04-08 23:42:38 +08:00
我喜欢 hooks 欸,我好像也挺喜欢一直掉组件函数🤣我是变成 react 的形状了吗🤣
sjhhjx0122
2022-04-09 13:07:16 +08:00
有没有像我一样就喜欢写 class 组件的
alexsunxl
2022-04-09 14:42:50 +08:00
@nanxiaobei 好吧。加油。能做出一些东西,还是挺棒的。
nanxiaobei
2022-04-09 16:30:41 +08:00
@alexsunxl #7 嗯不需要加油,我做了很多东西了,欢迎查看 😂 https://github.com/nanxiaobei
yukinotech
2022-04-22 05:11:21 +08:00
@wonderblank 对的,起码自己可以控制什么时候该 redner

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/845579

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX