# @inthedark/kitsui 中文文档

规范地址：https://npmcn.com/package/%40inthedark/kitsui/

版本：0\.1\.2
npm 元数据更新时间：2026-11-01T05:18:57.628Z
中文译文更新时间：2026-11-01T12:33:34.530Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40inthedark/kitsui
原文对照：https://npmcn.com/package/%40inthedark/kitsui/?lang=original
项目主页：https://github.com/ErisInTheDark/kitsui#readme
源码仓库：https://github.com/ErisInTheDark/kitsui

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

- 包体积（解压后）：1.8 MB，8 个文件
- 模块格式：CommonJS
- TypeScript 类型：包内自带
- Node.js 要求：\>=20
- 运行依赖：0 个
- 首次发布：2026-10-31T23:41:55.396Z
- 最新版本发布：2026-11-01T05:18:57.298Z
- 已发布版本数：4（近 12 个月 4 个）

## 中文简介

UI框架模板围绕可变组件包装器和显式状态转换构建。

## 安装

```sh
npm install @inthedark/kitsui
```

也可以使用：`pnpm add @inthedark/kitsui`、`yarn add @inthedark/kitsui`、`bun add @inthedark/kitsui`

## README

# kitsui

kitsui是一个dom优先的UI库 `Component` 和 `State` 对象。一个 `Component` 包裹一个真实的 `HTMLElement`;一个 `State` 是一个自有的无功值。

把喜水想象成反应，但没有那么神奇。每个可能的效果都由您显式声明和控制。直接构建和移动真正的组件；Kitsui不会在代码背后运行应用程序范围的渲染周期。

```sh
pnpm add @inthedark/kitsui
```

## 典型组件

```ts
import { Component, State, Style } from "@inthedark/kitsui"

const counterStyle = Style.Class("counter", {
    alignItems: "center",
    display: "flex",
    gap: 8,
})

function Counter (): Component {
    const root = Component("section")
        .class.add(counterStyle)

    const count = State(root, 0)

    Component("output")
        .text.set(count.map(value => `Count: ${value}`))
        .appendTo(root)

    Component("button")
        .attribute.set("type", "button")
        .text.set("Increment")
        .event.owned.on.click(() => {
            count.update(value => value + 1)
        })
        .appendTo(root)

    return root
}

Counter().appendTo(document.body)
```

这种风格使所有权保持简单： `root` 拥有状态、事件清理和子组件，因此可以删除 `root` 处理整个子树。

## 最佳实践

-   返回 `Component` 来自小型工厂函数的实例。
-   创建 `State` 应该处理它的组件或所有者。
-   使用 `Owner()` 为了一个没有归属的活镜 `Component`，然后将其传递给该范围内的每个感知所有者的API。
-   在手动之前使用派生状态、操纵符和条件放置 `subscribe` 加上DOM突变。
-   在一个流畅的链中构建大多数静态结构。
-   使用 `component.class`， `component.style`， `component.attribute`， `component.aria`， `component.text`, `component.event` 的DOM效果。
-   使用 `event.owned` 对于应该与组件一起清理的处理程序。
-   使用 `Style.Class` 用于可重用、可选择或共享的样式。使用 `component.style` 用于动态或反应性内联属性。
-   在事件处理程序或真正的自定义行为中保持直接的DOM访问。

## 可重用组件

可重用组件文件应该与组件名称完全匹配，并默认导出组件函数。使用一个局部符号作为接口名和函数名，然后导出一次 `export default Name`.

不强制赋值 `as Name` 在return语句中。给函数一个显式的返回类型和let `component.extend(...)` 检查结果的类型。

自定义组件方法应该返回 `this` 除非有强烈的理由退回其他东西。

```ts
import { Component } from "@inthedark/kitsui"

interface ButtonExtensions {
    setBusy (busy: boolean): this
    setLabel (label: string): this
}

interface Button extends Component<HTMLButtonElement>, ButtonExtensions { }

function Button (this: Component<HTMLButtonElement> | void, label: string): Button {
    const button = Component(this ?? "button", Button)
        .attribute.set("type", "button")

    const labelText = Component("span")
        .text.set(label)
        .appendTo(button)

    return button.extend<ButtonExtensions>(root => ({
        setBusy (busy) {
            root.attribute.toggle("disabled", busy)
            return root
        },

        setLabel (label) {
            labelText.text.set(label)
            return root
        },
    }))
}

export default Button
```

可组合构造器必须将自己的函数标识传递给初始的 `Component(...)` 呼叫。 `and` 将当前组件注入 `this`，转发参数，并跳过已经应用于该组件的构建器。 `is` 和 `as` 检查相同的构建器标识。

```ts
const standalone = Button("Save")
const composed = Component("button").and(Button, "Save")

if (composed.is(Button)) {
    composed.setBusy(true)
}
```

### 特定组件的事件

基本组件事件仅适用于每个组件上可用的事件，例如 `Mount` 和 `Dispose`． 特定于功能的组件应该通过 `Component.WithEvents<Events>` 而不是增加共享组件事件映射。

自定义kitsui组件事件名称应该是 `PascalCase`，例如 `DragStart` 或者 `WidgetCommit`，因此它们与本地DOM事件(如 `click` 和 `pointerdown`.

```ts
import { Component } from "@inthedark/kitsui"

interface WidgetEvents {
    WidgetCommit: CustomEvent<{
        readonly value: string
    }> & {
        readonly source: "user" | "api"
    }
}

interface WidgetExtensions {
    commit (value: string): this
}

interface Widget extends Component.WithEvents<WidgetEvents>, WidgetExtensions { }

function emitWidgetCommit (widget: Widget, value: string): boolean {
    return widget.event.emit.WidgetCommit({ value }, {
        tweak: event => Object.defineProperty(event, "source", {
            value: "api",
        }),
    })
}

declare const widget: Widget

widget.event.owned.on.WidgetCommit(event => {
    event.detail.value.toUpperCase()
    event.source
})
```

`event.emit.*` 和 `event.dispatch.*` 构造映射的是别名吗 `CustomEvent`，运行可选的 `tweak` 回调，并返回目标的native `dispatchEvent` 结果。本地的 `CustomEvent` 默认值被保留，所以kitsui自定义事件不会冒泡，除非 `bubbles: true` 显式提供。冒泡不会将特性事件添加到祖先组件类型；在类型化之前，祖先仍然必须选择进入事件映射 `event` API公开该名称。

同步 `Dispose` 处理程序可以发出最终的自定义通知，而所有者同时报告这两种通知 `disposed` 和 `disposing`． 这是一个狭窄的生命周期窗口：侦听器注册和其他组件突变仍然被禁止，并且在处理完成后再次拒绝自定义事件发射。

## 状态值和派生值

当立即使用派生值时，使用短形式派生状态。当派生状态被重用、分组、传递或需要明显的所有者时，使用所有者显式派生状态。

保持行为和UI状态一致。被禁用的按钮不应该是唯一强制执行不变量的东西。

```ts
const root = Component("section")
const count = State(root, 0)
const atLimit = count.map(root, value => value >= 10)

Component("output")
    .text.set(count.map(value => `Count: ${value}`))
    .appendTo(root)

Component("button")
    .attribute.set("type", "button")
    .attribute.bind(atLimit, "disabled")
    .text.set(atLimit.map(value => value ? "Limit reached" : "Increment"))
    .event.owned.on.click(() => {
        count.update(value => Math.min(value + 1, 10))
    })
    .appendTo(root)

Component("p")
    .text.bind(atLimit, "The counter is capped.")
    .appendTo(root)
```

使用 `State.Group(owner, states)` 当一个派生值需要多个状态的一致快照时。使用 `truthy`， `falsy`, `or(...)` 而不是手工编写重复的布尔或空值映射。

## 独立所有者范围

使用 `Owner()` 当一个活的范围没有所有权 `Component`． 将所有者传递给 `State` 和其他所有者感知的api，这样整个作用域共享一个生命周期。在范围结束时处置所有者。

```ts
const session = Owner()
const status = State(session, "connecting")
const label = status.map(session, value => `Status: ${value}`)

label.subscribe(session, value => {
    console.log(value)
})

session.dispose()
```

当作用域属于UI子树时，使用 `Component` 作为它的主人。用于路由器、套接字、观察者和其他系统的集成帮助程序可以接受 `Owner` 和使用 `owner.onCleanup(...)` 在内部;普通应用程序代码通常只将所有者传递给帮助程序。

每个所有者暴露的都是终身的 `signal` 用于可取消的平台工作。

## 机械手绑定

为响应式DOM效果使用操纵符。单个状态可以驱动文本、类、原始属性、类型化ARIA属性和事件，而无需手动DOM记账。

```ts
const expandedStyle = Style.Class("panel-expanded", {
    borderColor: "#0f766e",
})

const panel = Component("section")
const expanded = State(panel, false)

Component("button")
    .attribute.set("type", "button")
    .aria.expanded(expanded)
    .text.set(expanded.map(value => value ? "Collapse" : "Expand"))
    .event.owned.on.click(() => {
        expanded.update(value => !value)
    })
    .appendTo(panel)

Component("div")
    .class.bind(expanded, expandedStyle)
    .attribute.bind(expanded, "data-expanded")
    .aria.hidden(expanded.falsy)
    .text.set(expanded.map(value => value ? "Expanded" : "Collapsed"))
    .appendTo(panel)
```

使用 `attribute.toggle(name, enabled)` 用于在命令式方法中立即切换布尔属性。使用 `attribute.bind(state, name)` 什么时候属性的存在应该跟随状态。

## 运动和放置

使用 `appendTo`， `prependTo`, `insertTo` 当现有组件应该相对于另一个组件、节点或标记移动时。使用 `appendToWhen`， `prependToWhen`， `insertToWhen`， `appendWhen`， `prependWhen`, `insertWhen` 用于条件放置。使用 `place` 当目标位置本身是被动的。

每个组件或原始节点保持一个活跃的kitsui放置流。如果通过另一个kitsui API再次放置它，则新调用将获得控制权，而旧的条件流或选择流将停止移动它。从共享选择中替换一个组件不会阻止它的兄弟组件。

条件放置使隐藏节点在dom外保持活动状态，并在它们的条件为真时恢复它们。除去它们的终身主人，它们就被处理掉了。

```ts
const root = Component("section")
const open = State(root, false)
const details = Component("p")
    .text.set("More detail")
    .appendToWhen(open, root)
```

```ts
const root = Component("section")
const left = Component("div").appendTo(root)
const right = Component("div").appendTo(root)
const side = State<"left" | "right">(root, "left")

const badge = Component("span")
    .text.set("moving")

badge.place(root, (Place) => {
    const leftPlace = Place().appendTo(left)
    const rightPlace = Place().appendTo(right)

    return side.map(value => value === "left" ? leftPlace : rightPlace)
})
```

使用 `append`， `prepend`, `insert` 当主人应该选择哪些孩子在场。国家支持的儿童选择被固定在适当的位置；当状态发生变化时，kitsui替换和处置旧的选择。

```ts
const root = Component("section")
const view = State<"summary" | "details">(root, "summary")

Component("button")
    .attribute.set("type", "button")
    .text.set("Summary")
    .event.owned.on.click(() => view.set("summary"))
    .appendTo(root)

Component("button")
    .attribute.set("type", "button")
    .text.set("Details")
    .event.owned.on.click(() => view.set("details"))
    .appendTo(root)

root.append(view.map(root, value => {
    if (value === "summary") {
        return Component("p").text.set("Short summary")
    }

    return Component("p").text.set("Full details")
}))
```

## 样式

使用 `Style(...)` 对于可重用的样式片段和 `Style.Class(...)` 浏览已注册的CSS类。使用 `Style.after(...)` 当可重用类之间的顺序很重要时。

```ts
const cardBase = Style({
    borderRadius: 8,
    padding: 12,
})

const cardStyle = Style.Class("card", {
    ...cardBase,
    $accent: "#0a7",
    border: "1px solid $accent",
})

const raisedStyle = Style.after(cardStyle).Class("card-raised", {
    ...cardBase,
    boxShadow: "0 2px 12px rgba(0, 0, 0, 0.15)",
})

const card = Component("button")
const raised = State(card, false)

card
    .attribute.set("type", "button")
    .class.add(cardStyle)
    .class.bind(raised, raisedStyle)
    .style.set({
        $panelAccent: "#0a7",
        borderColor: "$panelAccent",
    })
    .text.set("Hover for elevation")
    .event.owned.on.pointerenter(() => raised.set(true))
    .event.owned.on.pointerleave(() => raised.set(false))
```

字符串样式的值支持计算速记。Kitsui编译 `[expression]` 来 `calc(expression)` 并在表达式内展开变量简写。当CSS需要文本括号时，例如命名的网格线，将方括号翻倍。

```ts
const layoutStyle = Style.Class("layout", {
    $gap: "1rem",
    gridTemplateColumns: "[[content-start]] [100% - ${sidebarWidth: 18rem} - $gap] [[content-end]]",
    width: "[100% - $gap]",
})
```

它发出文字 `[content-start]` 和 `[content-end]` 网格线名称。括号内的表达式变成 `calc(...)` 值, `$gap` 加上 `${sidebarWidth: 18rem}` 变成CSS变量引用。

## 分解

使用 `component.breakdown(...)` 当State驱动一个键列表或区域时，每个组件都应该被键重用。

```ts
interface Todo {
    id: string
    label: string
}

const list = Component("ul")
const todos = State(list, [
    { id: "first", label: "Write the README" },
])

list.breakdown(todos, (list, Part, todos) => {
    for (const todo of todos) {
        Part(todo.id, () => Component("li"))
            .text.set(todo.label)
            .appendTo(list)
    }
})
```

为每个逻辑项使用一个稳定的键。 `Part` 返回相同的组件，而该键仍然存在。它的构建器在该键需要一个新组件时运行，并且必须返回一个尚未放置的新的、活动的、无主的组件。

更新和放置每一个返回的零件在每一个过程中。 `Part` 永远不会为你放置组件，放置调用的顺序建立了呈现的顺序。如果一个成功的传球漏掉了一个关键，kitsui就会处理掉那个部分。

保持回调同步：不要使用异步回调， `await`，或延迟 `Part` 还有安置电话。以后的状态更新是批处理的，可以跳过中间值，因此每次都从提供给回调的值传递。

在回调中使用kitsui放置api。如果您需要检查物理DOM，请在回调返回后执行。即使你把每个部件放在别的地方，故障仍然占有它。

## 功能指数

-   [`Component`](https://npmcn.com/package/%40inthedark/kitsui/Component.html)： `append`， `prepend`， `insert`、条件变量； `clear`， `use`， `and`， `is`， `as`， `remove`， `extend`.
-   分解: `component.breakdown(...)` 和 `Component.Breakdown(...)` 用于从状态值进行稳定的键化呈现。
-   放置扩展: `appendTo`， `prependTo`， `insertTo`，条件变量，以及 `place`.
-   机械手: [`ClassManipulator`](https://npmcn.com/package/%40inthedark/kitsui/ClassManipulator.html)， [`StyleManipulator`](https://npmcn.com/package/%40inthedark/kitsui/StyleManipulator.html)， [`AttributeManipulator`](https://npmcn.com/package/%40inthedark/kitsui/AttributeManipulator.html)， [`TextManipulator`](https://npmcn.com/package/%40inthedark/kitsui/TextManipulator.html)， [`EventManipulator`](https://npmcn.com/package/%40inthedark/kitsui/EventManipulator.html)， [`AriaManipulator`](https://npmcn.com/package/%40inthedark/kitsui/AriaManipulator.html).
-   [`State`](https://npmcn.com/package/%40inthedark/kitsui/State.html)： `set`， `update`平等选项，订阅， `map`， `debounce`， `throttle`， `mapAsync`， `truthy`， `falsy`， `or`， `Group`， `extend`.
-   [`Style`](https://npmcn.com/package/%40inthedark/kitsui/Style.html)：片段、类、有序类 `after`、变量、嵌套选择器、媒体查询、伪选择器、动画帮助器和粘性帮助器。
-   ARIA：用于角色、标签、引用、布尔值、 `current`, `live`.
-   文档的例子: [计数器](https://npmcn.com/package/%40inthedark/kitsui/playground.html?example=counter.ts)， [作文](https://npmcn.com/package/%40inthedark/kitsui/playground.html?example=composition.ts)， [append-when](https://npmcn.com/package/%40inthedark/kitsui/playground.html?example=append-when.ts)， [bind-manipulators](https://npmcn.com/package/%40inthedark/kitsui/playground.html?example=bind-manipulators.ts)， [place-iframe](https://npmcn.com/package/%40inthedark/kitsui/playground.html?example=place-iframe.ts)， [状态组](https://npmcn.com/package/%40inthedark/kitsui/playground.html?example=state-group.ts)， [mount-dispose](https://npmcn.com/package/%40inthedark/kitsui/playground.html?example=mount-dispose.ts).

## 常见问题

### @inthedark/kitsui 是什么？

UI框架模板围绕可变组件包装器和显式状态转换构建。

### 如何安装 @inthedark/kitsui？

在项目目录运行 npm install @inthedark/kitsui，或使用 pnpm add / yarn add / bun add。

### @inthedark/kitsui 支持 TypeScript 吗？

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

### @inthedark/kitsui 支持 ESM 还是 CommonJS？

以 CommonJS 形式发布，可以直接 require\(\)；在 ES Module 项目中也可以用 import 引入（Node\.js 会把 module\.exports 作为默认导出）。

### @inthedark/kitsui 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=20。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @inthedark/kitsui 可以商用吗？

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

### @inthedark/kitsui 还在维护吗？

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

