refactor: Form.Item with pure children will only render once (#21991)

* refactor: Form.Item with pure children will only render once

* not skip for real render
This commit is contained in:
二货机器人 2020-03-08 16:28:33 +08:00 committed by GitHub
parent 76de873d87
commit 9fdb82b7df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 1 deletions

View File

@ -21,6 +21,21 @@ type RenderChildren = (form: FormInstance) => React.ReactNode;
type RcFieldProps = Omit<FieldProps, 'children'>;
type ChildrenType = React.ReactElement | RenderChildren | React.ReactElement[] | null;
interface MemoInputProps {
value: any;
update: number;
children: any;
}
const MemoInput = React.memo<MemoInputProps>(
({ children }) => {
return children;
},
(prev, next) => {
return prev.value === next.value && prev.update === next.update;
},
);
export interface FormItemProps
extends FormItemLabelProps,
FormItemInputProps,
@ -216,6 +231,10 @@ function FormItem(props: FormItemProps): React.ReactElement {
return renderLayout(children);
}
// Record for real component render
const updateRef = React.useRef(0);
updateRef.current += 1;
return (
<Field
{...props}
@ -296,7 +315,14 @@ function FormItem(props: FormItemProps): React.ReactElement {
};
});
childNode = React.cloneElement(children, childProps);
childNode = (
<MemoInput
value={mergedControl[props.valuePropName || 'value']}
update={updateRef.current}
>
{React.cloneElement(children, childProps)}
</MemoInput>
);
} else if (isRenderProps && shouldUpdate && !hasName) {
childNode = (children as RenderChildren)(context);
} else {

View File

@ -621,4 +621,35 @@ describe('Form', () => {
}
expect(instances.size).toEqual(1);
});
it('avoid re-render', async () => {
let renderTimes = 0;
const MyInput = ({ value = '', ...props }) => {
renderTimes += 1;
return <input value={value} {...props} />;
};
const Demo = () => (
<Form>
<Form.Item name="username" rules={[{ required: true }]}>
<MyInput />
</Form.Item>
</Form>
);
const wrapper = mount(<Demo />);
renderTimes = 0;
wrapper.find('input').simulate('change', {
target: {
value: 'a',
},
});
await delay();
expect(renderTimes).toEqual(1);
expect(wrapper.find('input').props().value).toEqual('a');
});
});