React 中 useContext 的用法与性能问题详解

文章目录

  • 一、useContext 是什么?
  • 二、useContext 使用场景
  • 三、使用步骤
    • 1.使用 createContext 创建一个 Context
    • 2.使用 Provider 提供值
    • 3.使用 useContext 访问 Context
    • 完整示例
  • 四、Provider 的 value 类型
  • 五、如何在子组件中修改 context 的数据?
  • 六、使用 `useContext` 的考量
  • 七、如何避免 useContext 带来不必要的重新渲染?
  • 八、Provider 的嵌套使用
  • 总结


一、useContext 是什么?

useContext 是 React 的一个 Hook,它允许你在组件树中跨多层级访问 context 的值,而无需通过每层手动传递 props。

二、useContext 使用场景

例如,可以在应用中使用 useContext 来访问用户认证信息、主题设置、语言偏好等全局状态,而不必在每个组件中手动传递这些信息。这样可以简化组件的逻辑,使代码更加清晰和易于维护。
举个例子:父组件A里面定义的状态state1,它的子组件B需要用到父组件的状态state1,通常我们都会通过父组件向子组件传入 props 属性,借助 props 进行父子通信。如果此时,子组件B中又有一个子组件C,子组件C中又有子组件D……等,这些组件都需要父组件A中的状态state1,那么我们该怎么办呢?可能还是会有些人想着用 props 一层层的向下传递,这种方式在这种场景下存在如下弊端

  1. 嵌套过深:随着数据向下传递,组件树会变得非常深,导致代码结构复杂,难以维护和理解。

  2. 组件耦合度高:如果需要传递的数据在多个层级的组件中都要用到,那么就需要在每个中间组件中传递这些数据,导致组件之间的耦合度增加。

  3. 组件复用性差:如果某个中间组件不需要使用传递下来的数据,但是为了传递给子组件而接收这些数据,会导致组件的复用性变差。

维护困难:当需要修改传递的数据时,需要逐层去修改传递的代码,容易出错且不易维护。

因此,使用 props 一层层地向下传递数据会导致代码结构复杂、耦合度高、维护困难等问题。而 useContext 提供了一种更方便、更高效的方式来在组件之间共享数据,能够更好地解决这些问题。

注:Redux、Mobx 等三方状态管理库也适合解决此类问题,但是本文主要探讨使用 React 自带的 hook 来做状态管理。

三、使用步骤

1.使用 createContext 创建一个 Context

首先需要创建一个 Context 对象。这通常在组件树的顶层完成。

javascript(示例):

import React from 'react';const MyContext = React.createContext(defaultValue);

这里的 defaultValue 是当组件上层没有匹配的 Provider 时,context 的默认值。

(怎么理解这句话?简单地说,如果没有相应的 Provider 去包裹当前子组件, 那么 useContext 将会返回你在创建 Context 时传入的 defaultValue 。Context 的 Provider 为子组件树提供一个值,当你把组件放入 Provider 内部时,你可以通过 useContext 获得这个值,而不用传递 props。如果组件不在 Provider 内部,useContext 将会返回创建 Context 时提供的 defaultValue。继续往下阅读,再回过头来看这句话你就会明白了。)

2.使用 Provider 提供值

在组件树中用 Provider 包裹住它的子组件,以提供 context 的当前值给这些子组件。

javascript(示例):

<MyContext.Provider value={/* 某个值 */}>{/* 子组件 */}
</MyContext.Provider>

通过将 value 属性设置为你希望在组件树中共享的数据,你可以在子组件中访问这个值。

扩展Provider 是由步骤1中 createContext 后返回 context 对象中的一个属性,可以看作一个 React 组件,它用于创建一个 Context,并将其提供给后代组件使用。Provider 组件接收一个 value 属性,用于传递给后代组件的数据。当后代组件使用 useContext Hook 时,它们会从最近的 Provider 中获取到对应的数据。是的,你没听错,从最近的 Provider 中获取,意味着 Provider 可以嵌套。文章后面会有相关示例代码。


3.使用 useContext 访问 Context

在你需要访问 context 的子组件中,使用 useContext Hook 来读取 context 的当前值。

import React, { useContext } from 'react';function MyComponent() {const contextValue = useContext(MyContext);return <div>{contextValue}</div>;
}

你必须将 createContext() 时返回的对象作为参数传递给 useContext。(上述例子中,创建的 context 对象叫 ‘MyContext’ ,所以这里就传 ‘MyContext’)

完整示例

import React, { useContext, createContext } from 'react';// 创建一个 Context 对象
const MyContext = createContext('defaultValue');function App() {// 使用 Provider 包裹子组件,并提供 "newValue" 作为 context 值return (<MyContext.Provider value="newValue"><MyComponent /></MyContext.Provider>);
}function MyComponent() {// 在子组件内使用 useContext 获取 context 的值const contextValue = useContext(MyContext);return (<div>Context Value: {contextValue}</div>);
}export default App;

在这个示例中,如果你将 <MyComponent /> 放在 <MyContext.Provider value="newValue"> 之外,则 MyComponent 组件中使用的 contextValue 将是默认值 'defaultValue'。相反,如果放在 Provider 里面,则 contextValue 为提供的值 "newValue"。如果放在了 Provider 里面,但是 Provider 没有提供 value,则 contextValue 仍是默认值 'defaultValue'

四、Provider 的 value 类型

Provider 的 value 可以是任何 JavaScript 数据类型,包括但不限于:

基本数据类型:例如字符串、数字、布尔值等。
对象:可以是普通对象、数组、函数等。
复杂数据结构:例如嵌套对象、数组、Map、Set 等。
函数:可以是普通函数、箭头函数等。
需要注意的是,当 Provider 的 value 发生变化时,所有使用该 Context 的后代组件都会重新渲染。因此,在使用时需要注意避免在每次渲染时都创建新的对象或函数,以免导致不必要的重新渲染。

五、如何在子组件中修改 context 的数据?

只需要在顶层组件中,定义一个修改状态的方法,通过 Provider 的 value 传给子组件,在子组件中调用该方法即可。

javascript(示例):

import React, { useState, createContext, useContext } from 'react';// 创建 Context 并附上默认值
const MyContext = createContext({user: 'Guest',isAuthenticated: false,setUser: () => {}
});function App() {const [user, setUser] = useState('');const [isAuthenticated, setIsAuthenticated] = useState(false);return (<MyContext.Provider value={{ user, isAuthenticated, setUser, setIsAuthenticated }}>{/* 子组件 */}<MyComponent /></MyContext.Provider>);
}function MyComponent() {// 使用 useContext 获取 context 的值const { user, isAuthenticated, setUser, setIsAuthenticated } = useContext(MyContext);// 修改 context 中的值const onclick = () => {setUser("李逍遥")}return (<div><button onClick={onclick}>点击修改用户名称</button>User: {user} <br />isAuthenticated: {isAuthenticated ? 'Yes' : 'No'}{/* 其他渲染逻辑 */}</div>);
}

注意:当使用 React 的 useContext 时,如果 context 的值发生变化所有使用该 context 的子组件都会重新渲染。这在某些情况下可能导致性能问题,特别是当有很多组件依赖于同一个 context 并且这些组件的渲染开销很大时。因此,引发了下面的思考。

六、使用 useContext 的考量

上面多次提到,Provider 中 value 值发生变化,导致内部所有子组件重新渲染,那么使用 useContext 进行状态管理是否还合理呢?
答案肯定是合理的。
使用 useContext 做组件树中跨多层级访问数据是合理的,尤其是在以下情况:

  1. 共享的数据不经常变化:如果共享的数据不是频繁变动的,那么使用 useContext 是合适的。
  2. 共享的数据量不大:如果你只是共享一些基本的数据,比如用户的登录状态,主题设置等,那么使用 useContext 是适当的。

总之要合理的去使用 useContext ,而不是毫无顾忌地过度使用。

七、如何避免 useContext 带来不必要的重新渲染?

要减少不必要的渲染,你可以采取以下措施:

  1. 拆分 Context:如果你的 context 包含了多个独立变化的值,考虑将它们拆分成多个独立的 context。这样,当某个特定部分的数据发生变化时,只有依赖于那部分数据的组件会重新渲染。

    const UserContext = createContext(userDefaultValue);
    const SettingsContext = createContext(settingsDefaultValue);
    
    <div><UserContext.Provider value={userValue}>// ...只需要 userValue 数据的子组件</UserContext.Provider><SettingsContext.Provider value={settingsValue}>// ...只需要 settingsValue 数据的子组件</SettingsContext.Provider>
    </div>
    
  2. 使用 React.memoshouldComponentUpdate:对于类组件,可以使用 shouldComponentUpdate 生命周期方法。对于函数组件,可以使用 React.memo 来避免不必要的渲染。

    const MyComponent = React.memo(function MyComponent(props) {// 渲染组件
    });
    

    React.memo 只会在组件的 props 发生变化时才会重新渲染组件。

  3. 精细管理 state:确保不是每次都更新整个 context 对象。使用 useReducer 或者将 state 分散到多个 useState 调用中,这样就只有相关的数据变化时才会触发更新。

通过这些方法,你可以有效地管理依赖于 context 的组件的渲染行为,同时还保持了 context 作为状态共享的优势。不过,每种方法都有其适用场景,需要根据具体情况选择使用。

八、Provider 的嵌套使用

多个 Context Provider 可以嵌套使用。在 React 中,这是一种常见的模式,用于将不同的数据和行为分散到组件树的不同层级。这样做可以创建多个独立的上下文环境,每个环境负责管理其自己的数据和逻辑,也是说对 context 进行模块化管理

javascript(示例):

<MyContext.Provider value={theme}>// ...子组件<UserContext.Provider value={userInfo}>// ...子组件</UserContext.Provider>
</MyContext.Provider>

这种模式的优点在于,它使得状态管理更加模块化和清晰。组件可以自由地选择订阅其中的一个或多个上下文,而不是从一个庞大、混杂的上下文对象中获取所需的所有信息。

注意事项

  1. 性能考量:虽然可以嵌套多个 Provider,但这也意味着组件树将变得更加复杂。如果每个 Provider 都管理着大量的状态,那么任何状态变化都可能导致大范围的组件重新渲染。因此,合理规划和组织你的上下文结构是非常重要的。

  2. 上下文分离:尽可能保持不同上下文的独立性。如果两个上下文之间存在强依赖关系,这可能是重新考虑你的状态管理策略的一个信号。

  3. 访问上下文:在子组件中,你可以通过 useContext Hook 来访问这些上下文。每个上下文提供的是其独立的数据和功能,这样你就可以在组件中灵活地选择所需的上下文。

总结

useContext 只在函数组件或自定义 Hook 中有效。
当 context 值变化时,所有使用 useContext Hook 的组件都将重新渲染。

使用 useContext 可以帮助你避免复杂的组件结构并简化数据传递。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/168625.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试:Docker相关问题

文章目录 请解释一下什么是 Docker&#xff0c;以及它在云环境中的应用请简述Docker和LXC的区别什么是Docker Compose&#xff1f;请简述其作用和使用场景在使用Docker时&#xff0c;如何为容器创建一个可访问的网络当一个Docker容器运行异常时&#xff0c;如何通过Docker命令查…

音视频项目—基于FFmpeg和SDL的音视频播放器解析(十八)

介绍 在本系列&#xff0c;我打算花大篇幅讲解我的 gitee 项目音视频播放器&#xff0c;在这个项目&#xff0c;您可以学到音视频解封装&#xff0c;解码&#xff0c;SDL渲染相关的知识。您对源代码感兴趣的话&#xff0c;请查看基于FFmpeg和SDL的音视频播放器 如果您不理解本…

springsecurity6配置一

springsecurity6默认的过滤器是UsernamePasswordAuthenticationToken。具体操作步骤如下: 一、定义一个实体实现springsecurity的UserDetails接口 package com.school.information.core.security.entity;import com.alibaba.fastjson.annotation.JSONField; import com.scho…

GitHub Copilot 替代品?

应该没人不知道代码补全这个东西了吧&#xff0c;第一次使用 GitHub Copilot 之后&#xff0c;只觉得真香&#xff0c;现在居然还有一点离不了了。后面因为收费原因&#xff0c;就没再用了&#xff0c;找了一个 tabnine 替代&#xff0c;用了几天&#xff0c;体验是真的比不上 …

Python中如何选择Web开发框架?

Python开发中Web框架可谓是百花齐放&#xff0c;各式各样的web框架层出不穷&#xff0c;那么对于需要进行Python开发的我们来说&#xff0c;如何选择web框架也就变成了一门学问了。本篇文章主要是介绍目前一些比较有特点受欢迎的Web框架&#xff0c;我们可以根据各个Web框架的特…

计算机组成原理-固态硬盘SSD

文章目录 总览机械硬盘vs固态硬盘固态硬盘的结构固态硬盘与机械硬盘相比的特点磨损均衡技术例题 总览 机械硬盘vs固态硬盘 固态硬盘采用闪存技术&#xff0c;是电可擦除ROM 下图右边黑色的块块就是一块一块的闪存芯片 固态硬盘的结构 块大小16KB~512KB 页大小512B~4KB 对固…

【Java】智慧工地云平台源码(APP+SaaS模式)

在谈论“智慧工地”之前&#xff0c;我们首先得知道传统工地为什么跟不上时代了。 说起传统工地&#xff0c;总有一些很突出的问题&#xff1a;比如工友多且杂&#xff0c;他们是否入场、身体状况如何&#xff0c;管理人员只能依靠巡查、手工纪录来判断&#xff0c;耗时耗力&am…

FANUC机器人系统配置相关--系统变量介绍

FANUC机器人系统配置相关–系统变量介绍 系统配置页相关变量 1- 停电处理$SEMIPOWERFL = TRUE(有效)/FALSE(无效) 2- 停电处理中的I/O $PWF_IO = 1(不恢复)/2(仿真恢复)/3(解除仿真)/4(恢复所有) 3- 停电处理无效时自动执行的程序 $PWR_NORMAL = ‘’ 4- 停电处理有效时自动…

【vue_1】console.log没有反应

1、打印不出来&#xff1f;2、警告也会出现问题3、插播&#xff1a;如何使用if-else 语句来处理逻辑 1、打印不出来&#xff1f; 要做一个权限不够的弹出消息框 const authority_message () > {ElMessage({type: warrnings,message: 当前用户的权限不够});console.log(he…

微服务qiankun通信方式

qiankun&#xff1a; 是一种类似于微服务的架构&#xff0c;是将一个大型应用拆分成若干个更小、更简单&#xff0c;可以独立开发、测试和部署的子应用&#xff0c;然后由一个基座应用根据路由进行应用切换&#xff0c;主要是为了解决大型工程在变更、维护、扩展等方面的困难而…

详解C语言中的指针数组和数组指针

指针数组和数组指针是 C 语言中比较常见的两种类型。它们虽然名字很相似&#xff0c;但是含义、用法以及指向类型都不同&#xff0c;需要分开理解。 指针数组 指针数组是一个数组&#xff0c;其中每个元素都是一个指针。这些指针可以指向不同类型的数据&#xff0c;也可以指向…

1457.二叉树中的伪回文路径

​​题目来源&#xff1a; leetcode题目&#xff0c;网址&#xff1a;1457. 二叉树中的伪回文路径 - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 深度优先遍历的同时对该路径上的 1~9 计数&#xff0c;当某条路径遍历完成后&#xff0c;若总的数据个数为偶数…

ES、TS和JS三者的关系和区别,还有nodejs

ES、TS和JS的分别是以下三个名字的简写: ECMAScript TypeScript JavaScript ECMAScript 与 JavaScript 的关系: JavaScript 由网景公司开发,之后提交给了ECMA(欧洲计算机制造商协会),他们制定了一个标准叫 ECMAScript 。 JavaScript 是 ECMAScript 标准执行的参考,也是…

Springboot学生疫情管理系统-计算机毕设 附源码 25567

Springboot学生疫情管理系统的设计与实现 摘 要 随着互联网趋势的到来&#xff0c;各行各业都在考虑利用互联网将自己推广出去&#xff0c;最好方式就是建立自己的互联网系统&#xff0c;并对其进行维护和管理。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xf…

linux DNS配置

DNS配置 linux的DNS配置文件/etc/resolv.conf用于设置DNS的地址与参数。具体配置参数说明可以参考resolv.conf(5) # cat /etc/resolv.conf options timeout:1 attempts:1 rotate nameserver 114.114.114.114 nameserver 8.8.8.8 nameserver 1.1.1.1 nameserver 223.5.5.5opti…

基于opencv+ImageAI+tensorflow的智能动漫人物识别系统——深度学习算法应用(含python、JS、模型源码)+数据集(二)

目录 前言总体设计系统整体结构图系统流程图 运行环境爬虫模型训练实际应用 模块实现1. 数据准备1&#xff09;爬虫下载原始图片2&#xff09;手动筛选图片 相关其它博客工程源代码下载其它资料下载 前言 本项目通过爬虫技术获取图片&#xff0c;利用OpenCV库对图像进行处理&a…

为何百兆静态库能打进数兆的可执行文件?

第三方库是工程开发必不可少的部分&#xff0c;而第三方库可以是.a和.framework的静态库&#xff0c;也可以是.framework的动态库&#xff0c;其中静态库是最常用的方式。 静态库往往比较大&#xff0c;可在打包到可执行文件之后&#xff0c;对安装包大小的增加远远小于静态库本…

原生小程序图表

原生小程序使用图表 话不多说直接进入正题 官方文档: https://www.ucharts.cn/v2/#/ 下载文件 首先去gitee上把文件下载到自己的项目中 https://gitee.com/uCharts/uCharts 找到微信小程序和里面的组件 把里面src下的文件全部下载下来放入自己项目中 项目文件 新建文件…

【海德教育】国家开放大学的学习形式

国家开放大学的学习形式主要是通过在线学习与面授学习相结合的形式。 在线学习&#xff1a;考生通过国开学习平台等网络工具&#xff0c;与同学、老师进行学习交流。 面授学习&#xff1a;考生到教学点参加集中面授学习或参加小组学习。

SpringBoot——定制错误页面及原理

优质博文&#xff1a;IT-BLOG-CN 一、SpringBoot 默认的错误处理机制 【1】浏览器返回的默认错误页面如下&#xff1a; ☞ 浏览器发送请求的请求头信息如下&#xff1a; text/html会在后面的源码分析中说到。 【2】如果是其他客户端&#xff0c;默认则响应错误的 JSON字符串&…