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
如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。