如何确保使用useState()挂钩的React状态已更新?

cbd*_*per 8 javascript reactjs react-hooks

我有一个<BasicForm>用于构建表单的类组件。它处理验证和所有表格state。它通过React上下文向输入(自渲染起)提供所有必需的功能(onChangeonSubmit等)。childrenBasicForm

它按预期工作。问题在于,现在我将其转换为使用React Hooks,在尝试复制以下是类时的行为时,我感到疑惑:

class BasicForm extends React.Component {

  ...other code...

  touchAllInputsValidateAndSubmit() {

    // CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
    let inputs = {};
    for (let inputName in this.state.inputs) {
      inputs = Object.assign(inputs, {[inputName]:{...this.state.inputs[inputName]}});
    }

    // TOUCH ALL INPUTS
    for (let inputName in inputs) {
      inputs[inputName].touched = true;
    }

    // UPDATE STATE AND CALL VALIDATION
    this.setState({
      inputs
    }, () => this.validateAllFields());  // <---- SECOND CALLBACK ARGUMENT
  }

  ... more code ...

}

Run Code Online (Sandbox Code Playgroud)

当用户单击“提交”按钮时,BasicForm应“触摸”所有输入,然后才调用validateAllFields(),因为仅在触摸输入后才会显示验证错误。因此,如果用户没有触摸任何东西,则BasicForm需要确保在调用validateAllFields()函数之前先“触摸”每个输入。

当我使用类时,我这样做的方法是在setState()函数上使用第二个回调参数,如您在上面的代码中所见。并确保validateAllField()只有在状态更新后(涉及所有字段的状态)才被调用。

但是,当我尝试将第二个回调参数与状态挂钩一起使用时,出现useState()以下错误:

const [inputs, setInputs] = useState({});

... some other code ...

setInputs(auxInputs, () => console.log('Inputs updated!'));
Run Code Online (Sandbox Code Playgroud)

警告:useState()和useReducer()挂钩的状态更新不支持第二个回调参数。要在渲染后执行副作用,请使用useEffect()在组件主体中声明它。

因此,根据上面的错误消息,我正在尝试使用useEffect()挂钩。但这使我有些困惑,因为据我所知,useEffect()它不是基于状态更新,而是基于渲染执行。它在每次渲染后执行。而且我知道React可以在重新渲染之前对一些状态更新进行排队,所以我觉得我无法完全控制useEffect()钩子何时执行,就像我在使用类和setState()第二个回调参数时一样。

到目前为止,我得到的是(似乎正在运行):

function BasicForm(props) {

  const [inputs, setInputs] = useState({});
  const [submitted, setSubmitted] = useState(false);

  ... other code ...

  function touchAllInputsValidateAndSubmit() {
    const shouldSubmit = true;

    // CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
    let auxInputs = {};
    for (let inputName in inputs) {
      auxInputs = Object.assign(auxInputs, {[inputName]:{...inputs[inputName]}});
    }

    // TOUCH ALL INPUTS
    for (let inputName in auxInputs) {
      auxInputs[inputName].touched = true;
    }

    // UPDATE STATE
    setInputs(auxInputs);
    setSubmitted(true);
  }

  // EFFECT HOOK TO CALL VALIDATE ALL WHEN SUBMITTED = 'TRUE'
  useEffect(() => {
    if (submitted) {
      validateAllFields();
    }
    setSubmitted(false);
  });

  ... some more code ...

}

Run Code Online (Sandbox Code Playgroud)

我正在使用useEffect()挂钩来调用该validateAllFields()函数。由于useEffect()每个渲染器执行,validateAllFields()因为我不想在每个渲染器上都需要,所以我需要一种方法来知道何时调用。因此,我创建了submitted状态变量,以便可以知道何时需要该效果。

这是一个好的解决方案吗?您可能想到什么其他解决方案?感觉真的很奇怪。

想象一下该validateAllFields()函数在任何情况下都不能被调用两次。我怎么知道在下一次渲染时我的submitted状态已经100%确定为“假”?

我可以依靠React在下一次渲染之前执行每个排队状态更新吗?这样可以保证吗?

Col*_*rdo 10

我最近遇到了这样的事情(这里有这样的问题),看起来你想出的方法是一个不错的方法。

您可以添加一个 arg 来useEffect()执行您想要的操作:

例如

useEffect(() => { ... }, [submitted])
Run Code Online (Sandbox Code Playgroud)

以观察 中的变化submitted

另一种方法可能是修改钩子以使用回调,例如:

import React, { useState, useCallback } from 'react';

const useStateful = initial => {
  const [value, setValue] = useState(initial);
  return {
    value,
    setValue
  };
};

const useSetState = initialValue => {
  const { value, setValue } = useStateful(initialValue);
  return {
    setState: useCallback(v => {
      return setValue(oldValue => ({
        ...oldValue,
        ...(typeof v === 'function' ? v(oldValue) : v)
      }));
    }, []),
    state: value
  };
};
Run Code Online (Sandbox Code Playgroud)

通过这种方式,您可以模拟 'classic' 的行为setState()