【问题标题】:React prevent child updateReact 防止子更新
【发布时间】:2021-07-27 14:13:42
【问题描述】:

我有一个简单的 for,其中包含一些字段,这些字段是该表单的子组件。每个字段都验证自己的值,如果它发生变化,它应该向父级报告,这会导致字段重新渲染并失去焦点。我想要一个子组件不更新的行为。这是我的代码:

父(表单):

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;

子(字段):

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;

通过为我的子元素设置key,我能够防止它在内容更改时失去焦点。我想我想将shouldComponentUpdate 实现为stated in React documentation,并尝试通过执行以下操作来实现它:

尝试 1:用 React.memo 包围孩子

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

export { InputField };

尝试 2:用 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>
    );

两者都不起作用。 如何才能让子组件在isValid状态发生变化时不重新渲染?

【问题讨论】:

  • 字段在重新渲染时不会“失去焦点”,但在“重新安装”时可能会失去焦点,但不确定。你找错树了。编辑:您看起来好像正在通过这条线重新安装:const StyledInput = isValid ? Input : ErrorInput;。解决方案是不要重新安装。我想到的第一个解决方案是将InputErrorInput 组合成一个组件,如果可能的话,用prop 控制样式。你能发布这两个组件吗?
  • 确实:您正在根据字段是否有效呈现两个完全独立的组件。这种方法将导致您描述的所有问题。使用单个 Input 并更改其样式。
  • @Adam 我不敢相信我没有早点意识到这一点。你完全正确。随时发表您的评论作为答案。我很乐意接受它。
  • 学习并使用useMemo进行记忆
  • @William 添加了一个答案,但我看到了其他可疑的东西。您正在调用 reportState 并在本地存储 isValid。您是否 100% 确定这是您的表单框架的工作方式?是否有一个道具可以用来告诉您该字段是否有效而不将其存储为本地状态?通常,后者是大多数表单框架的工作方式,因此您永远不应该真正拥有isValid 的本地状态,而应该“来自上方”

标签: javascript reactjs react-hooks


【解决方案1】:

问题不在于组件正在重新渲染,而在于该组件正在 unmounting 这行给出:

const StyledInput = isValid ? Input : ErrorInput;

当 react 卸载组件时,react-dom 将破坏该组件的子树,这就是输入失去焦点的原因。

正确的解决方法是始终渲染相同的组件。这对您意味着什么取决于您的代码的结构,但我会冒险猜测代码最终会看起来更像这样:

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>
    );
}

【讨论】:

    【解决方案2】:

    避免使用函数组件重新渲染的规范解决方案是React.useMemo

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

    但是,由于validateField 是传递给子组件的道具之一,您需要确保它在父渲染之间不会发生变化。使用useCallback 来做到这一点:

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

    您的useMemo 解决方案也应该可以工作,但您需要将计算包装在一个函数中(请参阅the documentation):

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

    【讨论】:

      猜你喜欢
      • 2012-11-30
      • 2019-09-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-15
      • 1970-01-01
      相关资源
      最近更新 更多