title: 工作原理

实现自定义类型需要了解 amis 的工作原理。

工作原理

amis 的渲染过程是将 json 转成对应的 React 组件。先通过 json 的 type 找到对应的 Component,然后把其他属性作为 props 传递过去完成渲染。

拿一个表单页面来说,如果用 React 组件开发一般长这样。

  1. <Page title="页面标题" subTitle="副标题">
  2. <Form title="用户登录">
  3. <InputText name="username" label="用户名" />
  4. </Form>
  5. </Page>

把以上配置方式换成 amis JSON, 则是:

  1. {
  2. "type": "page",
  3. "title": "页面标题",
  4. "subTitle": "副标题",
  5. "body": {
  6. "type": "form",
  7. "title": "用户登录",
  8. "body": [
  9. {
  10. "type": "input-text",
  11. "name": "username",
  12. "label": "用户名"
  13. }
  14. ]
  15. }
  16. }

其实只需要把 json 节点,根据 type 信息自动转成 React Component 即可。

原来组件注册是根据节点 path 注册,可以类型相同在不同的上下文中用不同的渲染器去渲染,后来发现这样反而增加了使用成本 所以新版本直接通过 type 类型来注册组件,跟节点所在上下文无关。

Page 组件的示例代码

  1. import * as React from 'react';
  2. import {Renderer} from 'amis-core';
  3. @Renderer({
  4. type: 'page'
  5. // ... 其他信息隐藏了
  6. })
  7. export class PageRenderer extends React.Component {
  8. // ... 其他信息隐藏了
  9. render() {
  10. const {
  11. title,
  12. body,
  13. render // 用来渲染孩子节点,如果当前是叶子节点则可以忽略。
  14. } = this.props;
  15. return (
  16. <div className="page">
  17. <h1>{title}</h1>
  18. <div className="body-container">
  19. {render('body', body) /*渲染孩子节点*/}
  20. </div>
  21. </div>
  22. );
  23. }
  24. }
  25. // 如果不支持 Decorators 语法也可以使用如下写法
  26. export Renderer({
  27. type: 'page'
  28. })(class PageRenderer extends React.Component {
  29. render() {
  30. // ...同上
  31. }
  32. })

Form 组件的示例代码

  1. @Renderer({
  2. type: 'form'
  3. // ... 其他信息隐藏了
  4. })
  5. export class FormRenderer extends React.Component {
  6. // ... 其他信息隐藏了
  7. render() {
  8. const {
  9. title,
  10. body,
  11. render // 用来渲染孩子节点,如果当前是叶子节点则可以忽略。
  12. } = this.props;
  13. return (
  14. <form className="form">
  15. {body.map((control, index) => (
  16. <div className="form-item" key={index}>
  17. {render(`${index}/control`, control)}
  18. </div>
  19. ))}
  20. </form>
  21. );
  22. }
  23. }

Text 组件的示例代码

  1. @Renderer({
  2. type: 'input-text'
  3. // ... 其他信息隐藏了
  4. })
  5. export class FormItemTextRenderer extends React.Component {
  6. // ... 其他信息隐藏了
  7. render() {
  8. const {
  9. label,
  10. name,
  11. onChange
  12. } = this.props;
  13. return (
  14. <div className="form-group">
  15. <label>{label}<label>
  16. <input type="text" onChange={(e) => onChange(e.currentTarget.value)} />
  17. </div>
  18. );
  19. }
  20. }

那么渲染过程就是根据节点 type 信息,跟组件池中的找到对应的组件实现,如果命中,则把当前节点转给对应组件渲染,节点中其他属性将作为目标组件的 props。需要注意的是,如果是容器组件,比如以上例子中的 page 组件,从 props 中拿到的 body 是一个子节点,由于节点类型是不固定,由使用者决定,所以不能直接完成渲染,所以交给属性中下发的 render 方法去完成渲染,{render('body', body)},他的工作就是拿子节点的 type 信息去组件池里面找到对应的渲染器,然后交给对应组件去完成渲染。