在上篇文章中,我们介绍了React组件通信中最常用的父向子传值,和父组件通过回调函数获得子组件的数据,详情请看让我带你迅速吃透React组件通信:从入门到精通(上篇),以及父子直接通讯,本文将主要介绍兄弟组件之间的传值方式,以及context API跨层级共享方法
一、兄弟组件之间通过状态提升的方法进行通讯
1.什么是兄弟组件以及什么什么是状态提升 在 React 组件开发中,兄弟组件是指拥有同一个直接 / 间接父组件、但互相之间没有直接引用关系的同级组件。由于 React 组件的内部状态是私有隔离的,兄弟组件无法直接访问对方的 state、props 或实例方法,而状态提升(State Lifting) 正是 React 官方推荐的、解决兄弟组件通讯的原生标准方案,完全契合 React 单向数据流的核心设计理念。
由于兄弟组件之间没有直接的层级关系,所以他们不能像父子组件一样直接进行通讯,必须借助共同的父组件做为中转站来进行通讯,这就是转态提升
状态提升这一方法的核心思想就是:将兄弟组件之间需要共享的状态,先提供到他们最近的共同父组件中,然后父组件通过 props 分别传递给两个子组件,最终实现兄弟组件间的双向数据同步与通讯
简单来说,共同父组件就是兄弟组件之间的通讯中转站,所以数据流必须经过这个中转站,没有任何私有的直接通讯
2. 实际用例
3.什么时候选择该方法进行通讯,为什么?
- 该方法在兄弟组件之间层级不深的情况下适用,逻辑清晰,易于使用
- 不需要而外引入任何库,原生支持
- 若兄弟组件之间嵌套的层级过深,或者需要共享的状态很多,则不适合使用,因为由于Prop Driling会导致父组件变得很臃肿,且需要进行多层传递
介绍Context API之前,先让我们了解了解Prop Drilling到底有多恶心?
1.举一个真实开发时会遇到的场景,当全局主题色需要根据从根组件App,传递给 Layout → Header → Nav → UserAvatar 这四层嵌套组件,只有最底层的 UserAvatar需要使用该主题时,中间三层组件都要写冗余的Props透传代码,一旦主题变量改名,所有的中间层都要进行修改,极易出现错误。这就是典型的Drilling场景,也就是我们使用Context API的目的:跳过中间层,实现顶层数据到底层数据的直接共享
二、 Context API跨组件通讯方法
由于兄弟组件之间使用状态提升方法容易形成多层嵌套,导致父组件臃肿且传递困难,于是React提供了Context API跨组件通讯方法来解决这个问题,类似于将需要传递的信息添加为全局变量。
- 为了方便理解,我们可以将这个方法比喻为快递的传递来形容
- createContext 用于创建一个「快递站」的角色,可以设定默认值
- Provide: 用于给指定区域的组件「派送包裹」 ,通过value 属性传递数据
- Consumer/useContext:「领取包裹的方法」(Hook更常用)
- contextType: 类组件中Context的专用属性
以「主题切换」为例,演示从创建 Context 到消费数据的全流程:
1.创建Context(定义共享数据的容器)
2.搭载Provider(包裹需要共享数据的组件树)
在顶层组件(如APP.js)中挂载Provider,让后代组件能够访问主题数据
3.使用Context(一般使用useContext):
其实,Context在这组件传递当中就类似于一个仓库的样子,将需要被派送的组件放在Provider这个派送区域,便可将需要派送的包裹派送往该派送区域内的组件中。
在使用Context API时需要注意的点:
- 只有当组件没有被对应的Provider包裹时,才会使用 createContext 的默认值,即使Provider 的value 为undefined的时候也不会触发该默认值
- 当Provider 的value 变化时,所有使用该 Context 的组件都会被重新渲染,因此:
- 我们可以使用 useCallback 缓存方法,避免方法引用频繁变化
- 或使用 usememo方法 缓存 value 对象,仅当依赖项变化时我们才更新
- 我们可以同时使用多个Provider 嵌套,分别提供不同的共享数据(如主题,用户信息),他们之间并不会互相起冲突
- 当value需要频繁变化时,(Context)重渲染的成本高、浅层组件通讯更高效(比如props)
Contex适用和不适应的场景:
适用场景:
使用在跨多层组件需要共享数据时(如主题、语言、用户登录状态)、小型应用的转态管理(无需引入Redux/Zustand)
不适用的场景: 高频更新状态:实时表单数据,滚动位置,倒计时等 购物车,数据流,大量异步请求等
Context频繁重渲染问题:
由于Context API 的value 只要发生引用变化,所有使用该 Context 的组件都会强制重新渲染,哪怕组件只用到了 value 的一小部分数据 比如 value 里包含 theme 和 user 两个数据,仅 user 变更, 使用 theme 的组件也会重新渲染,造成性能浪费,对于该问题,我们有两个较好的优化方案
1. 拆分 Context (可从根源解决)
不要把所有共享状态塞到一个大的 Context里,根据数据更新频率、业务关联性拆分成多个小的Context。比如低频更新的主题和语言拆成一个,高频更新的用户信息、购物车等内容拆成独立的Context。
2.使用 usememo缓存 value (基础优化)
每次父组件渲染时,直接写在Provider里的对象会被重新渲染,导致 value引用发生变化。用useMemo 缓存 value ,仅依赖项变更时才会更新,避免无效的重新渲染