简单说明下吧其实写useContext因为它是React原生自带所以用的代码还是挺多的,所以实际项目都是用的插件 zustand,这个做状态管理还是挺方便的,但是原生的这个useContext面试难免不问,还是大概总结记录一下。
react 父子子组件穿透 useContext的用法。
// LevelContext.js
step1: 创建
import { createContext } from 'react';
export const LevelContext = createContext(1);
// Heading.js
step2: 使用
import { useContext } from 'react';
import { levelContext } from './LevelContext.js';
从 heading 组件从props中读取level:
export default function Heading({ children }) {
// 从 Context 中获取 level
const level = useContext(LevelContext);
// 根据 level 动态渲染不同的标题
switch (level) {
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
case 4:
return <h4>{children}</h4>;
case 5:
return <h5>{children}</h5>;
case 6:
return <h6>{children}</h6>;
default:
return <div>{children}</div>;
}
}
// Section.js
从刚刚的 levelContext 中读取值:
import { LevelContext } from './LevelContext';
export default function Heading({ level, children }) {
return (
// 使用 LevelContext.Provider 将 level 传递给子组件
<LevelContext.Provider value={level}>
<section>
{children}
</section>
</LevelContext.Provider>
);
}
useContext 只能在React组件中(不是循环或者条件里)立即调用hook
在顶层组件中,使用 Section 和 Heading 组件来测试 useContext 的穿透效果。
// App.js
import Section from './Section';
import Heading from './Heading';
export default function App() {
return (
<div>
{/* 顶层 Section,level 为 1 */}
<Section level={1}>
<Heading>标题 1</Heading>
{/* 嵌套 Section,level 为 2 */}
<Section level={2}>
<Heading>标题 2</Heading>
{/* 再次嵌套 Section,level 为 3 */}
<Section level={3}>
<Heading>标题 3</Heading>
</Section>
</Section>
</Section>
</div>
);
}
运行结果分析
<div>
<section>
<h1>标题 1</h1>
<section>
<h2>标题 2</h2>
<section>
<h3>标题 3</h3>
</section>
</section>
</section>
</div>
本质就是创建一个建议状态树,
1.上下文更新会导致所以该消费Context的组件重新渲染,会导致性能问题,尤其是Context.Provider的value更新时
如果value是一个复杂的对象,且该对象引用发生变化(即使内容没有变化),也会触发重新渲染。
2. useContext 没有内置的浅比较或深比机制,会有不必要的渲染
3. 建议采用外部状态树例如zustand。
这条在补充下,如果是level是异步的情况,首先模拟一个api请求js
// api.js
export async function fetchlevel() {
// 模拟一个异步请求,2秒后返回一个level值
return new Promise((resolve) => {
setTimeout(() => {
resolve(Math.floor(Math.random() * 6) + 1); // 返回1到6的随机数
}, 2000)
})
}
// Section.js
在Section 组件中,我们通过fetchLevel 异步获取 level 值, 并将其通过 LevelContext.Provider 传递给子组件。
// Section.js
import { useState, useEffect } from 'react';
import { levelContext } from './LevelContext';
import { fetchLevel } from './api';
export default function Section({ children }) {
const [level, setLevel] = useState(null); // 用于存储异步获取的 level 值
const [loading, setLoading] = useState(false); // 表示加载状态
useEffect(() => {
async function getLevel() {
const resLevel = await fetchLevel();
setLevel(resLevel);
setLoading(false);
}
getLevel();
},[])
if (loading) {
// 正在加载中
return (<div>Loading...</div>);
}
return (
<LevelContext.Provider value={level}>
<section>
{children}
</section>
</LevelContext.Provider>
)
}
// Heading.js
import { useContext } from 'react';
import { LevelContext } from './LevelContext';
export default function Heading({ children }) {
const level = useContext(LevelContext);
switch (level) {
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
case 4:
return <h4>{children}</h4>;
case 5:
return <h5>{children}</h5>;
case 6:
return <h6>{children}</h6>;
default:
return <div>{children}</div>;
}
}
// App.js
import Section from './Section';
import Heading from './Heading';
export default function App() {
return (
<div>
<Section>
<Heading>标题 1</Heading>
<Section>
<Heading>标题 2</Heading>
<Section>
<Heading>标题 3</Heading>
</Section>
</Section>
</Section>
</div>
);
}
实话说我觉得用着不如 zustand 简洁,传值太多了写的代码也太多了。

文章采用 知识共享署名 4.0 国际许可协议 进行许可,转载时请注明原文链接。