关于前端:精读SolidJS

8次阅读

共计 5493 个字符,预计需要花费 14 分钟才能阅读完成。

SolidJS 是一个语法像 React Function Component,内核像 Vue 的前端框架,本周咱们通过浏览 Introduction to SolidJS 这篇文章来了解了解其外围概念。

为什么要介绍 SolidJS 而不是其余前端框架?因为 SolidJS 在教 React 团队正确的实现 Hooks,这在唯 React 概念与虚构 DOM 概念马首是瞻的年代十分难得,这也是开源技术的魅力:任何观点都能够被自在挑战,只有你是对,你就可能怀才不遇。

概述

整篇文章以一个新人视角交代了 SolidJS 的用法,但本文假如读者已有 React 根底,那么只有交代外围差别就行了。

渲染函数仅执行一次

SolidJS 仅反对 FunctionComponent 写法,无论内容是否领有状态治理,也无论该组件是否承受来自父组件的 Props 透传,都仅触发一次渲染函数。

所以其状态更新机制与 React 存在基本的不同:

  • React 状态变动后,通过从新执行 Render 函数体响应状态的变动。
  • Solid 状态变动后,通过从新执行用到该状态代码块响应状态的变动。

与 React 整个渲染函数从新执行绝对比,Solid 状态响应粒度十分细,甚至一段 JSX 内调用多个变量,都不会从新执行整段 JSX 逻辑,而是仅更新变量局部:

const App = ({var1, var2}) => (
  <>
    var1: {console.log("var1", var1)}
    var2: {console.log("var2", var2)}
  </>
);

下面这段代码在 var1 独自变动时,仅打印 var1,而不会打印 var2,在 React 里是不可能做到的。

这所有都源于了 SolidJS 叫板 React 的核心理念: 面相状态驱动而不是面向视图驱动 。正因为这个差别,导致了渲染函数仅执行一次,也顺便衍生出变量更新粒度如此之细的后果,同时也是其高性能的根底,同时也解决了 React Hooks 不够直观的顽疾,一箭 N 雕。

更欠缺的 Hooks 实现

SolidJS 用 createSignal 实现相似 React useState 的能力,尽管看上去长得差不多,但实现原理与应用时的心智却齐全不一样:

const App = () => {const [count, setCount] = createSignal(0);
  return <button onClick={() => setCount(count() + 1)}>{count()}</button>;
};

咱们要齐全以 SolidJS 心智了解这段代码,而不是 React 心智了解它,尽管它长得太像 Hooks 了。一个显著的不同是,将状态代码提到外层也齐全能 Work:

const [count, setCount] = createSignal(0);
const App = () => {return <button onClick={() => setCount(count() + 1)}>{count()}</button>;
};

这是最快了解 SolidJS 理念的形式,即 SolidJS 基本没有理 React 那套概念,SolidJS 了解的数据驱动是纯正的数据驱动视图,无论数据在哪定义,视图在哪,都能够建设绑定。

这个设计天然也不依赖渲染函数执行屡次,同时因为应用了依赖收集,也不须要手动申明 deps 数组,也齐全能够将 createSignal 写在条件分支之后,因为不存在执行程序的概念。

派生状态

用回调函数形式申明派生状态即可:

const App = () => {const [count, setCount] = createSignal(0);
  const doubleCount = () => count() * 2;
  return <button onClick={() => setCount(count() + 1)}>{doubleCount()}</button>;
};

这是一个不如 React 不便的点,因为 React 付出了微小的代价(在数据变更后从新执行整个函数体),所以能够用更简略的形式定义派生状态:

// React
const App = () => {const [count, setCount] = useState(0);
  const doubleCount = count * 2; // 这块反而比 SolidJS 定义的简略
  return (<button onClick={() => setCount((count) => count + 1)}>
      {doubleCount}
    </button>
  );
};

当然笔者并不推崇 React 的衍生写法,因为其代价太大了。咱们持续剖析为什么 SolidJS 这样看似简略的衍生状态写法能够失效。起因在于,SolidJS 收集所有用到了 count() 的依赖,而 doubleCount() 用到了它,而渲染函数用到了 doubleCount(),仅此而已,所以天然挂上了依赖关系,这个实现过程简略而稳固,没有 Magic。

SolidJS 还反对衍生字段计算缓存,应用 createMemo

const App = () => {const [count, setCount] = createSignal(0);
  const doubleCount = () => createMemo(() => count() * 2);
  return <button onClick={() => setCount(count() + 1)}>{doubleCount()}</button>;
};

同样无需写 deps 依赖数组,SolidJS 通过依赖收集来驱动 count 变动影响到 doubleCount 这一步,这样拜访 doubleCount() 时就不必总执行其回调的函数体,产生额定性能开销了。

状态监听

对标 React 的 useEffect,SolidJS 提供的是 createEffect,但相比之下,不必写 deps,是真的监听数据,而非组件生命周期的一环:

const App = () => {const [count, setCount] = createSignal(0);
  createEffect(() => {console.log(count()); // 在 count 变动时从新执行
  });
};

这再一次体现了为什么 SolidJS 有资格“教”React 团队实现 Hooks:

  • 无 deps 申明。
  • 将监听与生命周期离开,React 常常容易将其一概而论。

在 SolidJS,生命周期函数有 onMountonCleanUp,状态监听函数有 createEffect;而 React 的所有生命周期和状态监听函数都是 useEffect,尽管看上去更简洁,但即使是精通 React Hooks 的新手也不容易判断哪些是监听,哪些是生命周期。

模板编译

为什么 SolidJS 能够这么神奇的把 React 那么多历史顽疾解决掉,而 React 却不能够呢?外围起因还是在 SolidJS 减少的模板编译过程上。

以官网 Playground 提供的 Demo 为例:

function Counter() {const [count, setCount] = createSignal(0);
  const increment = () => setCount(count() + 1);

  return (<button type="button" onClick={increment}>
      {count()}
    </button>
  );
}

被编译为:

const _tmpl$ = /*#__PURE__*/ template(`<button type="button"></button>`, 2);

function Counter() {const [count, setCount] = createSignal(0);

  const increment = () => setCount(count() + 1);

  return (() => {const _el$ = _tmpl$.cloneNode(true);

    _el$.$$click = increment;

    insert(_el$, count);

    return _el$;
  })();}

首先把组件 JSX 局部提取到了全局模板。初始化逻辑:将变量插入模板;更新状态逻辑:因为 insert(_el$, count) 时曾经将 count_el$ 绑定了,下次调用 setCount() 时,只须要把绑定的 _el$ 更新一下就行了,而不必关怀它在哪个地位。

为了更残缺的实现该性能,必须将用到模板的 Node 彻底分离出来。咱们能够测试一下略微简单些的场景,如:

<button>
  count: {count()}, count+1: {count() + 1}
</button>

这段代码编译后的模板后果是:

const _el$ = _tmpl$.cloneNode(true),
  _el$2 = _el$.firstChild,
  _el$4 = _el$2.nextSibling;
_el$4.nextSibling;

_el$.$$click = increment;

insert(_el$, count, _el$4);

insert(_el$, () => count() + 1, null);

将模板分成了一个整体和三个子块,别离是字面量、变量、字面量。为什么最初一个变量没有加进去呢?因为最初一个变量插入间接放在 _el$ 开端就行了,而两头插入地位须要 insert(_el$, count, _el$4) 给出父节点与子节点实例。

精读

SolidJS 的神秘面纱曾经解开了,上面笔者自问自答一些问题。

为什么 createSignal 没有相似 hooks 的程序限度?

React Hooks 应用 deps 收集依赖,在下次执行渲染函数体时,因为没有任何方法标识“deps 是为哪个 Hook 申明的”,只能依附程序作为标识根据,所以须要稳固的程序,因而不能呈现条件分支在后面。

而 SolidJS 自身渲染函数仅执行一次,所以不存在 React 从新执行函数体的场景,而 createSignal 自身又只是创立一个变量,createEffect 也只是创立一个监听,逻辑都在回调函数外部解决,而与视图的绑定通过依赖收集实现,所以也不受条件分支的影响。

为什么 createEffect 没有 useEffect 闭包问题?

因为 SolidJS 函数体仅执行一次,不会存在组件实例存在 N 个闭包的状况,所以不存在闭包问题。

为什么说 React 是假的响应式?

React 响应的是组件树的变动,通过组件树自上而下的渲染来响应式更新。而 SolidJS 响应的只有数据,甚至数据定义申明在渲染函数内部也能够。

所以 React 尽管说本人是响应式,但开发者真正响应的是 UI 树的一层层更新,在这个过程中会产生闭包问题,手动保护 deps,hooks 不能写在条件分支之后,以及有时候分不清以后更新是父组件 rerender 还是因为状态变动导致的。

这一切都在阐明,React 并没有让开发者真正只关怀数据的变动,如果只有关怀数据变动,那为什么组件重渲染的起因可能因为“父组件 rerender”呢?

为什么 SolidJS 移除了虚构 dom 仍然很快?

虚构 dom 尽管躲避了 dom 整体刷新的性能损耗,但也带来了 diff 开销。对 SolidJS 来说,它问了一个问题:为什么要躲避 dom 整体刷新,部分更新不行吗?

对啊,部分更新并不是做不到,通过模板渲染后,将 jsx 动静局部独自提取进去,配合依赖收集,就能够做到变量变动时点对点的更新,所以无需进行 dom diff。

为什么 signal 变量应用 count() 不能写成 count

笔者也没找到答案,实践上来说,Proxy 应该能够实现这种显式函数调用动作,除非是不想引入 Mutable 的开发习惯,让开发习惯变得更加 Immutable 一些。

props 的绑定不反对解构

因为响应式个性,解构会失落代理的个性:

// ✅
const App = (props) => <div>{props.userName}</div>;
// ❎
const App = ({userName}) => <div>{userName}</div>;

尽管也提供了 splitProps 解决该问题,但此函数还是不天然。该问题比拟好的解法是通过 babel 插件来躲避。

createEffect 不反对异步

没有 deps 尽管十分便捷,但在异步场景下还是无解:

const App = () => {const [count, setCount] = createSignal(0);
  createEffect(() => {async function run() {await wait(1000);
      console.log(count()); // 不会触发
    }
    run();});
};

总结

SolidJS 的外围设计只有一个,即让数据驱动真的回归到数据上,而非与 UI 树绑定,在这一点上,React 误入歧途了。

尽管 SolidJS 很棒,但相干组件生态还没有起来,微小的迁徙老本是它难以疾速替换到生产环境的最大问题。前端生态想要无缝降级,看来第一步是想好“代码范式”,以及代码范式间如何转换,确定了范式后再由社区竞争实现实现,就不会遇到生态难以迁徙的问题了。

但以上假如是不成立的,技术迭代永远都以 BreakChange 为代价,而很多时候只能摈弃旧我的项目,在新我的项目实际新技术,就像 Jquery 时代一样。

探讨地址是:精读《SolidJS》· Issue #438 · dt-fe/weekly

如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。

关注 前端精读微信公众号

<img width=200 src=”https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg”>

版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)

正文完
 0