当前位置: 首页 > 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...

后进先出(LIFO)详解

LIFO 是 Last In, First Out 的缩写&#xff0c;中文译为后进先出。这是一种数据结构的工作原则&#xff0c;类似于一摞盘子或一叠书本&#xff1a; 最后放进去的元素最先出来 -想象往筒状容器里放盘子&#xff1a; &#xff08;1&#xff09;你放进的最后一个盘子&#xff08…...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

莫兰迪高级灰总结计划简约商务通用PPT模版

莫兰迪高级灰总结计划简约商务通用PPT模版&#xff0c;莫兰迪调色板清新简约工作汇报PPT模版&#xff0c;莫兰迪时尚风极简设计PPT模版&#xff0c;大学生毕业论文答辩PPT模版&#xff0c;莫兰迪配色总结计划简约商务通用PPT模版&#xff0c;莫兰迪商务汇报PPT模版&#xff0c;…...

解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist

现象&#xff1a; android studio报错&#xff1a; [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决&#xff1a; 不要动CMakeLists.…...

6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础

第三周 Day 3 &#x1f3af; 今日目标 理解类&#xff08;class&#xff09;和对象&#xff08;object&#xff09;的关系学会定义类的属性、方法和构造函数&#xff08;init&#xff09;掌握对象的创建与使用初识封装、继承和多态的基本概念&#xff08;预告&#xff09; &a…...

[拓扑优化] 1.概述

常见的拓扑优化方法有&#xff1a;均匀化法、变密度法、渐进结构优化法、水平集法、移动可变形组件法等。 常见的数值计算方法有&#xff1a;有限元法、有限差分法、边界元法、离散元法、无网格法、扩展有限元法、等几何分析等。 将上述数值计算方法与拓扑优化方法结合&#…...