简体   繁体   English

React:上下文提供者何时更新其消费者?

[英]React: When Does Context Provider Update its Consumers?

To my understanding, React's Context Provider updates its Consumers whenever the context value changes.据我了解,每当上下文值发生变化时,React 的上下文提供者都会更新其消费者

From the Docs :从文档

All consumers that are descendants of a Provider will re-render whenever the Provider's value prop changes.每当 Provider 的value属性发生变化时,所有作为 Provider 后代的消费者都会重新渲染。 The propagation from Provider to its descendant consumers is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component bails out of the update.从 Provider 到其后代消费者的传播不受shouldComponentUpdate方法的影响,因此即使祖先组件退出更新,消费者也会更新。

Changes are determined by comparing the new and old values using the same algorithm as Object.is .更改是通过使用与Object.is相同的算法比较新旧值来确定的。

However, the following code seems to indicate the opposite:但是,以下代码似乎表明相反:

 var themes = { light: { name: "Light", foreground: "#000000", background: "#eeeeee" }, dark: { name: "Dark", foreground: "#ffffff", background: "#222222" } }; const ThemeContext = React.createContext({ theme: themes.light, updateTheme: () => {} }); let prevTheme = undefined; function App() { console.log("RE-RENDERING App..."); const stateArray = React.useState(themes.light); const [theme, setTheme] = stateArray; const [otherState, setOtherState] = React.useState(true); function handleSetOtherState() { console.log("SETTING OTHER STATE....."); setOtherState(prevState => !prevState); } console.log("theme:", theme); console.log("prevTheme:", prevTheme); console.log(`Object.is(prevTheme, theme): ${Object.is(prevTheme, theme)}`); prevTheme = theme; return ( <ThemeContext.Provider value={stateArray}> <Toolbar /> <button onClick={handleSetOtherState}>Change OtherState</button> </ThemeContext.Provider> ); } class Toolbar extends React.PureComponent { render() { console.log("RE-RENDERING Toolbar (DOES NOT HAPPEN WHEN CHANGING OTHERSTATE)..."); return ( <div> <ThemedButton /> </div> ); } } function ThemedButton() { console.log("RE-RENDERING ThemedButton (SHOULD NOT HAPPEN WHEN CHANGING OTHERSTATE)..."); const themeContext = React.useContext(ThemeContext); const [theme, setTheme] = themeContext; console.log("themeContext:", themeContext); console.log("theme.name:", theme.name); console.log("setTheme:", setTheme); function handleToggleTheme() { console.log("SETTING THEME STATE....."); setTheme( prevState => themes.dark ); } return <button onClick={handleToggleTheme}>Click me: {theme.name}</button>; } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
 <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> <div id="root"/>

As seen, when clicking Change OtherState :如图所示,当点击Change OtherState

  1. The parent component enclosing the context Provider will re-render, thus allowing the Provider to see that there has indeed not been any change to the context value包含上下文Provider的父组件将重新渲染,从而允许Provider看到上下文值确实没有任何更改
  2. The children will re-render because their parent did, but that process is stopped mid-ways in the process by Toolbox being a PureComponent孩子们将重新渲染,因为他们的父母这样做了,但是由于ToolboxPureComponent ,该过程在过程PureComponent
  3. Now, the whole idea of the context Provider , is that it should only update its Consumers if the context value changed现在,上下文Provider的整个想法是,如果上下文值发生变化,它应该更新其Consumers
  4. The change check is done with Object.is , as specified in the Docs (see above)更改检查是使用Object.is完成的,如文档中所述(见上文)
  5. Regardless of that fact, the Consumer ( ThemedButton ) still updates when OtherState changes无论如何,当OtherState更改时, Consumer ( ThemedButton ) 仍会更新
  6. That should not happen, because the context value did in fact not change, and the child re-rendering is stopped in the middle with PureComponent这不应该发生,因为上下文值实际上并没有改变,并且使用PureComponent在中间停止了子重新渲染
  7. Only when the context value changes should the Consumers update, even if the re-rendering is stopped in intermediate components with PureComponent仅当上下文值更改时, Consumers应更新,即使在使用PureComponent中间组件中停止重新渲染

PS: You can see that context value does not change when Change OtherState is clicked, by looking at the Object.is console log. PS:通过查看Object.is控制台日志,您可以看到单击Change OtherState上下文值不会更改。

Question

Why does ThemedButton re-render, when the context value did not change?上下文值没有改变时,为什么ThemedButton重新渲染?

useState returns a new array every call. useState每次调用useState返回一个新数组。 So you pass a new array to context every render.因此,您将一个新数组传递给每次渲染的上下文。 useMemo to fix the issue. useMemo来解决这个问题。

 var themes = { light: { name: "Light", foreground: "#000000", background: "#eeeeee" }, dark: { name: "Dark", foreground: "#ffffff", background: "#222222" } }; const ThemeContext = React.createContext({ theme: themes.light, updateTheme: () => {} }); let prevTheme = undefined; let prevStateArray = undefined; function App() { console.log("RE-RENDERING App..."); const stateArray = React.useState(themes.light); console.log('stateArray', prevStateArray, stateArray, Object.is(prevStateArray, stateArray)); prevStateArray = stateArray; const [theme, setTheme] = stateArray; const memoState = React.useMemo(() => [theme, setTheme], [theme, setTheme]); const [otherState, setOtherState] = React.useState(true); function handleSetOtherState() { console.log("SETTING OTHER STATE....."); setOtherState(prevState => !prevState); } console.log("theme:", theme); console.log("prevTheme:", prevTheme); console.log(`Object.is(prevTheme, theme): ${Object.is(prevTheme, theme)}`); prevTheme = theme; return ( <ThemeContext.Provider value={memoState}> <Toolbar /> <button onClick={handleSetOtherState}>Change OtherState</button> </ThemeContext.Provider> ); } class Toolbar extends React.PureComponent { render() { console.log("RE-RENDERING Toolbar (DOES NOT HAPPEN WHEN CHANGING OTHERSTATE)..."); return ( <div> <ThemedButton /> </div> ); } } function ThemedButton() { console.log("RE-RENDERING ThemedButton (SHOULD NOT HAPPEN WHEN CHANGING OTHERSTATE)..."); const themeContext = React.useContext(ThemeContext); const [theme, setTheme] = themeContext; console.log("themeContext:", themeContext); console.log("theme.name:", theme.name); console.log("setTheme:", setTheme); function handleToggleTheme() { console.log("SETTING THEME STATE....."); setTheme( prevState => themes.dark ); } return <button onClick={handleToggleTheme}>Click me: {theme.name}</button>; } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
 <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> <div id="root"/>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM