{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}你这混蛋
Just Java 脚本 UI 框架
开始
脚手架新项目:
npm create crank
或者立刻试试 在线操场。 。 。 .
其他链接 :
动机
一个感觉像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 软件包在 国家预防机制 结束 页:1 @b9g 组织 (减: 页:1迈克沙文页:1.
npm i @b9g/crank
正在导入 Crank 中 自动 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 标记模板
函数 它提供了类似的语法和语义
随着JSX的转变. 这样您就可以在香草中写入 Crank 组件
Java脚本.
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://cdn.jsdelivr.net/npm/@b9g/crank/)和 esm.sh (英语) (https://esm.sh/@b9g/crank)用于无害环境管理准备环境中.
/** @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,
);
主要实例
一个简单的组件
import {renderer} from "@b9g/crank/dom";
function Greeting({name = "World"}) {
return (
<div>Hello {name}</div>
);
}
renderer.render(<Greeting />, document.body);
国家部分
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 组件
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);
装入组件
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);
通用工具配置
下面是用克兰克开始的配置的不完整列表.
类型脚本
TypeScript是被打入的JavaScript的超级集.
以下是您需要设置的 JSX 自动转写配置 .
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@b9g/crank"
}
}
克兰克用TypeScript来书写,并带有类型. 参见 指导 类型脚本 为了更多 有关克兰克类型的信息.
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);
}
宝贝
Babel是一个受欢迎的开源JavaScript编译器,它允许您用现代语法(包括JSX)来编写代码,并在不支持语法的环境中运行.
这是如何让Babel 转写JSX给克兰克.
自动转换 :
{
"plugins": [
"@babel/plugin-syntax-jsx",
[
"@babel/plugin-transform-react-jsx",
{
"runtime": "automatic",
"importSource": "@b9g/crank",
"throwIfNamespace": false,
"useSpread": true
}
]
]
}
ESLint 语录
ESLint是一个流行的开源工具,用于分析和检测JavaScript代码中的问题.
Crank 提供了在软件包名下与 ESLint 合作的配置预设 eslint-plugin-crank。 。 。 .
npm i eslint eslint-plugin-crank
在您的 Eslint 配置中 :
{
"extends": ["plugin:crank/recommended"]
}
天体
Astro.js是一个现代的静态站点建造者和框架.
Crank 提供了一个 Astro 集成系统 使服务器端渲染和客户端与Astro进行水合.
npm i astro-crank
在你身边 astro.config.mjs。 。 。 .
import {defineConfig} from "astro/config";
import crank from "astro-crank";
// https://astro.build/config
export default defineConfig({
integrations: [crank()],
});
API 参考
核心出口
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";
组件类型
职能构成部分 - 无国籍
function Greeting({name = "World"}) {
return <div>Hello {name}</div>;
}
发电机构成部分 - 严谨的 function*
function* Counter() {
let count = 0;
const onclick = () => this.refresh(() => count++);
for ({} of this) {
yield <button onclick={onclick}>Count: {count}</button>;
}
}
Async 组件 - 用途 async 承诺
async function UserProfile({userId}) {
const user = await fetch(`/api/users/${userId}`).then(r => r.json());
return <div>Hello, {user.name}!</div>;
}
Async 发电机组件 - 状态+同步
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个参数).
function Component(props, ctx) {
console.log(this === ctx); // true
return props.children;
}
属性
this.props - 当前道具(只读)
this.isExecuting - 该组件是否正在执行
this.isUnmounted - 组件是否已卸载
方法
this.refresh(callback?) - 触发器重新执行
this.refresh(); // Simple refresh
this.refresh(() => count++); // With state update (v0.7+)
this.schedule(callback?) - DOM 完成后执行
// 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直播后执行
// 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?) - 在卸载时执行
function* Component() {
const interval = setInterval(() => this.refresh(), 1000);
this.cleanup(() => clearInterval(interval));
for ({} of this) {
yield <div>Tick</div>;
}
}
this.addEventListener(type, listener, options?) - 倾听事件
this.addEventListener("click", (e) => console.log("Clicked!"));
this.dispatchEvent(event) - 调度活动
this.dispatchEvent(new CustomEvent("mybuttonclick", {
bubbles: true,
detail: {id: props.id}
}));
this.provide(key, value) 编号 : this.consume(key) - 上下文 API
// 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) - 渲染回路(同步)
function* Component() {
for ({} of this) {
yield <div>{this.props.message}</div>;
}
}
for await ({} of this) - Async为赛跑树制作回路
async function* AsyncComponent() {
for await ({} of this) {
// Multiple yields race - whichever completes first shows
yield <Loading />;
yield <Content />;
}
}
特殊道具
key - 唯一的调节标识符
<ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>
ref - 访问制成的DOM元素
<audio ref={(el) => (audio = el)} />
// Forward refs through components
function MyInput({ref, ...props}) {
return <input ref={ref} {...props} />;
}
copy - 预防/控制重新投降
// 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水分控制
<div hydrate={false}> // Skip hydration
<Portal hydrate={true}> // Force hydration
<input hydrate="!value" /> // Hydrate all except value
class - 字符串或对象(v0.7+)
<button class="btn active" />
<button class={{
btn: true,
'btn-active': isActive,
'btn-disabled': isDisabled
}} />
style - CSS 字符串或对象
<div style="color: red; font-size: 16px" />
<div style={{"font-size": "16px", color: "blue"}} />
innerHTML - Raw HTML 字符串( XSS 风险)
<div innerHTML="<strong>Bold</strong>" />
事件宣传 - 小写事件处理器
<button onclick={handler} />
<input onchange={handler} oninput={handler} />
<form onsubmit={handler} />
主张命名 - 支持方便 HTML 名称
<label class="my-label" for="my-id">Label</label>
// Instead of className and htmlFor
特殊元素标记
<Fragment> - 儿童无包装
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 /> - 防止元素重新落地
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节点
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 节点
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+)
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) - 懒惰部件
import {lazy} from "@b9g/crank/async";
const LazyComponent = lazy(() => import("./MyComponent.js"));
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
Suspense - 申报装载状态
import {Suspense} from "@b9g/crank/async";
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
SuspenseList - 协调多个合成组件
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
function* Component() {
console.log("Mounting...");
const interval = setInterval(() => this.refresh(), 1000);
for ({} of this) {
yield <div>Tick</div>;
}
clearInterval(interval); // Cleanup
}
更新 - 里面的代码使循环
function* Component() {
for ({} of this) {
console.log("Updated with:", this.props);
yield <div>{this.props.message}</div>;
}
}
清理 - 循环或通过后代码 this.cleanup()
function* Component() {
const interval = setInterval(() => this.refresh(), 1000);
this.cleanup(() => clearInterval(interval));
for ({} of this) {
yield <div>Tick</div>;
}
}
高级图案
上级部门
function withLogger(Component) {
return function* WrappedComponent(props) {
console.log("Rendering with:", props);
for ({} of this) {
yield <Component {...props} />;
}
};
}
钩子
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 优于全球扩展)
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>;
}
}
赛跑组件
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 />;
}
}
组件竞相制造。 对后退状态有用.
类型脚本支持
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 (英语)