目标:通过配置一段DSL来渲染任意组件
例如:画一个按钮和一个输入框
[{title: ‘我是按钮’, value: ‘开始’, type: ‘Button’},
{title: ‘我是输入框’, value: ‘’, placeholder: ‘请输入’, type: ‘Input’}]
效果图:
首先我们先来到 antd官网,找到 在typescript中使用
按流程先初始化一个 carrier 项目
yarn create create-app carrier --tempalte typescript  引入 antd 库
yarn add antd【点击文字】⛳️查看目录生成姿势⛳️
// 地址:https://github.com/yangshun/tree-node-cli 全局安装 tree-node-cli yarn global add tree-node-cli生成目录命令为:
tree -L 2 -I “node_modules”
carrier
├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.css
│   ├── App.tsx
│   ├── index.css
│   ├── index.tsx
│   ├── logo.svg
│   ├── react-app-env.d.ts
│   ├── reportWebVitals.ts
│   ├── setupTests.ts
├── tsconfig.json
└── yarn.lock我们的目标是创建一个传入配置就可以生成对应的组件载体
在根目录创建 components 将 antd 的所有通用组件再一次
封装,以 C 打头开始写组件,在 components/CButton 文件夹中创建
按钮组件 index.tsx  
components/CButton/index.tsx
import React from 'react';
import {Button} from 'antd';
import CBox from '../_CBox';
type ButtonType = 'primary' | 'ghost' | 'dashed' | 'link' | 'text' | 'default';
interface Props {
  title ?: string
  value ?: string
  type ?: ButtonType
  props ?: any
  onAttr ?: () => void
}
export default class CButton extends React.Component<Props> {
  static defaultProps = {
    type:'default'
  }
 
  render() {
    const {title, type, value, onAttr, props} = this.props
    return <CBox 
      title={title}
      component={
        <Button 
          type={type}
          {...props}
          onClick={onAttr}>
          {value}
        </Button>
      }
    />
  }
  
}PS: 考虑到后期会给每一个通用组件做统一布局,或样式统一处理,把公共
部分抽离出来,用一个组件 CBox 将通用组件包起来
components/_CBox/index.tsx
import '../common.css'
import React from 'react'
interface Props {
    title ?: string
    component?: any
  }
  
  export default class _CBox extends React.Component<Props> {
    render() {
      const {title, component} = this.props
      return <div className='row'>
        {title !== undefined ? <div className='title-box'><span className='title'>{title}</span></div> : ''}
        <div className="component">{component}</div>
      </div>
    }
    
  }PS: 出现一个ts的警告,需要把 .css 放到文件的头部来引入;
直接引入样式文件会报模块找不到,需要在 common.d.tsx 中声明
declare module ‘*.css’;
在 components/CInput 文件夹中创建
输入框组件 index.tsx  
import React from 'react';
import {Input} from 'antd';
import CBox from '../_CBox';
type ButtonType = 'primary' | 'ghost' | 'dashed' | 'link' | 'text' | 'default';
interface Props {
  title ?: string
  value ?: string
  type?: ButtonType
  props?: any
  onAttr?: any
}
export default class CInput extends React.Component<Props> {
  static defaultProps = {
    type:'default'
  }
  render() {
    const {title, type, value, onAttr, props} = this.props
    return <CBox 
      title={title}
      component={<Input 
        type={type}
        value={value}
        {...props}
        onChange={onAttr}>
      </Input>}
    />
    
  }
  
}等等…(✈️所有组件全部都需封装✈️)
创建一个载体组件来将装载这个通用组件
commons/carrier/index.tsx
import React from 'react';
import CButton from '../components/CButton'
import CInput from '../components/CInput'
interface conf {
    title ?: string | undefined
    value ?: any
    type ?: any
    propsType ?: any
    onAttr?: any
}
interface Props {
    pageData ?: Array<conf>
    onAttr?: () => void
  }
export default class Carrier extends React.Component<Props> {
    state = {
        pageData : this.props.pageData
    }
    render() {
      const {pageData} = this.state
      const onAttrParent = (target:any, e:any, i:number, onAttr: any)=>{
        const {value} = e.target
        target.state.pageData[i].value = value
        this.setState({
            pageData
        })
        onAttr && onAttr(e)
      }
      return <div className='components'>
        {
            pageData && pageData.map((v, i) => {
                const {title, value, type, propsType, onAttr, ...props} = v
                if(['Button'].includes(type)) return <CButton title={title} value={value} type={propsType} onAttr={onAttr} props={props} />
                if(['Input'].includes(type)) return <CInput title={title} value={value} type={propsType} onAttr={(e:any)=>onAttrParent(this, e, i, onAttr)} props={props} />
            })
        }
      </div>
    }
    
  }PS: 输入框内容改变时,对应的数据需实时更新,声明 onAttrParent 方法来统一处理组件数据的统一动作,
组件的默认行为为 onAttr 来统一处理,只要组件的数据有变更即触发此方法通知 carrier 载体数据有变动
载体组件封装完,我们来写一个使用方法的demo
例如:
demo.tsx
import React from 'react';
import Carrier from './ commons/carrier'
export default class Demo extends React.Component {
    state = {
      pageData: [
        {attr: 'btn', title: '我是按钮', value: '开始', type: 'Button', propsType: 'primary', size: 'default'},
        {attr: 'ipt', title: '我是输入框', value: '', type: 'Input', placeholder: '请输入', size: 'default'},
      ]
    }
    render() {
      const {pageData} = this.state
      return <Carrier 
        pageData={pageData}
      />
    }
    
  }考虑到配置会实时变更,我们将配置放到 state 中的 pageData 中,这样即可实时来检测数据,后期只要专注写
业务逻辑即可,真正做到了数据驱动视图 🎉
雏形的目录结构为:
carrier
├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├──  commons
│   │   └── carrier.tsx
│   ├── App.css
│   ├── App.test.tsx
│   ├── App.tsx
│   ├── components
│   │   ├── CButton
│   │   │   └── index.tsx
│   │   ├── CInput
│   │   │   └── index.tsx
│   │   ├── _CBox
│   │   │   └── index.tsx
│   │   ├── common.css
│   │   └── common.d.tsx
│   ├── demo.tsx
│   ├── index.css
│   ├── index.tsx
│   ├── logo.svg
│   ├── react-app-env.d.ts
│   ├── reportWebVitals.ts
│   └── setupTests.ts
├── tsconfig.json
└── yarn.lock未完待续…
组件间联动???
载体如何统一校验???
如何描绘一个表格???
表单内嵌在表格单元内???
等等问题!!!  
- 本文作者: MrRetro博客
- 本文链接: http://mrretro.github.io/2022/12/26/如何创建一个通过配置就可以渲染任意组件的载体?/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!

 
                    