探索 SolidJS - 反应式原语 (1)

2025-06-08

探索 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
Enter fullscreen mode Exit fullscreen mode

该模板使用 Vite 作为开发工具。而且,这是我第一次使用 Vite。Vite 速度非常快,以至于我不得不反复检查它是否会重新加载渲染后的页面。安装完成后,项目目录如下所示:

SolidJS 目录

它在很多方面与 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);
Enter fullscreen mode Exit fullscreen mode

Solid JS createSignal

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

1)组件状态访问和更新

SolidJS 将状态元素称为信号。但是,我更喜欢使用状态而不是信号。让我们在 App 组件中创建一个 Counter 组件。App.jsx文件内容如下:

SolidJS 改变状态

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;
Enter fullscreen mode Exit fullscreen mode

让我们检查一下浏览器和 SolidJS 的首次渲染。如你所见,没有多余的组件渲染。如果是 React,我们应该会在控制台上多次看到“Counter 组件已渲染”的字样。

SolidJS 的反应性

2)父组件状态访问与更新

让我们进一步将信号设置器传递给子组件并在那里使用它。像这样修改 App 和 Counter 组件:

SolidJS 更改父状态

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

如你所见,没有任何组件重新渲染。太棒了!

B)反应原语:createEffect

正如你所料,createEffect它相当于useEffectReact 中的 hooks。官方的解释和示例如下:

创建一个新的计算,自动跟踪依赖项,并在每次渲染后依赖项发生变化时运行。非常适合使用 refs 并管理其他副作用。

const [a, setA] = createSignal(initialValue);

// effect that depends on signal `a`
createEffect(() => doSideEffect(a()));
Enter fullscreen mode Exit fullscreen mode

现在该来试试这个函数了。官方示例返回一个以状态值作为参数的函数(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()));
Enter fullscreen mode Exit fullscreen mode

我们创建了一个负责副作用的函数 (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>
    );
}
Enter fullscreen mode Exit fullscreen mode

你会很容易区分,当应用程序第一次渲染时,文档标题是App: 0

之后,当我点击并增加 appCount 值时,文档标题也会更改为相应的值。您还会注意到,组件不会重新渲染。

SolidJS createEffect

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());
Enter fullscreen mode Exit fullscreen mode

另外,更新 App 组件的内容。这样我们就可以看到doubleCount信号正在工作。你也可以从下图中查看代码位置。

<h2>Double Count: {doubleCount()}</h2>
Enter fullscreen mode Exit fullscreen mode

SolidJS createMemo 函数

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();
Enter fullscreen mode Exit fullscreen mode

SolidJS 给我的第一印象非常棒。目前为止,它完全没有 React 那样的额外开销。我会持续关注 SolidJS 的发展。

鏂囩珷鏉ユ簮锛�https://dev.to/canburaks/introduction-to-solidjs-and-reactive-primitives-1o6h
PREV
给 JavaScript 初学者的 7 条糟糕建议
NEXT
免费设计工具 - 生成器、图形模板、原型设计……免费设计资源和图形设计工具