目标:通过配置一段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.gitee.io/retroblog/retroblog/2022/12/26/如何创建一个通过配置就可以渲染任意组件的载体?/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!