# @blockquote-web-components/blockquote-controller-xstate 中文文档

规范地址：https://npmcn.com/package/%40blockquote-web-components/blockquote-controller-xstate/

版本：2\.10\.0
npm 元数据更新时间：2026-10-31T14:59:11.007Z
中文译文更新时间：2026-10-31T16:53:56.474Z
软件包许可：MIT

本文由上游 README 机器翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40blockquote-web-components/blockquote-controller-xstate
原文对照：https://npmcn.com/package/%40blockquote-web-components/blockquote-controller-xstate/?lang=original
项目主页：https://github.com/oscarmarina/blockquote-web-components#readme
源码仓库：https://github.com/oscarmarina/blockquote-web-components

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：115 kB，9 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：2 个
- 首次发布：2023-11-25T21:27:46.615Z
- 最新版本发布：2026-10-31T14:59:10.638Z
- 已发布版本数：70（近 12 个月 21 个）

## 中文简介

用于构建可组合和可重用Web组件的现代体系结构

## 安装

```sh
npm install @blockquote-web-components/blockquote-controller-xstate
```

也可以使用：`pnpm add @blockquote-web-components/blockquote-controller-xstate`、`yarn add @blockquote-web-components/blockquote-controller-xstate`、`bun add @blockquote-web-components/blockquote-controller-xstate`

## README

# BlockquoteControllerXstate

![Lit](https://img.shields.io/badge/lit-3.0.0-blue.svg)

### 连接XState机与Lit

blockquoteconcontrollerxstate是一个Lit Reactive Controller，专门设计用于与XState集成。该控制器提供订阅XState参与者的功能。它还调用 `onSnapshot` 随着状态的变化。

-   [xstate v6](https://stately.ai/docs/installation)
-   [Xstate v6 -示例](https://stately.ai/docs/examples)

* * *

### 演示

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/oscarmarina/blockquote-web-components/tree/main/packages/controllers/blockquote-controller-xstate)

[![Open in Stately.ai](https://img.shields.io/badge/Open%20in%20Stately.ai-black.svg)](https://stately.ai/registry/editor/154a7a42-9338-4cc0-8c0c-131c859d8349)

### 使用

_**counterMachine.ts**_

```ts
import {setup, types} from 'xstate';

const counterSetup = setup({
  // v6: `schemas` (Standard Schema). `types<T>()` is type-only, no runtime validation.
  // Zod, Valibot, ArkType... can be used instead to validate at runtime.
  schemas: {
    context: types<{counter: number}>(),
    events: {
      INC: types<void>(),
      DEC: types<void>(),
      TOGGLE: types<void>(),
    },
    // The element's public events, dispatched on the host with `dispatchEmitted`
    emitted: {
      counterchange: types<{counter: number}>(),
    },
  },
  guards: {
    canIncrement: ({context}) => context.counter < 10,
    canDecrement: ({context}) => context.counter > 0,
  },
  delays: {
    backoff: ({context}) => context.counter * 1000,
  },
});

export const counterMachine = counterSetup.createMachine({
  context: {counter: 0},
  initial: 'enabled',
  states: {
    enabled: {
      on: {
        // Transition functions (typed from `schemas`): return the next `context`, or
        // `undefined` to ignore the event. The guard comes first: a function that calls
        // `enq` handles the event even when it returns nothing.
        INC: ({context, guards}, enq) => {
          if (!guards.canIncrement({context})) {
            return undefined;
          }
          const counter = context.counter + 1;
          enq.emit({type: 'counterchange', counter});
          return {context: {counter}};
        },
        DEC: ({context, guards}, enq) => {
          if (!guards.canDecrement({context})) {
            return undefined;
          }
          const counter = context.counter - 1;
          enq.emit({type: 'counterchange', counter});
          return {context: {counter}};
        },
        // v6 types do not accept the string shorthand (`TOGGLE: 'disabled'`)
        TOGGLE: {target: 'disabled'},
      },
    },
    disabled: {
      after: {
        backoff: {target: 'enabled'},
      },
      on: {
        TOGGLE: {target: 'enabled'},
      },
    },
  },
});
```

**`new BlockquoteControllerXstate(this, {machine, options, onSnapshot, onError, reconnect, dispatchEmitted})`**

-   `this`：主机组件(a) `ReactiveControllerHost`，通常是 `LitElement` 本身)。
-   `machine` （必选）：XState机。
-   `options`： `createActor` 选项(`input`， `inspect`,……)。可选，除非机器需要 输入:那 `{input}` 或者 `{snapshot}` 必须提供，当呼唤 `createActor`.
-   `onSnapshot` （可选）：每次新快照时调用：每次连接的第一个快照 最后一个(`snapshot.status === 'stopped'`)，此时主机已断开连接。
-   `onError` （可选）：当参与者出错时调用 `'error'` 快照也被发送到 `onSnapshot`）. 如果省略，XState将错误报告为未处理，除非另一个订阅者 演员处理它。
-   `reconnect` (可选): `'restart'` (默认)或 `'restore'`新演员的出发点是什么 当主机重新连接时。
-   `dispatchEmitted` （可选）：调度机器作为DOM事件在 主人。 `true` 使用 `{ composed: true }`.

_**需要输入的机器**_

```ts
const userMachine = setup({
  schemas: {
    input: types<{userId: string}>(),
    context: types<{userId: string}>(),
  },
}).createMachine({
  context: ({input}) => ({userId: input.userId}),
});

new BlockquoteControllerXstate(this, {machine: userMachine}); // type error: `input` is required
new BlockquoteControllerXstate(this, {machine: userMachine, options: {input: {userId: '123'}}});
```

输入可接受的机器 `undefined` （或者声明没有输入）可以省略 `options`.

actor是在构造函数中创建的，所以 `snapshot`， `actor`， `send`， `trigger` 和 `can` 在连接主机之前可用。它开始于 `hostConnected` 停了下来 `hostDisconnected`;如果主机再次连接，将创建一个新的参与者。与 `reconnect: 'restore'` 它恢复前参与者的状态（等待计时器保持其状态） 最后期限);默认情况下(`'restart'`)，它从初始状态开始。

_**xstate-counter.js**_

```javascript
import {html, LitElement} from 'lit';
import {BlockquoteControllerXstate} from '@blockquote-web-components/blockquote-controller-xstate';
import {counterMachine} from './counterMachine.js';
import {styles} from './styles/xstate-counter-styles.css.js';

export class XstateCounter extends LitElement {
  static styles = [styles];

  constructor() {
    super();
    // The controller re-renders the host on every snapshot. The machine emits the element's
    // public events, and `dispatchEmitted` dispatches them on it: `counterchange` with
    // `detail: {counter}`, `{composed: true}` (listen on the element, or capture).
    this.counterController = new BlockquoteControllerXstate(this, {
      machine: counterMachine,
      // Moving the element in the DOM keeps its state
      reconnect: 'restore',
      dispatchEmitted: true,
      options: {
        inspect: this._inspectEvents,
      },
    });
  }

  // xstate v6 inspection events: '@xstate.actor' | '@xstate.transition'
  _inspectEvents = (inspEvent) => {
    console.info('inspect event', inspEvent);
  };

  get #disabled() {
    return this.counterController.snapshot.matches('disabled');
  }

  render() {
    // `can.X()`: `false` when the event would be ignored (guards, `disabled` state) or the
    // actor has finished. `trigger` and `can` always target the current actor.
    const {can, trigger, snapshot} = this.counterController;
    return html`
      <slot></slot>
      <div data-disabled="${this.#disabled}">
        <span>
          <button
            ?disabled="${!can.INC()}"
            data-counter="increment"
            \@click=${() => trigger.INC()}>
            Increment
          </button>
          <button
            ?disabled="${!can.DEC()}"
            data-counter="decrement"
            \@click=${() => trigger.DEC()}>
            Decrement
          </button>
        </span>
        <p>${snapshot.context.counter}</p>
      </div>
      <div>
        <button \@click=${() => trigger.TOGGLE()}>
          ${this.#disabled ? 'Enabled counter' : 'Disabled counter'}
        </button>
      </div>
    `;
  }
}
```

_**`trigger` 和 `can`**_

`trigger` 中生成的类型化事件 `schemas.events`： `trigger.INC()`， `trigger.RENAME({label})`,就像 `send({type: 'RENAME', label})`． 它的签名和 XState v6的 `actor.trigger`，但有一点不同：XState属于一个参与者，而 控制器是一个稳定的代理(`ControllerTrigger`)，它总是发送给当前actor。

这很重要，因为每次主机再次连接（停止）时都会创建一个新的actor 演员无法重启)。 `actor.trigger` 保持从第一个连接将发送到一个停止 行动者，事件就会失去； `controller.trigger` 可安全保存或销毁。

`can` （`ControllerCan`)有相同的形状，也是一个稳定的代理，和回报 `snapshot.can(event)` 对于现任演员： `can.INC()`， `can.RENAME({label})`． 它是 `false` 当转换函数忽略事件(返回 `undefined`)或者演员已经演完了 （`'stopped'`， `'done'` 或者 `'error'`）. 机器仍然拒绝无效事件： `can` 只有 驱动UI。

_**断开连接和重新连接： `restart` 或者 `restore`**_

一个Lit元素被断开并再次连接的频率比看起来更频繁：移动它 DOM，重新排序 `repeat`，改变它的父级。actor在断开连接时停止，并且 停止的actor不能重新启动，因此在重新连接时创建一个新的actor。这个 `reconnect` 选项 决定它从哪里开始：

| `reconnect` | 新演员从 |
| --- | --- |
| `'restart'` (默认) | 初始状态，或者 `options.snapshot` |
| `'restore'` | 前一个参与者的状态：它的持久化快照 |

与 `'restore'`，则快照在参与者停止之前被持久化 （`actor.getPersistedSnapshot()`)，新的演员由此诞生 （`createActor(machine, {snapshot})`）. 它使:

-   `context`，国家价值和子女。
-   挂起计时器，具有其原始截止日期：在断开连接触发时过期的计时器 马上。
-   调用的工作在飞行中，如 `createAsyncLogic` 请求：停止中止它，而新的 Actor再次运行它。

演员演完了(`'done'` 或者 `'error'`)重新启动，而另一个 `machine` 是 与此同时生效。 `onSnapshot` 仍然收到 `'stopped'` 快照断开，然后 恢复一个。

_**发射事件: `on` 和 `dispatchEmitted`**_

机器发出事件 `enq.emit(...)` 表示某事发生过一次：计数器 更改，提交了一个表单。中声明 `schemas.emitted`，元素的公共事件直播 在机器中，没有DOM的类型和可测试的。

`dispatchEmitted: true` 将主机上发出的每个事件分派为 `CustomEvent`: 发出 `type` 是事件名，其余是其名 `detail`, `{composed: true}` (通过一个 对象来更改它)。事件不会冒泡：监听元素本身，或者监听对象 捕获阶段的父节点(`{capture: true}`）。 `EmittedEventMap` 类型 `addEventListener`：

```ts
declare global {
  interface HTMLElementEventMap extends EmittedEventMap<typeof counterMachine> {}
}

el.addEventListener('counterchange', (ev) => ev.detail.counter); // `number`
```

要处理代码中发出的事件，请使用 `controller.on(type, handler)` (或 `'*'`）. 它有 的签名 `actor.on`，但XState删除 `actor.on` 处理程序，当参与者停止时 `controller.on` 处理程序不断从重新连接时创建的参与者接收事件。

交付时：

-   转换应用后： `controller.snapshot` 已具有新状态（主机） 尚未重新渲染)。
-   当主机断开连接（停止actor不运行）时不会 `exit`)，当演员也是如此 恢复为 `reconnect: 'restore'` （`entry` （不再运行）：移动元素 DOM什么也不分派。
-   `can.X()` 不触发：它只计算转换。

类中同步运行侦听器 `trigger` (或 `send`)导致事件发生；

-   `controller.snapshot` 已经是新的状态，但是主机还没有重新渲染。阅读 它的渲染DOM， `await el.updateComplete` 第一。
-   一、 `trigger` 从侦听器中调用是排队的：当当前事件完成时，它被处理； 所以 `controller.snapshot` 之后它仍然是当前状态。它自己发出的事件 跟着，按顺序。
-   抛出的侦听器被报告为未捕获的错误。其他听众仍然收到 事件，actor继续运行。

_**`controller.trigger` vs `controller.actor.trigger`**_

|  | 发送给 | 重新连接主机后 |
| --- | --- | --- |
| `controller.trigger` | 现在的演员，每次打电话都抬头看 | 到达新演员 |
| `controller.actor.trigger` | 读取时当前的演员 | 到达停止的参与者：事件被丢弃（开发版本中的警告） |

使用 `controller.trigger` 获取来自UI的事件。的请求的响应 组件，这取决于 `reconnect`：

-   `'restore'`：新的参与者继续相同的会话，因此在 重新连接仍然有效。使用 `controller.trigger`.
-   `'restart'`：新actor启动新会话，因此响应属于旧会话。读 `controller.actor.trigger` 之前 `await`：响应被丢弃，而不是到达 新演员。

```ts
// `reconnect: 'restart'`
async #onInput(ev: InputEvent) {
  const query = (ev.target as HTMLInputElement).value;
  this.search.trigger.typed({query});

  // Bound to this session: read before the `await`
  const session = this.search.actor.trigger;
  const items = await fetchSuggestions(query);

  // Dropped if the actor that asked was stopped in the meantime.
  // `this.search.trigger.results({items})` would show results for `query` in the new session.
  session.results({items});
}
```

将请求移动到机器中（被调用） `createAsyncLogic`)避免选择：the 请求属于actor，停止actor将取消它 `AbortSignal` 是流产)。 与 `'restore'` 新演员再演一遍；与 `'restart'` 它随着旧的会议而消失了。

* * *

### `src/BlockquoteControllerXstate.ts`：

#### 类: `BlockquoteControllerXstate`

##### 字段

| 名字 | 隐私 | 类型 | 默认的 | 描述 | 继承自 |
| --- | --- | --- | --- | --- | --- |
| `machine` |  | `TMachine` | `machine` |  |  |
| `options` |  | `ControllerActorOptions<TMachine>['options']` | `options` | “createActor”选项。类型与构造函数参数类似，因此不能重新赋值 当机器需要输入时，没有输入（在重新连接时创建一个新的actor）。 |  |
| `onSnapshot` |  | `(snapshot: SnapshotFrom<TMachine>) => void | undefined` | `onSnapshot` |  |  |
| `onError` |  | `(error: unknown) => void | undefined` | `onError` | 是否设置它决定，当主机连接时，如果XState报告错误为未处理： 添加或删除它适用于下一个连接。 |  |
| `reconnect` |  | `'restart' | 'restore'` | `reconnect` | 当主机再次连接时读取。 |  |
| `actorRef` |  | `Actor<TMachine>` |  |  |  |
| `subscription` |  | `Subscription | undefined` |  |  |  |
| `currentSnapshot` |  | `SnapshotFrom<TMachine> | undefined` |  |  |  |
| `host` |  | `THost` |  |  |  |
| `trigger` |  | `ControllerTrigger<TMachine>` |  | 从模式生成的类型化事件发送器。“:”触发事件。公司()”, ”引发的反馈。更新‘\]({value}) ’，如' send({type，…有效载荷})”。 稳定代理：它总是发送给当前参与者（每次创建一个新参与者） 主机再次连接)，因此可以保留或销毁它。 不是“演员。Trigger '，它绑定到一个actor。使用‘ reconnect: ’restart' '，用于事件 这属于一个会话，例如对请求的响应，read ' actor。触发之前 ‘ await ’：如果主机同时断开连接，响应将被丢弃，而不是 接触新演员。使用“restore”，会话将继续，因此使用“trigger”。 |  |
| `can` |  | `ControllerCan<TMachine>` |  | 每个事件类型键入‘ snapshot.can（…）’，形状类似‘ trigger ’： “可以。公司()”、“可以\['反馈。更新‘\]({value}) ’。 ‘ false ’表示事件将被忽略或actor已经完成（'stopped' ', 'done' '）。 或“错误的”)。在主机连接之前，它应答初始状态。 一个稳定的代理，像“触发器”：它总是检查当前的参与者。 |  |
| `on` |  | `Actor<TMachine>['on']` | `((type: string, handler: (event: EventObject) => void): Subscription => { const entry = {type, handler}; this.#emittedHandlers.add(entry); return { unsubscribe: () => { this.#emittedHandlers.delete(entry); }, }; }) as unknown` | 处理机器发出的事件(' enq。Emit ‘)，就像’ actor '。On (type, handler) ‘，带有’\*' ' for 每一个事件。不同的演员。当actor停止时，其处理程序将被删除 当主机再次连接时，继续从创建的actor接收事件。 |  |
| `actor` |  | `Actor<TMachine>` |  | 来自XState的底层actor。每次创建主机时都会创建一个新的actor 再次连接，所以不要跨连接保持对它的引用。 |  |
| `snapshot` |  | `SnapshotFrom<TMachine>` |  | actor状态的最新快照 |  |
| `onNext` |  |  |  | 状态更改的内部订阅者 |  |
| `isActive` |  | `boolean` |  | ‘ true ’，而当前actor可以（或正在）运行。 快照在start（）之前和运行时都是“活动的”；后“停止()” 它是“停止”（当它完成或失败时是“完成”/“错误”）。 |  |

##### 方法

| 名字 | 隐私 | 描述 | 参数 | 返回 | 继承自 |
| --- | --- | --- | --- | --- | --- |
| `send` |  | 向actor服务发送事件 | `ev: EventFrom<typeof this.machine>` | `void` |  |
| `unsubscribe` |  |  |  | `void` |  |
| `startService` |  | 启动演员。如果前一个参与者不再活动，则创建一个新参与者 （例如，当主机断开连接时停止），因为停止的actor无法重新启动。 |  | `void` |  |
| `stopService` |  |  |  | `void` |  |
| `hostConnected` |  |  |  | `void` |  |
| `hostDisconnected` |  |  |  | `void` |  |

私有API

##### 字段

| 名字 | 隐私 | 类型 | 默认的 | 描述 | 继承自 |
| --- | --- | --- | --- | --- | --- |
| `#dispatchInit` | 私人 | `DispatchEmittedOptions | undefined` |  | ‘ dispatchemited ’解析为‘ EventInit ’，或‘ undefined ’当它关闭时。 |  |
| `#emittedHandlers` | 私人 |  | `new Set<{ type: string; handler: (event: EventObject) => void; }>()` |  |  |
| `#emittedSubscription` | 私人 | `Subscription | undefined` |  |  |  |
| `#persisted` | 私人 | ”{ |  |  |  |

```
machine: TMachine;
snapshot: ActorOptions<TMachine>['snapshot'];
```

} |未定义 `| | Persisted on disconnect with \`与它所属的机器重新连接：‘restore’ '。| | | `#onEmitted` | private | | |将当前actor发出的事件传递给DOM和‘ on ’处理程序。| |

##### 方法

| 名字 | 隐私 | 描述 | 参数 | 返回 | 继承自 |
| --- | --- | --- | --- | --- | --- |
| `#createActor` | 私人 |  | `snapshot: ActorOptions<TMachine>['snapshot']` | `Actor<TMachine>` |  |
| `#takePersisted` | 私人 | 在重新连接时要恢复的快照：仅使用‘ reconnect: ’restore'，并且仅用于 被持久化的机器（‘ Machine ’可能已被重新分配）。 |  | `ActorOptions<TMachine>['snapshot']` |  |

* * *

#### 出口

| 种类 | 名字 | 宣言 | 模块 | 包 |
| --- | --- | --- | --- | --- |
| `js` | `BlockquoteControllerXstate` | BlockquoteControllerXstate | src / BlockquoteControllerXstate.ts |  |

### `src/index.ts`：

#### 出口

| 种类 | 名字 | 宣言 | 模块 | 包 |
| --- | --- | --- | --- | --- |
| `js` | `BlockquoteControllerXstate` | BlockquoteControllerXstate | 。/ BlockquoteControllerXstate.js |  |
| `js` | `BlockquoteControllerXstateOptions` | BlockquoteControllerXstateOptions | 。/ BlockquoteControllerXstate.js |  |
| `js` | `ControllerCan` | ControllerCan | 。/ BlockquoteControllerXstate.js |  |
| `js` | `ControllerTrigger` | ControllerTrigger | 。/ BlockquoteControllerXstate.js |  |
| `js` | `DispatchEmittedOptions` | DispatchEmittedOptions | 。/ BlockquoteControllerXstate.js |  |
| `js` | `EmittedEventMap` | EmittedEventMap | 。/ BlockquoteControllerXstate.js |  |
| `js` | `UseMachineOptions` | UseMachineOptions | 。/ BlockquoteControllerXstate.js |  |

## 常见问题

### @blockquote-web-components/blockquote-controller-xstate 是什么？

用于构建可组合和可重用Web组件的现代体系结构

### 如何安装 @blockquote-web-components/blockquote-controller-xstate？

在项目目录运行 npm install @blockquote-web-components/blockquote-controller-xstate，或使用 pnpm add / yarn add / bun add。

### @blockquote-web-components/blockquote-controller-xstate 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @blockquote-web-components/blockquote-controller-xstate 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @blockquote-web-components/blockquote-controller-xstate 可以商用吗？

@blockquote-web-components/blockquote-controller-xstate 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @blockquote-web-components/blockquote-controller-xstate 还在维护吗？

最新版本 2\.10\.0 发布于 2026/10/31（2 天前）；至今共发布 70 个版本，其中过去 12 个月发布了 21 个；首次发布于 2023/11/26。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

