簡體   English   中英

為什么在 React 中,當父組件重新渲染時子組件不重新渲染(子組件未被 React.memo 包裝)?

[英]Why, in React, do children not re-render when parent component re-renders(children are not wrapped by React.memo)?

在這篇文章React Hooks - 了解組件重新渲染中,我了解到當我們在父組件中使用useContext Hook 時,只有使用上下文的子組件會重新渲染。

並且文章給出了context的兩種消費方式。 看一下片段:

高效消費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>
      &nbsp;&nbsp;
      <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;

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>
        &nbsp;&nbsp;
        <label>
          <input
            type="radio"
            value="light"
            checked={theme === "light"}
            onChange={() => onThemeChanged("light")}
          />
          Light
        </label>
      </div>
      <ThemedTickerComponent id={1} />
      <TickerComponent id={2} theme="" />
    </>
  );
}

useContext 的低效消費示例中,組件TickerComponent (2)沒有使用上下文重新渲染,因為<App />使用上下文並重新渲染。 但是在Efficient consumption of useContext示例中,TickerComponent (2) 沒有重新渲染,即使它的<ThemeContxtProvider>因為上下文的消耗而重新渲染。

我了解到沒有 React.memo 的孩子會在父母重新渲染時重新渲染,那么為什么在高效使用 useContext示例中沒有發生這種情況?

你的問題是你正在考慮像這樣的代碼

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的 state 中的更改不會影響ComponentToRender (剛剛作為子項傳遞)

App的另一種寫法,在第二個例子中,是:

function App() {
  const componentToRenderWithinApp = <ComponentToRender />

  return (
    <Clicker>
      {componentToRenderWithinApp}
    </Clicker>
  );
}

這個相當於<Clicker><ComponentToRender /></Clicker>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM