mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2024-11-27 08:39:59 +08:00
docs(component): add docs and readme
This commit is contained in:
parent
3f76911745
commit
3ccb7407fd
81
README.md
81
README.md
@ -1,7 +1,9 @@
|
||||
<div align="center">
|
||||
<h1>🪵 Sunmao-UI 🪚</h1>
|
||||
<img src="./docs/images/logo.png" alt="logo" width="200" />
|
||||
</div>
|
||||
<div align="center">
|
||||
<h1>Sunmao-UI</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<p align="center">
|
||||
<img alt="MIT License" src="https://img.shields.io/github/license/webzard-io/sunmao-ui"/>
|
||||
@ -10,46 +12,85 @@
|
||||
</a>
|
||||
<img alt="Github Stars" src="https://badgen.net/github/stars/webzard-io/sunmao-ui" />
|
||||
</p>
|
||||
Sunmao is a front-end low-code framework. Through Sunmao, you can easily encapsulate any front-end UI components into low-code component libraries, so as to build your own low-code UI development platform, making front-end development as tight as Sunmao.
|
||||
|
||||
> 🚧 **Sunmao-UI is heavily under construction!** 🚧 As excited as you may be, we don't recommend this early alpha for production use. Still, give it a try if you want to have some fun and don't mind [logging bugs](https://github.com/webzard-io/sunmao-ui/issues) along the way :)
|
||||
[中文](./docs/zh/README.md)
|
||||
|
||||
Sunmao-UI is a low code front end framework, by which you can easily build you front end app with any UI libray you like! Try online demo [here](https://deploy-preview-179--mystifying-kirch-d00a2f.netlify.app/).
|
||||
## Why Sunmao?
|
||||
|
||||
## 📖 Features
|
||||
### Responsive low-code framework
|
||||
|
||||
* Encapsulate any kind of ui components and reuse them in low code editor
|
||||
* Easily extend component abilities
|
||||
Sunmao chooses a responsive solution that is easy to understand and has excellent performance, making Sunmao intuitive and quick to use.
|
||||
|
||||
## 📁 Directory
|
||||
### Powerful low-code GUI editor
|
||||
|
||||
Sunmao-UI is a monorepo, includes:
|
||||
Sunmao has a built-in GUI editor, which almost includes all the capabilities that a complete low-code editor should have.
|
||||
|
||||
* Core: the type definition of Sunmao-UI.
|
||||
* Runtime: a runtime to render Sunmao-UI application.
|
||||
* Editor: a gui-editor of Sunmao-UI.
|
||||
### Strong extensibility
|
||||
|
||||
## 🖥️ Local development
|
||||
Both the UI component library itself and the low-code editor support custom extensions. Developers can register various components to cover application requirements and continue to use the existing visual design system.
|
||||
|
||||
### Type Safety
|
||||
|
||||
You are in type safety both when developing Sunmao components and when using the Sunmao editor. Sunmao heavily uses Typescript and JSON schema for a great type system.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
Sunmao is a monorepo project that includes the following packages:
|
||||
|
||||
| Name | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------------- |
|
||||
| core | Spec type definition of Sunmao |
|
||||
| runtime | Sunmao's runtime |
|
||||
| editor | Sunmao's GUI editor |
|
||||
| editor-sdk | sdk for Sunmao Editor |
|
||||
| shared | Types and utility functions shared by each package of the Sunmao project |
|
||||
| chakra-ui-lib | [chakra-ui](https://chakra-ui.com/) component library packaged by Sunmao |
|
||||
| arco-lib | [arco-design](https://arco.design/) component library packaged by Sunmao (recommend to use) |
|
||||
|
||||
![depend-graph](./docs/images/dependGraph.png)
|
||||
|
||||
## local development
|
||||
|
||||
### Start
|
||||
|
||||
```sh
|
||||
yarn global add lerna
|
||||
yarn
|
||||
cd packages/editor
|
||||
yarn dev
|
||||
```
|
||||
|
||||
### 🧪 Test
|
||||
### Test
|
||||
|
||||
```shell
|
||||
yarn test:ci
|
||||
```
|
||||
|
||||
### 🔧Build
|
||||
### Build
|
||||
|
||||
```shell
|
||||
yarn prepublish
|
||||
yarn
|
||||
```
|
||||
|
||||
After modifying `runtime` or `core` files, must rebuild them. Otherwise, the place where these two packages are called in the `editor` will not change.
|
||||
## ⚖️ LICENSE
|
||||
> When you run the runtime or editor locally, if you modify the code of other packages, you must rebuild the modified package, otherwise, the runtime and editor will still run the old code.
|
||||
|
||||
MIT © [Open Sauced](LICENSE)
|
||||
## Tutorial
|
||||
|
||||
Sunmao users are divided into two roles, one is a developer and the other is a user.
|
||||
|
||||
The responsibilities of developers are similar to those of common front-end developers. They are responsible for developing UI components and encapsulating common UI components to Sunmao components. Developers need to write code to implement the logic of the component.
|
||||
|
||||
The user's responsibility is to use the Sunmao components encapsulated by developers to build front-end applications in the Sunmao low-code editor. Users do not need front-end knowledge and programming skills. They can complete application construction only through UI interaction.
|
||||
|
||||
We have prepared two tutorials for different roles. The user only needs to read the user's tutorial, but the developer has to read both.
|
||||
|
||||
- [User's Tutorial](./docs/zh/user.md)
|
||||
- [Developer's Tutorial](./docs/zh/developer.md)
|
||||
|
||||
## Online Demo
|
||||
|
||||
[Sunmao Playground](https://sunmao-ui-cloud.vercel.app)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
BIN
docs/images/componentForm.png
Normal file
BIN
docs/images/componentForm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
BIN
docs/images/componentTree.png
Normal file
BIN
docs/images/componentTree.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
docs/images/dependGraph.png
Normal file
BIN
docs/images/dependGraph.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 232 KiB |
BIN
docs/images/eventHandler.png
Normal file
BIN
docs/images/eventHandler.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
BIN
docs/images/logo.png
Normal file
BIN
docs/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
docs/images/modifyStyle.png
Normal file
BIN
docs/images/modifyStyle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 125 KiB |
95
docs/zh/README.md
Normal file
95
docs/zh/README.md
Normal file
@ -0,0 +1,95 @@
|
||||
<div align="center">
|
||||
<img src="../images/logo.png" alt="logo" width="200" />
|
||||
</div>
|
||||
<div align="center">
|
||||
<h1>Sunmao-UI</h1>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<img alt="MIT License" src="https://img.shields.io/github/license/webzard-io/sunmao-ui"/>
|
||||
<a href="https://github.com/webzard-io/sunmao-ui/issues">
|
||||
<img src="https://img.shields.io/github/issues/webzard-io/sunmao-ui" alt="GitHub issues">
|
||||
</a>
|
||||
<img alt="Github Stars" src="https://badgen.net/github/stars/webzard-io/sunmao-ui" />
|
||||
</p>
|
||||
|
||||
Sunmao 是一个前端低代码框架。通过 Sunmao,您可以轻松将各种前端 UI 组件库和自己开发的前端组件,封装成低代码组件库,从而搭建您自己的低代码 UI 开发工具,使前端开发变得如榫卯般严丝合缝。
|
||||
|
||||
## 为什么选择 Sunmao?
|
||||
|
||||
### 响应式的低代码框架
|
||||
|
||||
Sunmao 选择了简单易懂且性能优秀的响应式方案,使得 Sunmao 用起来符合直觉,能够快速上手。
|
||||
|
||||
### 强大的低代码 GUI 编辑器
|
||||
|
||||
Sunmao 内置了低代码工具的 GUI 编辑器,几乎囊括了一个完整的低代码编辑器应该具备的所有能力。
|
||||
|
||||
### 强大的可扩展性
|
||||
|
||||
无论是 UI 组件库本身,还是低代码编辑器,都支持自定义扩展。开发者可以注册各类复杂、适用于特定领域的组件,以此覆盖应用需求以及延续已有的视觉设计体系。
|
||||
|
||||
### 类型安全
|
||||
|
||||
无论是在开发 Sunmao 组件时,还是在使用 Sunmao 编辑器时,你都处于类型安全之中。Sunmao 重度使用 Typescript 与 JSON schema 来实现极佳的类型系统。
|
||||
|
||||
## 目录结构
|
||||
|
||||
Sunmao 是一个 monorepo 项目,其中分别包含下面这些包:
|
||||
|
||||
| 名称 | 说明 |
|
||||
| ------------- | -------------------------------------------------------------------- |
|
||||
| core | Sunmao 的 Spec 类型定义 |
|
||||
| runtime | Sunmao 的运行时 |
|
||||
| editor | Sunmao 的 GUI 编辑器 |
|
||||
| editor-sdk | Sunmao Editor 的 sdk |
|
||||
| shared | Sunmao 项目各个包共用的类型和工具函数 |
|
||||
| chakra-ui-lib | Sunmao 封装的 [chakra-ui](https://chakra-ui.com/) 组件库 |
|
||||
| arco-lib | Sunmao 封装的 [arco-design](https://arco.design/) 组件库(推荐使用) |
|
||||
|
||||
![depend-graph](/Users/tanbowen/develop/sunmao-ui/docs/images/dependGraph.png)
|
||||
|
||||
## 本地开发
|
||||
|
||||
### 启动项目
|
||||
|
||||
```sh
|
||||
yarn
|
||||
cd packages/editor
|
||||
yarn dev
|
||||
```
|
||||
|
||||
### 测试
|
||||
|
||||
```shell
|
||||
yarn test:ci
|
||||
```
|
||||
|
||||
### 构建
|
||||
|
||||
```shell
|
||||
yarn
|
||||
```
|
||||
|
||||
> 当你本地运行 runtime 或 editor 时,如果你修改了别的包的代码,必须重新构建被修改的包,否则 runtime 和 editor 中运行的依旧是旧代码。
|
||||
|
||||
## 教程
|
||||
|
||||
Sunmao 的用户分为两种角色,一种是开发者,一种是使用者。
|
||||
|
||||
开发者的职责和寻常的前端开发者类似,负责开发 UI 组件,并且把普通的 UI 组件封装为 Sunmao 的组件。开发者需要通过写代码来实现组件的逻辑。
|
||||
|
||||
使用者的职责是利用开发者封装好的 Sunmao 组件,在 Sunmao 低代码编辑器中搭建前端应用。使用者不需要前端知识和编程能力,仅通过 UI 交互就可以完成应用搭建。
|
||||
|
||||
为此,我们准备了两份教程,分别面向不同的角色。使用者仅需阅读使用者的教程,但开发者则要先阅读使用者教程,再阅读开发者教程。
|
||||
|
||||
- [使用者的教程](./user.md)
|
||||
- [开发者的教程](./developer.md)
|
||||
|
||||
## 在线 DEMO
|
||||
|
||||
[Sunmao Playground](https://sunmao-ui-cloud.vercel.app)
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
517
docs/zh/component.md
Normal file
517
docs/zh/component.md
Normal file
@ -0,0 +1,517 @@
|
||||
# Component 开发文档
|
||||
|
||||
Component 由两部分构成,一个是 Spec,一个是 Implementation。如果把 Component 想象成一个类的话,Spec 就是类的接口,Implementation 就是类的实现。
|
||||
|
||||
- Spec:用来描述 Component 的元信息、参数、状态、行为的一个数据结构
|
||||
- Implementation:具体负责渲染 HTML 元素的函数。
|
||||
|
||||
## Component 开发教程
|
||||
|
||||
下面我们通过一个 Input Component 的例子,来学习如何开发一个 Component。这个 Input 组件有下面这些能力:
|
||||
|
||||
- 可以配置 placeholder,disabled 等参数
|
||||
- 可以让外界访问当前值
|
||||
- 可以发出 onBlur 等事件
|
||||
- 可以让外界更新自己的值。
|
||||
- 可以插入子组件,如前后缀等等。
|
||||
|
||||
### 编写 Component Spec
|
||||
|
||||
Spec 本质上是一个 JSON,它的作用是描述组件的参数、行为等信息。我们上述的所有能力都将会体现在 Spec 中。
|
||||
|
||||
首先我们来看一下这个 Input Component Spec 示例:
|
||||
|
||||
```javascript
|
||||
{
|
||||
version: "arco/v1",
|
||||
metadata: {
|
||||
name: "input",
|
||||
displayName: "Input",
|
||||
exampleProperties: {
|
||||
placeholder: "Input here",
|
||||
disabled: fasle,
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
properties: Type.Object({
|
||||
placeholder: Type.String(),
|
||||
disabled: Type.Boolean(),
|
||||
}),
|
||||
state: Type.Object({
|
||||
value: Type.String(),
|
||||
}),
|
||||
methods: {
|
||||
updateValue: Type.Object({
|
||||
value: Type.String(),
|
||||
}),
|
||||
},
|
||||
slots: {
|
||||
prefix: {
|
||||
slotProps: Type.Object({}),
|
||||
},
|
||||
suffix: {
|
||||
slotProps: Type.Object({}),
|
||||
},
|
||||
},
|
||||
styleSlots: ["content"],
|
||||
events: ["onBlur"],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
一开始面对这么多字段可能会比较迷茫,下面我们来逐一解释每个字段的含义。
|
||||
|
||||
> 详细的每个参数的类型和说明可以参考后面的 API 参考。
|
||||
|
||||
#### Component Spec Metadata
|
||||
|
||||
`metadata` 是一个 Component 的元信息,包括名称等信息。
|
||||
|
||||
首先我们来看一下 Spec 的完整的类型定义:
|
||||
|
||||
#### Component Spec Properties
|
||||
|
||||
`properties` 描述了 Component 能够接受的参数名称和类型。这里定义了两个参数,`placeholder`和`disabled` ,类型分别是 String 和 Boolean。
|
||||
|
||||
你可能对这种声明类型的方法感到陌生。前文已经说过,Spec 本质是一个 JSON,但 JSON 不像 Typescript 可以声明类型,所以当我们要在 Spec 中声明类型时,我们使用 [JSONSchema](https://json-schema.org/)。JSONSchema 本身也是 JSON,但是可以用来声明一个 JSON 数据结构的类型。
|
||||
|
||||
但手写 JSONSchema 比较困难,所以我们推荐使用 [TypeBox](https://github.com/sinclairzx81/typebox) 库来辅助生成 JSONSchema。示例中的写法就是调用了 TypeBox。
|
||||
|
||||
```
|
||||
properties: Type.Object({
|
||||
placeholder: Type.String(),
|
||||
disabled: Type.Boolean(),
|
||||
})
|
||||
```
|
||||
|
||||
#### Component Spec State
|
||||
|
||||
`state`描述了 Component 暴露的状态。Input Component 只会暴露一个 `value`。定义方式和 `properties` 类似。
|
||||
|
||||
state: Type.Object({
|
||||
value: Type.String(),
|
||||
})
|
||||
|
||||
#### Component Spec Method
|
||||
|
||||
`methods` 描述了 Component 暴露的方法。我们的 Input 打算暴露 `updateValue` 方法,以便让外界更新自己的值。
|
||||
|
||||
在 Spec 中 `updateValue` 这个键所对应的值是 `updateValue`可以接受的参数,同样是用 TypeBox 定义的。
|
||||
|
||||
methods: {
|
||||
updateValue: Type.Object({
|
||||
value: Type.String(),
|
||||
}),
|
||||
}
|
||||
|
||||
#### Component Spec 的其他属性
|
||||
|
||||
`slots `代表 Component 预留的 Slot。每个 Slot 都可以插入子 Component 。其中 `slotProps`代表可以这个插槽会传递给子 Component 的额外的 props,以供子 Component 渲染时使用。
|
||||
|
||||
`styleSlots` 代表 Component 预留的可以插入样式的 Slot。一般每个 Component 都需要预留一个 `content` 的 styleSlot。
|
||||
|
||||
`events `代表 Component 可以发出的事件,以便外界监听。
|
||||
|
||||
slots: {
|
||||
prefix: {
|
||||
slotProps: Type.Object({}),
|
||||
},
|
||||
suffix: {
|
||||
slotProps: Type.Object({}),
|
||||
},
|
||||
},
|
||||
styleSlots: ["content"],
|
||||
events: ["onBlur"],
|
||||
|
||||
#### Component Spec 示例解析
|
||||
|
||||
现在我们再来看一下一开始的示例,对照着解释一遍。
|
||||
|
||||
```javascript
|
||||
const InputSpec = {
|
||||
version: 'arco/v1',
|
||||
metadata: {
|
||||
name: 'input',
|
||||
displayName: 'Input',
|
||||
exampleProperties: {
|
||||
placeholder: 'Input here',
|
||||
disabled: fasle,
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
properties: Type.Object({
|
||||
placeholder: Type.String(),
|
||||
disabled: Type.Boolean(),
|
||||
}),
|
||||
state: Type.Object({
|
||||
value: Type.String(),
|
||||
}),
|
||||
methods: {
|
||||
updateValue: Type.Object({
|
||||
value: Type.String(),
|
||||
}),
|
||||
},
|
||||
slots: {
|
||||
prefix: {
|
||||
slotProps: Type.Object({}),
|
||||
},
|
||||
suffix: {
|
||||
slotProps: Type.Object({}),
|
||||
},
|
||||
},
|
||||
styleSlots: ['content'],
|
||||
events: ['onBlur'],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
这份 Spec 声明了一个 Input Component。它是 arco/v1 组件库的一部分,名字是 input。它的唯一标志符就是 arco/v1/input。
|
||||
|
||||
该 Component 的 properties 是用 TypeBox 声明的。它的 properties 包含两个参数,分别是 placeholder 和 disabled。它还会暴露一个状态 value 给外部访问。
|
||||
|
||||
在行为方面,它有一个 updateValue 方法,可以允许外部更新自己的 value。同时,作为一个 Input,它还会发出 onBlur 事件。
|
||||
|
||||
它还有两个 slot 可以插入子 Component,分别代表前缀和后缀。这两个 slot 没有要额外传递的 property。还有一个 content 的 styleSlot,可以添加自定义样式。
|
||||
|
||||
这就是这个 Input 组件的所有逻辑了,下面我们来看一下如何实现这个 Component 的 Implementation。
|
||||
|
||||
### Component Implementation
|
||||
|
||||
完成了 Component 的 Spec 之后,我们就要开发 Component 的具体实现,我们称之为 Component Implementation。理论上说,一份 Spec 可以对应很多个 Component,就好比一个接口可以对应多个类的实现一样。
|
||||
|
||||
Component Implementation 负责具体的渲染工作。它本质上是一个函数。它的参数比较复杂,我们按照 Input Component 的实际需求逐一介绍。
|
||||
|
||||
> 目前 Component Implementation 必须是一个 React 函数式组件,但以后不一定是一个 React 组件。以后我们计划让 Sunmao 支持用任何技术栈的组件,只要这个函数返回 DOM 元素就可以。
|
||||
|
||||
#### 读取 Component 的参数
|
||||
|
||||
首先,Component Implementation 应该要接受 Spec 中定义的 `properties`,也就是 `placeholder` 和 `disabled`。我们可以从参数中直接获取。然后,我们把这个参数传递给一个 input 的 JSX 元素,并返回。
|
||||
|
||||
```jsx
|
||||
const InputImpl = props => {
|
||||
const { disabled, placeholder } = props;
|
||||
|
||||
return <input disabled={disabled} placeholder={placeholder} />;
|
||||
};
|
||||
```
|
||||
|
||||
就这么简单!其实这已经是一个完整的 Component Implementation,但我们还有很多功能没有实现。
|
||||
|
||||
#### 暴露 Component 的状态
|
||||
|
||||
我们的 Input 将会暴露自己的状态,这就需要用到一个 Sunmao 内置的函数 `mergeState`。这个方法会被自动注入到 Component Implementation 中,可以像读取 `properties` 一样读取,调用方式如下。
|
||||
|
||||
```tsx
|
||||
const InputComponent = props => {
|
||||
const { mergeState } = props;
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
// 当 value 改变时,调用 mergeState 方法。
|
||||
// 每次调用 mergeState 时,最新的 value 值将会合并到 Sunmao 状态树中,以供其他 Component 访问。
|
||||
useEffect(() => {
|
||||
mergeState({
|
||||
value,
|
||||
});
|
||||
}, [mergeState, value]);
|
||||
|
||||
return <input value={value} onChange={newVal => setValue(newVal)} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### 暴露 Component 的方法
|
||||
|
||||
我们的 Input 还会暴露自己的方法 `updateValue`。这也需要用到一个内置的方法 `subscribeMethods`。
|
||||
|
||||
```typescript
|
||||
const InputComponent = props => {
|
||||
const { subscribeMethods } = props;
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
// 当dom元素挂载后,调用 subscribeMethods,注册 updateValue 方法。
|
||||
// 这样外界的 Component 就可以调用 updateValue,来改变 input 的 value了。
|
||||
useEffect(() => {
|
||||
subscribeMethods({
|
||||
updateValue: ({ value: newValue }) => {
|
||||
setValue(newValue);
|
||||
},
|
||||
});
|
||||
}, [subscribeMethods]);
|
||||
|
||||
return <input value={value} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### 发布 Event
|
||||
|
||||
我们的 Input 还会发布 onBlur 事件,来通知其他 Component 自己失焦了。这就需要用到另一个内置参数 `callbackMap`。
|
||||
|
||||
```jsx
|
||||
const InputComponent = props => {
|
||||
const { callbackMap } = props;
|
||||
|
||||
// callbackMap 中已经带有了对应事件的回调函数,在对应的时机直接调用即可。
|
||||
const onBlur = () => {
|
||||
if (callbackMap.onBlur) {
|
||||
callbackMap.onBlur();
|
||||
}
|
||||
};
|
||||
|
||||
return <input onBlur={onBlur} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### 预留 Slot 和 StyleSlot 的位置
|
||||
|
||||
Slot 和 StyleSlot 都是可以插入自定义内容的插槽,它们的位置需要事先预留。它们也同样需要对应的参数,分别是:`slotsElements`和`customStyle`。两个都是 js 对象,通过 slot 和 styleSlot 的名称就可以访问到对应的内容。
|
||||
|
||||
`slotsElements` 的内容一个函数,这个函数接受 `slotProps`,返回 JSX 元素。`slotProps` 是可选的,根据 spec 中的定义来传递。
|
||||
|
||||
`customStyle`是一个 styleSlot 和 CSS 字符串的 map。因为是 CSS 字符串,所以需要经过一定处理才能使用。我们推荐使用 `emotion` 作为 CSS-in-JS 的处理方案。
|
||||
|
||||
```tsx
|
||||
const InputImpl = props => {
|
||||
const { slotsElements, customStyle } = props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{slotsElements.prefix()}
|
||||
<input className={css(customStyle.content)} />
|
||||
{slotsElements.suffix()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
> 其实`customStyle`和`callbackMap`其实都是来自于 Trait 的参数,但目前你并不需要知道这一点,具体可以参考后面的 API 文档。
|
||||
|
||||
#### 暴露 DOM 元素给 Sunmao
|
||||
|
||||
#### elementRef & getElement
|
||||
|
||||
最后还有一步,这一步和 Component 自身的逻辑无关,但是 Sunmao 需要获取到 Componet 运行时的 DOM 元素,才能在 Editor 中获得这个组件。所以这一步需要把 Component 的 DOM 元素传递给 Sunmao。
|
||||
|
||||
Sunmao 提供了两个方法来传递 DOM 元素:`elementRef `和` getElement`。两个方法的作用是一样的,只是适合场景不同,只需选择一个实现即可。
|
||||
|
||||
如果 Component 是用 React 实现的,那么使用 `elementRef` 比较方便,只需要把 `elementRef` 传给 React 组件的 `ref `属性。如果这个方法不行,则只能用通用的 `getElement` 方法来注册组件的 DOM 元素了。
|
||||
|
||||
```typescript
|
||||
const InputComponent = props => {
|
||||
const { getElement } = props;
|
||||
const ref = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ele = ref.current?.dom;
|
||||
if (getElement && ele) {
|
||||
getElement(ele);
|
||||
}
|
||||
}, [getElement, ref]);
|
||||
|
||||
return <input ref={ref} />;
|
||||
};
|
||||
|
||||
// 或者
|
||||
|
||||
const InputComponent = props => {
|
||||
const { elementRef } = props;
|
||||
|
||||
return <input ref={elementRef} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### 完整的 Component Implementation
|
||||
|
||||
最后我们把所有功能结合起来,实现一开始的 Input Component Spec 的所有逻辑。
|
||||
|
||||
```tsx
|
||||
const InputImpl = props => {
|
||||
const {
|
||||
disabled,
|
||||
placeholder,
|
||||
elementRef,
|
||||
slotsElements,
|
||||
customStyle,
|
||||
callbackMap,
|
||||
mergeState,
|
||||
subscribeMethods,
|
||||
} = props;
|
||||
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
mergeState({
|
||||
value,
|
||||
});
|
||||
}, [mergeState, value]);
|
||||
|
||||
useEffect(() => {
|
||||
subscribeMethods({
|
||||
updateValue: newValue => {
|
||||
setValue(newValue);
|
||||
},
|
||||
});
|
||||
}, [subscribeMethods]);
|
||||
|
||||
const onChange = e => {
|
||||
setValue(e.target.value);
|
||||
};
|
||||
|
||||
const onBlur = () => {
|
||||
if (callbackMap.onBlur) {
|
||||
callbackMap.onBlur();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{slotsElements.prefix()}
|
||||
<input
|
||||
ref={elementRef}
|
||||
className={css(customStyle.content)}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
{slotsElements.suffix()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 封装 Spec 和 Implementation
|
||||
|
||||
写完 Component 的 Spec 和 Implementation 以后,离成功只差最后一步,就是把二者封装成 Sunmao runtime 能接受的格式。这一步很简单,只需要调用 `implementRuntimeComponent` 函数即可。
|
||||
|
||||
```javascript
|
||||
import { implementRuntimeComponent } from '@sunmao-ui/runtime';
|
||||
|
||||
const InputComponent = implementRuntimeComponent(InputSpec)(InputImpl);
|
||||
```
|
||||
|
||||
最后,这个组件添加到 lib 中即可,并在 Sunmao 启动时传给 `initSunmaoUI`就大功告成了。
|
||||
|
||||
```javascript
|
||||
const lib: SunmaoLib = {
|
||||
components: [InputComponent],
|
||||
traits: [],
|
||||
modules: [],
|
||||
utilMethods: [],
|
||||
};
|
||||
```
|
||||
|
||||
## Component API 文档
|
||||
|
||||
### Component Spec
|
||||
|
||||
Spec 的第一层字段比较简单明了。
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
| -------- | ------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| version | string | Component 在 Sunmao 中的分类。同一套 Component 的`version`通常是一样的。格式为 "xxx/vx" ,例如"`arco/v1`"。 |
|
||||
| kind | `"Component"` | 固定不变,表示这是一个 Component Spec。 |
|
||||
| metadata | | 详见下文 |
|
||||
| spec | | 详见下文 |
|
||||
|
||||
#### Component Spec 的 Metadata
|
||||
|
||||
Metadata 中包含了 Component 的元信息。
|
||||
|
||||
| 参数名 | 类型 | 备注 |
|
||||
| ----------------- | -------------------- | ------------------------------------------------------------------------------------- |
|
||||
| name | string | Component 的名字。Component 的 `version` 和 `name`共同构成了 Component 的唯一标志符。 |
|
||||
| description | string? | |
|
||||
| displayName | string? | 在 Editor 中的 Component 列表中展示的名字。 |
|
||||
| exampleProperties | Record<string, any> | Component 在 Editor 中被创建时的初始 `properties`。 |
|
||||
| annotations | Record<string, any>? | 可以自定义声明一些字段。 |
|
||||
|
||||
#### Component Spec 其余字段
|
||||
|
||||
定义 `properties` 和 `state` 时,我们使用 [JSONSchema](https://json-schema.org/)。JSONSchema 本身也是 JSON,但是可以用来声明一个 JSON 数据结构的类型。有了类型的帮助,Sunmao 就可以对参数和表达式进行校验和和输入提示。
|
||||
|
||||
| 参数名 | 类型 | 备注 |
|
||||
| ---------- | --------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| properties | JSONSchema | Component 接受的参数。 |
|
||||
| state | JSONSchema | Component 对外暴露的 State。 |
|
||||
| methods | Record<KMethodName, JSONSchema> | Component 对外暴露的 Method。key 是 Method 的名字,value 是 Method 的参数。 |
|
||||
| events | string[] | Component 会发出的 Event。数组元素是 Event 的名字。 |
|
||||
| slots | Record<string, {slotProps: JSONSchema}> | Component 预留的可以插入子 Component 的插槽。 |
|
||||
| styleSlots | string[] | Component 预留的可以添加样式的插槽。 |
|
||||
|
||||
### Component Implementation 参数
|
||||
|
||||
Component Implementation 的参数本质上一个 object,但是其实是由好几个组成部分合并而成的。大致可以分为:
|
||||
|
||||
- Component Spec 中声明的 Properties。这部分完全是 Component 自定义的。
|
||||
- Sunmao Component API。这是 Sunmao 注入到 Component 中的。
|
||||
- Trait 执行结果。这是 Trait 传递给组件的结果。
|
||||
- services。这是 Sunmao 运行时的各个服务实例。
|
||||
|
||||
| 参数名 | 类型 | 备注 | 来源 |
|
||||
| --------------- | -------------------------------------------------------------- | ----------------------------------- | -------- |
|
||||
| component | ComponentSchema | Component 的 Schema | API |
|
||||
| app | ApplicationSchema | 整个 Application 的 Schema | API |
|
||||
| slotsElements | Record<string, (slotProps: any) => ReactElement[]> | 子 Component 列表,详见下文 | API |
|
||||
| mergeState | (partialState: object) => void | 详见下文 | API |
|
||||
| subscribeMethod | (methodsMap: Record<string, (params: object) => void>) => void | 详见下文 | API |
|
||||
| elementRef | React.Ref | 详见下文 | API |
|
||||
| getElement | (ele: HTMLElement) => void | 详见下文 | API |
|
||||
| services | object | Sunmao 的各种服务实例,详见下文 | services |
|
||||
| customStyle | Record<string, string> | 来自于 Trait 的自定义样式,详见下文 | Trait |
|
||||
| callbackMap | Record<string, Function> | 来自于 Trait 的回调函数,详见下文 | Trait |
|
||||
|
||||
#### Services
|
||||
|
||||
Services 是 Sunmao 的各种服务的实例,包括状态管理、事件监听、组件注册等等。这些 Service 都是全局唯一的实例。
|
||||
|
||||
| 参数名 | 类型 | 备注 |
|
||||
| ---------------- | ------------------------ | ---------------------------------------------------------------------------------------------------- |
|
||||
| registry | Registry | Registry 上注册了 Sunmao 所有的 Component、Trait、Module,您可以在其中它们所对应的 Spec 和渲染组件。 |
|
||||
| stateManager | StateManager | StateManager 管理着 Sunmao 的全局状态 Store,而且还具 eval 表达式的功能。 |
|
||||
| globalHandlerMap | GlobalHandlerMap | GlobalHandlerMap 管理着所有 Component 的 Method 实例。 |
|
||||
| apiService | ApiService | ApiService 是全局事件总线。 |
|
||||
| eleMap | Map<string, HTMLElement> | eleMap 存放所有 Component 的 DOM 元素。 |
|
||||
|
||||
> ⚠️ 一般情况下,您不需要使用这些服务。只有在实现一些特殊需求时,才可能会用到它们。
|
||||
|
||||
#### Sunmao Component API
|
||||
|
||||
##### `mergeState`
|
||||
|
||||
Component 可以拥有自己的局部状态,但是如果 Component 把自己的局部状态暴露给 Sunmao 的其他组件,就要通过`mergeState`函数把状态合并到 Sunmao 的全局状态 store 中。
|
||||
|
||||
当`mergeState`被调用时,所有引用了该状态的表达式都会立刻更新,其对应的组件也会立即更新。
|
||||
|
||||
##### `subscribeMethods`
|
||||
|
||||
`subscribeMethods` 的作用就是把组件的行为,以函数的形式注册到 Sunmao 中,以供其他 Component 调用。
|
||||
|
||||
Component 注册的 Method 并没有限制,它可以接受自定义参数,参数类型应该已经 Component Spec 中声明过了。参数在调用时会由 Sunmao 负责传递。
|
||||
|
||||
#### Trait 执行结果
|
||||
|
||||
所有 Trait 的执行结果都会作为参数传递给 Component。这些参数都是按照约定的接口生成的。Trait 和 Component 之间只能通过这个接口进行交互。**Component 必须正确处理下面这些参数**,否则,Component 就不能和别的 Trait 交互。
|
||||
|
||||
##### `customStyle`
|
||||
|
||||
在 Sunmao 中,样式的表现形式是 CSS。customStyle 是一个 styleSlot 和 CSS 的 map。您需要自己决定如何使用 CSS。Sunmao 使用的是 emotion 作为运行时的 CSS-In-JS 方案,您也可以选择自己喜欢的方案。
|
||||
|
||||
**我们约定一个 Component 必须要至少实现一个 `content`的 styleSlot,作为默认的 styleSlot。**
|
||||
|
||||
##### `callbackMap`
|
||||
|
||||
`callbackMap` 是组件对外暴露事件的方式。它是一个 Event 名称和回调函数的 Map。如果有其他 Component 监听了某个 Component 的 Event,那么事件回调函数就会通过 `callbackMap` 传递给该 Component。您需要在 Event 对应的代码的位置调用这个回调函数,这样其他 Component 才能成功监听该 Component 的 Event。
|
||||
|
||||
#### Sunmao Runtime API
|
||||
|
||||
Sunmao 不会限制 Component 内部的逻辑和实现方式,但是有一些接口必须要实现,否则 Component 将无法与 Sunmao 交互。这些接口都会以参数的方式传给 Component Implementation,参数如下:
|
||||
|
||||
##### slotsElements
|
||||
|
||||
slotsElements 是每个 Slot 中的子 Component 的列表。Component 可以声明自己的 Slot,每个 Slot 就是子 Component 插入的位置。
|
||||
|
||||
> 如果 Component 只有一个 slot,我们约定这个 slot 名字是 content。
|
||||
|
||||
##### elementRef & getElement
|
||||
|
||||
这两个 API 的作用是将 Component 渲染的 DOM 元素注册到 Sunmao 中。Sunmao 必须获取到每个 Component 的 DOM 元素才能实现一些功能,比如编辑器中高亮 Component 的功能。别的 Component 和 Trait 也可以利用 Component 的 DOM 元素实现功能。
|
12
docs/zh/developer.md
Normal file
12
docs/zh/developer.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Sunmao 开发者教程
|
||||
|
||||
### 安装 Sunmao
|
||||
|
||||
### [Component 开发](./component.md)
|
||||
|
||||
### [Trait 开发](./trait.md)
|
||||
|
||||
### 进阶(TODO)
|
||||
|
||||
- Property 的 ExpressionOption
|
||||
- 生命周期
|
33
docs/zh/expression.md
Normal file
33
docs/zh/expression.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Expression 表达式设计
|
||||
|
||||
# 支持的写法
|
||||
|
||||
`'{{ value }}'` '100'
|
||||
|
||||
`'{{ value.toUppercase() }}'` 'ABC'
|
||||
|
||||
`'Hello, {{ value }}!'` 'Hello, world'
|
||||
|
||||
`'{{ $listItem.name }} is {{ $listItem.age }} years old'` 'Tom is 10 years old'
|
||||
|
||||
`'{{ $listItem.name }} is in {{ root.listTitle }} list'` 'Tom is in UserList list'
|
||||
|
||||
# 嵌套的表达式
|
||||
|
||||
表达式支持嵌套,这在列表和模块的场景下比较有用。例如:
|
||||
|
||||
`'{{ {{$listItem.value}}Input.value + {{$moduleId}}Fetch.value }}!'`
|
||||
|
||||
解析器将会按照从里到外的顺序逐级`eval` 和拼接字符串。
|
||||
|
||||
# 一些错误的用法
|
||||
|
||||
`{{ [1,2,3] }}string`(结果会是`'[Object Object]'` )。
|
||||
|
||||
# 特殊关键字
|
||||
|
||||
`$i` `$listItem` 和 `$moduleId`是关键字,用在列表和模块中。
|
||||
|
||||
# 错误处理
|
||||
|
||||
当表达式无法解析表达式或者解析过程中报错时,会直接返回表达式本身。
|
114
docs/zh/install.md
Normal file
114
docs/zh/install.md
Normal file
@ -0,0 +1,114 @@
|
||||
# 安装 Sunmao 教程
|
||||
|
||||
Sunmao 是一个低代码框架。您可以使用 Sunmao 的 Cloud 版本,也可以自己部署一个 Sunmao 应用。
|
||||
|
||||
### 安装 Sunmao
|
||||
|
||||
Sunmao 有 runtime 和 editor 两个包。runtime 是用来运行 Sunmao 应用的包,editor 则是用来运行 Sunmao 编辑器的。开发者根据场景的需求,选择对应的包。如果你是初次体验 Sunmao,建议选择 editor。
|
||||
|
||||
不管是选择 runtime 还是 editor,都需要安装 react,因为 Sunmao 是基于 react 运行的。
|
||||
|
||||
#### 安装 Sunmao runtime
|
||||
|
||||
```
|
||||
yarn add @sunmao-ui/runtime
|
||||
yarn add react
|
||||
yarn add react-dom
|
||||
```
|
||||
|
||||
#### 安装 Sunmao editor
|
||||
|
||||
```
|
||||
yarn add @sunmao-ui/editor
|
||||
yarn add react
|
||||
yarn add react-dom
|
||||
```
|
||||
|
||||
### 启动 Sunmao
|
||||
|
||||
runtime 和 editor 的启动方式大致一样,只是方法名称和参数不同。
|
||||
|
||||
#### 启动 Sunmao runtime
|
||||
|
||||
```
|
||||
import { initSunmaoUI } from "@sunmao-ui/runtime";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
const { App } = initSunmaoUIEditor({});
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById("root"));
|
||||
```
|
||||
|
||||
#### 启动 Sunmao editor
|
||||
|
||||
```
|
||||
import { initSunmaoUIEditor } from "@sunmao-ui/editor";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
const { Editor } = initSunmaoUIEditor({});
|
||||
|
||||
ReactDOM.render(<Editor />, document.getElementById("root"));
|
||||
```
|
||||
|
||||
### 引入 Sunmao Lib
|
||||
|
||||
Sunmao Lib 是自定义的 Sunmao Component、Trait、Module、UtilMethod 组成的数据结构,是开发者导入自定义组件库的接口。
|
||||
|
||||
```javascript
|
||||
const lib = {
|
||||
components: [],
|
||||
traits: [],
|
||||
modules: [],
|
||||
utilMethods: [],
|
||||
};
|
||||
```
|
||||
|
||||
导入时,只需要把 lib 传递给 `initSunmaoUI`。
|
||||
|
||||
```javascript
|
||||
const { App } = initSunmaoUIEditor({ libs: [lib] });
|
||||
```
|
||||
|
||||
关于如何封装自定义 Component 等,可以参考 Component 开发文档。
|
||||
|
||||
### API
|
||||
|
||||
Sunmao 的初始化函数会接受一些参数。这些参数都是可选的,作用都是为了实现一些自定义逻辑。
|
||||
|
||||
#### `initSunmaoUI` 的参数
|
||||
|
||||
| 名称 | 类型 | 介绍 |
|
||||
| ------------ | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| dependencies | Record<string, any> | 表达式求值时可以使用的依赖,可以是任何 JS 变量。 |
|
||||
| hooks | {<br /> didMount?: () => void;<br /> didUpdate?: () => void;<br /> didDomUpdate?: () => void;<br />} | Sunmao 应用的生命周期钩子。 |
|
||||
| libs | SunmaoLib[] | 用来加载自定义的 Component,Trait,Module,UtilMethod。 |
|
||||
|
||||
#### `initSunmaoUI` 的返回结果
|
||||
|
||||
| 名称 | 类型 | 介绍 |
|
||||
| -------- | --------------- | ------------------------------- |
|
||||
| App | React Component | 渲染 Sunmao 应用的 React 组件。 |
|
||||
| Registry | Registry | 注册自定义的 Component,Trait。 |
|
||||
|
||||
#### `initSunmaoUIEditor` 的参数
|
||||
|
||||
| 名称 | 类型 | 介绍 |
|
||||
| ------------------ | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| App | React Component | 渲染 Sunmao 应用的 React 组件。 |
|
||||
| widgets | ImplementedWidget[] | 自定义的 editor widget。 |
|
||||
| storageHandler | {<br />onSaveApp?: (app: Application) => void,<br />onSaveModules?: (module: Module[]) => void<br />} | 保存的回调函数,用于编写持久化存储 Application 和 Module。 |
|
||||
| defaultApplication | Application | 默认的 Application Schema。 |
|
||||
| defaultModules | Module[] | 默认的 Module。 |
|
||||
| runtimeProps | Sunmao Runtime Props | 同 `initSunmaoUI` 的参数 |
|
||||
|
||||
#### `initSunmaoUIEditor` 的返回结果
|
||||
|
||||
同`initSunmaoUI` 的返回结果。
|
||||
|
||||
### App 组件的参数
|
||||
|
||||
| 名称 | 类型 | 介绍 |
|
||||
| ------- | ----------- | ---------------------------------- |
|
||||
| options | Application | Sunmao 应用的 Application Schema。 |
|
251
docs/zh/trait.md
Normal file
251
docs/zh/trait.md
Normal file
@ -0,0 +1,251 @@
|
||||
# Trait 开发文档
|
||||
|
||||
在本章节你可以通过一个 Timer Trait 的实例学习到如何实现自定义的 Trait 。
|
||||
|
||||
Trait 的开发和 Component 有诸多相似之处。
|
||||
|
||||
| | Trait | Component |
|
||||
| ------------------------------- | ----- | --------- |
|
||||
| 拥有 Sepc | ✅ | ✅ |
|
||||
| 拥有 properties, state, methods | ✅ | ✅ |
|
||||
| 拥有 Implementation | ✅ | ✅ |
|
||||
| Implementation 是一个函数 | ✅ | ✅ |
|
||||
|
||||
Trait 本质上是一个函数。Trait 的作用是为了增加 Component 的能力。如果还是用面向对象来打比方,Trait 是 Component 的装饰器。比如说,给 Component 添加样式、添加状态等等。
|
||||
|
||||
Trait 的主要思路是,接受一系列参数,执行一些逻辑,返回 TraitResult,从而增强 Component 的能力。显然,这是一个纯函数的逻辑。因此,我们推荐把 Trait 实现为纯函数。即便不是纯函数,实现时也需要考虑到 Trait 被反复执行时的结果,尽量避免意料之外的副作用。
|
||||
|
||||
> ⚠️ 由于 Trait 是个纯函数,所以不能在 Trait 中使用 React hooks。
|
||||
|
||||
由于 Trait 没有自己的 id,所以 Trait 和 Component 共享 id。也就是说,**Trait 暴露的所有 State 和 Method 都会被添加到 Component 上**,访问和调用它们是和 Component 自己实现的无异。
|
||||
|
||||
## Trait 开发教程
|
||||
|
||||
接下来,让我们通过一个例子来学习如何开发一个 Trait。我们要实现一个 Timer Trait,它具有下面的能力:
|
||||
|
||||
- 指定一段时长的定时器,在定时器结束后弹出 alert
|
||||
- 提供清除定时器的功能
|
||||
- 可选择是否在 Component 挂载后立即开启一个定时器
|
||||
- 可获取定时器的状态(`waiting`, `finished`, `stopped`)
|
||||
|
||||
### Trait Spec
|
||||
|
||||
#### Trait Spec 示例
|
||||
|
||||
首先我们来编写一个 Trait Spec,给 Trait 定义一些基本信息,比如版本和名称等:
|
||||
|
||||
```jsx
|
||||
{
|
||||
version: 'custom/v1',
|
||||
metadata: {
|
||||
name: 'timer',
|
||||
description: 'Create a timer to alert, and could be clear.',
|
||||
},
|
||||
spec: {
|
||||
properties: PropsSpec, // 详见下文
|
||||
state: StateSpec, // 详见下文
|
||||
methods, // 详见下文
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### Trait Properties Spec
|
||||
|
||||
接下来就可以开始思考要如何设计我们的 Timer Trait,首先来设计下需要传入 Timer Trait 的参数,也就是 Properties 的 Spec。
|
||||
|
||||
首先需要一个 `time` 参数来控制定时器的时间,和 `content` 参数来展示 alert 的内容。
|
||||
|
||||
另外还需要控制是否要在 Component 挂载后立刻触发,因此还要一个 `immediate` 参数。
|
||||
|
||||
于是就可以得到我们的 Props Spec:
|
||||
|
||||
```jsx
|
||||
const PropsSpec = Type.Object({
|
||||
time: Type.Number(),
|
||||
content: Type.String(),
|
||||
immediate: Type.Boolean(),
|
||||
});
|
||||
```
|
||||
|
||||
> Trait 的 Props Spec 也是通过 TypeBox 定义的。
|
||||
|
||||
#### Trait State Spec
|
||||
|
||||
由于 Timer Trait 还需要暴露状态,所以我们还需要添加 State Spec ,来定义 Timer Trait 暴露的 State 以及其类型。
|
||||
|
||||
```jsx
|
||||
const StateSpec = Type.Object({
|
||||
status: Type.KeyOf(
|
||||
Type.Object({
|
||||
waiting: Type.Boolean(),
|
||||
finished: Type.Boolean(),
|
||||
stopped: Type.Boolean(),
|
||||
})
|
||||
),
|
||||
});
|
||||
```
|
||||
|
||||
#### Trait Methods Spec
|
||||
|
||||
我们还需要提供 Method 给外部进行调用,来开启或者清除定时器,所以还需要对 Method 进行定义。这里我们提供 `start` 和 `stop` 两个 Method。
|
||||
|
||||
```jsx
|
||||
const methods = [
|
||||
{
|
||||
name: 'start',
|
||||
parameters: Type.Object({}),
|
||||
},
|
||||
{
|
||||
name: 'clear',
|
||||
parameters: Type.Object({}),
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### Trait Factory & Implementation
|
||||
|
||||
在定义完成完整的 Trait Spec 之后,我们就可以开始着手实现 Timer Trait 的逻辑了。首先要实现一个 Trait 的工厂函数,其返回真正 Trait 的 Implementation。
|
||||
|
||||
在工厂函数中我们可以声明一些变量来存储数据,比如 Timer Trait 这里就需要创建一个 Map 来记录每个 Component 对应的 Timer ,以便用来清除计时器。
|
||||
|
||||
> 由于 Trait 本质是一个纯函数,无法拥有状态,所以 Trait 需要工厂函数。工厂函数提供了一个闭包,用来给 Trait 存储状态。要注意的是,对于同一种 Trait,所有的 Trait Implementation 实例,共用的是同一个闭包。
|
||||
|
||||
下面我们来看一下完整的实现代码:
|
||||
|
||||
```jsx
|
||||
const TimerTraitFactory = () => {
|
||||
const map = new Map();
|
||||
|
||||
return props => {
|
||||
const {
|
||||
time,
|
||||
content,
|
||||
immediate,
|
||||
services,
|
||||
componentId,
|
||||
subscribeMethods,
|
||||
mergeState,
|
||||
} = props;
|
||||
// 实现 clear method
|
||||
const clear = () => {
|
||||
const timer = map.get(componentId);
|
||||
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
mergeState({
|
||||
status: 'stopped',
|
||||
});
|
||||
map.delete(componentId);
|
||||
}
|
||||
};
|
||||
// 实现 start method
|
||||
const start = () => {
|
||||
clear();
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
// 计时器结束后,弹出 alert
|
||||
alert(content);
|
||||
mergeState({
|
||||
status: 'finished',
|
||||
});
|
||||
}, time || 0);
|
||||
|
||||
mergeState({
|
||||
status: 'waiting',
|
||||
});
|
||||
map.set(componentId, timer);
|
||||
};
|
||||
|
||||
// 注册 Method 到组件中
|
||||
subscribeMethods({
|
||||
start,
|
||||
clear,
|
||||
});
|
||||
|
||||
// 返回 Trait Result
|
||||
// 使用 componentDidMount 和 componentDidUnmount 生命周期来开启和清除计时器
|
||||
return {
|
||||
props: {
|
||||
// 在 Component 挂在后启动计时器
|
||||
componentDidMount: [
|
||||
() => {
|
||||
if (immediate) {
|
||||
start();
|
||||
}
|
||||
},
|
||||
],
|
||||
componentDidUnmount: [
|
||||
() => {
|
||||
clear();
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Create Trait
|
||||
|
||||
最后,通过 `runtime` 提供的 `implementRuntimeTrait` 来生成最终的 Trait 。至此,完整的 Timer Trait 的实现就到这里完成啦。
|
||||
|
||||
```jsx
|
||||
export default implementRuntimeTrait({
|
||||
version: 'custom/v1',
|
||||
metadata: {
|
||||
name: 'timer',
|
||||
description: 'Create a timer to alert, and could be clear.',
|
||||
},
|
||||
spec: {
|
||||
properties: PropsSpec,
|
||||
state: StateSpec,
|
||||
methods,
|
||||
},
|
||||
})(TimerTraitFactory);
|
||||
```
|
||||
|
||||
## Trait API 文档
|
||||
|
||||
Trait Spec 和 Component 基本上是一样的,仅有些许不同。
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
| -------- | --------- | -------------------------------------- |
|
||||
| version | string | |
|
||||
| kind | `"Trait"` | 固定不变,表示这是一个 Trait 的 Spec。 |
|
||||
| metadata | | 详见下文 |
|
||||
| spec | | 详见下文 |
|
||||
|
||||
### Trait Spec 的 Metadata
|
||||
|
||||
Trait Spec 的 metadata 内容比 Component 少一些。
|
||||
|
||||
| 参数名 | 类型 | 备注 |
|
||||
| ----------- | ------------------- | ------------ |
|
||||
| name | string | Trait 的名称 |
|
||||
| annotations | Record<string, any> | |
|
||||
|
||||
### Trait Spec 的 Spec 字段
|
||||
|
||||
| 参数名 | 类型 | 备注 |
|
||||
| ---------- | ------------------------------- | ---- |
|
||||
| properties | JSONSchema | |
|
||||
| state | JSONSchema | |
|
||||
| methods | Record<KMethodName, JSONSchema> | |
|
||||
|
||||
### Trait Implementation 的参数
|
||||
|
||||
Trait Implementation 接受的参数和 Component 几乎完全相同,可以参考 Component 的相关章节。
|
||||
|
||||
### TraitResult
|
||||
|
||||
TraitResult 是 Trait 最重要的部分,它是 Trait 函数的返回结果,将会被直接传给 Component 作为参数。更多具体内容,可以参考 Component Implementation 的参数的相关章节。
|
||||
|
||||
TraitResult 是一个 object,属性如下:
|
||||
|
||||
| 参数名 | 类型 | 是否必须 | 说明 |
|
||||
| ------------------- | ------------------------ | -------- | ---------------------------------------------------------------- |
|
||||
| customStyle | Record<string, string> | 否 | 传递给 Component 的样式 Map。object 的 value 应该是 CSS 字符串。 |
|
||||
| callbackMap | Record<string, Function> | 否 | Component 的回调函数 Map。主要用于 Event Trait。 |
|
||||
| componentDidUnmount | Function[] | 否 | 生命周期钩子。将会在 Component 卸载后执行。 |
|
||||
| componentDidMount | Function[] | 否 | 生命周期钩子。将会在 Component 挂载后执行。 |
|
||||
| componentDidUpdate | Function[] | 否 | 生命周期钩子。将会在 Component 更新后执行。 |
|
113
docs/zh/user.md
Normal file
113
docs/zh/user.md
Normal file
@ -0,0 +1,113 @@
|
||||
# Sunmao 使用者教程
|
||||
|
||||
## 基本概念
|
||||
|
||||
### Application
|
||||
|
||||
Application 是 Sunmao 中最大的概念。一个 Application 就是一个可以运行的 Sunmao 应用。
|
||||
|
||||
### Component
|
||||
|
||||
Component 是 Sunmao 中负责渲染 UI 的最小单位。
|
||||
|
||||
Component 大多是常见的 UI 组件,例如 Button、Input 等等,也可以是一些特制的高度封装的业务逻辑组件。
|
||||
|
||||
我们可以给通过给 Component 传递参数,来定义 Component 的表现、样式和行为。
|
||||
|
||||
### DataSource
|
||||
|
||||
每个应用都少不了数据,DataSource 就是 SunmaoUI 中承载数据的组件。DataSource 可以有多种类型:全局状态、API 请求、LocalStorage……
|
||||
|
||||
它本质上也是 Component,只不过它不会渲染 UI,只负责获取数据。
|
||||
|
||||
### Trait
|
||||
|
||||
Trait 是 Sunmao 的特殊概念。Trait 可以理解为一种特性,当 Trait 被添加到一个 Component 上时,这个 Component 就具备了这种特性。例如,如果给一个 Component 添加 State Trait,那么这个 Component 上就会被添加一个状态。又比如,如果给一个 Component 添加 Style Trait,那么这个 Component 就会被添加上自定义的样式。
|
||||
|
||||
# Sunmao 开发指南
|
||||
|
||||
### 如何布局?
|
||||
|
||||
在 Sunmao 中,我们推荐使用 Stack 来布局。Sunmao 已经内置了一个 Stack Component ,名字叫做 `core/v1/stack`。通过这个组件,您可以调整 Component 的间距、方向、对齐……
|
||||
|
||||
> Stack 布局本质上就是 Flex 布局。
|
||||
|
||||
使用 Stack 布局时,Component 之间的顺序和父子关系十分重要。下面我们就来介绍在 Sunmao 中如何表达 Component 的父子关系。
|
||||
|
||||
#### Slot
|
||||
|
||||
在 Sunmao 中,父子关系是通过 Slot 体现的。
|
||||
|
||||
Slot 是 Component 插入子组件的插槽。每个 Slot 都可以插入无限个子组件。一个 Component 可以拥有多个 Slot,所以 Slot 的数量和名字都是由 Component 自己定义的。通常,我们约定如果一个 Component 如果只有一个 Slot,这个 Slot 默认名叫 `content`。
|
||||
|
||||
在下面这个例子中:
|
||||
|
||||
modal6 拥有两个 Slot,分别是`content` 和`footer` 。两个 Slot 目前都没有 Component,所以呈现为 Empty。
|
||||
|
||||
button5 没有 Slot,所以没有左边的展开的箭头。
|
||||
|
||||
hstack4 有一个 Slot,是`content` 。因为它只有一个 slot,所以显示的时候省略了 slot 名称。
|
||||
|
||||
vstack2 有一个 Slot,其中目前有一个 text3Component。
|
||||
|
||||
![componentTree.png](../images/componentTree.png)
|
||||
|
||||
### 如何给组件配置参数?
|
||||
|
||||
首先,在画布或者左侧栏中选中一个组件,然后右侧栏就会展示组件的参数表单。![截屏2022-05-07 下午5.37.44](../images/componentForm.png)
|
||||
|
||||
如果你需要输入复杂的逻辑或者使用别的 Component 的状态,就需要使用到表达式了。
|
||||
|
||||
#### 表达式
|
||||
|
||||
例如,您想根据一个 Input 的 value 值,来决定是否禁用一个 button 按钮。那么,您需要把 button 的 `disabled` 值设置为`{{input.value !== ''}}`。这就是一个表达式。其中,`input`是您要访问的对象的 ID,必须使用正确的 ID 才能访问到对象。`value`是 Input 暴露的状态。
|
||||
|
||||
表达式是由 `{{ }}` 包围的字符串。这个字符串可以是任何合法的 Javascript 代码。表达式支持嵌套、与普通字符串拼接。
|
||||
|
||||
更多表达式用法,请参考:[Expression 表达式设计](./expression.md)。也可以参考,[Javascript in Retool](https://docs.retool.com/docs/javascript-in-retool)。虽然这是 Retool 的文档,但是这部分内容跟 Sunmao 基本通用。
|
||||
|
||||
> 对于表单中非 input 的控件,你可以通过点击控件旁边的 JS 按钮,把这个空间切换成 input,然后就可以输入表达式了。
|
||||
|
||||
### 如何管理数据?
|
||||
|
||||
一个应用除了需要 UI ,还需要数据。在 Sunmao 中,数据的逻辑一般由 DataSource 来管理。Sunmao 内置了一些常用的 DataSource,例如 State,LocalStorage,API 等等。
|
||||
|
||||
在表达式中,你可以像访问普通 Component 那样访问 DataSource 的值。
|
||||
|
||||
| DataSource | 用途 |
|
||||
| ------------ | -------------------------- |
|
||||
| state | 添加全局状态 |
|
||||
| localStorage | 保存数据到 localStorage 中 |
|
||||
| API | 发送 HTTP 请求 |
|
||||
|
||||
### 如何修改样式?
|
||||
|
||||
修改样式需要在 Component 的表单中,点击 Style 右侧的 "+" 按钮。
|
||||
|
||||
然后选择要添加样式的 Style Slot,也就是需要修改样式的 HTML 元素。Style Slot 的种类是由 Component 开发者决定的。
|
||||
|
||||
最后在黑色区域输入 CSS 代码,样式就会在 Component 上生效了。
|
||||
|
||||
![截屏2022-05-07 下午5.49.24](../images/modifyStyle.png)
|
||||
|
||||
### 如何监听组件的事件?
|
||||
|
||||
每个 Component 都有自己的 Event 和 Method。Event 是这个 Component 会触发的事件,Method 是这个 Component 可以被调用的行为。举个例子,当一个 Button 被点击的时候,打开一个 Dialog。这就需要 Button 能够发出一个`onlick` 的 Event,并且 Dialog 上有一个`openDialog ` 的 Method。
|
||||
|
||||
#### Event Handler
|
||||
|
||||
要想监听一个 Event,则必须在发出 Event 的 Component 上面添加一个 Event Handler。
|
||||
|
||||
Event Handler 有这些要素:
|
||||
|
||||
- 监听的 Event
|
||||
- 调用的 Component 的 id
|
||||
- 调用的 Component 的 Method
|
||||
- 调用 Method 时传递的参数
|
||||
- 其他一些配置
|
||||
|
||||
> 一个 Component 上可以添加多个 Event Handler。
|
||||
|
||||
下面就是一个例子,展示了如何在点击 Button 时,打开 Dialog。
|
||||
|
||||
![Image.png](../images/eventHandler.png)
|
Loading…
Reference in New Issue
Block a user