信号

¥Signals

信号是用于管理应用状态的反应原语。

¥Signals are reactive primitives for managing application state.

Signals 的独特之处在于状态更改会以最有效的方式自动更新组件和 UI。自动状态绑定和依赖跟踪使 Signals 能够提供出色的人机工程学设计和生产力,同时消除最常见的状态管理脚枪。

¥What makes Signals unique is that state changes automatically update components and UI in the most efficient way possible. Automatic state binding and dependency tracking allows Signals to provide excellent ergonomics and productivity while eliminating the most common state management footguns.

信号在任何规模的应用中都有效,其人机工程学设计可加快小型应用的开发速度,而性能特性可确保任何规模的应用在默认情况下都快速运行。

¥Signals are effective in applications of any size, with ergonomics that speed up the development of small apps, and performance characteristics that ensure apps of any size are fast by default.


重要

¥Important

本指南将介绍在 Preact 中使用信号,虽然这在很大程度上适用于核心库和 React 库,但使用上会有一些差异。有关其用法的最佳参考位于其各自的文档中:@preact/signals-core, @preact/signals-react

¥This guide will go over using Signals in Preact, and while this is largely applicable to both the Core and React libraries, there will be some usage differences. The best references for their usage is in their respective docs: @preact/signals-core, @preact/signals-react



介绍

¥Introduction

JavaScript 中状态管理的大部分痛苦在于对给定值的变化做出反应,因为值是不能直接观察的。解决方案通常通过将值存储在变量中并不断检查它们是否已更改来解决此问题,这很麻烦并且对性能而言并不理想。理想情况下,我们需要一种表达值的方式来告诉我们它何时发生变化。这就是信号所做的。

¥Much of the pain of state management in JavaScript is reacting to changes for a given value, because values are not directly observable. Solutions typically work around this by storing values in a variable and continuously checking to see if they have changed, which is cumbersome and not ideal for performance. Ideally, we want a way to express a value that tells us when it changes. That's what Signals do.

从本质上讲,信号是一个具有 .value 属性并保存值的对象。这有一个重要的特点:信号的值可以改变,但信号本身始终保持不变:

¥At its core, a signal is an object with a .value property that holds a value. This has an important characteristic: a signal's value can change, but the signal itself always stays the same:

import { signal } from "@preact/signals";

const count = signal(0);

// Read a signal’s value by accessing .value:
console.log(count.value);   // 0

// Update a signal’s value:
count.value += 1;

// The signal's value has changed:
console.log(count.value);  // 1
Run in REPL

在 Preact 中,当信号作为 props 或 context 通过树向下传递时,我们只是传递对信号的引用。可以更新信号而无需重新渲染任何组件,因为组件看到的是信号而不是其值。这让我们可以跳过所有昂贵的渲染工作,并立即跳转到树中实际访问信号的 .value 属性的任何组件。

¥In Preact, when a signal is passed down through a tree as props or context, we're only passing around references to the signal. The signal can be updated without re-rendering any components, since components see the signal and not its value. This lets us skip all of the expensive rendering work and jump immediately to any components in the tree that actually access the signal's .value property.

信号有第二个重要特性,即它们跟踪其值何时被访问以及何时更新。在 Preact 中,从组件内访问信号的 .value 属性会在信号值发生变化时自动重新渲染组件。

¥Signals have a second important characteristic, which is that they track when their value is accessed and when it is updated. In Preact, accessing a signal's .value property from within a component automatically re-renders the component when that signal's value changes.

import { signal } from "@preact/signals";

// Create a signal that can be subscribed to:
const count = signal(0);

function Counter() {
  // Accessing .value in a component automatically re-renders when it changes:
  const value = count.value;

  const increment = () => {
    // A signal is updated by assigning to the `.value` property:
    count.value++;
  }

  return (
    <div>
      <p>Count: {value}</p>
      <button onClick={increment}>click me</button>
    </div>
  );
}
Run in REPL

最后,Signals 被深度集成到 Preact 中,以提供最佳的性能和人机工程学设计。在上面的示例中,我们访问 count.value 来检索 count 信号的当前值,但这是不必要的。相反,我们可以直接在 JSX 中使用 count 信号,让 Preact 为我们完成所有工作:

¥Finally, Signals are deeply integrated into Preact to provide the best possible performance and ergonomics. In the example above, we accessed count.value to retrieve the current value of the count signal, however this is unnecessary. Instead, we can let Preact do all of the work for us by using the count signal directly in JSX:

import { signal } from "@preact/signals";

const count = signal(0);

function Counter() {
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => count.value++}>click me</button>
    </div>
  );
}
Run in REPL

安装

¥Installation

可以通过将 @preact/signals 包添加到你的项目来安装信号:

¥Signals can be installed by adding the @preact/signals package to your project:

npm install @preact/signals

通过你选择的包管理器安装后,你就可以将其导入你的应用中。

¥Once installed via your package manager of choice, you're ready to import it in your app.

使用示例

¥Usage Example

让我们在现实场景中使用信号。我们将构建一个待办事项列表应用,你可以在其中添加和删除待办事项列表中的项目。我们将从对状态建模开始。我们首先需要一个包含待办事项列表的信号,我们可以用 Array 来表示:

¥Let's use signals in a real world scenario. We're going to build a todo list app, where you can add and remove items in a todo list. We'll start by modeling the state. We're going to need a signal that holds a list of todos first, which we can represent with an Array:

import { signal } from "@preact/signals";

const todos = signal([
  { text: "Buy groceries" },
  { text: "Walk the dog" },
]);

为了让用户输入新待办事项的文本,我们还需要一个信号,表明我们很快就会连接到 <input> 元素。现在,我们已经可以使用这个信号来创建一个函数,将待办事项添加到我们的列表中。请记住,我们可以通过分配信号的 .value 属性来更新信号的值:

¥To let the user enter text for a new todo item, we'll need one more signal that we'll connect up to an <input> element shortly. For now, we can use this signal already to create a function that adds a todo item to our list. Remember, we can update a signal's value by assigning to its .value property:

// We'll use this for our input later
const text = signal("");

function addTodo() {
  todos.value = [...todos.value, { text: text.value }];
  text.value = ""; // Clear input value on add
}

💡提示:仅当你为其分配新值时,信号才会更新。如果你分配给信号的值等于其当前值,则它不会更新。

¥💡 Tip: A signal will only update if you assign a new value to it. If the value you assign to a signal is equal to its current value, it won't update.

const count = signal(0);

count.value = 0; // does nothing - value is already 0

count.value = 1; // updates - value is different

让我们检查一下到目前为止我们的逻辑是否正确。当我们更新 text 信号并调用 addTodo() 时,我们应该看到一个新项目被添加到 todos 信号中。我们可以通过直接调用这些函数来模拟这种场景 - 还不需要用户界面!

¥Let's check if our logic is correct so far. When we update the text signal and call addTodo(), we should see a new item being added to the todos signal. We can simulate this scenario by calling these functions directly - no need for a user interface yet!

import { signal } from "@preact/signals";

const todos = signal([
  { text: "Buy groceries" },
  { text: "Walk the dog" },
]);

const text = signal("");

function addTodo() {
  todos.value = [...todos.value, { text: text.value }];
  text.value = ""; // Reset input value on add
}

// Check if our logic works
console.log(todos.value);
// Logs: [{text: "Buy groceries"}, {text: "Walk the dog"}]


// Simulate adding a new todo
text.value = "Tidy up";
addTodo();

// Check that it added the new item and cleared the `text` signal:
console.log(todos.value);
// Logs: [{text: "Buy groceries"}, {text: "Walk the dog"}, {text: "Tidy up"}]

console.log(text.value);  // Logs: ""
Run in REPL

我们要添加的最后一个功能是能够从列表中删除待办事项。为此,我们将添加一个函数,用于从 todos 数组中删除给定的待办事项:

¥The last feature we'd like to add is the ability to remove a todo item from the list. For this, we'll add a function that deletes a given todo item from the todos array:

function removeTodo(todo) {
  todos.value = todos.value.filter(t => t !== todo);
}

构建用户界面

¥Building the UI

现在我们已经对应用的状态进行了建模,是时候连接一个用户可以与之交互的漂亮 UI 了。

¥Now that we've modeled our application's state, it's time to wire in up to a nice UI that users can interact with.

function TodoList() {
  const onInput = event => (text.value = event.currentTarget.value);

  return (
    <>
      <input value={text.value} onInput={onInput} />
      <button onClick={addTodo}>Add</button>
      <ul>
        {todos.value.map(todo => (
          <li>
            {todo.text}{' '}
            <button onClick={() => removeTodo(todo)}></button>
          </li>
        ))}
      </ul>
    </>
  );
}

这样我们就有了一个完全可用的待办事项应用!你可以尝试完整的应用 在这里 🎉

¥And with that we have a fully working todo app! You can try out the full app over here 🎉

通过计算信号导出状态

¥Deriving state via computed signals

让我们为我们的待办事项应用添加一项功能:每个待办事项都可以在已完成时进行核对,我们将向用户显示他们已完成的项目数。为此,我们将导入 computed(fn) 函数,该函数允许我们创建一个根据其他信号的值计算的新信号。返回的计算信号是只读的,当从回调函数内访问的任何信号发生变化时,其值会自动更新。

¥Let's add one more feature to our todo app: each todo item can be checked off as completed, and we'll show the user the number of items they've completed. To do that we'll import the computed(fn) function, which lets us create a new signal that is computed based on the values of other signals. The returned computed signal is read-only, and its value is automatically updated when any signals accessed from within the callback function change.

import { signal, computed } from "@preact/signals";

const todos = signal([
  { text: "Buy groceries", completed: true },
  { text: "Walk the dog", completed: false },
]);

// create a signal computed from other signals
const completed = computed(() => {
  // When `todos` changes, this re-runs automatically:
  return todos.value.filter(todo => todo.completed).length;
});

// Logs: 1, because one todo is marked as being completed
console.log(completed.value);
Run in REPL

我们简单的待办事项列表应用不需要很多计算信号,但更复杂的应用往往依赖 compute()来避免在多个地方重复状态。

¥Our simple todo list app doesn't need many computed signals, but more complex apps tend to rely on computed() to avoid duplicating state in multiple places.

💡提示:派生尽可能多的状态可确保你的状态始终具有单一的事实来源。这是信号的一个关键原理。这使得调试变得更加容易,以防以后应用逻辑出现缺陷,因为需要担心的地方更少。

¥💡 Tip: Deriving as much state as possible ensures that your state always has a single source of truth. It is a key principle of signals. This makes debugging a lot easier in case there is a flaw in application logic later on, as there are less places to worry about.

管理全局应用状态

¥Managing global app state

到目前为止,我们只在组件树之外创建了信号。这对于像待办事项列表这样的小型应用来说很好,但对于更大、更复杂的应用来说,这可能会使测试变得困难。测试通常涉及更改应用状态中的值以重现特定场景,然后将该状态传递给组件并在渲染的 HTML 上进行断言。为此,我们可以将待办事项列表状态提取到一个函数中:

¥Up until now, we've only created signals outside the component tree. This is fine for a small app like a todo list, but for larger and more complex apps this can make testing difficult. Tests typically involve changing values in your app state to reproduce a certain scenario, then passing that state to components and asserting on the rendered HTML. To do this, we can extract our todo list state into a function:

function createAppState() {
  const todos = signal([]);

  const completed = computed(() => {
    return todos.value.filter(todo => todo.completed).length
  });

  return { todos, completed }
}

💡提示:请注意,我们有意识地不在此包含 addTodo()removeTodo(todo) 函数。将数据与修改数据的函数分离通常有助于简化应用架构。欲了解更多详情,请查看 面向数据的设计

¥💡 Tip: Notice that we're consciously not including the addTodo() and removeTodo(todo) functions here. Separating data from functions that modify it often helps simplify application architecture. For more details, check out data-oriented design.

现在,我们可以在渲染时将待办事项应用状态作为属性传递:

¥We can now pass our todo application state as a prop when rendering:

const state = createAppState();

// ...later:
<TodoList state={state} />

这在我们的待办事项列表应用中有效,因为状态是全局的,但是较大的应用通常最终会具有需要访问相同状态部分的多个组件。这通常涉及 "提升状态" 到一个共同的共享祖级组件。为了避免通过 props 手动通过每个组件传递状态,可以将状态放入 上下文 中,以便树中的任何组件都可以访问它。这是一个通常看起来如何的简单示例:

¥This works in our todo list app because the state is global, however larger apps typically end up with multiple components that require access to the same pieces of state. This usually involves "lifting state up" to a common shared ancestor component. To avoid passing state manually through each component via props, the state can be placed into Context so any component in the tree can access it. Here is a quick example of how that typically looks:

import { createContext } from "preact";
import { useContext } from "preact/hooks";
import { createAppState } from "./my-app-state";

const AppState = createContext();

render(
  <AppState.Provider value={createAppState()}>
    <App />
  </AppState.Provider>
);

// ...later when you need access to your app state
function App() {
  const state = useContext(AppState);
  return <p>{state.completed}</p>;
}

如果你想了解有关上下文如何工作的更多信息,请前往 上下文文档

¥If you want to learn more about how context works, head over to the Context documentation.

有信号的本地状态

¥Local state with signals

大多数应用状态最终都是使用 props 和 context 来传递的。然而,在很多情况下,组件都有自己特定于该组件的内部状态。由于没有理由让这种状态作为应用全局业务逻辑的一部分存在,因此它应该仅限于需要它的组件。在这些场景中,我们可以使用 useSignal()useComputed() 钩子直接在组件内创建信号和计算信号:

¥The majority of application state ends up being passed around using props and context. However, there are many scenarios where components have their own internal state that is specific to that component. Since there is no reason for this state to live as part of the app's global business logic, it should be confined to the component that needs it. In these scenarios, we can create signals as well as computed signals directly within components using the useSignal() and useComputed() hooks:

import { useSignal, useComputed } from "@preact/signals";

function Counter() {
  const count = useSignal(0);
  const double = useComputed(() => count.value * 2);

  return (
    <div>
      <p>{count} x 2 = {double}</p>
      <button onClick={() => count.value++}>click me</button>
    </div>
  );
}

这两个钩子是 signal()computed() 周围的薄封装器,它们在组件第一次运行时构造一个信号,并在后续渲染中简单地使用相同的信号。

¥Those two hooks are thin wrappers around signal() and computed() that construct a signal the first time a component runs, and simply use that same signal on subsequent renders.

💡在幕后,这是实现:

¥💡 Behind the scenes, this is the implementation:

function useSignal(value) {
 return useMemo(() => signal(value), []);
}

高级信号使用

¥Advanced signals usage

到目前为止我们介绍的主题是你开始学习所需的全部内容。以下部分面向希望通过完全使用信号对应用状态进行建模来获得更多收益的读者。

¥The topics we've covered so far are all you need to get going. The following section is aimed at readers who want to benefit even more by modeling their application state entirely using signals.

对组件外部的信号做出反应

¥Reacting to signals outside of components

当使用组件树外部的信号时,你可能已经注意到,计算后的信号不会重新计算,除非你主动读取它们的值。这是因为信号默认是惰性的:它们仅在访问其值时计算新值。

¥When working with signals outside of the component tree, you may have noticed that computed signals don't re-compute unless you actively read their value. This is because signals are lazy by default: they only compute new values when their value has been accessed.

const count = signal(0);
const double = computed(() => count.value * 2);

// Despite updating the `count` signal on which the `double` signal depends,
// `double` does not yet update because nothing has used its value.
count.value = 1;

// Reading the value of `double` triggers it to be re-computed:
console.log(double.value); // Logs: 2

这就提出了一个问题:我们如何订阅组件树之外的信号?也许我们希望在信号值发生变化时将某些内容记录到控制台,或者将状态保留到 LocalStorage

¥This poses a question: how can we subscribe to signals outside of the component tree? Perhaps we want to log something to the console whenever a signal's value changes, or persist state to LocalStorage.

要运行任意代码来响应信号变化,我们可以使用 effect(fn)。与计算信号类似,效果跟踪哪些信号被访问,并在这些信号发生变化时重新运行其回调。与计算信号不同,effect() 不返回信号 - 这是一系列变化的结束。

¥To run arbitrary code in response to signal changes, we can use effect(fn). Similar to computed signals, effects track which signals are accessed and re-run their callback when those signals change. Unlike computed signals, effect() does not return a signal - it's the end of a sequence of changes.

import { signal, computed, effect } from "@preact/signals";

const name = signal("Jane");
const surname = signal("Doe");
const fullName = computed(() => `${name.value} ${surname.value}`);

// Logs name every time it changes:
effect(() => console.log(fullName.value));
// Logs: "Jane Doe"

// Updating `name` updates `fullName`, which triggers the effect again:
name.value = "John";
// Logs: "John Doe"

或者,你可以从提供给 effect() 的回调返回一个清理函数,该函数将在下次更新发生之前运行。这允许你消除副作用,并可能为回调的后续触发重置任何状态。

¥Optionally, you can return a cleanup function from the callback provided to effect() that will be run before the next update takes place. This allows you to "clean up" the side effect and potentially reset any state for the subsequent trigger of the callback.

effect(() => {
  Chat.connect(username.value)

  return () => Chat.disconnect(username.value)
})

你可以通过调用返回的函数来销毁效果并取消订阅它访问的所有信号。

¥You can destroy an effect and unsubscribe from all signals it accessed by calling the returned function.

import { signal, effect } from "@preact/signals";

const name = signal("Jane");
const surname = signal("Doe");
const fullName = computed(() => name.value + " " + surname.value);

const dispose = effect(() => console.log(fullName.value));
// Logs: "Jane Doe"

// Destroy effect and subscriptions:
dispose();

// Updating `name` does not run the effect because it has been disposed.
// It also doesn't re-compute `fullName` now that nothing is observing it.
name.value = "John";

💡提示:如果你广泛使用效果,请不要忘记清理它们。否则你的应用将消耗比所需更多的内存。

¥💡 Tip: Don't forget to clean up effects if you're using them extensively. Otherwise your app will consume more memory than needed.

读取信号而不订阅它们

¥Reading signals without subscribing to them

在极少数情况下,你需要写入 effect(fn) 内的信号,但不希望该效果在该信号更改时重新运行,你可以使用 .peek() 来获取信号的当前值而无需订阅。

¥On the rare occasion that you need to write to a signal inside effect(fn), but don't want the effect to re-run when that signal changes, you can use .peek() to get the signal's current value without subscribing.

const delta = signal(0);
const count = signal(0);

effect(() => {
  // Update `count` without subscribing to `count`:
  count.value = count.peek() + delta.value;
});

// Setting `delta` reruns the effect:
delta.value = 1;

// This won't rerun the effect because it didn't access `.value`:
count.value = 10;

💡提示:你不想订阅信号的情况很少见。在大多数情况下,你希望效果订阅所有信号。仅在确实需要时才使用 .peek()

¥💡 Tip: The scenarios in which you don't want to subscribe to a signal are rare. In most cases you want your effect to subscribe to all signals. Only use .peek() when you really need to.

将多个更新合并为一个

¥Combining multiple updates into one

还记得我们之前在待办事项应用中使用的 addTodo() 函数吗?回顾一下它的样子:

¥Remember the addTodo() function we used earlier in our todo app? Here is a refresher on what it looked like:

const todos = signal([]);
const text = signal("");

function addTodo() {
  todos.value = [...todos.value, { text: text.value }];
  text.value = "";
}

请注意,该函数触发两个单独的更新:一个在设置 todos.value 时,另一个在设置 text 的值时。有时,这可能是不可取的,并且出于性能或其他原因,需要将这两种更新合并为一个。batch(fn) 函数可用于在回调结束时将多个值更新合并为一个 "commit":

¥Notice that the function triggers two separate updates: one when setting todos.value and the other when setting the value of text. This can sometimes be undesirable and warrant combining both updates into one, for performance or other reasons. The batch(fn) function can be used to combine multiple value updates into one "commit" at the end of the callback:

function addTodo() {
  batch(() => {
    todos.value = [...todos.value, { text: text.value }];
    text.value = "";
  });
}

访问批次内已修改的信号将反映其更新值。访问已被批次内的另一个信号无效的计算信号将仅重新计算必要的依赖,以返回该计算信号的最新值。任何其他无效信号均不受影响,并且仅在批量回调结束时更新。

¥Accessing a signal that has been modified within a batch will reflect its updated value. Accessing a computed signal that has been invalidated by another signal within a batch will re-compute only the necessary dependencies to return an up-to-date value for that computed signal. Any other invalidated signals remain unaffected and are only updated at the end of the batch callback.

import { signal, computed, effect, batch } from "@preact/signals";

const count = signal(0);
const double = computed(() => count.value * 2);
const triple = computed(() => count.value * 3);

effect(() => console.log(double.value, triple.value));

batch(() => {
  // set `count`, invalidating `double` and `triple`:
  count.value = 1;

  // Despite being batched, `double` reflects the new computed value.
  // However, `triple` will only update once the callback completes.
  console.log(double.value); // Logs: 2
});
Run in REPL

💡提示:批次也可以嵌套,在这种情况下,只有在最外面的批次回调完成后才会刷新批次更新。

¥💡 Tip: Batches can also be nested, in which case batched updates are flushed only after the outermost batch callback has completed.

渲染优化

¥Rendering optimizations

通过信号,我们可以绕过虚拟 DOM 渲染并将信号更改直接绑定到 DOM 突变。如果你将信号传递到 JSX 的文本位置,它将渲染为文本并自动就地更新,无需虚拟 DOM 比较:

¥With signals we can bypass Virtual DOM rendering and bind signal changes directly to DOM mutations. If you pass a signal into JSX in a text position, it will render as text and automatically update in-place without Virtual DOM diffing:

const count = signal(0);

function Unoptimized() {
  // Re-renders the component when `count` changes:
  return <p>{count.value}</p>;
}

function Optimized() {
  // Text automatically updates without re-rendering the component:
  return <p>{count}</p>;
}

要启用此优化,请将信号传递到 JSX 而不是访问其 .value 属性。

¥To enable this optimization, pass the signal into JSX instead of accessing its .value property.

当将信号作为 DOM 元素上的 props 传递时,也支持类似的渲染优化。

¥A similar rendering optimization is also supported when passing signals as props on DOM elements.

API

本节概述了信号 API。它的目的是为已经知道如何使用信号并需要提醒可用信号的人们提供快速参考。

¥This section is an overview of the signals API. It's aimed to be a quick reference for folks who already know how to use signals and need a reminder of what's available.

signal(initialValue)

使用给定参数作为其初始值创建一个新信号:

¥Creates a new signal with the given argument as its initial value:

const count = signal(0);

在组件内创建信号时,请使用钩子变体:useSignal(initialValue)

¥When creating signals within a component, use the hook variant: useSignal(initialValue).

返回的信号具有 .value 属性,可以获取或设置该属性来读取和写入其值。要读取信号而不订阅信号,请使用 signal.peek()

¥The returned signal has a .value property that can be get or set to read and write its value. To read from a signal without subscribing to it, use signal.peek().

computed(fn)

创建一个根据其他信号的值计算的新信号。返回的计算信号是只读的,当从回调函数内访问的任何信号发生变化时,其值会自动更新。

¥Creates a new signal that is computed based on the values of other signals. The returned computed signal is read-only, and its value is automatically updated when any signals accessed from within the callback function change.

const name = signal("Jane");
const surname = signal("Doe");

const fullName = computed(() => `${name.value} ${surname.value}`);

在组件内创建计算信号时,请使用钩子变体:useComputed(fn)

¥When creating computed signals within a component, use the hook variant: useComputed(fn).

effect(fn)

要运行任意代码来响应信号变化,我们可以使用 effect(fn)。与计算信号类似,效果跟踪哪些信号被访问,并在这些信号发生变化时重新运行其回调。如果回调返回一个函数,则该函数将在下一次值更新之前运行。与计算信号不同,effect() 不返回信号 - 这是一系列变化的结束。

¥To run arbitrary code in response to signal changes, we can use effect(fn). Similar to computed signals, effects track which signals are accessed and re-run their callback when those signals change. If the callback returns a function, this function will be run before the next value update. Unlike computed signals, effect() does not return a signal - it's the end of a sequence of changes.

const name = signal("Jane");

// Log to console when `name` changes:
effect(() => console.log('Hello', name.value));
// Logs: "Hello Jane"

name.value = "John";
// Logs: "Hello John"

当响应组件内的信号变化时,使用钩子变体:useSignalEffect(fn)

¥When responding to signal changes within a component, use the hook variant: useSignalEffect(fn).

batch(fn)

batch(fn) 函数可用于在提供的回调结束时将多个值更新合并到一个 "commit" 中。批次可以嵌套,并且只有在最外面的批次回调完成后才会刷新更改。访问批次内已修改的信号将反映其更新值。

¥The batch(fn) function can be used to combine multiple value updates into one "commit" at the end of the provided callback. Batches can be nested and changes are only flushed once the outermost batch callback completes. Accessing a signal that has been modified within a batch will reflect its updated value.

const name = signal("Jane");
const surname = signal("Doe");

// Combine both writes into one update
batch(() => {
  name.value = "John";
  surname.value = "Smith";
});
Preact 中文网 - 粤ICP备13048890号