Solid-js

用于构建用户界面的申明式、高效且灵便的 JavaScript 库
您能够在 官网教程 中尝试上面提到的局部例子,本文援用并简化了官网教程中的局部例子

本文讲述局部 solid 次要内容,更多具体内容,移步 Solid API 文档

Solid 应用了和 React 类似的语法和相似 Svelte 的预编译
Solid 应用上相似于 React,应用 JSX 语法,但不同于 React, 组件只会初始化一次,并不是 state 扭转就从新运行渲染整个组件,这相似于 Vue3 的 setup

为什么抉择 Solid

Solid 官网 给出了以下理由

  • 高性能 - 始终在公认的 UI 速度和内存利用率基准测试中名落孙山
  • 弱小 - 可组合的反应式原语与 JSX 的灵活性相结合
  • 求实 - 正当且量身定制的 API 使开发变得乏味而简略
  • 生产力 - 人体工程学和相熟水平使构建简略或简单的货色变得轻而易举

次要劣势

高性能 - 靠近原生的性能,在 js-framework-benchmark 排名中名落孙山
极小的打包体积 - 编译为间接的DOM操作,无虚构DOM,极小的运行时(相似于 Svelte),适宜打为独立的 webComponent 在其它利用中嵌入
易于应用 - 近似 React 的应用体验,便于疾速上手

疾速开始

新建我的项目

npx degit solidjs/templates/js my-appcd my-appnpm inpm run dev

根本示例

这里将 App 组件渲染到 body 容器中

这里批改默认示例, 从零开始尝试
// App.JSXimport { render } from "solid-js/web";function App() {  return (    <div>Solid My App</div>  );}// 组件申明也能够间接用箭头函数/*const App = ()=> (<div>Solid My App</div>);*/render(() => <App />, document.querySelector("body"));

是不是看起来十分相熟,就和 React 一样,十分难受

导入组件,传递组件,props

与 React 相似的应用办法, 但不能解构 props,否则将失去反馈性

// App.JSXimport { render } from "solid-js/web";import Component1 from "./Component1.jsx";function App() {  return (    <div>      Solid My App      <Component1 text={"component1"}>        <div>children</div>      </Component1>    </div>  );}render(() => <App />, document.querySelector("body"));// Component1.jsxexport default function Component1(props) {  return (    <div>      {props.text}      {props.children}    </div>  )}

反馈性

createSignal

signal 是 Solid 中最根本的反馈性单元,此函数相似于 React 的 useState,但返回函数用于获取调用它获取值,而不是像 React 一样间接获得值,下列是一个根本的 Counter 示例

import { createSignal } from "solid-js";export default function Counter() {  const [count, setCount] = createSignal(0);  return (    <button onClick={()=> setCount(count() + 1)}>      {count()}    </button>  )}
createMemo

createMemo 用于生成只读的派生值,相似于 Vue 中的 computed,与下面的雷同,也须要通过调用来获取值

import { createSignal, createMemo } from "solid-js";export default function Counter() {  const [count, setCount] = createSignal(0);  // count 的平方派生自 count,在依赖扭转的时候自动更新  const countPow2 = createMemo(()=> count() ** 2);  return (    <button onClick={()=> setCount(count() + 1)}>      {count()} | {countPow2()}    </button>  )}
createEffect

createEffect 个别用于副作用,在状态扭转的时候运行副作用
它相似于 React 中的 useEffect 但其主动收集依赖,无需显式申明依赖,这和 Vue 中的 watchEffect 作用雷同

import { createSignal, createEffect } from "solid-js";export default function Counter() {  const [count, setCount] = createSignal(0);  // 每当依赖扭转就会从新运行该副作用  createEffect(()=> console.log(count()));  return (    <button onClick={()=> setCount(count() + 1)}>      {count()}    </button>  )}

如果须要显式申明依赖,参考 Solid createEffect 显式申明依赖

batch

Solid 的反馈性是同步的,这意味着在任何更改后的下一行 DOM 都会更新。在大多数状况下,这齐全没问题,因为 Solid 的粒度渲染只是反应式零碎中更新的流传。“渲染”两次无关的更改实际上并不意味着节约工作。
如果更改是相干的怎么办?Solid 的batch助手容许将多个更改排队,而后在告诉观察者之前同时利用它们。在批处理中更新的信号值直到实现才会提交。
参考以下不应用 batch 的例子

import { render } from "solid-js/web";import { createSignal, batch } from "solid-js";const App = () => {  const [firstName, setFirstName] = createSignal("John");  const [lastName, setLastName] = createSignal("Smith");  const fullName = () => {    console.log("Running FullName");    return `${firstName()} ${lastName()}`  }   const updateNames = () => {    console.log("Button Clicked");    setFirstName(firstName() + "n");    setLastName(lastName() + "!");  }    return <button onClick={updateNames}>My name is {fullName()}</button>};render(App, document.getElementById("app"));

在这个例子中,咱们在按钮点击时更新了两个状态,它触发了两次更新,您能够在控制台中看到日志,因而,让咱们批改 updateNames 将 set 调用打包成一个批处理。

 const updateNames = () => {    console.log("Button Clicked");    batch(() => {      setFirstName(firstName() + "n");      setLastName(lastName() + "!");    })  }

当初,对于同一个元素只会触发一次更新

款式

先创立一个款式文件以便上面应用

/* main.css */.container {  width: 100px;  height: 100px;  background-color: green;}.text {  font-size: 20px;  color: red;}
根本应用

款式应用也与 React 十分相似,只是应用 class 而不是 className

import style from "./main.css";export default function Container() {  return (    <div class={style.container}>      <span class={style.text}>text</span>    </div>  )}
classList

用于设置给定的 class 是否存在, 也能够绑定响应式
下列是一个点击切换 class 的示例

import style from "./main.css";import { createSignal } from "solid-js";export default function Container() {  const [hasTextClassName, setHasTextClassName] = createSignal(false);  return (    <div       classList={        {          [style.container]: true,          [style.text]: hasTextClassName()        }      }       onClick={        ()=> setHasTextClassName(!hasTextClassName())      }    >    text    </div>  )}

根本的控制流

控制流大多能够用 JSX 实现雷同性能,然而应用其则具备高于 JSX 的性能,Solid 能够对其进行更多优化
fallback 是在失败后的显示
For

简略的援用键控循环管制流程。

export default function Container() {  return (    <div>      <For         each={[1,2,3,4,5,6,7,8,9,10]}         fallback={<div>Failed</div>}      >        {(item) => <div>{item}</div>}      </For>    </div>  )}
Show

Show 控制流用于有条件地渲染视图的一部分。它相似于三元运算符 (a ? b : c),但非常适合模板化 JSX。

import { createSignal } from "solid-js";export default function Container() {  const [count, setCount] = createSignal(10);  return (    <div>      {/* 在 count 大于 5 的时候渲染*/}      <Show         when={count() > 5}         fallback={<div>Failed</div>}      >        <div>content</div>      </Show>    </div>  )}
Switch

Switch 在有 2 个以上的互斥条件时很有用。能够用来做一些简略的路由之类的事件。

import { createSignal } from "solid-js";export default function Container() {  const [count, setCount] = createSignal(10);  return (    <div>      <Switch fallback={<div>Failed</div>}>        <Match when={count() > 5}>          <div>count > 5</div>        </Match>        <Match when={count() < 5}>          <div>count < 5</div>        </Match>      </Switch>    </div>  )}
Index

非索引迭代循环管制流程,如果要迭代的不是数组,而是相似对象这类,应用 Index

export default function Container() {  return (    <div>      <Index         each={{          name: "name",          gender: "male",          age: 100,          address: "address",        }}         fallback={<div>Failed</div>}      >        {(item) => <div>{item}</div>}      </Index>    </div>  )}
ErrorBoundary

谬误边界

function ErrorComponent() {  // 抛出谬误  throw new Error("component error");  return (    <div>content</div>  )}export default function Container() {  return (    <ErrorBoundary fallback={<div>Failed</div>}>      <ErrorComponent></ErrorComponent>    </ErrorBoundary>  )}
Portal

和 React Portal 作用雷同
用于将元素渲染到组件之外的中央,这对于模态窗,信息提醒等是刚需
示例:将元素间接渲染到 body 下

export default function Container() {  return (    <Portal mount={document.querySelector("body")}>      <div>content</div>    </Portal>  )}
其余控制流

参考 API 文档

生命周期

挂载时:onMount
卸载时:onCleanup

import { onMount, onCleanup } from "solid-js";export default function Container() {  onMount(()=> {    console.log("onMount");  });  onCleanup(()=> {    console.log("onCleanup");  });  return (    <div>content</div>  )}

绑定

ref
ref 用于获取 DOM 节点自身
export default function Container() {  let $container;  return (    <div ref={$container}>      container    </div>  )}

传递 ref 则间接在元素上绑定,如
\<div ref={props.ref}\>\</div\>

spread

有时您的组件和元素承受可变数量的属性,将它们作为对象而不是独自传递是有意义的。在组件中包装 DOM 元素时尤其如此,这是制作设计零碎时的常见做法
为此,咱们应用扩大运算符...。
咱们能够传递一个具备可变数量属性的对象:

function Info(props) {  return (    <div>      <div>{props.name}</div>      <div>{props.speed}</div>      <div>{props.version}</div>      <div>{props.website}</div>    </div>  );}const pkg = {  name: "solid-js",  version: 1,  speed: "⚡",  website: "https://solidjs.com",};function Main() {  return (    <Info       name={pkg.name}      version={pkg.version}      speed={pkg.speed}      website={pkg.website}    >    </Info>  )}// 等同于function Main() {  return (    <Info       {...pkg}    >    </Info>  )}

store/嵌套反馈

Solid 中细粒度反馈性的起因之一是它能够独立解决嵌套更新。你能够有一个用户列表,当咱们更新一个名字时,咱们只更新 DOM 中的一个地位,而不会对列表自身进行差异化。很少(甚至是反应式)UI 框架能够做到这一点。

createStore

用于创立一个 store,store 可用于准确地嵌套反馈
此函数将创立一个信号树作为代理,容许独立跟踪嵌套数据结构中的各个值。create 函数返回一个只读代理对象和一个 setter 函数

后面所说的 For 标签在这里会很有用,因为间接应用 JSX 则会间接刷新整个表达式,从而无奈细粒度更新

先看一个没有应用 store 的例子
这里应用一些示例数据并应用 For 标签迭代渲染
点击复选框能够切换其抉择状态,这里先简略的映射原始数据生成新数据在 set 过来
复制代码运行,并尝试点击复选框,查看控制台输入

import { render } from "solid-js/web";import { For, createSignal } from "solid-js";const App = () => {  const [state, setState] = createSignal(    {      // 初始化一个具备 id, text, completed 属性的对象组成的数组      todos: [        {id: 1, text: 1, completed: false},        {id: 2, text: 2, completed: false},        {id: 3, text: 3, completed: false},        {id: 4, text: 4, completed: false}      ]    }  );  // 批改点击的复选框的抉择状态  const toggleTodo = (id) => {    setState({      todos: state().todos.map((todo) => (        todo.id !== id         ? todo         : { ...todo, completed: !todo.completed }      ))    });  }  return (    <>      <For each={state().todos}>        {(todo) => {          const { id, text } = todo;          console.log(`Creating ${text}`)          return <div>            <input              type="checkbox"              checked={todo.completed}              onchange={[toggleTodo, id]}            />            <span              style={{ "text-decoration": todo.completed ? "line-through" : "none"}}            >{text}</span>          </div>        }}      </For>    </>  );};render(App, document.getElementById("app"));

会发现控制台随点击每次输入,这是因为每次都销毁重建了元素,咱们只是批改了一个属性,却要重建元素,这是一种节约
如果咱们应用 store 能够有更准确的反馈,而不须要重建元素,只会在原有的地位更新
把下面的代码批改为以下代码,再次运行并且点击,会发现,元素不再被销毁重建,这保障了高性能

store 的具体应用在下文具体解释
import { render } from "solid-js/web";import { For } from "solid-js";import { createStore } from "solid-js/store";const App = () => {  const [store, setStore] = createStore(    {      // 初始化一个具备 id, text, completed 属性的对象组成的数组      todos: [        {id: 1, text: 1, completed: false},        {id: 2, text: 2, completed: false},        {id: 3, text: 3, completed: false},        {id: 4, text: 4, completed: false}      ]    }  );  // 批改点击的复选框的抉择状态  const toggleTodo = (id) => {    setStore(      "todos",       (t) => t.id === id,       'completed',       (completed) => !completed    );  };  return (    <>      <For each={store.todos}>        {(todo) => {          const { id, text } = todo;          console.log(`Creating ${text}`)          return <div>            <input              type="checkbox"              checked={todo.completed}              onchange={[toggleTodo, id]}            />            <span              style={{ "text-decoration": todo.completed ? "line-through" : "none"}}            >{text}</span>          </div>        }}      </For>    </>  );};render(App, document.getElementById("app"));

认真比照下面两个例子,咱们会发现次要批改与区别如下
createSignal 批改为了 createStore
因为 createStore 间接返回只读代理,而不是 Getter,所以无需调用,间接应用
signal 设置值只是简略的遍历原始数据,扭转并产生新数据,在大多数利用中都是如此,但 Solid 对于这种状况有肯定的优化策略
设置 store 的值能够像相似 react setState 一样,让对象浅合并
然而此处咱们应用了 solid 所反对的另外一种形式,这能够让 solid 通晓咱们具体变动了哪些货色,从而细粒度地更新
在下面的例子中
咱们将 toggleTodo 批改为了这种样子

  (id) => {    setStore(      "todos",       (t) => t.id === id,       'completed',       (completed) => !completed    );  };

这种是 Solid 中的门路语法,参考 官网 API 文档
门路能够是字符串键、键数组、迭代对象({from、to、by})或过滤器函数。这为形容状态变动提供了令人难以置信的表达能力。

const [state, setState] = createStore({  todos: [    { task: 'Finish work', completed: false }    { task: 'Go grocery shopping', completed: false }    { task: 'Make dinner', completed: false }  ]});setState('todos', [0, 2], 'completed', true);// {//   todos: [//     { task: 'Finish work', completed: true }//     { task: 'Go grocery shopping', completed: false }//     { task: 'Make dinner', completed: true }//   ]// }setState('todos', { from: 0, to: 1 }, 'completed', c => !c);// {//   todos: [//     { task: 'Finish work', completed: false }//     { task: 'Go grocery shopping', completed: true }//     { task: 'Make dinner', completed: true }//   ]// }setState('todos', todo => todo.completed, 'task', t => t + '!')// {//   todos: [//     { task: 'Finish work', completed: false }//     { task: 'Go grocery shopping!', completed: true }//     { task: 'Make dinner!', completed: true }//   ]// }setState('todos', {}, todo => ({ marked: true, completed: !todo.completed }))// {//   todos: [//     { task: 'Finish work', completed: true, marked: true }//     { task: 'Go grocery shopping!', completed: false, marked: true }//     { task: 'Make dinner!', completed: false, marked: true }//   ]// }
produce

Solid 强烈建议应用浅层不可变模式来更新状态。通过拆散读取和写入,咱们能够更好地控制系统的反馈性,而不会在通过组件层时失落对代理更改的跟踪的危险
然而,有时候渐变更容易了解
为此,受 Immer 启发的 Solid 提供了一个 produce,用于让 store 可变
沿用下面的例子,批改 toggleTodo 为

  const toggleTodo = (id) => {    setStore(      "todos",       produce((todos) => {        todos.push({ id: ++todoId, text, completed: false });      }),    );  };
更多 store 相干内容

异步

lazy

在利用中,某些组件只在应用时加载,这些组件会被独自打包,在某个工夫被按需加载,solid 也提供了办法
应用 lazy 替换一般的动态 import 语句

import Component1 from "./Component1.jsx";

替换为

const Component1 = lazy(() => import("./Component1"));
因为 lazy 接接管的参数只是返回 Solid 组件的 Promise,因而,还能够在加载的时候附加一些行为
createResource

创立一个能够治理异步申请的信号。fetcher 是一个异步函数,它承受sourceif 提供的返回值并返回一个 Promise,其解析值设置在资源中。fetcher 不是响应式的,因而如果您心愿它运行屡次,请应用可选的第一个参数。如果源解析为 false、null 或 undefined,则不会获取。
官网在线尝试

const [data, { mutate, refetch }] = createResource(getQuery, fetchData);// 获取值data();// 查看其是否加载中data.loading;// 查看是否出错data.error;// 间接设置值mutate(optimisticValue);// 刷新,从新申请refetch();
Suspense

Suspense 配合异步组件应用
在尚未加载结束时显示 fallback 中给定的内容

const Component1 = lazy(() => import("./Component1"));export default function App() {  return (    <Suspense fallback={<div>loading...</div>}>      <Component1></Component1>    </Suspense>  )}
更多异步内容

总结

  • Solid 具备高性能,并且具备极小的打包体积,适宜打包为独立的模块嵌入其它我的项目
  • Solid 上手简略,贴合 React 或是 Vue3 开发者的应用习惯
  • Solid 中 JSX 间接返回 DOM 元素,合乎直觉,并且很污浊
  • Solid 某些中央须要应用其指定的货色能力达到高性能,高性能并不是毫无代价的
  • Solid 目前应用并不多,生态有待欠缺