如何在useState中同步更改状态

时间:2020-07-24 20:16:03

标签: reactjs react-hooks use-state

大家好,我是使用React Hooks的新手,但无法直接在Google上找到它。我正在尝试嵌套setState回调,以便状态可以同步更新,但由于获得未定义的值而没有成功。状态内的值依赖于状态内的其他值,因此理想情况下,我希望它可以同步运行。我尝试在下面嵌套它,当它是一个类组件并使用this.setState并使用回调时,它可以工作,但是当我尝试在功能类中使用React钩子时,则不起作用。

这是我的代码:

const [state, setState] = useState({numCols: 0, numRows: 0, cardWidth: 0, cardHeight: 0, containerWidth: 0});
  
  const {
    sheetList,
    sheetsTotalCount,
    sheetsMetadata,
    id,
    projectId,
    onEditButtonClick,
    handleInfiniteLoad,
    sheetsAreLoading,
    classes,
    permissions,
    onItemClick,
  } = props;

  const setCardSize = ({ width }) {
    setState({
      containerWidth: width > 0 ? width : defaultWidth
    },() => { 
      setState({numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }, () => {
        setState({numRows: Math.ceil(sheetList.size / state.numCols)}, () => {
          setState({cardWidth: Math.floor(state.containerWidth / state.numCols - 2 * marginBetweenCards)}, () => {
            setState({cardHeight: Math.round(state.cardWidth * thumbnailProportion)});
          });
        });
      });
    });
  }

理想情况下,我希望先更新containerWidth变量,然后更新numCols变量,然后再更新cardWidth,然后再更新cardHeight。有什么办法可以同步执行此操作,这样我就不会得到未定义的值?

2 个答案:

答案 0 :(得分:2)

我对要实现的目标感到困惑。但是请不要忘记,与类不同,您每次必须将所有属性都设置为a状态。

setState({numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }

此代码将仅用numCols替换您的所有状态。您希望像这样的其余状态,现在只有numCols会改变,其他所有都一样。

setState({...state, numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }

接下来要记住的是,如果要在一个渲染中多次更改状态,请使用以下格式:

setState(oldState => {...oldState, newValue: 'newValue'});

这将允许多次更新,以在设置了最后一个值而不是最后一个渲染的一个渲染中声明状态。例如:

const [state, setState] = useState(0); // -> closed on this state's value!

setState(state + 1);
setState(state + 1);
setState(state + 1); //State is still 1 on next render 
// because it is using the state which happened on the last render.
// When this function was made it "closed" around the value 0
// (or the last render's number) hence the term closure.

与此:

const [state, setState] = useState(0);

setState(state => state + 1);
setState(state => state + 1);
setState(state => state + 1); //State is 3 on next render.

但是为什么不同步计算值呢?

  const setCardSize = (width) => {
    const containerWidth = width > 0 ? width : defaultWidth;
    const numCols = Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1);
    const numRows = Math.ceil(sheetList.size / numCols);
    const cardWidth = Math.floor(containerWidth / numCols - 2 * marginBetweenCards);
    const cardHeight = Math.round(cardWidth * thumbnailProportion);
    setState({containerWidth, numCols, numRows, cardWidth, cardHeight});
  }

查看讨论的docs

与在类组件中找到的setState方法不同,useState可以 不会自动合并更新对象。

如果新状态是使用先前状态计算的,则可以传递一个 函数为setState。该函数将接收先前的值, 并返回更新的值。这是计数器组件的示例 使用两种形式的setState:

答案 1 :(得分:2)

看到您正在计算一个依赖于另一个变量的负载,并且希望状态同时更新,为什么不将计算结果拆分出来并在最后一次设置状态呢?更具可读性,只需要一个setState调用。

 const setCardSize = ({ width }) => {
    const containerWidth = width > 0 ? width : defaultWidth;
    const numCols = Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1,);
    const numRows = Math.ceil(sheetList.size / numCols);
    const cardWidth = Math.floor(containerWidth / numCols - 2 * marginBetweenCards);
    const cardHeight = Math.round(cardWidth * thumbnailProportion);
    setState({ containerWidth, numCols, numRows, cardWidth, cardHeight });
  };

但是,要回答实际问题,如果要使一个变量的状态更新立即(或如您所说的那样“同步”)更新另一个状态变量,请使用useEffect

您只需给useEffect两个参数:每次因变量变化时运行的函数,然后是这些变量的数组以引起关注。

每个状态变量具有自己的useState而不是仅是一个大对象,这是更干净的方法(并且速度更快,更不易出错,通常建议功能部件使用),而我在这里也做了一个比较大的对象。

  const [containerWidth, setContainerWidth] = useState(0);
  const [numCols, setNumCols] = useState(0);
  const [numRows, setNumRows] = useState(0);
  const [cardWidth, setCardWidth] = useState(0);
  const [cardHeight, setCardHeight] = useState(0);

  const setCardSize = ({ width }) => setContainerWidth(width > 0 ? width : defaultWidth)
  useEffect(() => setNumCols(Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1)) , [containerWidth])
  useEffect(() => setNumRows(Math.ceil(sheetList.size / numCols)), [numCols])
  useEffect(() => setCardWidth(Math.floor(containerWidth / numCols - 2 * marginBetweenCards)), [containerWidth])
  useEffect(() => setCardHeight(Math.round(cardWidth * thumbnailProportion)), [cardWidth])