# @b9g/crank 中文文档

规范地址：https://npmcn.com/package/%40b9g/crank/

版本：0\.7\.12
npm 元数据更新时间：2026-09-04T17:54:12.579Z
中文译文更新时间：2026-11-07T17:24:13.838Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40b9g/crank
原文对照：https://npmcn.com/package/%40b9g/crank/?lang=original
项目主页：https://crank.js.org/
源码仓库：https://github.com/bikeshaving/crank

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

- 包体积（解压后）：2 MB，66 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- 运行依赖：0 个
- 首次发布：2021-10-09T21:31:32.759Z
- 最新版本发布：2026-09-04T17:54:12.424Z
- 近 30 天下载量：3691（2026-10-08 至 2026-11-06，npm 官方下载统计）
- 已发布版本数：37（近 12 个月 10 个）

## 中文简介

Just JavaScript UI 框架.

## 安装

```sh
npm install @b9g/crank
```

也可以使用：`pnpm add @b9g/crank`、`yarn add @b9g/crank`、`bun add @b9g/crank`

## README

![Crank.js Logo](https://npmcn.com/package/%40b9g/crank/logo.svg)

# {\\fn黑体\\fs22\\bord1\\shad0\\3aHBE\\4aH00\\fscx67\\fscy66\\2cHFFFFFF\\3cH808080}你这混蛋

Just Java 脚本 UI 框架

## 开始

脚手架新项目:

```shell
npm create crank
```

或者立刻试试 [在线操场](https://crank.js.org/playground)。 。 。 .

其他链接 :

-   [crant.js.org (英语)](https://crank.js.org/)
-   [国家预防机制](https://www.npmjs.com/package/@b9g/crank)
-   [介绍 Crank.js](https://crank.js.org/blog/introducing-crank/)
-   [实例](https://github.com/bikeshaving/crank/tree/main/examples)
-   [Python 的缩放](https://github.com/bikeshaving/crankpy)
-   [深维基](https://deepwiki.com/bikeshaving/crank)

## 动机

**一个感觉像JavaScript的框架.**

```javascript
// State is defined with generator components
function* Timer() {
  // setup goes here
  let seconds = 0;
  const interval = setInterval(() => this.refresh(() => seconds++), 1000);

  for ({} of this) {
    yield <div>Seconds: {seconds}</div>;
  }

  clearInterval(interval); // Cleanup just works
}

renderer.render(<Timer />, document.body);

// Async components just work on client and server
async function UserProfile({userId}) {
  const user = await fetchUser(userId);
  return <div>Hello, {user.name}!</div>;
}

await renderer.render(<UserProfile />, document.body);
```

### 为什么是克兰克?

克兰克依靠语言而不是再造语言. 州为地 变量,在生成器范围内。 更新是重新执行:呼叫 `refresh()` 再次运行循环。 装入状态是 `async` 函数, 等着呢 设置是循环之前的代码;清理是循环之后的代码. 没有钩子,没有依赖数组,没有编译器,也没有规则 控制流动是生命的循环.

因为组件是简单的函数和发电机,所有你已经 了解JavaScript应用. 关闭持有状态,模块共享逻辑,以及 `try`编号 :`finally` 处理撕裂。 如果你能读取 JavaScript, 你可以读取一个 Crank组件.

## 安装

Crank 软件包在 [国家预防机制](https://npmjs.org/@b9g/crank) 结束 页:1 [@b9g 组织](https://www.npmjs.com/org/b9g) (减: 页:1_迈克沙文_页:1.

```shell
npm i @b9g/crank
```

### 正在导入 Crank 中 **自动** JSX转型.

```jsx
/** @jsxImportSource @b9g/crank */
import {renderer} from "@b9g/crank/dom";

renderer.render(
  <p>This paragraph element is transpiled with the automatic transform.</p>,
  document.body,
);
```

### 导入 JSX 模板标签 .

从版本开始 `0.5`,克兰克包船a [标记模板 函数](https://npmcn.com/guides/jsx-template-tag) 它提供了类似的语法和语义 随着JSX的转变. 这样您就可以在香草中写入 Crank 组件 Java脚本.

```js
import {jsx} from "@b9g/crank/standalone";
import {renderer} from "@b9g/crank/dom";

renderer.render(jsx`
  <p>No transpilation is necessary with the JSX template tag.</p>
`, document.body);
```

### ECMAScript 模块 CDNs

CRANK 也可以在CDN上读取,比如: [日记](https://www.jsdelivr.com/) (https://cdn.jsdelivr.net/npm/@b9g/crank/)和 [esm.sh (英语)](https://esm.sh/) (https://esm.sh/@b9g/crank)用于无害环境管理准备环境中.

```jsx
/** @jsx createElement */
import {createElement} from "https://cdn.jsdelivr.net/npm/@b9g/crank/crank.js";
import {renderer} from "https://cdn.jsdelivr.net/npm/@b9g/crank/dom.js";

renderer.render(
  <div id="hello">
    Running on <a href="https://www.jsdelivr.com">jsDelivr</a>
  </div>,
  document.body,
);
```

## 主要实例

### 一个简单的组件

```jsx
import {renderer} from "@b9g/crank/dom";

function Greeting({name = "World"}) {
  return (
    <div>Hello {name}</div>
  );
}

renderer.render(<Greeting />, document.body);
```

### 国家部分

```jsx
function *Timer(this: Context) {
  let seconds = 0;
  const interval = setInterval(() => this.refresh(() => seconds++), 1000);
  for ({} of this) {
    yield <div>Seconds: {seconds}</div>;
  }

  clearInterval(interval);
}
```

### Async 组件

```jsx
import {renderer} from "@b9g/crank/dom";
async function Definition({word}) {
  // API courtesy https://dictionaryapi.dev
  const res = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`);
  const data = await res.json();
  if (!Array.isArray(data)) {
    return <p>No definition found for {word}</p>;
  }

  const {phonetic, meanings} = data[0];
  const {partOfSpeech, definitions} = meanings[0];
  const {definition} = definitions[0];
  return <>
    <p>{word} <code>{phonetic}</code></p>
    <p><b>{partOfSpeech}.</b>{" "}{definition}</p>
  </>;
}

await renderer.render(<Definition word="framework" />, document.body);
```

### 装入组件

```jsx
import {Fragment} from "@b9g/crank";
import {renderer} from "@b9g/crank/dom";

async function LoadingIndicator() {
  await new Promise(resolve => setTimeout(resolve, 1000));
  return (
    <div>
      🐕 Fetching a good boy...
    </div>
  );
}

async function RandomDog({throttle = false}) {
  const res = await fetch("https://dog.ceo/api/breeds/image/random");
  const data = await res.json();
  if (throttle) {
    await new Promise(resolve => setTimeout(resolve, 2000));
  }

  return (
    <div>
      <a href={data.message} target="_blank" style="text-decoration: none; color: inherit;">
        <img
          src={data.message}
          alt="A Random Dog"
          width="300"
        />
        <div>
          Click to view full size
        </div>
      </a>
    </div>
  );
}

async function *RandomDogLoader({throttle}) {
  // for await can be used to race component trees
  for await ({throttle} of this) {
    yield <LoadingIndicator />;
    yield <RandomDog throttle={throttle} />;
  }
}

function *RandomDogApp() {
  let throttle = false;
  this.addEventListener("click", (ev) => {
    if (ev.target.tagName === "BUTTON") {
      this.refresh(() => throttle = !throttle);
    }
  });

  for ({} of this) {
    yield (
      <div>
        <RandomDogLoader throttle={throttle} />
        <div>
          <button>
            Show me another dog!
          </button>
          <div>
            {throttle ? "Slow mode" : "Fast mode"}
          </div>
        </div>
      </div>
    );
  }
}

renderer.render(<RandomDogApp />, document.body);
```

## 通用工具配置

下面是用克兰克开始的配置的不完整列表.

### [类型脚本](https://www.typescriptlang.org/)

TypeScript是被打入的JavaScript的超级集.

以下是您需要设置的 JSX 自动转写配置 .

```tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@b9g/crank"
  }
}
```

克兰克用TypeScript来书写,并带有类型. 参见 [指导 类型脚本](https://crank.js.org/guides/working-with-typescript) 为了更多 有关克兰克类型的信息.

```tsx
import type {Context} from "@b9g/crank";
function *Timer(this: Context<typeof Timer>) {
  let seconds = 0;
  const interval = setInterval(() => this.refresh(() => seconds++), 1000);
  for ({} of this) {
    yield <div>Seconds: {seconds}</div>;
  }

  clearInterval(interval);
}
```

### [宝贝](https://babeljs.io/)

Babel是一个受欢迎的开源JavaScript编译器,它允许您用现代语法(包括JSX)来编写代码,并在不支持语法的环境中运行.

这是如何让Babel 转写JSX给克兰克.

自动转换 :

```.babelrc.json
{
  "plugins": [
    "@babel/plugin-syntax-jsx",
    [
      "@babel/plugin-transform-react-jsx",
      {
        "runtime": "automatic",
        "importSource": "@b9g/crank",

        "throwIfNamespace": false,
        "useSpread": true
      }
    ]
  ]
}
```

### [ESLint 语录](https://eslint.org/)

ESLint是一个流行的开源工具,用于分析和检测JavaScript代码中的问题.

Crank 提供了在软件包名下与 ESLint 合作的配置预设 `eslint-plugin-crank`。 。 。 .

```bash
npm i eslint eslint-plugin-crank
```

在您的 Eslint 配置中 :

```.eslintrc.json
{
  "extends": ["plugin:crank/recommended"]
}
```

### [天体](https://astro.build/)

Astro.js是一个现代的静态站点建造者和框架.

Crank 提供了一个 [Astro 集成系统](https://docs.astro.build/en/guides/integrations-guide/) 使服务器端渲染和客户端与Astro进行水合.

```bash
npm i astro-crank
```

在你身边 `astro.config.mjs`。 。 。 .

```astro.config.mjs
import {defineConfig} from "astro/config";
import crank from "astro-crank";

// https://astro.build/config
export default defineConfig({
  integrations: [crank()],
});
```

## API 参考

### 核心出口

```javascript
import {
  createElement,
  Fragment,
  Copy,
  Portal,
  Raw,
  Text,
  Context
} from "@b9g/crank";

import {renderer} from "@b9g/crank/dom"; // Browser DOM
import {renderer} from "@b9g/crank/html"; // Server-side HTML

import {jsx, html} from "@b9g/crank/standalone"; // Template tag (no build)

import {Suspense, SuspenseList, lazy} from "@b9g/crank/async";
```

* * *

### 组件类型

**职能构成部分** - 无国籍

```javascript
function Greeting({name = "World"}) {
  return <div>Hello {name}</div>;
}
```

**发电机构成部分** - 严谨的 `function*`

```javascript
function* Counter() {
  let count = 0;
  const onclick = () => this.refresh(() => count++);

  for ({} of this) {
    yield <button onclick={onclick}>Count: {count}</button>;
  }
}
```

**Async 组件** - 用途 `async` 承诺

```javascript
async function UserProfile({userId}) {
  const user = await fetch(`/api/users/${userId}`).then(r => r.json());
  return <div>Hello, {user.name}!</div>;
}
```

**Async 发电机组件** - 状态+同步

```javascript
async function* DataLoader({url}) {
  for ({url} of this) {
    const data = await fetch(url).then(r => r.json());
    yield <div>{data.message}</div>;
  }
}
```

* * *

### 上下文 API

内容如下: `this` 在组件中(或作为第2个参数).

```javascript
function Component(props, ctx) {
  console.log(this === ctx); // true
  return props.children;
}
```

#### 属性

**`this.props`** - 当前道具(只读)

**`this.isExecuting`** - 该组件是否正在执行

**`this.isUnmounted`** - 组件是否已卸载

#### 方法

**`this.refresh(callback?)`** - 触发器重新执行

```javascript
this.refresh();                    // Simple refresh
this.refresh(() => count++);       // With state update (v0.7+)
```

**`this.schedule(callback?)`** - DOM 完成后执行

```javascript
// el is whatever the component returns Node/Text/HTMLElement/null, an array of dom nodes, etc
this.schedule((el) => {
  console.log("Component rendered", el.innerHTML);
});
```

**`this.after(callback?)`** -DOM直播后执行

```javascript
// this runs after the DOM nodes have finally entered the DOM
// this is where you put things like autofocus
this.after((el) => {
  console.log(el.isConnected); // true
});
```

**`this.cleanup(callback?)`** - 在卸载时执行

```javascript
function* Component() {
  const interval = setInterval(() => this.refresh(), 1000);
  this.cleanup(() => clearInterval(interval));

  for ({} of this) {
    yield <div>Tick</div>;
  }
}
```

**`this.addEventListener(type, listener, options?)`** - 倾听事件

```javascript
this.addEventListener("click", (e) => console.log("Clicked!"));
```

**`this.dispatchEvent(event)`** - 调度活动

```javascript
this.dispatchEvent(new CustomEvent("mybuttonclick", {
  bubbles: true,
  detail: {id: props.id}
}));
```

**`this.provide(key, value)`** 编号 : **`this.consume(key)`** - 上下文 API

```javascript
// Provider
function* ThemeProvider() {
  this.provide("theme", "dark");
  for ({} of this) {
    yield this.props.children;
  }
}

// Consumer
function ThemedButton() {
  const theme = this.consume("theme");
  return <button class={theme}>Click me</button>;
}
```

#### 重复

**`for ({} of this)`** - 渲染回路(同步)

```javascript
function* Component() {
  for ({} of this) {
    yield <div>{this.props.message}</div>;
  }
}
```

**`for await ({} of this)`** - Async为赛跑树制作回路

```javascript
async function* AsyncComponent() {
  for await ({} of this) {
    // Multiple yields race - whichever completes first shows
    yield <Loading />;
    yield <Content />;
  }
}
```

* * *

### 特殊道具

**`key`** - 唯一的调节标识符

```javascript
<ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>
```

**`ref`** - 访问制成的DOM元素

```javascript
<audio ref={(el) => (audio = el)} />

// Forward refs through components
function MyInput({ref, ...props}) {
  return <input ref={ref} {...props} />;
}
```

**`copy`** - 预防/控制重新投降

```javascript
// Boolean: prevent rendering when truthy
<li copy={!el.hasChanged}>{el.value}</li>

// string: copy specific props
<input copy="!value" type="text" />        // Copy all except value
<div copy="class id" />                    // Copy only class and id
<div copy="children" />                    // Copy children
```

**`hydrate`** - SSR水分控制

```javascript
<div hydrate={false}>                      // Skip hydration
<Portal hydrate={true}>                    // Force hydration
<input hydrate="!value" />                 // Hydrate all except value
```

**`class`** - 字符串或对象(v0.7+)

```javascript
<button class="btn active" />

<button class={{
  btn: true,
  'btn-active': isActive,
  'btn-disabled': isDisabled
}} />
```

**`style`** - CSS 字符串或对象

```javascript
<div style="color: red; font-size: 16px" />
<div style={{"font-size": "16px", color: "blue"}} />
```

**`innerHTML`** - Raw HTML 字符串( XSS 风险)

```javascript
<div innerHTML="<strong>Bold</strong>" />
```

**事件宣传** - 小写事件处理器

```javascript
<button onclick={handler} />
<input onchange={handler} oninput={handler} />
<form onsubmit={handler} />
```

**主张命名** - 支持方便 HTML 名称

```javascript
<label class="my-label" for="my-id">Label</label>
// Instead of className and htmlFor
```

* * *

### 特殊元素标记

**`<Fragment>`** - 儿童无包装

```javascript
import {Fragment} from "@b9g/crank";

<Fragment>
  <div>Child 1</div>
  <div>Child 2</div>
</Fragment>

// Or use: <>...</>
// The Fragment tag is the empty string
```

**`<Copy />`** - 防止元素重新落地

```javascript
import {Copy} from "@b9g/crank";

function memo(Component) {
  return function* Wrapped(props) {
    yield <Component {...props} />;
    for (const newProps of this) {
      if (equals(props, newProps)) {
        yield <Copy />;  // Reuse previous render
      } else {
        yield <Component {...newProps} />;
      }
      props = newProps;
    }
  };
}
```

**`<Portal>`** - 进入不同的DOM节点

```javascript
import {Portal} from "@b9g/crank";

const modalRoot = document.getElementById("modal-root");

function Modal({children}) {
  return (
    <Portal root={modalRoot}>
      <div class="modal">
        {children}
      </div>
    </Portal>
  );
}
```

**`<Raw>`** - 插入原始 HTML 或 DOM 节点

```javascript
import {Raw} from "@b9g/crank";

function MarkdownViewer({markdown}) {
  const html = marked(markdown);
  return <div><Raw value={html} /></div>;
}

// Or insert DOM node
<Raw value={domNode} />
```

**`<Text>`** - 明确文本节点创建(v0.7+)

```javascript
import {Text} from "@b9g/crank";

<Text value="Hello world" />

// Access Text nodes in lifecycle
function* Component() {
  this.schedule((node) => {
    if (node instanceof Text) {
      console.log("Text node:", node);
    }
  });
  for ({} of this) {
    yield "Text content";  // Becomes a Text node
  }
}
```

* * *

### Async公用事业(v0.7+)

**`lazy(loader)`** - 懒惰部件

```javascript
import {lazy} from "@b9g/crank/async";

const LazyComponent = lazy(() => import("./MyComponent.js"));

<Suspense fallback={<div>Loading...</div>}>
  <LazyComponent />
</Suspense>
```

**`Suspense`** - 申报装载状态

```javascript
import {Suspense} from "@b9g/crank/async";

<Suspense fallback={<div>Loading...</div>}>
  <AsyncComponent />
</Suspense>
```

**`SuspenseList`** - 协调多个合成组件

```javascript
import {SuspenseList} from "@b9g/crank/async";

<SuspenseList>
  <Suspense fallback={<div>Loading 1...</div>}>
    <Item1 />
  </Suspense>
  <Suspense fallback={<div>Loading 2...</div>}>
    <Item2 />
  </Suspense>
</SuspenseList>
```

* * *

### 生命周期模式

**挂载** - 代码之前 `yield`

```javascript
function* Component() {
  console.log("Mounting...");
  const interval = setInterval(() => this.refresh(), 1000);

  for ({} of this) {
    yield <div>Tick</div>;
  }

  clearInterval(interval);  // Cleanup
}
```

**更新** - 里面的代码使循环

```javascript
function* Component() {
  for ({} of this) {
    console.log("Updated with:", this.props);
    yield <div>{this.props.message}</div>;
  }
}
```

**清理** - 循环或通过后代码 `this.cleanup()`

```javascript
function* Component() {
  const interval = setInterval(() => this.refresh(), 1000);
  this.cleanup(() => clearInterval(interval));

  for ({} of this) {
    yield <div>Tick</div>;
  }
}
```

* * *

### 高级图案

**上级部门**

```javascript
function withLogger(Component) {
  return function* WrappedComponent(props) {
    console.log("Rendering with:", props);
    for ({} of this) {
      yield <Component {...props} />;
    }
  };
}
```

**钩子**

```javascript
function useInterval(ctx, callback, delay) {
  let interval = setInterval(callback, delay);
  ctx.cleanup(() => clearInterval(interval));
  return (newDelay) => {
    delay = newDelay;
    clearInterval(interval);
    interval = setInterval(callback, delay);
  };
}
```

**背景扩展名** (QQ 优于全球扩展)

```javascript
import {Context} from "@b9g/crank";

Context.prototype.setInterval = function(callback, delay) {
  const interval = setInterval(callback, delay);
  this.cleanup(() => clearInterval(interval));
};

// Use in components
function* Timer() {
  let seconds = 0;
  this.setInterval(() => this.refresh(() => seconds++), 1000);

  for ({} of this) {
    yield <div>Seconds: {seconds}</div>;
  }
}
```

**赛跑组件**

```javascript
async function* DataComponent({url}) {
  for await ({url} of this) {
    // This is the equivalent of calling
    //   renderer.render(<Spinner />, document.body);
    //   renderer.render(<Data />, document.body);
    // but in a component.
    yield <Spinner />;
    yield <Data />;
  }
}
```

组件竞相制造。 对后退状态有用.

* * *

### 类型脚本支持

```typescript
import type {Context} from "@b9g/crank";
import {ComponentProps} from "@b9g/crank";  // v0.7+

// Component with typed props
interface Props {
  name: string;
  age?: number;
}

function Greeting({name, age}: Props) {
  return <div>Hello {name}, age {age}</div>;
}

// Generator with typed context
function* Greeting(this: Context<typeof Greeting>, {name}: {name: string}) {
  for ({name} of this) {
    yield <div>Hello {name}</div>;
  }
}

// Extract component props type
function Button({variant}: {variant: "primary" | "secondary"}) {
  return <button class={`btn-${variant}`}>Click</button>;
}

type ButtonProps = ComponentProps<typeof Button>;
```

综合指南和文件,访问 [crant.js.org (英语)](https://crank.js.org/)

## 常见问题

### @b9g/crank 是什么？

Just JavaScript UI 框架\.

### 如何安装 @b9g/crank？

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

### @b9g/crank 支持 TypeScript 吗？

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

### @b9g/crank 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @b9g/crank 可以商用吗？

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

### @b9g/crank 的下载量有多少？

根据 npm 官方下载统计，@b9g/crank 在 2026/10/08 – 2026/11/06 这 30 天内被下载 3,691 次，日均约 123 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @b9g/crank 还在维护吗？

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

