React:上下文提供者何时更新其消费者?
React: When Does Context Provider Update its Consumers?
据我了解,React 的上下文 Provider 会在 context value 更改时更新其 Consumers .
All consumers that are descendants of a Provider will re-render
whenever the Provider’s value
prop changes. 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.
Changes are determined by comparing the new and old values using the
same algorithm as Object.is.
然而,下面的代码似乎表明了相反的意思:
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"/>
如图所示,点击Change OtherState
时:
- 包含上下文
Provider
的 parent 组件将 re-render,从而允许 Provider
看到确实有 not 已 任何 更改为 上下文值
- child人会 re-render 因为他们的 parent 做了,但是那个过程停止了 mid-ways 在这个过程中
Toolbox
是 PureComponent
- 现在,上下文
Provider
的整个想法是,如果 上下文值,它应该 只 更新它的 Consumers
改变了
- 更改检查是使用
Object.is
完成的,如文档中所述(见上文)
- 尽管如此,
Consumer
(ThemedButton
) 仍会在 OtherState
更改时更新
- 这不应该发生,因为 上下文值 实际上 没有 改变,而 child re-rendering 在
PureComponent
中间停止
- 只有当 上下文值 发生变化时
Consumers
才会更新,即使 re-rendering 在具有 PureComponent
的中间组件中停止
PS:通过查看 Object.is
控制台日志,您可以看到单击 Change OtherState
时 上下文值 没有改变.
问题
为什么 ThemedButton
re-render,而 上下文值 没有改变?
useState
returns 每次调用一个新数组。因此,您将一个新数组传递给每个渲染的上下文。
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"/>
据我了解,React 的上下文 Provider 会在 context value 更改时更新其 Consumers .
All consumers that are descendants of a Provider will re-render whenever the Provider’s
value
prop changes. The propagation from Provider to its descendant consumers is not subject to theshouldComponentUpdate
method, so the consumer is updated even when an ancestor component bails out of the update.Changes are determined by comparing the new and old values using the same algorithm as Object.is.
然而,下面的代码似乎表明了相反的意思:
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"/>
如图所示,点击Change OtherState
时:
- 包含上下文
Provider
的 parent 组件将 re-render,从而允许Provider
看到确实有 not 已 任何 更改为 上下文值 - child人会 re-render 因为他们的 parent 做了,但是那个过程停止了 mid-ways 在这个过程中
Toolbox
是PureComponent
- 现在,上下文
Provider
的整个想法是,如果 上下文值,它应该 只 更新它的Consumers
改变了 - 更改检查是使用
Object.is
完成的,如文档中所述(见上文) - 尽管如此,
Consumer
(ThemedButton
) 仍会在OtherState
更改时更新 - 这不应该发生,因为 上下文值 实际上 没有 改变,而 child re-rendering 在
PureComponent
中间停止
- 只有当 上下文值 发生变化时
Consumers
才会更新,即使 re-rendering 在具有PureComponent
的中间组件中停止
PS:通过查看 Object.is
控制台日志,您可以看到单击 Change OtherState
时 上下文值 没有改变.
问题
为什么 ThemedButton
re-render,而 上下文值 没有改变?
useState
returns 每次调用一个新数组。因此,您将一个新数组传递给每个渲染的上下文。
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"/>