为啥在 React 中,当父组件重新渲染时子组件不会重新渲染(子组件没有被 React.memo 包裹)?
Posted
技术标签:
【中文标题】为啥在 React 中,当父组件重新渲染时子组件不会重新渲染(子组件没有被 React.memo 包裹)?【英文标题】:Why, in React, do children not re-render when parent component re-renders(children are not wrapped by React.memo)?为什么在 React 中,当父组件重新渲染时子组件不会重新渲染(子组件没有被 React.memo 包裹)? 【发布时间】:2021-11-24 01:04:59 【问题描述】:在React Hooks - Understanding Component Re-renders这篇文章中,我了解到当我们在父组件中使用useContext
Hook 时,只有消耗上下文的子组件会重新渲染。
并且文章给出了上下文的两种消费方式。看看sn-p:
Efficient consumption of useContext\
import React from "react";
import ReactDOM from "react-dom";
import TickerComponent from "./tickerComponent";
import ThemedTickerComponent from "./themedTickerComponent";
import ThemeContextProvider from "./themeContextProvider";
import ThemeSelector from "./themeSelector";
import "./index.scss";
import logger from "./logger";
function App()
logger.info("App", `Rendered`);
return (
<ThemeContextProvider>
<ThemeSelector />
<ThemedTickerComponent id=1 />
<TickerComponent id=2 />
</ThemeContextProvider>
);
import React, useState from "react";
const defaultContext =
theme: "dark",
setTheme: () =>
;
export const ThemeContext = React.createContext(defaultContext);
export const ThemeContextProvider = props =>
const setTheme = theme =>
setState( ...state, theme: theme );
;
const initState =
...defaultContext,
setTheme: setTheme
;
const [state, setState] = useState(initState);
return (
<ThemeContext.Provider value=state>
props.children
</ThemeContext.Provider>
);
;
import React from "react";
import useContext from "react";
import ThemeContext from "./themeContextProvider";
function ThemeSelector()
const theme, setTheme = useContext(ThemeContext);
const onThemeChanged = theme =>
logger.info("ThemeSelector", `Theme selection changed ($theme)`);
setTheme(theme);
;
return (
<div style= padding: "10px 5px 5px 5px" >
<label>
<input
type="radio"
value="dark"
checked=theme === "dark"
onChange=() => onThemeChanged("dark")
/>
Dark
</label>
<label>
<input
type="radio"
value="light"
checked=theme === "light"
onChange=() => onThemeChanged("light")
/>
Light
</label>
</div>
);
module.exports = ThemeSelector;
import React from "react";
import ThemeContext from "./themeContextProvider";
import TickerComponent from "./tickerComponent";
import useContext from "react";
function ThemedTickerComponent(props)
const theme = useContext(ThemeContext);
return <TickerComponent id=props.id theme=theme />;
module.exports = ThemedTickerComponent;
import React from "react";
import useState from "react";
import stockPriceService from "./stockPriceService";
import "./tickerComponent.scss";
function TickerComponent(props)
const [ticker, setTicker] = useState("AAPL");
const currentPrice = stockPriceService.fetchPricesForTicker(ticker);
const componentRef = React.createRef();
setTimeout(() =>
componentRef.current.classList.add("render");
setTimeout(() =>
componentRef.current.classList.remove("render");
, 1000);
, 50);
const onChange = event =>
setTicker(event.target.value);
;
return (
<>
<div className="theme-label">
props.theme ? "(supports theme)" : "(only dark mode)"
</div>
<div className=`ticker $props.theme || ""` ref=componentRef>
<select id="lang" onChange=onChange value=ticker>
<option value="">Select</option>
<option value="NFLX">NFLX</option>
<option value="FB">FB</option>
<option value="MSFT">MSFT</option>
<option value="AAPL">AAPL</option>
</select>
<div>
<div className="ticker-name">ticker</div>
<div className="ticker-price">currentPrice</div>
</div>
</div>
</>
);
module.exports = TickerComponent;
Inefficient consumption of useContext
import React from "react";
import ReactDOM from "react-dom";
import useContext from "react";
import TickerComponent from "./tickerComponent";
import ThemedTickerComponent from "./themedTickerComponent";
import ThemeContextProvider from "./themeContextProvider";
import ThemeContext from "./themeContextProvider";
function App()
const theme, setTheme = useContext(ThemeContext);
const onThemeChanged = theme =>
setTheme(theme);
;
return (
<>
<div style= padding: "10px 5px 5px 5px" >
<label>
<input
type="radio"
value="dark"
checked=theme === "dark"
onChange=() => onThemeChanged("dark")
/>
Dark
</label>
<label>
<input
type="radio"
value="light"
checked=theme === "light"
onChange=() => onThemeChanged("light")
/>
Light
</label>
</div>
<ThemedTickerComponent id=1 />
<TickerComponent id=2 theme="" />
</>
);
在低效使用useContext示例中,child组件TickerComponent (2)
自parent后未使用上下文重新渲染<App />
使用上下文并重新渲染。但是在 高效使用 useContext 示例中,child TickerComponent (2) 没有重新渲染,即使它是 parent <ThemeContxtProvider>
重新渲染因为上下文的消耗。
我了解到,没有 React.memo 的子级在父级重新渲染时会重新渲染,那么为什么在 useContext 的高效消费示例中不会发生这种情况?
【问题讨论】:
【参考方案1】:你的问题是你正在考虑像这样的代码
function ComponentToRender()
const count = React.useRef(0)
React.useEffect(() =>
console.log('component rendered', count.current++)
)
return null
function App()
const [count, setCount] = useState(0);
return (
<div>
<h2>You clicked count times!</h2>
<button onClick=() => setCount(count + 1)>Increment</button>
<ComponentToRender />
</div>
);
和
function ComponentToRender()
const count = React.useRef(0)
React.useEffect(() =>
console.log('component rendered', count.current++)
)
return null
function Clicker( children )
const [count, setCount] = useState(0);
return (
<div>
<h2>You clicked count times!</h2>
<button onClick=() => setCount(count + 1)>Increment</button>
children
</div>
);
function App()
return (
<Clicker>
<ComponentToRender />
</Clicker>
);
等效。虽然它们做同样的事情,并且或多或少地以相同的方式表现,但第二个示例将只呈现一次ComponentToRender
,即使在多次按下“增量”按钮之后也是如此。 (而第一个将在每次按下按钮时重新渲染。)
这个概念也适用于您的示例。您的“低效消耗”将触发 App
的重新渲染,并强制刷新该组件的每个直接子级。 “有效消费”没有,因为事实并非如此。在我的简化示例中,ComponentToRender
实际上是由App
渲染的,而不是Clicker
。所以Clicker
的状态变化不会影响ComponentToRender
(只是作为孩子传递)
在第二个示例中,App
的另一种写法是:
function App()
const componentToRenderWithinApp = <ComponentToRender />
return (
<Clicker>
componentToRenderWithinApp
</Clicker>
);
这个相当于<Clicker><ComponentToRender /></Clicker>
【讨论】:
感谢您的回答!但是恕我直言,ComponentToRender
作为孩子粘贴到Clicker
,那么为什么Clicker
的状态变化不会影响ComponentToRender
?那是我无法理解的。
!draft
在你的图片中,ComponentToRender 实际上并不是 Clicker > div 的直接子级,它实际上是上一层。您应该将它放在树表示中的 App 下。
ComponentToRender作为children prop粘贴到Clicker,为什么要放在App下面呢?
不同之处在于创建ComponentToRender的是App。位置无关紧要(即,我传递了要在点击器中呈现的元素)。 Clicker 中的状态更改不需要重新渲染,因为 ComponentToRender 要求重新渲染的唯一方法是让 App 更改以上是关于为啥在 React 中,当父组件重新渲染时子组件不会重新渲染(子组件没有被 React.memo 包裹)?的主要内容,如果未能解决你的问题,请参考以下文章
为啥组件在状态更改后不重新渲染。在 react-native 函数组件中
React.memo()、useCallback()、useMemo() 区别及基本使用