Bobril - VII - 组件和 TSX
本文将指导您使用 TSX 组件创建 Bobril 应用程序。
引言
在第六篇文章中,我们创建了一个简单的 TODO 应用程序,它使用 bobx 并且由通过 b.createComponent
创建的通用组件组成。 在过去的一年里,bobril 已经以类似 React 的方式发展,并带来了对 TSX、有状态类组件和无状态函数组件的全面支持。
TSX (类型安全的 JSX) 是一种扩展 TypeScript 的模板语言。 它用于在一个地方定义 UI 和逻辑。 它生成 Bobril 元素。
本文将介绍如何使用这两种类型的组件和 TSX 创建一个 TODO 应用程序。
- Bobril - I - 入门
- Bobril - II - Bobflux 应用程序架构
- Bobril - III - 本地化和格式化
- Bobril - IV - 路由
- Bobril - V - Bobril-build
- Bobril - VI - BobX 应用程序商店管理
- Bobril - VII - 组件和 TSX
准备项目
以与之前示例类似的方式创建项目
npm i bobril-build -g
npm init
npm i bobril bobx --save
添加 index.tsx 并运行
bb
函数组件
最简单的组件是函数式
组件。 它不保留任何状态,只是反映输入数据
。 它由一个函数定义,该函数带有一个数据参数并返回 b.IBobrilNode
。 为了遵循 TSX 组件的规则,函数的名称必须以大写字母开头。 数据的定义方式与往常一样。
创建一个 components/listItem.tsx 并编写以下代码
import * as b from "bobril";
export interface IItem {
id: number;
text: string;
done: boolean;
}
export interface IItemData extends IItem {
index: number;
onItemChecked(index: number, value: boolean): void;
}
export function ListItem(data: IItemData): b.IBobrilNode {
return (
<li key={data.id} style={data.done && strikeOut}>
<input
type="checkbox"
value={data.done}
onChange={value => data.onItemChecked(data.index, value)}
/>
{data.text}
</li>
);
}
const strikeOut = b.styleDef({ textDecoration: "line-through" });
您可以看到组件的 TSX 定义非常简单。 您可以使用原生元素并使用{expression}
将数据填充到其属性中。
在这个组件中,您还可以看到 bobril 键和样式的定义。
这种组件可以用作 TSX 元素。 将以下代码放入 components/list.tsx
import * as b from "bobril";
import { ListItem, IItem } from "./listItem";
export interface IListData {
items: IItem[];
onItemChecked(index: number, value: boolean): void;
}
export function List(data: IListData): b.IBobrilNode {
return (
<ul style={noBullets}>
{data.items.map((item, index) => (
<ListItem {...item} index={index} onItemChecked={data.onItemChecked} />
))}
</ul>
);
}
const noBullets = b.styleDef({ listStyleType: "none" });
这是一个很好的例子,说明您如何使用内联 TypeScript 函数 map 来生成内容。 此函数将输入数据映射到 TSX 元素列表。
您可以看到的下一个模式是使用展开运算符 {...item}
将数据作为属性提供给子节点。
类组件
第二种组件类型是有状态类组件。 因为它是一个类,所以它允许您保留内部状态。
在 components/form.tsx 中创建一个表单组件
import * as b from "bobril";
import { observable } from "bobx";
export interface IFormData {
onSubmit(value: string): void;
}
export class Form extends b.Component<IFormData> {
@observable
private _value: string = "";
render(): b.IBobrilChildren {
return (
<>
<input
type="text"
value={this._value}
onChange={newValue => this.updateValue(newValue)}
onKeyUp={ev => ev.which === 13 && this.submit()}
style={spaceOnRight}
/>
<button onClick={() => this.submit()}>OK</button>
</>
);
}
private updateValue(newValue: string): void {
this._value = newValue;
}
private submit(): boolean {
this.data.onSubmit(this._value);
this._value = "";
return true;
}
}
const spaceOnRight = b.styleDef({ marginRight: 5 });
您可以看到它与通用组件定义非常相似。 它有一个方法 render()
,返回 b.IBobrilChildren
。 可以通过 this.data
访问 数据
。 它必须从 b.Component
派生。 它还将其内部状态保持为 this._value
作为可观察属性。
片段
在上面的代码中,您可以看到 <>...</>
元素。 它被称为片段,它用于将子元素包装成虚拟节点。 它基本上用于定义返回多个根元素的组件。
插槽
有时,您需要创建用于布局的特殊组件。 如果数据定义包含属性 children: b.IBobrilChildren
, 那么它可以简单地以表达式的形式添加为 TSX 内容 {data.children}
。
如果您需要组合更复杂的布局,那么您可以使用 插槽
模式。
使用以下代码创建 components/layout.tsx
import * as b from "bobril";
export interface ILayoutData {
children: {
header: b.IBobrilChildren;
body: b.IBobrilChildren;
footer: b.IBobrilChildren;
};
}
export function Layout(data: ILayoutData): b.IBobrilNode {
return (
<>
<div>{data.children.header}</div>
<div>{data.children.body}</div>
<div>{data.children.footer}</div>
</>
);
}
ILayoutData
具有复杂类型的 children 而不是 b.IBobrilChildren
。 它允许您通过多个特定的子属性定义内容。 TypeScript 将使您保持正确的用法,因此它是类型安全的。
BobX 存储
我们需要做的下一件事是 bobx
存储,正如我们从之前的文章中了解到的那样。 在 store.ts 中定义一个
import { observable } from "bobx";
import { IItem } from "./components/listItem";
export class TodoStore {
@observable
private _todos: IItem[] = [];
get list(): IItem[] {
return this._todos;
}
add(text: string): void {
this._todos.push({ id: Date.now(), text, done: false });
}
edit(index: number, value: boolean): void {
this._todos[index].done = value;
}
}
组合页面
最后,我们可以在整个页面组件 To do
的 index.tsx 中组合逻辑和组件
import * as b from "bobril";
import { Layout } from "./components/layout";
import { List } from "./components/list";
import { Form } from "./components/form";
import { TodoStore } from "./store";
class To do extends b.Component {
todos = new TodoStore();
render(): b.IBobrilChildren {
return (
<Layout>
{{
header: <h1>TODO</h1>,
body: (
<List
items={this.todos.list}
onItemChecked={(index, value) => this.todos.edit(index, value)}
/>
),
footer: <Form onSubmit={text => this.todos.add(text)} />
}}
</Layout>
);
}
}
b.init(() => <To do />);
您可以看到它具有存储在 todos
属性中的内部状态,并且它由布局中的组件创建,使用带有对象作为插槽内容定义的表达式。
最后,To do
组件在 b.init
函数中用作入口点。
历史
- 2020-01-20:更新的 bobril@13.1.1 和 bobx@0.27.1 示例
- 2020-01-08:为 bobril@11.2.0 和 bobx@0.27.1 创建的文章