自定义控件与容器相关实践
自定义控件
普通控件
import { Checkbox as NusiCheckbox } from '@terminus/nusi'import React from 'react'
export const Checkbox = React.forwardRef((props, ref) => { const { value: checked, onChange: onChangeEvent } = props // 监听当前控件的值变化,并抛出当前value值 const onChange = (e: any) => { const value: boolean = e.target.checked || false // 抛出当前的value值 onChangeEvent(value) } const otherProps = { ref, // 为了向下传递当前控件的节点 checked, // 当前控件value值 onChange // 传递当前控件的valu值 在当前的写入控件类型必备 }
return( <NusiCheckbox {...otherProps} /> )})以上就是一个非常简单的写入控件的实现,其中实现过程如下
- 首先通过forwordRef api向外部暴露当前控件的ref
- 因为是写入控件,所以必备onChange监听事件,来监听当前value值变化并抛出当前控件的value值
示例-联动组件
import React from 'react'import { Input } from '@terminus/nusi'import { isEqual } from 'lodash'import './index.scss'
export default class Demo extends React.Component { // 旧的省值 public oldProvince: any = null
public getSnapshotBeforeUpdate(prevProps: any) { // 比较省值是否发生变化 if (!isEqual(this.oldProvince, this.props.context.province)) // 发生变化,抛出最新的province值 this.props.onChange(this.props.context.province) // 缓存旧province,做下次更新比对 this.oldProvince = this.props.context.province return true } return null }
render() { return ( // 渲染当前上下文city值 <Input value={this.props.context.city}/> ) }}以上就是通过props.context来处理字段渲染判断,以当前案例说明, 判断当前省是否发生了变化,如果发生变化则通知并获取最新的省的value值,来更新对应的city值,保存到全量数据context中,以此做联动效果
示例-简单字段校验
import React from 'react';import { Input } from '@terminus/nusi'import { IField } from '@terminus/nusi-engine'
// props 类型定义interface IProps<T=any> { id: string value: T field: IField context: any size: 'large' | 'small' | 'default' onChange: (value: T)=> void _registerValidator: (...args: any[])=> void}
class Widget extends React.Component<IProps<string>>{ // 在 componentDidMount 声明周期阶段去校验字段 public componentDidMount = ()=>{ const { _registerValidator } = this.props; _registerValidator((callback)=>{ // 校验失败信息 callback('这里是报错') }, { helper: '哈哈哈哈哈哈' }) } // 监听当前value值的事件 public handleChange = (value)=>{ const { context, id, onChange } = this.props; // 传递当前控件最新的value值 onChange(value) // 或 context._setValues({ [id]: value }); } // 渲染视图 render(){ const { size, value } = this.props return <Input size={size} value={value} onChange={(e)=>this.handleChange(e.target.value)} /> }}
export default Widget;注意
- _registerValidator 第一个参数 为回调函数,onCkeck: ( callback: (err)=>void )=>void。
- 当callback 传递的为 string 为校验失败。
- 当callback 传递为 null 或者不传递,为校验成功。
示例-复杂表单验证&多字段校验&JSON 字段控件
import React from 'react'import { observable, toJS, computed } from 'mobx'import { observer } from 'mobx-react'import { remove, isArray, keyBy } from 'lodash'import { Select, Form, Icon } from '@terminus/nusi'import { IWidgetProps } from '@terminus/nusi-engine'import { formItemLayout } from '../../../config'import './index.scss'
const { Option } = Selectconst { Item } = Form
const tempArray = [1,2,3]
interface { help: string, //提示信息}
interface IProps extends IWidgetProps { form: any}
@observerclass Demo extends React.Component<IProps> { @observable groupedOtherAttributes: any[] = []
componentDidMount() { // 注册当前的校验信息 this.props._registerValidator(this.onCheck, { help: '' }) new Promise((resolve) => { // mock异步 mock当前的field JSON 信息 渲染当前视图 setTimeout(() => { resolve([ { key: '可自定义数字', name: '可自定义数字',type: 'sku' }, { key: '自定义时间', name: '自定义时间',type: 'sku' }, { key: '草', name: '草',type: 'sku' }, { key: 'dddda', name: 'dddda',type: 'DEFAULT' }, { key: 'test626属性', name: 'test626属性',type: 'DEFAULT' }, { key: 'sadff', name: 'sadff',type: 'DEFAULT' }, { key: '989', name: '989',type: 'DEFAULT' }, ]) }, 300); }).then((groupedOtherAttributes: any[]) => { this.groupedOtherAttributes = groupedOtherAttributes // 抛出最新的groupedOtherAttributes值 this.props.onChange(groupedOtherAttributes) }) }
@computed get groupedSkuAttributesMap() { let groupedSkuAttributes = this.props.context.name || [] if (!isArray(groupedSkuAttributes)) { groupedSkuAttributes = [] } return keyBy(groupedSkuAttributes, 'key') } // 移除操作 removeSku = (sku: string) => { const groupedOtherAttributes = toJS(this.groupedOtherAttributes) remove(groupedOtherAttributes, ({ key }) => sku === key) this.groupedOtherAttributes = groupedOtherAttributes // 抛出最新的groupedOtherAttributes值 this.props.onChange(groupedOtherAttributes) }
// 触发当前校验,校验不通过callback(err) // err 不为空 且为string类型则被捕获为error, 通过callback回调向外抛出 // err 为 null 或者不传递,为校验成功。 onCheck = (callback) => { this.props.form.validateFields((err: any, values: any) => { if (err) { callback(err) return } callback() }) } // 渲染控件内容 render() { return ( <> {this.groupedOtherAttributes.map(({ key, name, type }) => ( <Item key={key} label={name} {...formItemLayout}> {this.props.form.getFieldDecorator(key, { rules: [{ required: true, message: '请选择' }], })( <Select style={{ width: 200 }} disabled={!!this.groupedSkuAttributesMap[key]}> {tempArray.map((i) => { return <Option value={`${key}${i}`} key={`${key}${i}`}>{`${name}${i}`}</Option> })} </Select> )} {type === 'sku' ? <span>销售属性<Icon type='delete' onClick={() => this.removeSku(key)}/></span> : 'xxx'} </Item> ))} </> ) }}
export default Form.create()(Demo)注意
- Form容器下不能的子表单不能再使用 限制参考
- 校验借助this.props._registerValidator完成
自定义容器
自定义流程/步骤
- 继承任意一个数据容器类型
- 按照容器的展示需求处理fields、actions
- 如果默认方法不能完全满足,则覆盖类变量/方法,完成相关内容的自定义处理
- 发布相关内容
自定义容器demo
import React from 'react'import { set, toJS } from 'mobx'import { ItemContainer } from '@terminus/nusi-engine'import { Icon, Select, Menu, Dropdown, Modal, Form, Input, InputNumber, Switch, Table, Button, Upload, RichTextEditor } from '@terminus/nusi'import './index.scss'interface IProps { }const Option = Select.Optionconst { TextArea } = Inputconst detailType: any = { pcDetail: 'PC详情信息', wapDetail: '移动端详情信息'}// 继承自 单条数据数据容器父类// 继承任意一个数据容器类型export default class ItemInfo extends ItemContainer<any> { // 自定义状态 state = { itemInfoList: [{ title: '', content: '' }] } // 覆盖ItemContainer的fetchDataEnd数据请求方法 protected fetchDataEnd(result: any, actionConfig: any) { const res = result.data const list = res['spuDetail'] && res['spuDetail'][this.props.type] ? JSON.parse(res['spuDetail'][this.props.type]) : [{ title: '', content: '' }] set(this.data, 'spuDetail', { ...res['spuDetail'], }) this.reOrganizeData(list) } // 做相应的数据赋值 private reOrganizeData = (list: any) => { const { spuDetail = {} } = toJS(this.data) as any set(this.data, 'spuDetail', { ...spuDetail, [this.props.type]: JSON.stringify(list) }) // 设置最新的itemInfoList的值,做渲染操作 this.setState({ itemInfoList: list }) } // 监听数据变更, 获取当前最新的value值,并赋值和相应的渲染的操作 private handleChange = (value, index) => { const { itemInfoList } = this.state const item = itemInfoList[index] itemInfoList.splice(index, 1) if (value - 1 >= index) { itemInfoList.splice(value * 1, 1, item) } else { itemInfoList.splice(value - 1, 0, item) } // this.setState({ // itemInfoList // }) // 做相应的赋值操作,更新容器 this.reOrganizeData(itemInfoList) } // 监听标题值变化,并做赋值与渲染操作 private inputChange = (e, index) => { const { itemInfoList } = this.state itemInfoList[index]['title'] = e.target.value // this.setState({ // itemInfoList // }) this.reOrganizeData(itemInfoList) } // 监听area值变换,并做赋值和渲染操作 private textAreaChange = (e, index) => { const { itemInfoList } = this.state itemInfoList[index]['content'] = e.target.value // this.setState({ // itemInfoList // }) this.reOrganizeData(itemInfoList) } // 获取Select的options数据 private renderOption = (arr) => { return arr.map((item, index) => ({ label: index + 1, value: index + 1, })) } // 添加操作 private addItemInfo = () => { const { itemInfoList } = this.state itemInfoList.push({ title: '', content: '' }) // this.setState({ // itemInfoList: itemInfoList // }) this.reOrganizeData(itemInfoList) } // 容器内容渲染 render() { const { type } = this.props const { itemInfoList } = this.state console.log(toJS(this.data)) return ( <div> <div className="itemInfo-list-wrap"> <div className="itemInfo-header"> <div className="itemInfo-title">{detailType[type]}</div> <Icon className="itemInfo-title-add" onClick={this.addItemInfo} type="plus" /> </div> <div className="itemInfo-list"> { itemInfoList.map((item, index) => { return (<div key={index} className="itemInfo-item"> <div className="itemInfo-item-header"> <div className="header-title-wrap"> <div className="header-title">列表标题</div> <Input placeholder='请输入' value={item.title} onChange={(e) => this.inputChange(e, index)} /> </div> <div className="header-sort-wrap"> <div className="header-title">排序</div> <Select value={index + 1} options={this.renderOption(itemInfoList)} onChange={(e) => this.handleChange(e, index)} style={{ width: '340px' }} /> </div> </div> <div className="itemInfo-item-content"> <TextArea rows={4} value={item.content} placeholder='无长度限制' onChange={(e) => this.textAreaChange(e, index)} /> </div> </div>) }) }
</div> </div> </div> ) }}