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

news2024/9/25 21:24:54

目录

透传 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 组件的技巧你都用过么? 没用过的话不妨从今天开始用起来吧。

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

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

相关文章

LeetCode111. 二叉树的最小深度

111. 二叉树的最小深度 文章目录 [111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/)一、题目二、题解方法一&#xff1a;迭代方法二&#xff1a;递归 一、题目 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子…

理光310/320/325系列激光打印机加粉后不换芯片清零方法

设置步骤&#xff1a; 依次按停止107开始键进入维修模式&#xff0c; 按下键两次选择Engine Maintenance,点OK键进入&#xff0c; 按上键选择Refill mode项后点OK键&#xff0c; 按下键选择到Pure refill mode后点Ok键(默认是Auto refill mode)&#xff0c; 然后按两次后退…

GB/T 25000.51解读——软件产品的功能性怎么测?

前面的文章中&#xff0c;我们为大家整体介绍了GB/T 25000.51-2016《软件产品质量要求和测试细则》国家标准的结构和所涵盖的内容&#xff0c;从本文开始&#xff0c;我们将针对标准中规定的软件产品的八大质量特性进行详细解读。本文为大家解读软件产品的功能性测试。 软件产…

微服务契约测试框架-Pact

契约测试 契约测试的思想就是将原本的 Consumer 与 Provider 间同步的集成测试&#xff0c;通过契约进行解耦&#xff0c;变成 Consumer 与 Provider 端两个各自独立的、异步的单元测试。 契约测试的优点&#xff1a; 契约测试与单元测试以及其它测试之间没有重复&#xff0c…

java商城系统和php商城系统有什么差异?如何选择?

java商城系统和php商城系统是两种常见的电子商务平台&#xff0c;它们都具有一定的优势和劣势。那么&#xff0c;java商城系统和php商城系统又有哪些差异呢&#xff1f; 一、开发难度 Java商城系统和PHP商城系统在开发难度方面存在一定的差异。Java商城系统需要使用Java语言进…

小红书课程发光社群知识库,点亮哥专为超级个体设计解决方案

小红书课程点亮哥知识库 开创了学习小红书教育培训先河 针对超级个体轻创业的学习需求场景 创新推出了“知识库全新学习方式”。 一个人如何做好小红书? 超级个体轻创业,如何做好小红书? 通过打造个人IP、或者塑造老板个人品牌,来实现互联网变现,如何做好小红书? 就像挑…

系统架构设计师-软件架构设计(5)

目录 一、构件与中间件技术 1、软件复用 2、构件与中间件技术的概念 3、构件的复用 3.1 检索与提取构件 3.2 理解与评价构件 3.3 修改构件 3.4 组装构件 4、中间件 4.1 采用中间件技术的优点&#xff1a; 4.2 中间件的分类&#xff1a; 5、构件标准 5.1 CORBA&#xff08;公共…

Android 电子称定标流程

1、首先确保电子称正确安装&#xff0c;底部悬空&#xff0c;托盘悬空。 2、去皮&#xff0c;把去皮数据保存到本地 3、定标、例如拿100克的砝码放入托盘&#xff0c; 获取值-去皮值及得到定标值 4、通过定标值计算出需要设置的满量程&#xff0c;或者计算对应的重量&#x…

vue-element-admin中实现自适应功能

npm install postcss-px-to-viewport --save-dev 项目根目录下建一个名字为 .postcssrc.js 的js文件(前边的.别忘了)&#xff0c;在该文件里写以下代码 //添加如下配置&#xff1a; module.exports {plugins: {autoprefixer: {}, // 用来给不同的浏览器自动添加相应前缀&a…

小学期笔记——天天酷跑4

效果&#xff1a; 点击登录&#xff1a; ------------------------ 效果&#xff1a; 静态的一张图 ------------------------ 完善一下会变成那张静态的图从左往右移动&#xff0c;但是这一张图到后面会拉丝 -------------------- 再完善一下&#xff1a; &#xff08;再…

洗地机有没有必要买?好用的洗地机推荐

随着科技的发展&#xff0c;越来越多的家用电器出现。就比如在清洁家电方面&#xff0c;相继出现了吸尘器、扫地机、洗地机&#xff01;其中洗地机更是近年来爆火的一个智能清洁家电&#xff01;而如果你们和小编一样是个上班族&#xff0c;然后每天下班回家面对脏乱的地板&…

汽车UDS诊断深度学习专栏

1.英文术语 英文术语翻译Diagnostic诊断Onboard Diagnostic 在线诊断 Offboard Diagnostic离线诊断Unified diagnostic service简称 UDS 2.缩写表 缩写解释ISO国际标准化组织UDSUnified diagnostic service&#xff0c;统一的诊断服务ECU电控单元DTC 诊断故障码 ISO14229UD…

Modbus TCP/IP之异常响应

文章目录 一、异常响应二、异常码分析2.1 异常码0x012.2 异常码0x022.3 异常码0x032.4 异常码0x04、0x05等 一、异常响应 对于查询报文&#xff0c;存在以下四种处理反馈&#xff1a; 正常接收&#xff0c;正常处理&#xff0c;返回正常响应报文&#xff1b;因为通信错误等原因…

我对牟长青分享的各个私董会数据分析

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 其实之前&#xff0c;我也想写一个关于各个草根社群的数据分析&#xff0c;但这样的文章容易得罪人&#xff0c;因为我一直喜欢直言不讳&#xff0c;所以一直没有动笔。例如&#xff0c;我在6月份写…

OpenGl中的VAO、VBO与EBO

文章目录 VBO(顶点缓冲区对象)VBO的使用 EBO(索引缓冲对象)EBO的使用 VAO(顶点数组对象)VAO的使用 三者的区别someting。。。 哎&#xff0c;很离谱&#xff0c;上个月学learnopengl学到一半跑去看庄懂老师的视频&#xff0c;结果该还的东西迟早得还&#xff0c;再打开之前的工…

NineData支持最受欢迎数据库PostgreSQL

根据在 Stack Overflow 发布的 2023 开发者调研报告中显示&#xff0c;PostgreSQL 以 45% vs 41% 的受欢迎比率战胜 MySQL&#xff0c;成为新的最受欢迎的数据库。NineData 也在近期支持了 PostgreSQL&#xff0c;用户可以在 NineData 平台上进行创建数据库/Schema、管理用户与…

BTTES,2101505-88-6,是各种化学生物实验中生物偶联的理想选择

资料编辑|陕西新研博美生物科技有限公司小编MISSwu​ 规格单位&#xff1a;g |货期&#xff1a;按照具体的库存进行提供 | 纯度&#xff1a;95% PART1----​试剂描述&#xff1a; BTTES是铜&#xff08;I&#xff09;催化的叠氮化物-炔烃环加成&#xff08;CuAAC&#x…

整数替换(力扣)HashMap + 递归 JAVA

给定一个正整数 n &#xff0c;你可以做如下操作&#xff1a; 如果 n 是偶数&#xff0c;则用 n / 2替换 n 。 如果 n 是奇数&#xff0c;则可以用 n 1或n - 1替换 n 。 返回 n 变为 1 所需的 最小替换次数 。 示例 1&#xff1a; 输入&#xff1a;n 8 输出&#xff1a;3 解释…

系统架构设计师-软件架构设计(4)

目录 一、软件架构评估 1、敏感点 2、权衡点 3、风险点 4、非风险点 5、架构评估方法 5.1 基于调查问卷或检查表的方式 5.2 基于度量的方式 5.3 基于场景的方式 6、基于场景的评估方法 6.1 软件架构分析法&#xff08;SAAM&#xff09; 6.2 架构权衡分析法&#xff08;ATAM&am…

vue 封装一个鼠标拖动选择时间段功能

<template><div class"timeRange"><div class"calendar"><table><thead><tr><th rowspan"6" class"weekRow"><b>周/时间</b></th><th colspan"24"><…