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 一层层的向下传递,这种方式在这种场景下存在如下弊端:
-
嵌套过深:随着数据向下传递,组件树会变得非常深,导致代码结构复杂,难以维护和理解。
-
组件耦合度高:如果需要传递的数据在多个层级的组件中都要用到,那么就需要在每个中间组件中传递这些数据,导致组件之间的耦合度增加。
-
组件复用性差:如果某个中间组件不需要使用传递下来的数据,但是为了传递给子组件而接收这些数据,会导致组件的复用性变差。
维护困难:当需要修改传递的数据时,需要逐层去修改传递的代码,容易出错且不易维护。
因此,使用 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
做组件树中跨多层级访问数据是合理的,尤其是在以下情况:
- 共享的数据不经常变化:如果共享的数据不是频繁变动的,那么使用
useContext
是合适的。 - 共享的数据量不大:如果你只是共享一些基本的数据,比如用户的登录状态,主题设置等,那么使用
useContext
是适当的。
总之要合理的去使用 useContext ,而不是毫无顾忌地过度使用。
七、如何避免 useContext 带来不必要的重新渲染?
要减少不必要的渲染,你可以采取以下措施:
-
拆分 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>
-
使用
React.memo
或shouldComponentUpdate
:对于类组件,可以使用shouldComponentUpdate
生命周期方法。对于函数组件,可以使用React.memo
来避免不必要的渲染。const MyComponent = React.memo(function MyComponent(props) {// 渲染组件 });
React.memo
只会在组件的 props 发生变化时才会重新渲染组件。 -
精细管理 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>
这种模式的优点在于,它使得状态管理更加模块化和清晰。组件可以自由地选择订阅其中的一个或多个上下文,而不是从一个庞大、混杂的上下文对象中获取所需的所有信息。
注意事项:
-
性能考量:虽然可以嵌套多个 Provider,但这也意味着组件树将变得更加复杂。如果每个 Provider 都管理着大量的状态,那么任何状态变化都可能导致大范围的组件重新渲染。因此,合理规划和组织你的上下文结构是非常重要的。
-
上下文分离:尽可能保持不同上下文的独立性。如果两个上下文之间存在强依赖关系,这可能是重新考虑你的状态管理策略的一个信号。
-
访问上下文:在子组件中,你可以通过 useContext Hook 来访问这些上下文。每个上下文提供的是其独立的数据和功能,这样你就可以在组件中灵活地选择所需的上下文。
总结
useContext 只在函数组件或自定义 Hook 中有效。
当 context 值变化时,所有使用 useContext Hook 的组件都将重新渲染。
使用 useContext 可以帮助你避免复杂的组件结构并简化数据传递。
相关文章:
React 中 useContext 的用法与性能问题详解
文章目录 一、useContext 是什么?二、useContext 使用场景三、使用步骤1.使用 createContext 创建一个 Context2.使用 Provider 提供值3.使用 useContext 访问 Context完整示例 四、Provider 的 value 类型五、如何在子组件中修改 context 的数据?六、使…...

流程图是什么,用什么软件做?
在工作流程中,经常会遇到需要图形化呈现整个流程的情况。流程图就是一种一目了然的图形化表现方式,便于人们理解、沟通和管理整个流程。 1.Visio Visio是一款微软公司的图表软件,可以用于创建各种类型的流程图、组织结构图、网络图、平面图…...

Linux 家目录和根目录
摘要: 在 Linux 操作系统中,家目录和根目录是两个非常重要的概念。它们是 Linux 文件系统中的两个关键节点,为用户和系统进程提供存储、管理和访问文件和目录的接口。本文旨在深入探讨和理解这两个目录的结构、功能和使用方式,同时…...
js前端跨屏效果
效果: 三个球 源码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>三个球</title> </h…...

配置华为云镜像加速器
登录华为云官网,点击控制台 在服务列表里面寻找swr服务 点击镜像中心,点击镜像加速器 {"registry-mirrors": [ "https://301dc05233c6419b810bdb22135af9eb.mirror.swr.myhuaweicloud.com" ]}配置镜像加速器 vim /etc/docker…...
Redis的四种模式:单机、主从、哨兵、集群
一、简单理解 单机模式:安装你的redis,启动服务即为单机模式。 主从模式:一个主节点搭配一个或多个从节点,无自动故障转移功能,主节点发生故障后,需要人工将其中一个从节点设置为主节点。 哨兵模式&…...

【开源】基于Vue.js的民宿预定管理系统
项目编号: S 058 ,文末获取源码。 \color{red}{项目编号:S058,文末获取源码。} 项目编号:S058,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用例设计2.2 功能设计2.2.1 租客角色…...
网络安全深入学习第九课——本机信息收集
文章目录 一、Windows基本信息收集1、查看当前权限2、查看指定用户的详细信息3、查看用户SID4、查看网卡配置5、查看服务器版本\补丁等6、查看系统架构7、查看安装的软件及版本8、查看本机服务信息9、查询进程信息和列表10、查看启动程序信息11、查看计划任务12、查看主机开机时…...
深入理解C语言指针基础概念:定义、内存地址与声明初始化
导言: 在C语言中,指针是一项强大而重要的概念,直接涉及内存的底层操作。理解指针的基础概念对于熟练运用C语言以及理解底层系统工作原理至关重要。本文将深入研究指针的定义、内存地址的概念以及指针的声明和初始化,帮助读者建立对…...
Django DRF版本号的处理
在restful规范中,后端的API中需要体现版本。如果项目比较大,需要些很多的视图类,在每一个类中都写一遍会比较麻烦,所以drf中也支持了全局配置。在每个版本处理的类中还定义了reverse方法,他是用来反向生成URL并携带相关…...

[工业自动化-25]:IDEC和泉RU2S-24D/RU4S-24D继电器的使用说明和接线方式
目录 一、外观 1.1 继电器整体: 1.2 继电器主体: 1.3 底座: 二、RU系列通用继电器介绍 2.1 总体 2.2 性能规格 2.3 锁存杆 2.4 信号定义与连线 - 2S系列 (1)24V输入 (2)第一路输出 …...
如何通过短视频提高转化率?
在当今信息爆炸的时代,如何让自己的品牌在众多的短视频中脱颖而出,吸引更多的潜在客户,是许多企业面临的问题。抖音和快手作为目前最受欢迎的短视频平台,为企业提供了无限的营销机会。在这篇文章中,我们将探讨如何通过…...

微软离Altman越近,离OpenAI就越远!
大数据产业创新服务媒体 ——聚焦数据 改变商业 在OpenAI这场连续剧中(之所以说是连续剧,这个事情肯定没完,后面肯定还会出续集),让我倍感意外的是,Altman刚跟OpenAI分手,“离婚手续”都还没办…...

minio集群部署(k8s内)
一、前言 minio的部署有几种方式,分别是单节点单磁盘,单节点多磁盘,多节点多磁盘三种方式,本次部署使用多节点多磁盘的方式进行部署,minio集群多节点部署最低要求需要4个节点,集群扩容时也是要求扩容的节点…...

【C语言】函数(四):函数递归与迭代,二者有什么区别
目录 前言递归定义递归的两个必要条件接受一个整型值(无符号),按照顺序打印它的每一位使用函数不允许创建临时变量,求字符串“abcd”的长度求n的阶乘求第n个斐波那契数 迭代总结递归与迭代的主要区别用法不同结构不同时间开销不同…...

[原创](免改BIOS)使用Clover升级旧电脑-(高阶玩法)让固态硬盘内置Win11 PE启动系统
[简介] 常用网名: 猪头三 出生日期: 1981.XX.XXQQ: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、Delphi…...

React项目中发生空白但不报错的原因分析和解决?
文章目录 前言组件渲染问题状态管理问题异步操作问题代码错误但未抛出异常如果我们使用的是chorme浏览器的话,可以下载一个开发者工具,例如下图:代码审查使用调试工具日志和输出检查外部依赖异步操作终极大法,不到万不得已不可以使…...

redis运维(十七)事务
一 redis事务 事务核心参考 ① 基础概念 1、场景引入核心:通过现象思考原因? 2、事务的概念 3、事务四大特性说明: redis只具备部分特性 重点1: 原子性和一致性 重点2: 隔离性和持久性 ② redis的事务 1、基础铺垫备注&…...

Vue框架学习笔记——Vue实例中el和data的两种写法
文章目录 前文提要Vue实例的el第一种写法第二种写法小结 Vue实例中data第一种写法,对象式效果图片第二种写法,函数式效果图片小结 前文提要 本文仅做自己的学习记录,如有错误,请多谅解 Vue实例的el 第一种写法 <body><…...
libbz2 for Mac OS makefile
git地址:git://sourceware.org/git/bzip2.git a文件Makefile # ------------------------------------------------------------------ # This file is part of bzip2/libbzip2, a program and library for # lossless, block-sorting data compression. # # bzip…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...