forked from p96170835/amis
form 渲染性能优化
This commit is contained in:
parent
d93f311f6f
commit
ecefaab794
|
@ -43,7 +43,10 @@ interface CheckboxProps {
|
|||
}
|
||||
|
||||
export class Checkbox extends React.Component<CheckboxProps, any> {
|
||||
static defaultProps = {
|
||||
static defaultProps: Pick<
|
||||
CheckboxProps,
|
||||
'trueValue' | 'falseValue' | 'type'
|
||||
> = {
|
||||
trueValue: true,
|
||||
falseValue: false,
|
||||
type: 'checkbox'
|
||||
|
|
|
@ -6,7 +6,6 @@ import chunk = require('lodash/chunk');
|
|||
|
||||
export interface CheckboxesProps extends OptionsControlProps {
|
||||
placeholder?: any;
|
||||
disabled?: boolean;
|
||||
itemClassName?: string;
|
||||
columnsCount?: number;
|
||||
labelClassName?: string;
|
||||
|
|
|
@ -107,6 +107,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||
];
|
||||
|
||||
subForms: Array<any> = [];
|
||||
subFormDefaultValues: Array<{index: number; values: any}> = [];
|
||||
keys: Array<string> = [];
|
||||
dragTip?: HTMLElement;
|
||||
sortable?: Sortable;
|
||||
|
@ -326,19 +327,31 @@ export default class ComboControl extends React.Component<ComboProps> {
|
|||
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) {
|
||||
|
|
|
@ -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<ControlProps> {
|
||||
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<ControlProps> = {};
|
||||
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<ControlProps> {
|
|||
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<ControlProps> {
|
|||
labelField,
|
||||
joinValues,
|
||||
extractValue
|
||||
});
|
||||
}));
|
||||
|
||||
if (
|
||||
this.model.unique &&
|
||||
|
@ -105,6 +120,15 @@ export default class FormControl extends React.Component<ControlProps> {
|
|||
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<ControlProps> {
|
|||
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<ControlProps> {
|
|||
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<ControlProps> {
|
|||
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<ControlProps> {
|
|||
(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<ControlProps> {
|
|||
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<ControlProps> {
|
|||
|
||||
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<ControlProps> {
|
|||
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<ControlProps> {
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -664,6 +664,63 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
|||
}
|
||||
}
|
||||
|
||||
// 白名单形式,只有这些属性发生变化,才会往下更新。
|
||||
// 除非配置 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<FormControlProps>;
|
||||
|
||||
|
@ -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<Option> | Option = '';
|
||||
|
||||
|
@ -375,6 +366,7 @@ export function registerOptionsControl(config: OptionsConfig) {
|
|||
@autobind
|
||||
handleToggleAll() {
|
||||
const {
|
||||
value,
|
||||
onChange,
|
||||
joinValues,
|
||||
extractValue,
|
||||
|
@ -389,8 +381,9 @@ export function registerOptionsControl(config: OptionsConfig) {
|
|||
return;
|
||||
}
|
||||
|
||||
const selectedOptions = formItem.getSelectedOptions(value);
|
||||
let valueArray =
|
||||
formItem.selectedOptions.length === formItem.filteredOptions.length
|
||||
selectedOptions.length === formItem.filteredOptions.length
|
||||
? []
|
||||
: formItem.filteredOptions.concat();
|
||||
|
||||
|
|
|
@ -187,9 +187,14 @@ export const FormStore = ServiceStore.named('FormStore')
|
|||
self.updateData(data);
|
||||
}
|
||||
|
||||
function syncOptions() {
|
||||
self.items.forEach(item => item.syncOptions());
|
||||
}
|
||||
const syncOptions = debounce(
|
||||
() => self.items.forEach(item => item.syncOptions()),
|
||||
250,
|
||||
{
|
||||
trailing: true,
|
||||
leading: false
|
||||
}
|
||||
);
|
||||
|
||||
const saveRemote: (
|
||||
api: Api,
|
||||
|
@ -448,12 +453,18 @@ export const FormStore = ServiceStore.named('FormStore')
|
|||
self.inited = value;
|
||||
}
|
||||
|
||||
const setPersistData = debounce(() => {
|
||||
localStorage.setItem(
|
||||
location.pathname + self.path,
|
||||
JSON.stringify(self.data)
|
||||
);
|
||||
}, 250);
|
||||
const setPersistData = debounce(
|
||||
() =>
|
||||
localStorage.setItem(
|
||||
location.pathname + self.path,
|
||||
JSON.stringify(self.data)
|
||||
),
|
||||
250,
|
||||
{
|
||||
trailing: true,
|
||||
leading: false
|
||||
}
|
||||
);
|
||||
|
||||
function getPersistData() {
|
||||
self.persistData = true;
|
||||
|
|
|
@ -11,6 +11,7 @@ import {IRendererStore} from '.';
|
|||
import {normalizeOptions} from '../components/Select';
|
||||
import find = require('lodash/find');
|
||||
import {SimpleMap} from '../utils/SimpleMap';
|
||||
import memoize = require('lodash/memoize');
|
||||
|
||||
interface IOption {
|
||||
value?: string | number | null;
|
||||
|
@ -105,10 +106,8 @@ export const FormItemStore = types
|
|||
return getLastOptionValue();
|
||||
},
|
||||
|
||||
getSelectedOptions(value: any = getValue()) {
|
||||
if (value === getValue()) {
|
||||
return self.selectedOptions;
|
||||
} else if (typeof value === 'undefined') {
|
||||
getSelectedOptions: memoize((value: any = getValue()) => {
|
||||
if (typeof value === 'undefined') {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -165,7 +164,7 @@ export const FormItemStore = types
|
|||
});
|
||||
|
||||
return selectedOptions;
|
||||
}
|
||||
})
|
||||
};
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in New Issue