当前位置: 首页 > news >正文

让你 React 组件水平暴增的 5 个技巧

目录

透传 className、style

通过 forwardRef 暴露一些方法

useCallback、useMemo

用 Context 来跨组件传递值

React.Children、React.cloneElement

总结


最近看了一些 Ant Design 的组件源码,学到一些很实用的技巧,这篇文章来分享一下。

首先,我们用 create-react-app 创建个 React 项目(选择 typescript 模版):

npx create-react-app --template=typescript component-test

图片

进入项目目录,把开发服务跑起来:

npm run start

图片

然后引入 antd:

npm install --save antd

在 App.tsx 里引入几个 antd 组件:

图片

页面上可以看到这俩组件都成功渲染了:

图片

然后我们来看一下 Ant Design 组件里的一些技巧:

透传 className、style

        我们可以给组件设置 className 和 style:

import './App.css';
import { Button } from 'antd';function App() {return (<div className="App"><Button className="aaa bbb" style={{width: '100px',height: '50px'}} type="primary">测试</Button></div>);
}export default App;

在页面里打开 DevTools 可以看到 className 和 style 都被设置到了 button 上。

图片

        这种功能的实现就是透传 className 和 style 的 props。基本 antd 所有的组件都会做这个。比如 VisualList 组件的源码:

图片

        它取了传入的 className、style 的 props,还有剩余的所有 props。对 className 做了一些处理,添加了两个 className:

图片

对 style 也做了扩展,添加了个 position: relative 的样式。

图片

        然后把 style、className,额外的 props 都设置给最外层的 div。

        这样,使用这个组件的时候,就可以自己定义一些样式,设置一些 props。其中,classnames 是用来动态产生 className 的一个包,用起来很简单。

比如这样调用:

classNames('aaa', { bbb: true, ccc: false }, false, { eee: true }); 

        那么最终的 className 就是 'aaa bbb eee'。

        这样,组件用起来体验就和 html 标签差不多,可以自己控制一些样式。这样写 props 的类型的时候,也是直接用了 html 标签的类型。

        比如这个 List 的参数就继承了 React.HTMLArrtibutes<any>,也就是任意 html 标签的属性:

图片

        当然,children 属性是不可以设置的。因为 React 用 children 参数来传递子组件。比如 form 组件,它的参数是继承了 React.FormHTMLAttributes<HTMLFormElement>:

图片

        去掉了 children 和 onSubmit 这俩属性,因为这俩是 From 组件的参数。

        也就是说:antd 的组件基本都支持传入 className、style 或者任何 html 标签的 props,会透传 props 到组件内的容器标签,所以用起来体验和原生标签很类似。但这也要求 props 实现 React.FormHTMLAttributes 的 type。

通过 forwardRef 暴露一些方法

        外界控制组件的方式就是通过传 props,但有时候想调用组件的一些方法呢?这时候就需要 ref 了。我们先来试一下 ref:

图片

        通过 useRef 创建个 ref 对象,然后把 input 标签设置到 ref。在 useEffect 里就可以调用 input 的方法了:

图片

        但这是原生标签,如果是组件呢?这时候就需要 forwardRef 了,也就是把组件内的 ref 转发一下。比如这样:

import './App.css';
import { useRef } from 'react';
import { useEffect } from 'react';
import React from 'react';const Guang: React.ForwardRefRenderFunction<HTMLInputElement> = (props, ref) => {return <div><input ref={ref}></input></div>
}const WrapedGuang = React.forwardRef(Guang);function App() {const ref = useRef<HTMLInputElement>(null);useEffect(()=> {console.log('ref', ref.current)ref.current?.focus()}, []);return (<div className="App"><WrapedGuang ref={ref}/></div>);
}export default App;

其实 forwardRef 这个 api 做的事情也很容易懂。就是把 ref 转发到组件内部来设置:

图片

这样就把组件内的 input 通过 ref 的方式传递到了组件外。效果和之前一样:

图片

不过被 forwardRef 包裹的组件的类型就要用 React.forwardRefRenderFunction 了:

图片

        第一个类型参数是 ref 的 content 的类型。但有的时候,我不是想把原生标签暴露出去,而是暴露一些自定义方法。这时候就需要 useImperativeHandle 的 hook 了。

这样写:

import './App.css';
import { useRef } from 'react';
import { useEffect } from 'react';
import React from 'react';
import { useImperativeHandle } from 'react';interface RefProps {aaa: () => void;
}const Guang: React.ForwardRefRenderFunction<RefProps> = (props, ref) => {const inputRef = useRef<HTMLInputElement>(null);useImperativeHandle(ref, () => {return {aaa() {inputRef.current?.focus();}}});return <div><input ref={inputRef}></input></div>
}const WrapedGuang = React.forwardRef(Guang);function App() {const ref = useRef<RefProps>(null);useEffect(()=> {console.log('ref', ref.current)ref.current?.aaa();}, []);return (<div className="App"><WrapedGuang ref={ref}/></div>);
}export default App;

也就是用 useImperativeHanlde 自定义了 ref 对象:

图片

小结一下:

        React 可以用 ref 保存原生标签,通过 ref.current 调用这个对象的属性、方法。跨组件传递 ref 需要用 forwardRef 方法。如果你要进一步自定义 ref,那就要用 useImperativeHandle 的 hook。

        然后看看 antd 组件是怎么用 ref 的。就如说 VisualList 组件:

图片

它也是包了一层 React.forwardRef,内部用 useImperativeHandle 自定义了 ref:

图片

这样外部就可以调用这个 ref 的方法了:

图片

再比如 Form 组件。它也是被 forwarRef 包裹的函数组件:

图片

内部用 useImperativeHandle 返回了自定义的对象:

图片

所以你才可以这样调用 form 组件的方法:

图片

        这就是说:antd 的组件都会用 forwardRef 包裹一层,用来转发 ref,或者是转发内部的 html 标签的引用,或者是用 useImperativeHandle 自定义 ref 对象,来暴露一些方法。

useCallback、useMemo

        useMemo 和 useCallback 是性能优化相关的 hook。很多人不知道啥时候用,其实看下 antd 怎么用的就知道了:

图片

比如 VisualList 组件里计算 start、end、scrollHeight 这些值需要大量的计算。

        这些计算需要每次 render 都跑一遍么?不需要,只有在某些值变化的时候才需要重新计算。

        这时候用 React.useMemo 包裹就可以减少计算量,它只会在 deps 数组变化的时候执行第一个参数的函数。

        useMemo 是 deps 变化之后重新执行函数创建值,而 useCallback 并不会执行函数,它只是在 deps 变化的时候返回第一个参数的函数:

图片

这样有什么用呢?

        react 重新渲染的依据是 props 是否有变化,如果每次都创建新的函数,那是不是每次都会重新渲染?

        所以用 useCallback 包裹的函数参数,就可以在 deps 没变的时候,始终返回同一个函数,这样避免了没必要的渲染。

        当然,useMemo 也有这个作用。比如说 Form 组件源码里的这个 useMemo:

图片

        你说它是为了减少计算量么?并不是,它没有做任何计算,只是把参数原封不动返回了。这也同样是为了避免 props 变化。

        也就是说:antd 里很多地方都用了 useMemo 和 useCallback 来进行渲染性能优化。useMemo 只有在 deps 数组变化的时候才会执行第一个函数,返回新的值,可以用来减少不必要的计算,也可以保证 props 不变来避免不要的渲染。useCallback 是只有 deps 数组变化的时候才返回第一个函数的值,可以保证 props 不变来用来避免不必要的渲染

用 Context 来跨组件传递值

        antd 里很多配置的传递都是通过 Context。比如 disabled 的设置:

图片

通过 React.createContext 创建 context 对象,通过 Provider 修改 context 的值。

在最外层包裹这个 Provider 组件来修改 context 值:

图片

然后你可以在任意的组件把 context 值取出来用:

图片

图片

        像什么主题、大小等配置,都是通过 Context 传递的。除了用来传递配置外,很多组件也依赖 Context 来传递一些值。

比如 Form:

图片

        在 Form 组件里设置 form 对象,然后 setFieldValue 设置字段值。

        为什么 Form.Item 里加个 name 就可以取出来了呢?我并没有传递 form 参数过去呀?很明显,这里也是用 Context 来传递的。antd 会创建这样一个 context 对象:

图片

然后在外层用 Provider 设置 context 值:

图片

也就是我们这里传的 form:

图片

那 Form.Item 里自然可以拿到 context 的值,从而取到具体字段信息了:

图片

图片

        也就是说:antd 里大量用到了 Context,除了用来传递 config、theme、size 等全局配置信息外,还用来跨组件传递数据,比如 Form、Form.Item 组件,就是通过 Provider、useContext 来存取值的。

React.Children、React.cloneElement

        React 组件可以设置内容,在组件内通过 props.children 来取。

import React from 'react';interface GuangProps {children: React.ReactNode[];
}const Guang: React.FunctionComponent<GuangProps> = (props) => {console.log(props);return <div className="guang">{props.children}</div>
}function App() {return (<div className="App"><Guang><p>111</p><p>222</p></Guang></div>);
}export default App;

比如我在组件里把 props.children 取出来,放到 className 为 guang 的 div 下:

图片

图片

        如果想对这些 children 做一些操作,就需要用 React.Children 的 api 了,比如 React.Children.toArray、React.Children.forEach、React.Children.map。

        有同学说,props.children 本来就是数组啊,直接操作不就行了?不行的,直接操作有一些问题,比如我 sort 一下:

图片

会报错:

图片

所以 props.children 不能直接当做数组用,需要 toArray 一下:

图片

这样就没有报错了:

图片

        同理,React.Children 的 forEach 和 map 也很容易理解。而且还可以用 React.cloneElement 复制下传入的 ReactElement。比如这样:

图片

        用 React.Children.map 遍历 children,对每个 child 复制一份出来,修改下 props ,并且添加一个 children。

效果是这样的:

图片

React.cloneElement 的第二个参数是修改的 props,后面的参数是 children:

图片

        结合 React.Children 的 api 和 React.cloneElement 的 api 就可以任意修改 children 渲染的结果。

        在 antd 里也有大量运用,比如 button 组件里,通过 map + cloneElement 来处理中文字符的问题:

图片

或者用 map + cloneElement 给 child 的 children 外包一层组件:

图片

更巧妙的是 VirtualList 里的应用:

图片

        你不需要给传入的 children 设置 ref,antd 会通过 map + cloneElement 给你加上 ref 的 props,然后在回调函数里把这个 ref 保存下来。

        这样就拿到了你传入的每一个 children 的 ref。比如根据 key 来保存每个 Item 的 ref:

图片

        也就是说:antd 组件里大量用到了 React.Children + React.cloneElement 的 api 对 props.children 做一些修改,比如包一层组件、添加 ref 等参数、添加一些 children 等。

总结

这篇文章总结了 ant design 组件源码里的 5 个技巧:

  • 透传 className、style,还有其他 html 标签的 props,让你的组件用起来体验和原生 html 标签一样

  • 通过 forwardRef + useImperativeHandle 暴露一些方法,每个组件都可以通过 ref 暴露一些 api 出来

  • useCallback、useMemo 缓存计算结果,通过让 props 不变来减少没必要的渲染

  • 用 Context 的 Provider + useContext 来跨组件传递值,可以用来传递全局配置,也可以用来做业务组件的跨层传递数据

  • 通过 React.Children + React.cloneElement 的 api 对 props.children 做各种修改

这些都是在 antd 里随处可见的技巧,可以说任何一个组件里都有这些东西。

这些写 React 组件的技巧你都用过么? 没用过的话不妨从今天开始用起来吧。

相关文章:

让你 React 组件水平暴增的 5 个技巧

目录 透传 className、style 通过 forwardRef 暴露一些方法 useCallback、useMemo 用 Context 来跨组件传递值 React.Children、React.cloneElement 总结 最近看了一些 Ant Design 的组件源码&#xff0c;学到一些很实用的技巧&#xff0c;这篇文章来分享一下。 首先&am…...

阿里云部署 ChatGLM2-6B 与 langchain+ChatGLM

1.ChatGLM2-6B 部署 更新系统 apt-get update 安装git apt-get install git-lfs git init git lfs install 克隆 ChatGLM2-6B 源码 git clone https://github.com/THUDM/ChatGLM2-6B.git 克隆 chatglm2-6b 模型 #进入目录 cd ChatGLM2-6B #创建目录 mkdir model #进入目录 cd m…...

F12开发者工具的简单应用

目录 elements 元素 1、元素的定位和修改 2、UI自动化应用 console 控制台 sources 源代码 network 网络 1、定位问题 2、接口测试 3、弱网测试 performance 性能 memory 存储 application 应用 recorder 记录器 界面展示如下&#xff08;设置中可以切换中英文&am…...

【 Python 全栈开发 - 人工智能篇 - 45 】决策树与随机森林

文章目录 一、概念与原理1.1 决策树1.1.1 概念1.1.2 原理特征选择分割方法 1.1.3 优点与缺点1.1.4 Python常用决策树算法 1.2 随机森林1.2.1 概念1.2.2 原理1.2.3 优点与缺点1.2.4 Python常用随机森林算法 1.3 决策树与随机森林的比较1.3.1 相同之处1.3.2 不同之处 二、决策树算…...

SpringBoot集成kafka全面实战

本文是SpringBootKafka的实战讲解&#xff0c;如果对kafka的架构原理还不了解的读者&#xff0c;建议先看一下《大白话kafka架构原理》、《秒懂kafka HA&#xff08;高可用&#xff09;》两篇文章。 一、生产者实践 普通生产者 带回调的生产者 自定义分区器 kafka事务提交…...

新建Git仓库,将本地文件上传至仓库

1、新建仓库&#xff0c;勾选初始化仓库 2、复制仓库链接 3、打开本地文件目录 右键选择 Git Bash Here 打开命令窗口 4、依次按照下面的步骤&#xff08;*如果报错&#xff0c;看原目录下是否存在 .git 需要删除&#xff09; // 生成git文件 git init // 把文件加入暂存区 g…...

算法练习——力扣随笔【LeetCode】【C++】

文章目录 LeetCode 练习随笔力扣上的题目和 OJ题目相比不同之处&#xff1f;定义问题排序问题统计问题其他 LeetCode 练习随笔 做题环境 C 中等题很值&#xff0c;收获挺多的 不会的题看题解&#xff0c;一道题卡1 h &#xff0c;多来几道&#xff0c;时间上耗不起。 力扣上的题…...

web服务器(Tomcat)

目录 一、web服务器 1. 常见web服务器 2. web服务器简介 二、 Apache Tomcat服务器 1. Tomcat服务器简介 2. Tomcat服务器基本使用 3. 启动tomcat常见问题 &#xff08;1&#xff09;启动tomcat控制台乱码 &#xff08;2&#xff09;启动tomcat闪退问题 &#xff08;…...

测试方案、功能测试报告、性能测试报告

测试方案内容概要&#xff1a; 项目内容介绍&#xff0c;测试计划安排&#xff08;人员时间&#xff09;&#xff0c;测试环境&#xff08;系统配置&#xff09;需求功能点&#xff08;内容介绍&#xff0c;测试安排&#xff09;&#xff0c;重点难点场景&#xff0c;系统集成…...

【代码随想录day21】二叉搜索树的最近公共祖先

题目 思路 解题的关键是知道自顶向低递归遍历&#xff0c;第一次遇到root在p和q的区间中时&#xff0c;则root就是p和q的最近公共祖先节点。 递归法 # Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val x # …...

ssm文章发布管理系统java小说作品发表jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 ssm文章发布管理系统 系统有2权限&#xff1a;前台账…...

AXI协议之AXILite开发设计(二)

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 二、AXI-Lite关键代码分析 1、时钟与…...

Qgis二次开发-QgsMapTool地图交互工具详解

1.简介 QgsMapTool地图工具是用于操作地图画布的用户交互式工具。例如&#xff0c;地图平移和缩放功能被实现为地图工具。 QgsMapTool是抽象基类&#xff0c;以下是类的继承关系&#xff1a; 2.常用接口 virtual void canvasDoubleClickEvent (QgsMapMouseEvent *e)重写鼠标…...

MySQL基础(四)数据库备份

目录 前言 一、概述 1.数据备份的重要性 2.造成数据丢失的原因 二、备份类型 &#xff08;一&#xff09;、物理与逻辑角度 1.物理备份 2.逻辑备份 &#xff08;二&#xff09;、数据库备份策略角度 1.完整备份 2.增量备份 三、常见的备份方法 四、备份&#xff08…...

子类化QThread来实现多线程,moveToThread函数的作用

子类化QThread来实现多线程&#xff0c; QThread只有run函数是在新线程里的&#xff0c;其他所有函数都在QThread生成的线程里。正确启动线程的方法是调用QThread::start()来启动。 一、步骤 子类化 QThread&#xff1b;重写run&#xff0c;将耗时的事件放到此函数执行&#…...

经典面试题(力扣,接雨水)

接雨水 方法一思路测试代码复杂度测试结果 方法二思路测试代码复杂度测试结果 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1]…...

2023年深圳杯数学建模C题无人机协同避障航迹规划

2023年深圳杯数学建模 C题 无人机协同避障航迹规划 原题再现&#xff1a; 平面上A、B两个无人机站分别位于半径为500 m的障碍圆两边直径的延长线上&#xff0c;A站距离圆心1 km&#xff0c;B站距离圆心3.5 km。两架无人机分别从A、B两站同时出发&#xff0c;以恒定速率10 m/s…...

PostgreSQL--实现数据库备份恢复详细教学

前言 这是我在这个网站整理的笔记&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;RodmaChen PostgreSQL--实现数据库备份恢复详细教学 一. 数据库备份二. 数据库恢复三. 存留问题 数据库备份恢复功能是每个产品所需的&#xff0c;以下是简单的脚本案例&a…...

JDK工具之jstack说明

JDK工具之jstack说明 前言什么是jstack&#xff1f;如何使用jstack&#xff1f;获取Java进程的PID分析jstack输出 常用的jstack命令选项jstack的应用场景结论 前言 作为Java开发人员&#xff0c;在开发和维护复杂的Java应用程序时&#xff0c;我们经常会遇到各种各样的问题&am…...

34 | 牛顿迭代法

文章目录 牛顿迭代法一、原理二、Python实现三、练习题四、总结牛顿迭代法 一、原理 牛顿迭代法(Newton’s Method)是一种用于寻找方程的实根的数值方法。其基本思想是通过一系列逼近来求解方程的根。对于方程 f ( x ) = 0 f(x) = 0 f(x...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...