探索 SolidJS - 反应式原语 (1)
SolidJS 是一个真正的响应式库,它允许你将 JSX 用于前端项目。在这篇博文中,我将分享我对SolidJS UI 库及其响应式原语的初步印象。原文可在此处找到:“SolidJS 简介”
我喜欢在构建前端项目时使用响应式的概念。尽管 React 的名字如此,但它并非一个真正意义上的响应式库。我也喜欢 Svelte,因为它具有响应式特性。我之前写过一篇关于Django 和 Svelte 的教程。然而,我发现用 Svelte 编写的项目不像 React 项目那样具有可扩展性,因为 React 和 JSX 提供了很好的模块化。
然而,SolidJS 兼具了两者的优点。
无耻的促销
我目前不打算用 SolidJS 做真正的项目,除非我熟练掌握它。目前,我正在搭建一个电商平台,İzmir Güvenlik Kamerası(安全摄像头系统)和美术印刷品商店,我会用 SolidJS 做一些小项目。你也可以查看博客网站列表
介绍
好的,让我们深入探讨一下这个话题。在回顾 SolidJS 之前,最好先熟悉一下相关概念。我会简单介绍一下什么是响应式系统?以及响应式原语是什么?
什么是反应系统?
根据《反应式宣言》,反应式系统具有响应性、弹性、可伸缩性和消息驱动性。我们称之为反应式系统。
采用反应式系统构建的系统更加灵活、松散耦合且可扩展。这使得它们更易于开发且易于更改。
他们对失败的容忍度明显更高,当失败发生时,他们会优雅地应对,而不是以灾难的方式应对。
反应系统做什么
许多编程语言中都有许多反应式库,例如 JS 中的 SolidJS。
反应式系统必须对数据变化做出反应。通常,这些变化发生在收到新数据或更新旧数据时。
响应式编程的特点
反应式宣言定义了它的关键特征,例如:
- 响应迅速:这些系统响应及时。当然,这里的“及时性”会因应用和领域的不同而有所差异。
- 弹性。反应系统在出现故障时仍能保持响应。
- 弹性。随着工作负载的增加,系统应持续保持响应。
- 消息驱动。反应式系统的各个元素之间通过消息交换信息。这确保了这些组件之间的松散耦合、隔离和位置透明性。
SolidJS 的反应原语是什么?
在 SolidJS 中,库的作者Ryan Carniato将它们定义为更像网络原语,而非 JavaScript 原语。正如您稍后会看到的,信号本质上是可观察对象。
SolidJS模板的安装
你可以使用 degit 轻松安装一个SolidJS入门模板。你也可以从这里查看其他官方模板:SolidJS 官方模板。我更喜欢 JS 模板,而不是 TypeScript 模板。
# Javascript template
npx degit solidjs/templates/js solid
cd solid
# install the dependencies
yarn install
该模板使用 Vite 作为开发工具。而且,这是我第一次使用 Vite。Vite 速度非常快,以至于我不得不反复检查它是否会重新加载渲染后的页面。安装完成后,项目目录如下所示:
它在很多方面与 React 非常相似。我将检查一些组件的渲染过程。
在本文中,我将以入门的方式探索 SolidJS。我将首先创建一个 Counter 组件并检查它的重新渲染过程。
响应式 JavaScript 库:SolidJS
A)反应原语:createSignal
SolidJS 有一些基本的响应式原语,信号就是其中之一。它看起来像是 React Hooks 的“useState”替代方案。与“useState”钩子的一个区别是,信号返回两个函数:一个 getter 和一个 setter。以下是创建信号的官方示例:
- createSignal 函数接受一个初始值并返回一个具有访问和更新函数的数组。
- 您应该执行 getter 函数(访问)才能获取该值。
- 你可以将函数传递给更新函数(设置函数)。在此函数中,你也可以访问之前的状态。
const [getValue, setValue] = createSignal(initialValue);
// read value
getValue();
// set value
setValue(nextValue);
// set value with a function setter
setValue((prev) => prev + next);
import { createSignal } from "solid-js";
function Counter({ initial }) {
const [count, setCount] = createSignal(initial || 0);
return (
<div>
{/* Notice the usage of count! It is a function*/}
<h2>Count: {count()}</h2>
</div>
);
}
1)组件状态访问和更新
SolidJS 将状态元素称为信号。但是,我更喜欢使用状态而不是信号。让我们在 App 组件中创建一个 Counter 组件。App.jsx
文件内容如下:
import logo from "./logo.svg";
import styles from "./App.module.css";
import { createSignal } from "solid-js";
function App() {
/**
* CHECKPOINT
* if the App component renders
* it will print to console
*/
//
console.log("App component rendered.");
return (
<div class={styles.App}>
<header class={styles.header}>
<img src={logo} class={styles.logo} alt="logo" />
<p>
Edit <code>src/App.jsx</code> and save to reload.
</p>
<a
class={styles.link}
href="https://github.com/solidjs/solid"
target="_blank"
rel="noopener noreferrer"
>
Learn Solid
</a>
<Counter />
</header>
</div>
);
}
function Counter({ initial }) {
const [count, setCount] = createSignal(initial || 0);
/**
* CHECKPOINT
* if the Counter component renders. it will print to console.
* Also, I put another print statement for the count function.
*/
//
console.log("Counter component rendered.");
console.log("Counter component count value: ", count());
return (
<div style={{ width: "100%", height: "auto" }}>
{/* Notice the usage of count! It is a function*/}
<h2>Count: {count()}</h2>
<button onClick={() => setCount((c) => c + 1)}>Increase</button>
<button onClick={() => setCount((c) => c - 1)}>Decrease</button>
</div>
);
}
export default App;
让我们检查一下浏览器和 SolidJS 的首次渲染。如你所见,没有多余的组件渲染。如果是 React,我们应该会在控制台上多次看到“Counter 组件已渲染”的字样。
2)父组件状态访问与更新
让我们进一步将信号设置器传递给子组件并在那里使用它。像这样修改 App 和 Counter 组件:
function App() {
/**
* CHECKPOINT
* if the App component renders
* it will print to console
*/
//
const [appCount, setAppCount] = createSignal(0);
console.log("App: count: ", appCount());
console.log("App component rendered.");
return (
<div class={styles.App}>
<header class={styles.header}>
<img src={logo} class={styles.logo} alt="logo" />
<p>
Edit <code>src/App.jsx</code> and save to reload.
</p>
<a
class={styles.link}
href="https://github.com/solidjs/solid"
target="_blank"
rel="noopener noreferrer"
>
Learn Solid
</a>
{/* NEW */}
<h2>App Count: {appCount()}</h2>
<Counter
initial={appCount()}
setAppCount={setAppCount} // NEW
/>
</header>
</div>
);
}
function Counter({ initial, setAppCount }) {
const [count, setCount] = createSignal(initial || 0);
/**
* CHECKPOINT
* if the Counter component renders. it will print to console.
* Also, I put another print statement for the count function.
*/
//
console.log("Counter component rendered.");
console.log("Counter component count value: ", count());
return (
<div style={{ width: "100%", height: "auto" }}>
{/* Notice the usage of count! It is a function*/}
<h2>Count: {count()}</h2>
<button onClick={() => setCount((c) => c + 1)}>Increase</button>
<button onClick={() => setCount((c) => c - 1)}>Decrease</button>
<hr />
{/* Buttons changes the signal value of its parent component */}
<button onClick={() => setAppCount((c) => c + 1)}>
AppCount Increase
</button>
<button onClick={() => setAppCount((c) => c - 1)}>
AppCount Decrease
</button>
</div>
);
}
如你所见,没有任何组件重新渲染。太棒了!
B)反应原语:createEffect
正如你所料,createEffect
它相当于useEffect
React 中的 hooks。官方的解释和示例如下:
创建一个新的计算,自动跟踪依赖项,并在每次渲染后依赖项发生变化时运行。非常适合使用 ref
s 并管理其他副作用。
const [a, setA] = createSignal(initialValue);
// effect that depends on signal `a`
createEffect(() => doSideEffect(a()));
现在该来试试这个函数了。官方示例返回一个以状态值作为参数的函数(doSideEffect)。即使返回的函数不是以状态值作为参数,而是以内部值作为参数,该createEffect
函数也能成功产生副作用。
让我们将它们添加到App
组件中。
// The function creates side-effect
const changeTitle = (val) => (window.document.title = `#App: ${val}`);
// effect that depends on signal `a`
createEffect(() => changeTitle(appCount()));
我们创建了一个负责副作用的函数 (changeTitle)。它接受一个值并根据该值更改文档标题。它还接受 App 组件的状态值,即 appCount。你的应用组件应该如下所示。
function App() {
const [appCount, setAppCount] = createSignal(0);
console.log("App: count: ", appCount());
console.log("App component rendered.");
// The function creates side-effect
const changeTitle = (val) => (window.document.title = `#App: ${val}`);
// effect that depends on signal `a`
createEffect(() => changeTitle(appCount()));
return (
<div class={styles.App}>
<header class={styles.header}>
<img src={logo} class={styles.logo} alt="logo" />
<p>
Edit <code>src/App.jsx</code> and save to reload.
</p>
<a
class={styles.link}
href="https://github.com/solidjs/solid"
target="_blank"
rel="noopener noreferrer"
>
Learn Solid
</a>
{/* NEW */}
<h2>App Count: {appCount()}</h2>
<Counter
initial={appCount()}
setAppCount={setAppCount} // NEW
/>
</header>
</div>
);
}
你会很容易区分,当应用程序第一次渲染时,文档标题是App: 0
之后,当我点击并增加 appCount 值时,文档标题也会更改为相应的值。您还会注意到,组件不会重新渲染。
C)反应原语:createMemo
这个响应式原语返回一个函数,该函数返回一个只读的派生信号。每当依赖项更新时,它的值都会重新计算。createMemo
原语相当于useMemo
钩子。
根据以下内容编辑 App 组件:
// Add those to the App component
// It recalculate the value whenever the dependencies are updates.
const makeDouble = (val) => val * 2
const doubleCount = createMemo(() => makeDouble(appCount()))
console.log("doubleCount ", doubleCount());
另外,更新 App 组件的内容。这样我们就可以看到doubleCount
信号正在工作。你也可以从下图中查看代码位置。
<h2>Double Count: {doubleCount()}</h2>
D)反应原语:createResource
此函数创建一个负责异步请求的信号。官方的解释和示例如下:
创建一个可以管理异步请求的信号。 fetcher
是一个异步函数,它接受源的返回值(如果提供),并返回一个 Promise,其解析值在资源中设置。该获取器不是响应式的,因此如果您希望它多次运行,请使用可选的第一个参数。如果源解析为 false、null 或 undefined,则不会获取。此外,loading
和 error
是响应式 getter,可以被跟踪。
const [data, { mutate, refetch }] = createResource(getQuery, fetchData);
// read value
data();
// check if loading
data.loading;
// check if errored
data.error;
// directly set value without creating promise
mutate(optimisticValue);
// refetch last request just because
refetch();
SolidJS 给我的第一印象非常棒。目前为止,它完全没有 React 那样的额外开销。我会持续关注 SolidJS 的发展。
鏂囩珷鏉ユ簮锛�https://dev.to/canburaks/introduction-to-solidjs-and-reactive-primitives-1o6h