React如何使用useEffect监视和更新状态?

时间:2020-07-19 10:53:54

标签: javascript reactjs typescript react-hooks use-effect

示例代码

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函数陷入循环,如果我删除“状态”依赖关系会很好,但是intelliSense请求“状态”依赖关系,那么解决方案是什么?

2 个答案:

答案 0 :(得分:2)

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 版本,而不是第一次创建效果回调时的状态,因为用于更新的部分内容不是依赖项(因此可能变得陈旧)。

答案 1 :(得分:1)

您可以查看此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,并将变量视为独立的部件。