React useEffect 如何监视和更新状态? [复制]

Posted

技术标签:

【中文标题】React useEffect 如何监视和更新状态? [复制]【英文标题】:How can React useEffect watch and update state? [duplicate] 【发布时间】:2020-11-08 18:09:15 【问题描述】:

示例代码

export function useShape ()
  const [state, setState] = useState(
    shape: 'square',
    size: 
      width: 100,
      height: 100
    
  )

  // Change shape name while size update
  useEffect(()=>
    const size: width, height = state

    setState(
      ...state,
      shape: width===height ? 'square' : 'rect'
    )
  , [state, state.size])

预期

当尺寸更新时,副作用会根据宽度,高度改变尺寸名称。

目标是使状态保持一致,因此无论大小如何变化,我都能得到正确的形状。

但有问题

useEffect函数进入循环,如果我去掉'state'依赖就好了,但是intelliSense请求了'state'依赖,那么解决办法是什么?

【问题讨论】:

每个状态值都应该用自己的 useState 钩子存储,不要将所有状态存储在同一个对象中。更不用说您根本不应该将计算值存储为状态。如果 shape 的值可以从 size 计算出来,那么就不要将 shape 存储在 state 中,只需要在需要的时候计算即可。 我试过了没有报错! 【参考方案1】:

useEffect 函数进入循环...

那是因为你每次都为state 创建一个新对象,而state 被列为依赖项。

当使用钩子而不是构建多部分状态对象时,通常最好使用较小的部分。在这种情况下,我会使用三个:

const [shape, setShape] = useState("square");
const [width, setWidth] = useState(100);
const [height, setHeight] = useState(100);

useEffect(() => 
    setShape(width === height ? "square" : "rect");
, [width, height]);

现在,您设置的 (shape) 不是效果挂钩的依赖项,因此它不会无休止地触发。

const useState, useEffect = React;

function Example() 
    const [shape, setShape] = useState("square");
    const [width, setWidth] = useState(100);
    const [height, setHeight] = useState(100);
    
    useEffect(() => 
        setShape(width === height ? "square" : "rect");
    , [width, height]);

    function onWidthInput(target: value) 
        setWidth(+value);
    

    function onHeightInput(target: value) 
        setHeight(+value);
    

    return <div>
        <div>
            Width: <input type="number" value=width onInput=onWidthInput />
        </div>
        <div>
            Height: <input type="number" value=height onInput=onHeightInput />
        </div>
        <div>
            Shape: shape
        </div>
    </div>;


ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

如果你愿意的话,你可以用你现有的状态对象来做到这一点:

useEffect(() => 
    setState(current => 
        const size: width, height = current;
        return 
            ...current,
            shape: width === height ? "square" : "rect"
        ;
    );
, [state.size, state.size.width, state.size.height]);

const useState, useEffect = React;

function Example() 
    const [state, setState] = useState(
        shape: 'square',
        size: 
            width: 100,
            height: 100
        
    );

    useEffect(() => 
        setState(current => 
            const size: width, height = current;
            return 
                ...current,
                shape: width === height ? "square" : "rect"
            ;
        );
    , [state.size, state.size.width, state.size.height]);

    function onSizePropInput(target: name, value) 
        setState(current => 
            return 
                ...current,
                size: 
                    ...current.size,
                    [name]: +value
                
            ;
        );
    

    const shape, size: width, height = state;
    return <div>
        <div>
            Width: <input type="number" name="width" value=width onInput=onSizePropInput />
        </div>
        <div>
            Height: <input type="number" name="height" value=height onInput=onSizePropInput />
        </div>
        <div>
            Shape: shape
        </div>
    </div>;


ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

注意其中setState 的回调形式的使用。您需要状态的 then-current 版本,而不是第一次创建效果回调时的状态,因为您用于更新的部分内容不是依赖项(因此可能是陈旧的)。

【讨论】:

【参考方案2】:

您可以查看此sandbox link 以获得解决方案

无限循环是由于在 useEffect 中修改状态本身时将“状态”作为依赖项。

解决方案是通过将视图变量与受控变量分开来解耦状态。

这是定义 useShape 钩子的方法。

function useShape() 
  const [shape, setShape] = useState("square");
  const [dimension, setDimension] = useState(
    width: 100,
    height: 100
  );

  // Change shape name while size update
  useEffect(() => 
    const  height, width  = dimension;

    setShape(height === width ? "square" : "react");
  , [dimension, dimension.height, dimension.width]);

  return [shape, setDimension];


这样您可以公开您的 Dimension setter,并将变量视为独立的 Pieces。

【讨论】:

谢谢,这真的很有帮助。

以上是关于React useEffect 如何监视和更新状态? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何使用/不使用 useEffect 钩子在 React JS 函数中获取更改的(新)状态值?

React 无法在 useEffect 内部设置状态,不会在当前周期更新状态,而是在下一个周期更新状态。这可能是啥原因造成的?

无法对未安装的组件执行 React 状态更新(useEffect 反应挂钩)

React:“无法在未安装的组件上执行 React 状态更新”,没有 useEffect 功能

useEffect 尝试在 React 中更新状态之前读取对象的属性

React useEffect 抛出错误:无法对未安装的组件执行 React 状态更新