SolidJS:一款高性能的响应式JavaScript UI库
Solid是一个用于创建用户界面的响应式JavaScript库,它无需虚拟DOM。它将模板编译成真正的DOM节点,并将更新包装在细粒度的反应中,因此当状态更新时,只有相关的代码才会运行。
这种方式使得编译器可以优化初始渲染,运行时可以优化更新。这种对性能的关注使其成为最受好评的JavaScript框架之一。
我对此很好奇,想尝试一下,所以我花了一些时间创建了一个小型待办事项应用程序,来探索这个框架如何处理渲染组件、更新状态、设置存储等等。
如果您迫不及待地想查看最终代码和结果,请查看最终演示: [此处应插入最终演示链接,原文未提供]
与大多数框架一样,我们可以从安装npm包开始。要将该框架与JSX一起使用,请运行:
npm install solid-js babel-preset-solid
然后,我们需要将babel-preset-solid添加到我们的Babel、webpack或Rollup配置文件中:
"presets": ["solid"]
或者,如果您想搭建一个小型应用程序,您也可以使用他们的模板之一:
# 从Solid模板创建一个小型应用程序 npx degit solidjs/templates/js my-app # 更改到创建的项目目录 cd my-app # 安装依赖项 npm i # 或 yarn 或 pnpm # 启动开发服务器 npm run dev
支持TypeScript,如果您想启动一个TypeScript项目,请将第一个命令更改为npx degit solidjs/templates/ts my-app
。
渲染组件的语法类似于React.js,因此可能看起来很熟悉:
import { render } from "solid-js/web"; const HelloMessage = props => <div>Hello {props.name}</div>; render( () => <hellomessage name="Taylor"></hellomessage>, document.getElementById("hello-example") );
我们需要先导入render函数,然后创建一个带有文本和prop的div,并调用render,传入组件和容器元素。
这段代码随后被编译成真正的DOM表达式。例如,上面的代码示例,一旦被Solid编译,看起来像这样:
import { render, template, insert, createComponent } from "solid-js/web"; const _tmpl$ = template(`<div>Hello </div>`); const HelloMessage = props => { const _el$ = _tmpl$.cloneNode(true); insert(_el$, () => props.name); return _el$; }; render( () => createComponent(HelloMessage, { name: "Taylor" }), document.getElementById("hello-example") );
Solid Playground非常酷,它显示Solid有不同的渲染方式,包括客户端、服务器端和带有水合的客户端。
Solid使用一个名为createSignal
的hook,它返回两个函数:一个getter和一个setter。如果您习惯使用像React.js这样的框架,这可能看起来有点奇怪。您通常期望第一个元素是值本身;但是,在Solid中,我们需要显式调用getter来拦截读取值的位置,以便跟踪其更改。
例如,如果我们正在编写以下代码:
const [todos, addTodos] = createSignal([]);
记录todos
不会返回值,而是一个函数。如果我们想使用该值,我们需要调用该函数,例如todos()
。
对于一个小的待办事项列表,这将是:
import { createSignal } from "solid-js"; const TodoList = () => { let input; const [todos, addTodos] = createSignal([]); const addTodo = value => { return addTodos([...todos(), value]); }; return ( <h1>To do list:</h1> <input type="text" ref="{el"> input = el} /> <button onclick="{()"> addTodo(input.value)}>Add item</button>
上面的代码示例将显示一个文本字段,单击“添加项目”按钮后,将使用新项目更新todos并在列表中显示它。
这看起来可能与使用useState
非常相似,那么使用getter有什么不同呢?考虑以下代码示例:
console.log("Create Signals"); const [firstName, setFirstName] = createSignal("Whitney"); const [lastName, setLastName] = createSignal("Houston"); const [displayFullName, setDisplayFullName] = createSignal(true); const displayName = createMemo(() => { if (!displayFullName()) return firstName(); return `${firstName()} ${lastName()}`; }); createEffect(() => console.log("My name is", displayName())); console.log("Set showFullName: false "); setDisplayFullName(false); console.log("Change lastName "); setLastName("Boop"); console.log("Set showFullName: true "); setDisplayFullName(true);
运行上面的代码将得到:
<code>Create Signals My name is Whitney Houston Set showFullName: false My name is Whitney Change lastName Set showFullName: true My name is Whitney Boop</code>
需要注意的主要一点是,在设置新的lastName后,“My name is...”没有被记录。这是因为此时没有任何内容正在监听lastName()
的更改。只有当displayFullName()
的值更改时,displayName()
的新值才会被设置,这就是为什么当setShowFullName
被设置为true时,我们可以看到新的lastName被显示。
这为我们提供了一种更安全的方式来跟踪值的更新。
在最后一个代码示例中,我介绍了createSignal
,还有一些其他的原语:createEffect
和createMemo
。
createEffect
跟踪依赖项,并在每次依赖项发生更改的渲染后运行。
// 不要忘记首先使用 'import { createEffect } from "solid-js";' 导入它 const [count, setCount] = createSignal(0); createEffect(() => { console.log("Count is at", count()); });
每次count()
的值发生更改时,都会记录“Count is at...”
createMemo
创建一个只读信号,每当执行的代码的依赖项更新时,它都会重新计算其值。当您想要缓存一些值并访问它们而无需重新评估它们(直到依赖项更改)时,可以使用它。
例如,如果我们想显示一个计数器100次并在单击按钮时更新值,使用createMemo
将允许重新计算仅在每次点击时发生一次:
function Counter() { const [count, setCount] = createSignal(0); // 不用createMemo包装counter会调用100次 // const counter = () => { // return count(); // } // 用createMemo包装counter,每次更新只调用一次 // 不要忘记首先使用 'import { createMemo } from "solid-js";' 导入它 const counter = createMemo(() => count()); return ( <div> <button onclick="{()"> setCount(count() 1)}>Count: {count()}</button> <div>1. {counter()}</div> <div>2. {counter()}</div> <div>3. {counter()}</div> <div>4. {counter()}</div> </div> ); }
Solid公开了几个生命周期方法,例如onMount
、onCleanup
和onError
。如果我们希望某些代码在初始渲染后运行,我们需要使用onMount
:
// 不要忘记首先使用 'import { onMount } from "solid-js";' 导入它 onMount(() => { console.log("I mounted!"); });
onCleanup
类似于React中的componentDidUnmount
——它在响应式作用域重新计算时运行。
onError
在最近的子作用域中发生错误时执行。例如,当数据获取失败时,我们可以使用它。
要为数据创建存储,Solid公开了createStore
,其返回值是一个只读代理对象和一个setter函数。
例如,如果我们将我们的待办事项示例更改为使用存储而不是状态,它将如下所示:
const [todos, addTodos] = createStore({ list: [] }); createEffect(() => { console.log(todos.list); }); onMount(() => { addTodos('list', (list) => [...list, { item: "a new todo item", completed: false }]); });
上面的代码示例将首先记录一个带有空数组的代理对象,然后记录一个带有数组的代理对象,该数组包含对象{item: "a new todo item", completed: false}
。
需要注意的是,如果不访问其属性,则无法跟踪顶级状态对象——这就是为什么我们记录todos.list
而不是todos
的原因。
如果我们只在createEffect
中记录todos
,我们将看到列表的初始值,但不会看到在onMount
中进行更新后的值。
要更改存储中的值,我们可以使用在使用createStore
时定义的设置函数来更新它们。例如,如果我们想将待办事项列表项更新为“已完成”,我们可以通过这种方式更新存储:
const [todos, setTodos] = createStore({ list: [{ item: "new item", completed: false }] }); const markAsComplete = text => { setTodos( "list", (i) => i.item === text, "completed", (c) => !c ); }; return ( <button onclick="{()"> markAsComplete("new item")}>Mark as complete</button> );
为了避免在使用.map()
等方法时在每次更新时浪费性地重新创建所有DOM节点,Solid允许我们使用模板助手。
其中一些可用,例如For
用于循环遍历项目,Show
用于有条件地显示和隐藏元素,Switch
和Match
用于显示与特定条件匹配的元素,等等!
以下是一些显示如何使用它们的示例:
<for each="{todos.list}" fallback="{<div">Loading...}> {(item) => <div>{item}</div>} </for> <show when="{todos.list[0]?.completed}" fallback="{<div">Loading...}> <div>1st item completed</div> </show> <switch fallback="{<div">No items}> <match when="{todos.list[0]?.completed}"><completedlist></completedlist></match> <match when="{!todos.list[0]?.completed}"><todolist></todolist></match> </switch>
这是对Solid基础知识的快速介绍。如果您想试用它,我创建了一个入门项目,您可以通过单击下面的按钮将其自动部署到Netlify并克隆到您的GitHub!
[此处应插入部署到Netlify的按钮,原文未提供] 该项目包括Solid项目的默认设置,以及我在这篇文章中提到的基本概念的示例待办事项应用程序,以帮助您入门!
这个框架比我在这里介绍的要多得多,所以请随意查看文档以了解更多信息!
以上是固体JavaScript库简介的详细内容。更多信息请关注PHP中文网其他相关文章!