在之前的文章 探讨:围绕 props 阐述 React 通信 中总结了关于“父子”组件传值,但是当需要在组件树中深层传递参数以及需要在组件间复用相同的参数时,传递 props 就会变得很麻烦。
实际案例: 下述展示有两种状态:① 详情态;② 编辑态(tag形式)
组件设计:通过 isDetailed
来决定是“详情”还是“编辑”。
开发过程:拆分了四层,根组件设置了 isDetailed
来确定最终确定 <ViolationGroup>
中展示。
{data.map((item) =>
isDetailed ? (
<div>{item.disposeReasonName}</div>
) : (
<Tag bordered={false}>{item.disposeReasonName}</Tag>
),
)}
props 传递(逐层传递)
根组件设置 isDetailed
,然后逐层传递。
☝️缺点:需要找到最近的父节点,“状态提升” 到太高的层级会导致“逐层传递props”的情况。
✌️优势:这样做可以让哪些组件用了哪些数据变得十分清晰!
context 传递(深层传递)
Context 使组件向其下方的整个树提供信息,会穿过中间的任何组件。子组件可以通过某种方式“访问”到组件树中某处在其上层的数据。
无需逐层透传,直接广播形式!需要的组件直接获取。
- 创建 一个 context。(可以将其命名为
IsDetailedContext
)
export const IsDetailedContext = createContext(false);
- 在需要数据的组件内 使用 刚刚创建的 context。(
ViolationGroup
将会使用IsDetailedContext
)
const isDetailed = useContext(IsDetailedContext);
- 在指定数据的组件中 提供 这个 context。 (
根组件
将会提供IsDetailedContext
)
<IsDetailedContext.Provider value={true}>
<DetailViolationGroupList violationGroupList={data?.detail?.violationGroupList}></DetailViolationGroupList>
</IsDetailedContext.Provider>
☝️缺点:对数据的抽离,导致理解成本略高。
✌️优势:许多组件需要相同的信息,避免通过许多中间组件向下传递 props(冗长)!
⚓ Context 的工作方式类似于 CSS 属性继承。在 React 中,覆盖来自上层的某些 context 的唯一方法是将子组件包裹到一个提供不同值的 context provider 中。
包装成组件形式
定义:组件形式
/* context.js */
import { createContext, useContext } from 'react';
export const IsDetailedContext = createContext(false);
export const IsDetailedProvider = ({ children, isDetailed }) => {
const originalIsDetailed = useContext(IsDetailedContext); // 原始默认值 false
return (
<IsDetailedContext.Provider value={isDetailed ?? originalIsDetailed}>
{children}
</IsDetailedContext.Provider>
);
};
使用方
<IsDetailedProvider isDetailed={true}>
<DetailViolationGroupList
violationGroupList={data?.detail?.violationGroupList}
></DetailViolationGroupList>
</IsDetailedProvider>
知识点:空值合并运算符
🐾 空值合并运算符(??
)是一个逻辑运算符,当左侧的操作数为 null
或者 undefined
时,返回其右侧操作数,否则返回左侧操作数。与逻辑或运算符(||
)不同,逻辑或运算符会在左侧操作数为假值时返回右侧操作数。也就是说,如果使用 ||
来为某些变量设置默认值,可能会遇到意料之外的行为。
'' ?? 1 // ''
'' || 1 // 1
0 ?? 1 // 0
0 || 1 // 1
与 state 结合
Context 不局限于静态值。如果在下一次渲染时传递不同的值,React 将会更新读取它的所有下层组件!可以和 state 结合使用。
children 传递(jsx)
抽象组件并将 JSX 作为 children
传递。
上述示例,让 Item
把 children
当做一个参数,渲染 <Item><ViolationGroup isDetailed={true} /></Item>
,然后去掉<DetailViolationGroupList>
层。这样就减少了定义数据的组件和使用数据的组件之间的层级。=> 直接在父组件中引用使用!