如果您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>
片段 #2:
这个 sn-p 显示了如何重新渲染所有内容,而不仅仅是按钮。
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:
这个 sn-p 显示如果您将状态等移动到 <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:
这个 sn-p 更像是一个思想实验,展示了如何使用渲染道具。
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>
片段 #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>