React利用useContext()越级继承/越级执行/越级渲染现象
发布于 作者:苏南大叔 来源:程序如此灵动~
本文属于React上下文context相关的createContext()和useContext()钩子的复习文章,不过从代码上却审视出了一种新的现象结论,就是渲染可能并不是从上到下,一个一个变的,可以越过一些层级进行渲染变化。那么,可以引发组件到底是如何为什么会重新渲染的思考。

苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。本文测试环境:nodejs@20.18.0,create-react-app@5.0.1,react-router-dom@6.27.0,react@18.3.1。定义一个上下文context,然后通过设置其value为其包裹的任意层级的组件,传递函数或数据。
使用context的基本套路
定义一个上下文,以及初始值。
const TestContext = React.createContext("初始值");然后,定义一个接收(消费)上下文的组件。
function L2() {
let context = useContext(TestContext);
console.log(context);
}context.Provider覆盖初始值
使用<.Provider value={}>可以覆盖上下文定义时的初始值,value是必填项目。
// const [token, setToken] = useState("覆盖值");
// 赋予数据在渲染界面里面的可变性
// const getToken = () => {};
// 赋予子代组件,修改祖辈组件数据的能力
//...
<TestContext.Provider value={{ token, setToken, getToken }}>
<L2 />
</TestContext.Provider>输出:
{token: '覆盖值', setConfig: ƒ, getInfo: ƒ}支持组件无限嵌套
后代任意层次的组件,使用上下文,都可以获得传递的函数或数据。不过,这个数据是个副本。直接修改它的话,仅在本层生效。
function L1({ children }) {
console.log("L1");
return <>{children}</>; // 注意这里的写法,不是组件<>而是{}字符串
}单向数据流
值得特别说明的是:context是个单向的数据流,子组件不能修改父组件里面的context,每个子组件拿到的都是一个副本,仅在其组件内生效。
如果想子组件改父组件的值,可以在父组件里面使用useState(),然后赋予子组件对应的委托修改能力。
if (context.setToken && context.getToken) {
context.setToken(context.getToken());
// 这个能够改变渲染中的{context.token},因为属于顶层setState的范畴
context.token = context.getToken();
// 这个不能改变渲染中的{context.token},因为属于非reactive的自身变量
console.log(context.token);
}完整代码
代码一
组件不使用context.provider,也可以拿到值。
import React, { useContext } from "react";
const TestContext = React.createContext({ token: "初始值" });
function L2() {
let context = useContext(TestContext);
return (
<>{context.token}</>
);
}
export default function App() {
return (<L2 />);
}代码二
context的value可以是普通字符串,也可以是函数。
context.provider value={}可以改写上下文的导入值。- 通过
useState()的导出函数,子组件可以执行函数以刷新父组件。 - 后代组件中,不管包裹多少层,都可以根据
context拿到对应数据。 - 点击按钮后,注意:
root和l2都被重新渲染了,L1同时顺次被渲染。
import React, { useCallback, useContext, useState } from "react";
const TestContext = React.createContext({ token: "初始值" });
function L1({ children }) {
console.log("L1");
return <>{children}</>; // 注意这里的写法,不是组件<>而是{}字符串
}
function L2() {
console.log("L2");
let context = useContext(TestContext);
return (
<>
<button
onClick={() => {
if (context.setToken && context.getToken) {
context.setToken(context.getToken());
}
}}
>
直接变换顶层状态,{context.token}
</button>
<br />
</>
);
}
export default function Root() {
console.log("Root");
const [token, setToken] = useState("覆盖值");
const getToken = useCallback(() => {
return Date.now().toString().slice(-6);
}, []);
return (
<TestContext.Provider value={{ token, setToken, getToken }}>
<L1>
<L2 />
</L1>
</TestContext.Provider>
);
}
代码三【本文重点内容】
Context.Provider也可以包装成组件。- 仔细观察本地代码的输出:点击按钮后,
Root被渲染,L2被渲染,但是两者之间的【L1并没有被重新渲染】。
import React, { useCallback, useContext, useState } from "react";
const TestContext = React.createContext({ token: "初始值" });
function L1({ children }) {
console.log("L1");
return <>{children}</>; // 注意这里的写法,不是组件<>而是{}字符串
}
function L2() {
console.log("L2");
let context = useContext(TestContext);
return (
<>
<button
onClick={() => {
if (context.setToken && context.getToken) {
context.setToken(context.getToken());
}
}}
>
直接变换顶层状态,{context.token}
</button>
<br />
</>
);
}
function Root({ children }) {
console.log("Root");
const [token, setToken] = useState("覆盖值");
const getToken = useCallback(() => {
return Date.now().toString().slice(-6);
}, []);
return (
<TestContext.Provider value={{ token, setToken, getToken }}>
{children}
</TestContext.Provider>
);
}
export default function App() {
return (
<Root>
<L1>
<L2 />
</L1>
</Root>
);
}
相关文章
- https://newsn.net/say/react-context.html
- https://newsn.net/say/react-usecontext-usereducer.html
- https://newsn.net/say/react-usereducer-usecontext.html