diff --git a/src/components/Checkbox.tsx b/src/components/Checkbox.tsx index e9d62061..f53eeea3 100644 --- a/src/components/Checkbox.tsx +++ b/src/components/Checkbox.tsx @@ -43,7 +43,10 @@ interface CheckboxProps { } export class Checkbox extends React.Component { - static defaultProps = { + static defaultProps: Pick< + CheckboxProps, + 'trueValue' | 'falseValue' | 'type' + > = { trueValue: true, falseValue: false, type: 'checkbox' diff --git a/src/renderers/Form/Checkboxes.tsx b/src/renderers/Form/Checkboxes.tsx index e8d896b8..71970ae6 100644 --- a/src/renderers/Form/Checkboxes.tsx +++ b/src/renderers/Form/Checkboxes.tsx @@ -6,7 +6,6 @@ import chunk = require('lodash/chunk'); export interface CheckboxesProps extends OptionsControlProps { placeholder?: any; - disabled?: boolean; itemClassName?: string; columnsCount?: number; labelClassName?: string; diff --git a/src/renderers/Form/Combo.tsx b/src/renderers/Form/Combo.tsx index 84e60f89..bc301322 100644 --- a/src/renderers/Form/Combo.tsx +++ b/src/renderers/Form/Combo.tsx @@ -107,6 +107,7 @@ export default class ComboControl extends React.Component { ]; subForms: Array = []; + subFormDefaultValues: Array<{index: number; values: any}> = []; keys: Array = []; dragTip?: HTMLElement; sortable?: Sortable; @@ -326,19 +327,31 @@ export default class ComboControl extends React.Component { return; } - let value = this.getValueAsArray(); - const newValue = flat ? values.flat : {...values}; + this.subFormDefaultValues.push({ + index, + values + }); - if (!isObjectShallowModified(value[index], newValue)) { + if (this.subFormDefaultValues.length !== this.subForms.length) { return; } - value[index] = flat ? values.flat : {...values}; + let value = this.getValueAsArray(); + this.subFormDefaultValues.forEach(({index, values}) => { + const newValue = flat ? values.flat : {...values}; + + if (!isObjectShallowModified(value[index], newValue)) { + return; + } + + value[index] = flat ? values.flat : {...values}; + }); if (flat && joinValues) { value = value.join(delimiter || ','); } - this.props.onChange(value); + + this.props.setPrinstineValue(value); } handleSingleFormInit(values: any) { diff --git a/src/renderers/Form/Control.tsx b/src/renderers/Form/Control.tsx index 97a8f842..fd384b4c 100644 --- a/src/renderers/Form/Control.tsx +++ b/src/renderers/Form/Control.tsx @@ -8,6 +8,7 @@ import {anyChanged, promisify, isObject, getVariable} from '../../utils/helper'; import {Schema} from '../../types'; import {IIRendererStore} from '../../store'; import {ScopedContext, IScopedContext} from '../../Scoped'; +import {reaction} from 'mobx'; export interface ControlProps extends RendererProps { control: { @@ -35,15 +36,25 @@ export interface ControlProps extends RendererProps { removeHook: (fn: () => any) => void; } -export default class FormControl extends React.Component { +interface ControlState { + value: any; +} + +export default class FormControl extends React.PureComponent< + ControlProps, + ControlState +> { public model: IFormItemStore | undefined; control: any; hook?: () => any; hook2?: () => any; + reaction?: () => void; - static defaultProps: Partial = {}; + static defaultProps = {}; lazyValidate: Function; + lazyEmitChange: (value: any, submitOnChange: boolean) => void; + state = {value: this.props.control.value}; componentWillMount() { const { formStore: form, @@ -76,12 +87,16 @@ export default class FormControl extends React.Component { trailing: true, leading: false }); + this.lazyEmitChange = debouce(this.emitChange.bind(this), 250, { + trailing: true, + leading: false + }); if (!name) { return; } - this.model = form.registryItem(name, { + const model = (this.model = form.registryItem(name, { id, type, required, @@ -95,7 +110,7 @@ export default class FormControl extends React.Component { labelField, joinValues, extractValue - }); + })); if ( this.model.unique && @@ -105,6 +120,15 @@ export default class FormControl extends React.Component { const combo = form.parentStore as IComboStore; combo.bindUniuqueItem(this.model); } + + // 同步 value + this.setState({ + value: model.value + }); + this.reaction = reaction( + () => model.value, + value => this.setState({value}) + ); } componentDidMount() { @@ -118,7 +142,7 @@ export default class FormControl extends React.Component { if (name && form !== store) { const value = getVariable(store.data, name); if (typeof value !== 'undefined' && value !== this.getValue()) { - this.handleChange(value); + this.emitChange(value, false); } } @@ -151,7 +175,7 @@ export default class FormControl extends React.Component { this.model && this.disposeModel(); // name 是后面才有的,比如编辑模式下就会出现。 - this.model = form.registryItem(nextProps.control.name, { + const model = (this.model = form.registryItem(nextProps.control.name, { id: nextProps.control.id, type: nextProps.control.type, required: nextProps.control.required, @@ -165,8 +189,16 @@ export default class FormControl extends React.Component { joinValues: nextProps.control.joinValues, extractValue: nextProps.control.extractValue, messages: nextProps.control.validationErrors - }); + })); // this.forceUpdate(); + this.setState({ + value: model.value + }); + this.reaction && this.reaction(); + this.reaction = reaction( + () => model.value, + value => this.setState({value}) + ); } if ( @@ -227,7 +259,7 @@ export default class FormControl extends React.Component { (value = getVariable(data as any, name)) !== getVariable(prevProps.data, name) ) { - this.handleChange(value); + this.emitChange(value, false); } } @@ -235,6 +267,9 @@ export default class FormControl extends React.Component { this.hook && this.props.removeHook(this.hook); this.hook2 && this.props.removeHook(this.hook2); this.disposeModel(); + (this.lazyValidate as any).cancel(); + (this.lazyEmitChange as any).cancel(); + this.reaction && this.reaction(); } disposeModel() { @@ -305,18 +340,12 @@ export default class FormControl extends React.Component { handleChange( value: any, - submitOnChange: boolean = this.props.control.submitOnChange + submitOnChange: boolean = this.props.control.submitOnChange, + changeImmediately: boolean = false ) { const { - formStore: form, onChange, - control: { - validateOnChange, - name, - pipeOut, - onChange: onFormItemChange, - type - } + control: {type} } = this.props; // todo 以后想办法不要強耦合类型。 @@ -324,6 +353,31 @@ export default class FormControl extends React.Component { onChange && onChange(...(arguments as any)); return; } + + this.setState( + { + value + }, + () => + changeImmediately + ? this.emitChange(value, submitOnChange) + : this.lazyEmitChange(value, submitOnChange) + ); + } + + emitChange( + value: any, + submitOnChange: boolean = this.props.control.submitOnChange + ) { + const { + formStore: form, + onChange, + control: {validateOnChange, name, pipeOut, onChange: onFormItemChange} + } = this.props; + if (!this.model) { + return; + } + const oldValue = this.model.value; if (pipeOut) { @@ -411,11 +465,8 @@ export default class FormControl extends React.Component { } getValue() { - const {control, formStore: form} = this.props; - - const model = this.model; - // let value:any = model ? (typeof model.value === 'undefined' ? '' : model.value) : (control.value || ''); - let value: any = model ? model.value : control.value; + const {formStore: form, control} = this.props; + let value: any = this.state.value; if (control.pipeIn) { value = control.pipeIn(value, form.data); diff --git a/src/renderers/Form/Item.tsx b/src/renderers/Form/Item.tsx index 4f956394..e1edb690 100644 --- a/src/renderers/Form/Item.tsx +++ b/src/renderers/Form/Item.tsx @@ -664,6 +664,63 @@ export class FormItemWrap extends React.Component { } } +// 白名单形式,只有这些属性发生变化,才会往下更新。 +// 除非配置 strictMode +export const detectProps = [ + // 'formPristine', // 理论来说,不需要,因为 formPristine 更新到时候 value 肯定也会更新。 + 'addable', + 'addButtonClassName', + 'addButtonText', + 'addOn', + 'btnClassName', + 'btnLabel', + 'btnDisabled', + 'className', + 'clearable', + 'columns', + 'columnsCount', + 'controls', + 'desc', + 'description', + 'disabled', + 'draggable', + 'editable', + 'editButtonClassName', + 'formHorizontal', + 'formMode', + 'hideRoot', + 'horizontal', + 'icon', + 'inline', + 'inputClassName', + 'label', + 'labelClassName', + 'labelField', + 'language', + 'level', + 'max', + 'maxRows', + 'min', + 'minRows', + 'multiLine', + 'multiple', + 'option', + 'placeholder', + 'removable', + 'required', + 'remark', + 'hint', + 'rows', + 'searchable', + 'showCompressOptions', + 'size', + 'step', + 'showInput', + 'unit', + 'value', + 'diffValue' +]; + export function registerFormItem(config: FormItemConfig): RendererConfig { let Control = config.component; @@ -700,7 +757,6 @@ export function registerFormItem(config: FormItemConfig): RendererConfig { renderDescription: config.renderDescription, sizeMutable: config.sizeMutable, wrap: config.wrap, - strictMode: config.strictMode, ...Control.defaultProps }; static propsList: any = [ @@ -709,6 +765,7 @@ export function registerFormItem(config: FormItemConfig): RendererConfig { 'onChange', 'setPrinstineValue', 'readOnly', + 'strictMode', ...((Control as any).propsList || []) ]; @@ -736,71 +793,12 @@ export function registerFormItem(config: FormItemConfig): RendererConfig { } shouldComponentUpdate(nextProps: FormControlProps) { - if (nextProps.strictMode === false) { + if (nextProps.strictMode === false || config.strictMode === false) { return true; } // 把可能会影响视图的白名单弄出来,减少重新渲染次数。 - if ( - anyChanged( - [ - 'formPristine', - 'addable', - 'addButtonClassName', - 'addButtonText', - 'addOn', - 'btnClassName', - 'btnLabel', - 'btnDisabled', - 'className', - 'clearable', - 'columns', - 'columnsCount', - 'controls', - 'desc', - 'description', - 'disabled', - 'draggable', - 'editable', - 'editButtonClassName', - 'formHorizontal', - 'formMode', - 'hideRoot', - 'horizontal', - 'icon', - 'inline', - 'inputClassName', - 'label', - 'labelClassName', - 'labelField', - 'language', - 'level', - 'max', - 'maxRows', - 'min', - 'minRows', - 'multiLine', - 'multiple', - 'option', - 'placeholder', - 'removable', - 'required', - 'remark', - 'hint', - 'rows', - 'searchable', - 'showCompressOptions', - 'size', - 'step', - 'showInput', - 'unit', - 'value', - 'diffValue' - ], - this.props, - nextProps - ) - ) { + if (anyChanged(detectProps, this.props, nextProps)) { return true; } diff --git a/src/renderers/Form/Options.tsx b/src/renderers/Form/Options.tsx index 696262db..cf21cdcc 100644 --- a/src/renderers/Form/Options.tsx +++ b/src/renderers/Form/Options.tsx @@ -14,7 +14,12 @@ import { getTree } from '../../utils/helper'; import {reaction} from 'mobx'; -import {FormControlProps, registerFormItem, FormItemBasicConfig} from './Item'; +import { + FormControlProps, + registerFormItem, + FormItemBasicConfig, + detectProps as itemDetectProps +} from './Item'; import {IFormItemStore} from '../../store/formItem'; export type OptionsControlComponent = React.ComponentType; @@ -71,6 +76,21 @@ export interface OptionsProps extends FormControlProps, OptionProps { optionLabel?: string; } +export const detectProps = itemDetectProps.concat([ + 'options', + 'size', + 'buttons', + 'columnsCount', + 'multiple', + 'hideRoot', + 'checkAll', + 'showIcon', + 'showRadio', + 'btnDisabled', + 'joinValues', + 'extractValue' +]); + export function registerOptionsControl(config: OptionsConfig) { const Control = config.component; @@ -93,7 +113,7 @@ export function registerOptionsControl(config: OptionsConfig) { : []; static ComposedComponent = Control; - reaction: any; + reaction?: () => void; input: any; componentWillMount() { @@ -110,19 +130,15 @@ export function registerOptionsControl(config: OptionsConfig) { addHook, formInited, valueField, - options + options, + value } = this.props; if (formItem) { formItem.setOptions(normalizeOptions(options)); this.reaction = reaction( - () => - JSON.stringify([ - formItem.loading, - formItem.selectedOptions, - formItem.filteredOptions - ]), + () => JSON.stringify([formItem.loading, formItem.filteredOptions]), () => this.forceUpdate() ); } @@ -140,12 +156,15 @@ export function registerOptionsControl(config: OptionsConfig) { if (formItem && joinValues === false && defaultValue) { const selectedOptions = extractValue - ? formItem.selectedOptions.map( - (selectedOption: Option) => selectedOption[valueField || 'value'] - ) - : formItem.selectedOptions; + ? formItem + .getSelectedOptions(value) + .map( + (selectedOption: Option) => + selectedOption[valueField || 'value'] + ) + : formItem.getSelectedOptions(value); setPrinstineValue( - multiple ? selectedOptions.concat() : formItem.selectedOptions[0] + multiple ? selectedOptions.concat() : selectedOptions[0] ); } @@ -164,37 +183,7 @@ export function registerOptionsControl(config: OptionsConfig) { return true; } - if ( - anyChanged( - [ - 'formPristine', - 'addOn', - 'disabled', - 'placeholder', - 'required', - 'formMode', - 'className', - 'inputClassName', - 'labelClassName', - 'label', - 'inline', - 'options', - 'size', - 'btnClassName', - 'btnActiveClassName', - 'buttons', - 'columnsCount', - 'multiple', - 'hideRoot', - 'checkAll', - 'showIcon', - 'showRadio', - 'btnDisabled' - ], - this.props, - nextProps - ) - ) { + if (anyChanged(detectProps, this.props, nextProps)) { return true; } @@ -280,10 +269,9 @@ export function registerOptionsControl(config: OptionsConfig) { extractValue === false && (typeof value === 'string' || typeof value === 'number') ) { + const selectedOptions = formItem.getSelectedOptions(value); formItem.changeValue( - multiple - ? formItem.selectedOptions.concat() - : formItem.selectedOptions[0] + multiple ? selectedOptions.concat() : selectedOptions[0] ); } else if ( extractValue === true && @@ -297,9 +285,11 @@ export function registerOptionsControl(config: OptionsConfig) { typeof value === 'number' ) ) { - const selectedOptions = formItem.selectedOptions.map( - (selectedOption: Option) => selectedOption[valueField || 'value'] - ); + const selectedOptions = formItem + .getSelectedOptions(value) + .map( + (selectedOption: Option) => selectedOption[valueField || 'value'] + ); formItem.changeValue( multiple ? selectedOptions.concat() : selectedOptions[0] ); @@ -326,14 +316,15 @@ export function registerOptionsControl(config: OptionsConfig) { clearable, resetValue, multiple, - formItem + formItem, + value } = this.props; if (!formItem) { return; } - let valueArray = formItem.selectedOptions.concat(); + let valueArray = formItem.getSelectedOptions(value).concat(); const idx = valueArray.indexOf(option); let newValue: string | Array