3

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

 2 years ago
source link: https://www.v2ex.com/t/845579
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK