V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
nanxiaobei
V2EX  ›  React

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

  •  1
     
  •   nanxiaobei ·
    nanxiaobei · 2022-04-07 22:30:14 +08:00 · 3462 次点击
    这是一个创建于 720 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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. 还要做什么?

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

    • createEffect (用于监听数据更新)
    • createMemo (用于创建 computed 数据)
    • onMount (用于发送请求)
    • onCleanup (用于取消订阅)
    • 如果数据是 object 或 array 怎么办?(这是最复杂的,上文其实只考虑了基本数据类型)
    • 如何实现 JSX 中的三目表达式或函数调用?(三目或函数,都只在初始化执行一次,无法响应变化)
    • 如何响应 HMR ?数据首次没出现在 JSX 中怎么办?组件 unmount 后如何取消订阅 ...

    5. solid-react 登场

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

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

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

    下面是 solid-react 的 API:

    • useSignal (对应 createSignal ,用于创建数据)
    • useUpdate (对应 createEffect ,用于监听数据更新)
    • useAuto (对应 createMemo ,用于创建 computed 数据)
    • useMount (对应 onMount ,用于发送请求)
    • useCleanup (对应 onCleanup ,用于取消订阅)
    • 数据是 object 或 array (使用 proxy 处理此棘手情况)
    • Run (用于 JSX 中的三目表达式或函数,Run(() => fn(value())

    请注意 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 混用的情况 ... 是的,我连这种情况都考虑到了 🙈

    9 条回复    2022-04-22 05:11:21 +08:00
    wonderblank
        1
    wonderblank  
       2022-04-08 07:31:14 +08:00 via iPhone
    东西蛮好的,op 是怎么理解 “ React 是把 App 整个函数死去活来的调用(即 re-render )” ?我反而觉得这个是个好事情。
    KuroNekoFan
        2
    KuroNekoFan  
       2022-04-08 15:20:17 +08:00
    要把一个 expression 变成一个 call....这么核心的改动怎么可以说“完全是基于 React 的 API 做的”....
    alexsunxl
        3
    alexsunxl  
       2022-04-08 17:51:39 +08:00
    没啥意义啊, 你要自己怼一个生态出来吗?
    复杂的场景搞不定。。。
    玩具都不好玩的感觉。
    nanxiaobei
        4
    nanxiaobei  
    OP
       2022-04-08 18:10:28 +08:00
    @KuroNekoFan #2
    @alexsunxl #3
    知道我为啥写 "7. 还有什么" 吗?
    就是为了防止你俩这样的回复  ̄□ ̄||
    IvanLi127
        5
    IvanLi127  
       2022-04-08 23:42:38 +08:00 via Android
    我喜欢 hooks 欸,我好像也挺喜欢一直掉组件函数🤣我是变成 react 的形状了吗🤣
    sjhhjx0122
        6
    sjhhjx0122  
       2022-04-09 13:07:16 +08:00
    有没有像我一样就喜欢写 class 组件的
    alexsunxl
        7
    alexsunxl  
       2022-04-09 14:42:50 +08:00
    @nanxiaobei 好吧。加油。能做出一些东西,还是挺棒的。
    nanxiaobei
        8
    nanxiaobei  
    OP
       2022-04-09 16:30:41 +08:00
    @alexsunxl #7 嗯不需要加油,我做了很多东西了,欢迎查看 😂 https://github.com/nanxiaobei
    yukinotech
        9
    yukinotech  
       2022-04-22 05:11:21 +08:00
    @wonderblank 对的,起码自己可以控制什么时候该 redner
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3245 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 14:00 · PVG 22:00 · LAX 07:00 · JFK 10:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.