我想知道设置器在const [state, setState] = useState(0)
中触发组件重新渲染是否符合预期。因此,如果我将setter作为道具传递给组件,是否应该触发重新渲染?如果是,为什么?有什么办法可以避免这种情况?
我创建了一个非常简单的沙箱来演示此行为:https://codesandbox.io/s/bold-maxwell-qj5mi
在这里,我们可以看到,在查看console.logs
时,每次单击都会重新渲染按钮组件,而传递到其中的incrementCounter
函数没有改变。有什么作用?
答案 0 :(得分:3)
如果您memoize按钮,您不会遇到这种情况。
具体来说,这个:
const Button = memo(({ incrementCounter }) => {
const renderCount = useRef(1);
console.log("button rendered: ", renderCount.current);
renderCount.current++;
return <button onClick={incrementCounter}>Increment</button>;
});
CodeSandbox镜像:
更新
如果您想要something from the docs,第一句话将告诉您为什么会这样。我知道,setState
就是这样,但是useState
钩子也有相同的概念,但是文档很烂。 You can check out这部分文档,特别是上面写着“第9行:” ...
请记住,当X组件的状态发生变化时,X组件及其所有子组件都将重新呈现。我知道React会告诉您“提升状态”,这是我从未理解的事情,因为提升状态会导致大量的重新渲染。
这就是为什么按钮会重新渲染..因为状态在其父级中正在更改的原因。父(<App />
)的counter
状态已更改,这会触发<App />
组件及其子对象(包括<Button />
)的重新渲染。
在我看来,React很难控制状态和重新渲染,而Redux可以提供帮助,但是memo
,useCallback
等整体事物都感觉像乐队-对我有帮助。如果将状态放在错误的组件中,那将是一段糟糕的时光。
包装<Button />
组件in memo
基本上是说:如果该组件有一个父级(在我们的情况下为<App />
),并且该父级重新渲染,那么我想看看的道具,如果我们的道具与上次收到的一样,请不要重新渲染。本质上,只有在我们的道具改变的情况下才重新渲染。这就是memo
修复此问题的原因。.因为我们用来处理incrementCounter
道具的函数不会改变-它保持不变。
我在下面添加了一些示例来说明这一点。
原始答案/摘要:
const { memo, useState, useCallback, useEffect, useRef } = React;
const { render } = ReactDOM;
const App = () => {
const [counter, setCounter] = useState(0);
const incrementCounter = useCallback(() => {
setCounter(c => c + 1);
}, [setCounter]);
useEffect(() => {
console.log("increment changed!");
}, [incrementCounter]);
return (
<div>
<CountValue counter={counter} />
<Button incrementCounter={incrementCounter} />
</div>
);
}
const CountValue = ({ counter }) => {
return <div>Count value: {counter}</div>;
};
const Button = memo(({ incrementCounter }) => {
const renderCount = useRef(1);
console.log("button rendered: ", renderCount.current);
renderCount.current++;
return <button onClick={incrementCounter}>Increment</button>
});
render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
SNIPPET#2:
此代码段显示了如何重新渲染所有内容,而不仅仅是按钮。
const { useState, useEffect } = React;
const { render } = ReactDOM;
const App = () => {
console.log("App rendered");
const [counter, setCounter] = useState(0);
const incrementCounter = () => setCounter(c => c + 1);
useEffect(() => {
console.log(" - Increment fired!");
console.log();
}, [incrementCounter]);
return (
<div>
<CountValue counter={counter} />
<Button incrementCounter={incrementCounter} />
<p>Open console</p>
</div>
);
}
const CountValue = ({ counter }) => {
console.log("CountValue rendered");
return <div>Count value: {counter}</div>;
};
const Button = ({ incrementCounter }) => {
console.log("Button rendered");
return <button onClick={incrementCounter}>Increment</button>
};
render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
片段#3:
此代码段显示了如何将状态等移动到<CountValue />
组件中,而<App />
组件不会重新呈现。.
const { useState, useEffect } = React;
const { render } = ReactDOM;
const App = () => {
console.log("App rendered");
return (
<div>
<CountValue />
<p>Open console</p>
</div>
);
}
const CountValue = () => {
console.log("CountValue rendered");
const [counter, setCounter] = useState(0);
const incrementCounter = () => setCounter(c => c + 1);
return (
<div>
<div>Count value: {counter}</div>
<Button incrementCounter={incrementCounter} />
</div>
);
};
const Button = ({ incrementCounter }) => {
console.log("Button rendered");
console.log();
return <button onClick={incrementCounter}>Increment</button>
};
render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
片段#4:
此代码段更多是一个思想实验,展示了如何使用渲染道具。
const { useState, useEffect } = React;
const { render } = ReactDOM;
const App = () => {
console.log("App rendered");
return (
<div>
<CountValue present={({ increment, counter }) => {
return (
<div><Button incrementCounter={() => increment()} />
<p>Counter Value: {counter}</p></div>
)
}} />
<p>Open console</p>
</div>
);
}
const CountValue = ({ present }) => {
const [counter, setCounter] = useState(0);
const increment = () => {
setCounter(c => c + 1);
}
console.log("CountValue rendered");
return (
<React.Fragment>
{present({ increment, counter })}
</React.Fragment>
);
};
const Button = ({ incrementCounter }) => {
console.log("Button rendered");
return <button onClick={incrementCounter}>Increment</button>
};
render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
SNIPPET#5:
这似乎是您要追求的。.这只会重新呈现CountValue ..这是通过将setCounter
产生的useState
方法传递到父对象,通过回调对象。
通过这种方式,父级可以操纵状态,而不必实际保持状态。
const { useState, useEffect } = React;
const { render } = ReactDOM;
const App = () => {
console.log("App rendered");
let increaseCount;
return (
<div>
<CountValue callback={({increment}) => increaseCount = increment} />
<Button incrementCounter={() => increaseCount()} />
<p>Open console</p>
</div>
);
}
const CountValue = ({ callback }) => {
console.log("CountValue rendered");
const [counter, setCounter] = useState(0);
callback && callback({
increment: () => setCounter(c => c + 1)
});
return <p>Counter Value: {counter}</p>;
};
const Button = ({ incrementCounter }) => {
console.log("Button rendered");
return <button onClick={incrementCounter}>Increment</button>
};
render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
答案 1 :(得分:0)
是的,这是预期的。
如果避免重新渲染,则不会在屏幕上看到设置的值。
如果您要创建一个可以更改而不会触发重新渲染的值,请使用useRef
。