简体   繁体   English

反应阻止子更新

[英]React prevent child update

I have a simple for with some fields in it, the fields being child components to that form.我有一个简单的 for ,其中包含一些字段,这些字段是该表单的子组件。 Each field validates its own value, and if it changes it should report back to the parent, which causes the field to re-render and lose focus.每个字段都验证自己的值,如果它发生变化,它应该向父项报告,这会导致该字段重新呈现并失去焦点。 I want a behavior in which the child components do not update.我想要一个子组件不更新的行为。 Here's my code:这是我的代码:

Parent (form):父母(表格):

function Form() {

    const [validFields, setValidFields] = useState({});

    const validateField = (field, isValid) => {
        setValidFields(prevValidFields => ({ ...prevValidFields, [field]: isValid }))
    }

    const handleSubmit = (event) => {
        event.preventDefault();
        //will do something if all fields are valid
        return false;
    }

    return (
        <div>
            <Title />
            <StyledForm onSubmit={handleSubmit}>
                <InputField name="fooField" reportState={validateField} isValidCondition={fooRegex} />
                <Button type="submit" content="Enviar" maxWidth="none" />
            </StyledForm>
        </div>
    );
}

export default Form;

Child (field):孩子(领域):

function InputField(props) {

    const [isValid, setValid] = useState(true);
    const [content, setContent] = useState("");
    const StyledInput = isValid ? Input : ErrorInput;

    const validate = (event) => {
        setContent(event.target.value);
        setValid(stringValidator.validateField(event.target.value, props.isValidCondition))
        props.reportState(props.name, isValid);
    }

    return (
        <Field>
            <Label htmlFor={props.name}>{props.name + ":"}</Label>
            <StyledInput
                key={"form-input-field"}
                value={content}
                name={props.name}
                onChange={validate}>
            </StyledInput>
        </Field>
    );
}

export default InputField;

By setting a key for my child element I was able to prevent it to lose focus when content changed.通过为我的子元素设置一个key ,我能够防止它在内容更改时失去焦点。 I guess I want to implement the shouldComponentUpdate as stated in React documentation , and I tried to implement it by doing the following:我想我想实现React 文档中所述的shouldComponentUpdate ,并且我尝试通过执行以下操作来实现它:

Attempt 1: surround child with React.memo尝试 1:用 React.memo 包围孩子

const InputField = React.memo((props) {
    //didn't change component content
})

export { InputField };

Attempt 2: intanciate child with useMemo on parent尝试 2:在父useMemo上使用 useMemo 对子级进行useMemo

const fooField = useMemo(<InputField name="fooField" reportState={validateField} isValidCondition={fooRegex} />, [fooRegex]);

    return (
        <div>
            <Title />
            <StyledForm onSubmit={handleSubmit}>
                {fooField}
                <Button type="submit" content="Enviar" maxWidth="none" />
            </StyledForm>
        </div>
    );

Both didn't work.两者都不起作用。 How can I make it so that when the child component isValid state changes, it doesn't re-render?我怎样才能做到当子组件 isValid 状态改变时,它不会重新渲染?

The problem is not that the component is re-rendering, it is that the component is unmounting given by this line:问题不在于组件正在重新渲染,而是组件正在卸载由这一行给出:

const StyledInput = isValid ? Input : ErrorInput;

When react unmounts a component, react-dom will destroy the subtree for that component which is why the input is losing focus.当 react 卸载组件时, react-dom会破坏该组件的子树,这就是输入失去焦点的原因。

The correct fix is to always render the same component.正确的解决方法是始终渲染相同的组件。 What that means to you is based on how your code is structured, but I would hazard a guess that the code would end up looking a bit more like this:这对您来说意味着什么取决于您的代码的结构,但我会冒险猜测代码最终看起来更像这样:

function InputField(props) {

    const [isValid, setValid] = useState(true);
    const [content, setContent] = useState("");

    const validate = (event) => {
        setContent(event.target.value);
        setValid(stringValidator.validateField(event.target.value, props.isValidCondition))
        props.reportState(props.name, isValid);
    }

    return (
        <Field>
            <Label htmlFor={props.name}>{props.name + ":"}</Label>
            <Input
                valid={isValid} <-- always render an Input, and do the work of displaying error messages/styling based on the `valid` prop passed to it
                value={content}
                name={props.name}
                onChange={validate}>
            </Input>
        </Field>
    );
}

The canonical solution to avoiding rerendering with function components is React.useMemo :避免使用函数组件React.useMemo渲染的规范解决方案是React.useMemo

const InputField = React.memo(function (props) {
    // as above
})

However, because validateField is one of the props passed to the child component, you need to make sure it doesn't change between parent renders.但是,因为validateField是传递给子组件的道具之一,所以您需要确保它不会在父渲染之间发生变化。 UseuseCallback to do that:使用useCallback来做到这一点:

    const validateField = useCallback((field, isValid) => {
        setValidFields(prevValidFields => ({ ...prevValidFields, [field]: isValid }))
    }, []);

Your useMemo solution should also work, but you need to wrap the computation in a function (see the documentation ):您的useMemo解决方案也应该有效,但您需要将计算包装在一个函数中(请参阅文档):

const fooField = useMemo(() => <InputField name="fooField" reportState={validateField} isValidCondition={fooRegex} />, [fooRegex]);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM