跳转到内容

自定义控件与容器相关实践

自定义控件

普通控件

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} />
)
})

以上就是一个非常简单的写入控件的实现,其中实现过程如下

  1. 首先通过forwordRef api向外部暴露当前控件的ref
  2. 因为是写入控件,所以必备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 } = Select
const { Item } = Form
const tempArray = [1,2,3]
interface {
help: string, //提示信息
}
interface IProps extends IWidgetProps {
form: any
}
@observer
class 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完成

自定义容器

自定义流程/步骤

  1. 继承任意一个数据容器类型
  2. 按照容器的展示需求处理fields、actions
  3. 如果默认方法不能完全满足,则覆盖类变量/方法,完成相关内容的自定义处理
  4. 发布相关内容

自定义容器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.Option
const { TextArea } = Input
const 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>
)
}
}