Solid-js的定义
一个声明式、高效且灵活用于构建用户界面的 JavaScript 库(官方文档)
为什么选择Solidjs
- 高性能 - 始终在公认的 UI 速度和内存利用率基准测试中名列前茅
- 强大 - 可组合的反应式原语与 JSX 的灵活性相结合
- 务实 - 合理且量身定制的 API 使开发变得有趣而简单
- 生产力 - 人体工程学和熟悉程度使构建简单或复杂的东西变得轻而易举
优势
高性能 - 接近原生的性能,在 js-framework-benchmark 排名中名列前茅
极小的打包体积 - 编译为直接的DOM操作,无虚拟DOM,极小的运行时(类似于 Svelte),适合打为独立的 webComponent 在其它应用中嵌入
易于使用 - 近似 React 的使用体验,便于快速上手
快速开始
新建项目
> npx degit solidjs/templates/js my-app
> cd my-app
> npm i # or yarn or pnpm
> npm run dev # or yarn or pnpm
示例
将HelloWorld渲染到app容器中
import { render } from 'solid-js/web';
function HelloWorld() {
return <div>Hello World!</div>;
}
render(() => <HelloWorld />, document.getElementById('app'))
是不是看起来非常熟悉,和 React 一样
基本概念
jsx
Signal
Signal 是 Solid 中最基本的反应性单元,此函数类似于 React 的 useState,但返回函数用于获取调用它获取值,而不是像 React 一样直接取得值。使用createSignal创建
const [count, setCount] = createSignal(0);
使用方式
setCount(count() + 1)
setCount((c) => c + 1); // c代表前一个值
读取Signal
<div>Count: {count()}</div>;
Effect
Signal 是可追踪的值,但它们只是等式的一半。另一半是观察者,也就是计算。最基本的计算称为 Effect,它产生副作用 —— 我们系统的输出。
可以通过从 solid-js
导入 createEffect
来创建 Effect。createEffect
接收一个函数,并监视其执行情况。createEffect
会自动订阅在执行期间读取的所有 Signal,并在这些 Signal 值之一发生变化时重新运行该函数。
createEffect(() => {
console.log("The count is now", count());
});
Memo
Memo 既是是跟踪计算,类似 Effect,又是只读 Signal。由于知道对应依赖关系及其观察者,Memo 可以确保他们只对任何更改运行一次。
生命周期
onMount
它只是一个 Effect 调用,但你可以放心使用它,一旦所有初始渲染完成,它只会在组件中运行一次
onMount(async () => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/photos?_limit=20`
);
setPhotos(await res.json());
});
onCleanup
一些框架的清理方法直接是框架的副作用或生命周期方法的返回值。由于 Solid 渲染树中的所有内容都存在于(可能是惰性的)Effect 中并且可以嵌套,因此我们将 onCleanup
设为一级方法。您可以在任何范围内调用它,它会在该范围被触发以重新求值以及最终销毁时运行。
onCleanup(() => clearInterval(timer));
流程控制
控制流大多可以用 JSX 实现相同功能,但是使用其则具有高于 JSX 的性能,Solid 可以对其进行更多优化
fallback 是在失败后的显示
Show
JSX 允许使用 JavaScript 来控制模板中的逻辑流。然而,如果没有虚拟 DOM,天真地使用诸如 Array.prototype.map
之类的东西会在每次更新时很浪费地重新创建所有 DOM 节点。相反,Reactive 库使用模板工具是很常见。在 Solid 中,我们将其封装在组件中
<Show
when={loggedIn()}
fallback={() => <button onClick={toggle}>Log in</button>}
>
<button onClick={toggle}>Log out</button>
</Show>
fallback
属性充当 else
,在传递给 when
的条件不为 true
时显示。
For
<For each={cats()}>
{(cat, i) => (
<li>
<a target="_blank" href={`https://www.youtube.com/watch?v=${cat.id}`}>
{i() + 1}: {cat.name}
</a>
</li>
)}
</For>
index
是一个 Signal,因此它可以在移动行时独立更新
Index
<Index each={cats()}>
{(cat, i) => (
<li>
<a target="_blank" href={`https://www.youtube.com/watch?v=${cat().id}`}>
{i + 1}: {cat().name}
</a>
</li>
)}
</Index>
<Index>
与 <For>
具有相似的签名,除了数据项是 Signal 并且索引是固定的。
Switch
<Switch>
和 <Match>
组件类比 javaScript的Switch/case
<Switch fallback={<p>{x()} is between 5 and 10</p>}>
<Match when={x() > 10}>
<p>{x()} is greater than 10</p>
</Match>
<Match when={5 > x()}>
<p>{x()} is less than 5</p>
</Match>
</Switch>
Dynamic
<Dynamic>
可以让你将元素的字符串或组件函数传递给它,并使用提供的其余 props 来渲染组件
<Dynamic component={options[selected()]} />
除此之外,还有Portal、ErrorBoundary等组件,详情见官方文档
绑定
事件绑定
常规事件绑定
<div onMouseMove={handleMouseMove}>
The mouse position is {pos().x} x {pos().y}
</div>
Solid 支持数组语法调用事件处理程序
const handler = (data, event /*...*/) => (
<button onClick={[handler, data]}>Click Me</button>
);
on:
命名空间来匹配冒号后面的事件处理程序
<button on:WierdEventName={() => /* Do something */} >Click Me</button>
样式
先写一段css
/* main.module.css */
.container {
width: 100px;
height: 100px;
background-color: green;
}
.text {
font-size: 20px;
color: red;
}
样式使用也与 React 非常类似,只是使用 class 而不是 className
import style from "./main.module.css";
export default function Container() {
return (
<div class={style.container}>
<span class={style.text}>text</span>
</div>
)
}
Solid 中的 style 属性接受样式字符串或对象。然而,对象形式不同于
Element.prototype.style
,Solid 通过调用style.setProperty
的封装来进行样式设置。这意味着键需要采用破折号的形式,如background-color
而不是backgroundColor
。
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>
)
}
ref/ref转发
<div ref={myDiv}>My Element</div>;
<div ref={el => /* 处理 el... */}>My Element</div>
<canvas ref={props.ref} width="256" height="256" />
指令
Solid 通过
use:
命名空间支持自定义指令。但这只是ref
一个有用的语法糖,类似于原生的绑定,并且可以在同一个元素上有多个绑定而不会发生冲突。这可以让我们更好地利用可重用 DOM 元素行为。
<div class="modal" use:clickOutside={() => setShow(false)}>
Some Modal
</div>
click-outside.js
//click-outside.js
export default function clickOutside(el, accessor) {
const onClick = (e) => !el.contains(e.target) && accessor()?.();
document.body.addEventListener("click", onClick);
onCleanup(() => document.body.removeEventListener("click", onClick));
}
Props
设置props默认值(mergeProps)
一般情况
export default function Greeting(props) {
return <h3>{props.greeting || "Hi"} {props.name || "John"}</h3>
}
使用mergeProps
export default function Greeting(props) {
const merged = mergeProps({ greeting: "Hi", name: "John" }, props);
return <h3>{merged.greeting} {merged.name}</h3>
}
解构Props(splitProps
)
使用直接解构的方式,会失去响应性
export default function Greeting(props) {
const { greeting, name, ...others } = props;
return <h3 {...others}>{greeting} {name}</h3>
}
使用splitProps可以保持其相应行
export default function Greeting(props) {
const [local, others] = splitProps(props, ["greeting", "name"]);
return <h3 {...others}>{local.greeting} {local.name}</h3>
}
注意:solid中的props读取时不可以使用延展操作符,否则会失去响应式
异步
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 目前使用并不多,生态有待完善