reactjs React Context重新渲染子组件

pb3skfrl  于 2023-03-22  发布在  React
关注(0)|答案(1)|浏览(229)

已创建表单组件

用法

<Form
     initialValues={{
                title: '',
                name: ''
            }}
            onSubmit={formSubmit}
        >
            <Form.Field
                name="title"
            >
                <Input placeholder="Наименование" />
            </Form.Field>
            <Form.Field
                name="name"
            >
                <Input placeholder="Еще поле" />
            </Form.Field>
            <Button>Сохранить</Button>
        </Form>

表单组件

import { FormField } from './FormField';

import { FormProps, FormContextT } from './../../../types/form';

import './style.scss';

export const FormContext = React.createContext<FormContextT>({
    formData: {},
    handleFieldChange: () => {}
});

export const Form = (props: FormProps): JSX.Element => {

        const { children, initialValues, className, onSubmit } = props;
        
        const [ formData, setFormData] = useState(initialValues);
        
        const handlerSubmit = (e: React.SyntheticEvent): void => {
            e.preventDefault();
            
            onSubmit();
        }
    
        const handleFieldChange = (name: string, value: string) => {
            setFormData({
                ...formData,
                [name]: value
            });
        }
    
        const context:FormContextT = {
            formData,
            handleFieldChange
        }
    
        return (
            <FormContext.Provider value={context}>
                <form 
                    onSubmit={handlerSubmit}
                >
                    {children}
                </form>
            </FormContext.Provider>
        )
    }
    
    Form.Field = FormField

FormField组件

import React, { useContext } from 'react';
import { FormContext } from './Form';
import { FormFieldProps } from './../../../types/form';
export const FormField = (props: FormFieldProps): JSX.Element => {

    const { name, children } = props;
    
    const formContext = useContext(FormContext);
    const { formData, handleFieldChange } = formContext;
    
    const fieldChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        handleFieldChange(name, e.target.value);
    
        if('onChange' in children.props) {
            children.props.onChange(e);
        }
    }
    
    console.log('re-render: ' + name);
    
    return (
        React.cloneElement(children, {
            name,
            onChange: fieldChange,
            value: formData[name]
        })
    );

}

输入组件

export const Input = (props: PropsTypes) => {

    const { className, value, name, placeholder, disabled, onChange, type = 'text' } = props;
    
    console.log('re-render-input: ' + name);
    
    return (
        <input
            type={type}
            name={name!}
            value={value!}
            placeholder={placeholder!}
            disabled={disabled!}
            onChange={onChange!}
        />
    )

}

所有表单域值都以{field:value},并通过React Context传递给FormField。问题是,当字段值被覆盖时,父组件中的formData会被更新,所有子FormField都会被重新渲染。告诉我如何使它只被更改的子项被重新渲染。
已尝试使用UseMemo钩子,但无法正确应用。

m1m5dgzv

m1m5dgzv1#

这里有几样东西

  1. FormField使用的是整个表单数据,所以即使你找到了一种方法,只重新呈现那些数据发生了变化的组件,它们仍然会重新呈现--它们只需要获取自己的值。
    1.如果父组件被重新渲染,那么它的所有子组件都被重新渲染。你可以用React.memo Package 它们,但是记忆化是有代价的。
  2. React将上下文值视为单个值,并通过引用进行比较。so
  • 该值应该被 Package 在useMemo中,否则上下文值将在每次组件重新呈现时因任何原因而改变。
const context:FormContextT = React.useMemo(() = ({
           formData,
           handleFieldChange
       }), [formData, handleFieldChange]);
  • 所有使用上下文值的组件在上下文值中的任何内容被改变(引用改变)时都被重新呈现,React.memo在这里没有帮助- docs

1.为什么决定使用context?字段是表单的直接子项。
1.为什么它们不重新渲染很重要?它们有很多吗?它们的渲染函数中有很多逻辑吗?如果没有,就让它们重新渲染(你可以改变输入,这样它只会保存模糊的值,而不是每个新字母)
可能的解决方案(假设字段不会重新呈现很重要):
1.不使用上下文,只将字段值传递给表单字段,并使用React.memo Package 字段。
1.使用表单值创建一个小型外部存储区,并仅公开一个API(useValue(fieldId)setValue(fieldId, value))。here是此解决方案的沙箱

相关问题