new React Context API 会触发重新渲染吗?
Does new React Context API trigger re-renders?
我一直在尝试理解新的 React Context API 并正在使用它。我只是想检查一个简单的案例——当提供者的数据更新时,所有重新呈现的内容。
勾选this small example on Codesandbox
因此,在我的示例中,我有一个 App
组件 - 具有类似这样的状态 -
this.state = {
number - A random number
text - A static text
}
我从这里创建一个新的 React 上下文,其中包含来自状态的 number
和 text
,并将值传递给两个消费者 Number
和 Text
。
所以我的假设是如果随机数更新,它会改变上下文并且两个组件都应该触发重新渲染。
但实际上,值正在更新,但没有发生重新渲染。
所以,我的问题 -
是否更新到未通过常用重新渲染传播的上下文?因为当上下文改变时我看不到我的日志/颜色改变。
该提供商的所有消费者是否都已更新?
Are updated to the context not propagated via the ususal rerenders? As I cannot see my logs / color changes when context changes.
上下文值的更新不会触发提供者的所有子项的重新呈现,而只会触发从消费者内部呈现的组件,因此在您的情况下,尽管数字组件包含消费者,但数字组件不是't 重新渲染,而不仅仅是 Consumer 中的渲染函数,因此值会随着上下文更新而改变。这样它的性能非常好,因为它不会触发所有子项的重新渲染。
Are all the consumers to that Provider updated or not ?
该提供者的所有消费者都将经历一个更新周期,但他们是否重新渲染由反应虚拟 DOM 比较决定。您可以在控制台中看到此演示 sandbox
编辑
您需要确保组件呈现为 ContextProvider 组件的子组件,并且您将处理程序传递给它,而不是内联呈现它们并更新 ContextProvider 的状态,因为这将触发重新呈现ContextProvider
中的所有组件
性能用法
App.js
constructor() {
super();
this.state = {
number: Math.random() * 100,
text: "testing context api"
updateNumber: this.updateNumber,
};
}
render() {
return (
<AppContext.Provider
value={this.state}
>
{this.props.children}
</AppContext.Provider>
);
}
index.js
class Data extends React.Component {
render() {
return (
<div>
<h1>Welcome to React</h1>
<Number />
<Text />
<TestComp />
<AppContext.Consumer>
{({ updateNumber }) => (
<button onClick={updateNumber}>Change Number </button>
)}
</AppContext.Consumer>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<App>
<Data />
</App>,
rootElement
);
较少的性能使用
App.js
class App extends Component {
constructor() {
super();
this.state = {
number: Math.random() * 100,
text: "testing context api"
};
}
updateNumber = () => {
const randomNumber = Math.random() * 100;
this.setState({ number: randomNumber });
};
render() {
return (
<AppContext.Provider value={this.state}>
<div>
<h1>Welcome to React</h1>
<Number />
<Text />
<TestComp />
<button onClick={this.updateNumber}>Change Number </button>
</div>
</AppContext.Provider>
);
}
}
根据 useContext
Hook 更新您的问题:
const value = useContext(MyContext)
When the nearest <MyContext.Provider>
above the component updates, this Hook will trigger a rerender with the latest context value
passed to that MyContext
provider. Even if an ancestor uses React.memo
or shouldComponentUpdate
, a rerender will still happen starting at the component itself using useContext
.
A component calling useContext
will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization.
因此在下面的代码示例中,组件 Number
和 Text
将随着每个上下文值的更改而重新呈现,因为它们都直接包含 useContext(AppContext)
.
const AppContext = React.createContext();
const Number = React.memo(props => {
const renderCount = useRenderCount();
const contextNo = React.useContext(AppContext);
return (
<div style={{ backgroundColor: `${randomColor()}` }}>
Number: rendered {renderCount.current} times.
</div>
);
});
const Text = React.memo(() => {
const renderCount = useRenderCount();
const context = React.useContext(AppContext);
return (
<div style={{ backgroundColor: `${randomColor()}` }}>
Text: rendered {renderCount.current} times. I rerender with context value
changes!
</div>
);
});
const App = () => {
const [ctxVal, setCtxVal] = React.useState(0);
const [prop, setProp] = React.useState(0);
return (
<AppContext.Provider value={ctxVal}>
<Number prop={prop} />
<Text />
<button onClick={() => setCtxVal(ctxVal + 1)}>
Change context value
</button>
<button onClick={() => setProp(prop + 1)}>
Only change prop in Number
</button>
</AppContext.Provider>
);
};
function useRenderCount() {
const renderCount = React.useRef(1);
React.useEffect(() => {
renderCount.current += 1;
});
return renderCount;
}
function randomColor() {
const letters = "0123456789ABCDEF"; let color = "#";
for (let i = 0; i < 6; i++) color += letters[Math.floor(Math.random() * 16)];
return color;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
我一直在尝试理解新的 React Context API 并正在使用它。我只是想检查一个简单的案例——当提供者的数据更新时,所有重新呈现的内容。
勾选this small example on Codesandbox
因此,在我的示例中,我有一个 App
组件 - 具有类似这样的状态 -
this.state = {
number - A random number
text - A static text
}
我从这里创建一个新的 React 上下文,其中包含来自状态的 number
和 text
,并将值传递给两个消费者 Number
和 Text
。
所以我的假设是如果随机数更新,它会改变上下文并且两个组件都应该触发重新渲染。
但实际上,值正在更新,但没有发生重新渲染。
所以,我的问题 -
是否更新到未通过常用重新渲染传播的上下文?因为当上下文改变时我看不到我的日志/颜色改变。
该提供商的所有消费者是否都已更新?
Are updated to the context not propagated via the ususal rerenders? As I cannot see my logs / color changes when context changes.
上下文值的更新不会触发提供者的所有子项的重新呈现,而只会触发从消费者内部呈现的组件,因此在您的情况下,尽管数字组件包含消费者,但数字组件不是't 重新渲染,而不仅仅是 Consumer 中的渲染函数,因此值会随着上下文更新而改变。这样它的性能非常好,因为它不会触发所有子项的重新渲染。
Are all the consumers to that Provider updated or not ?
该提供者的所有消费者都将经历一个更新周期,但他们是否重新渲染由反应虚拟 DOM 比较决定。您可以在控制台中看到此演示 sandbox
编辑
您需要确保组件呈现为 ContextProvider 组件的子组件,并且您将处理程序传递给它,而不是内联呈现它们并更新 ContextProvider 的状态,因为这将触发重新呈现ContextProvider
性能用法
App.js
constructor() {
super();
this.state = {
number: Math.random() * 100,
text: "testing context api"
updateNumber: this.updateNumber,
};
}
render() {
return (
<AppContext.Provider
value={this.state}
>
{this.props.children}
</AppContext.Provider>
);
}
index.js
class Data extends React.Component {
render() {
return (
<div>
<h1>Welcome to React</h1>
<Number />
<Text />
<TestComp />
<AppContext.Consumer>
{({ updateNumber }) => (
<button onClick={updateNumber}>Change Number </button>
)}
</AppContext.Consumer>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<App>
<Data />
</App>,
rootElement
);
较少的性能使用
App.js
class App extends Component {
constructor() {
super();
this.state = {
number: Math.random() * 100,
text: "testing context api"
};
}
updateNumber = () => {
const randomNumber = Math.random() * 100;
this.setState({ number: randomNumber });
};
render() {
return (
<AppContext.Provider value={this.state}>
<div>
<h1>Welcome to React</h1>
<Number />
<Text />
<TestComp />
<button onClick={this.updateNumber}>Change Number </button>
</div>
</AppContext.Provider>
);
}
}
根据 useContext
Hook 更新您的问题:
const value = useContext(MyContext)
When the nearest
<MyContext.Provider>
above the component updates, this Hook will trigger a rerender with the latest contextvalue
passed to thatMyContext
provider. Even if an ancestor usesReact.memo
orshouldComponentUpdate
, a rerender will still happen starting at the component itself usinguseContext
.A component calling
useContext
will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization.
因此在下面的代码示例中,组件 Number
和 Text
将随着每个上下文值的更改而重新呈现,因为它们都直接包含 useContext(AppContext)
.
const AppContext = React.createContext();
const Number = React.memo(props => {
const renderCount = useRenderCount();
const contextNo = React.useContext(AppContext);
return (
<div style={{ backgroundColor: `${randomColor()}` }}>
Number: rendered {renderCount.current} times.
</div>
);
});
const Text = React.memo(() => {
const renderCount = useRenderCount();
const context = React.useContext(AppContext);
return (
<div style={{ backgroundColor: `${randomColor()}` }}>
Text: rendered {renderCount.current} times. I rerender with context value
changes!
</div>
);
});
const App = () => {
const [ctxVal, setCtxVal] = React.useState(0);
const [prop, setProp] = React.useState(0);
return (
<AppContext.Provider value={ctxVal}>
<Number prop={prop} />
<Text />
<button onClick={() => setCtxVal(ctxVal + 1)}>
Change context value
</button>
<button onClick={() => setProp(prop + 1)}>
Only change prop in Number
</button>
</AppContext.Provider>
);
};
function useRenderCount() {
const renderCount = React.useRef(1);
React.useEffect(() => {
renderCount.current += 1;
});
return renderCount;
}
function randomColor() {
const letters = "0123456789ABCDEF"; let color = "#";
for (let i = 0; i < 6; i++) color += letters[Math.floor(Math.random() * 16)];
return color;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>