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

如何使用React,透传各类组件能力/属性?

在23年的时候,我主要使用的框架还是Vue,当时写了一篇“如何二次封装一个Vue3组件库?”的文章,里面涉及了一些如何使用Vue透传组件能力的方法。在我24年接触React之后,我发现这种扩展组件能力的方式有一个专门的术语:高阶组件(HOC)。但在Vue开发中,这个词很少听到。

这篇文章中会描述使用React透传组件各类能力的方式。这些透传方式经常在高阶组件中使用,但并不只有高阶组件会用到它们。React有类式组件和函数式组件两种,我们会分别介绍。

问题描述

首先我们列举下简单的场景,说明我们为什么需要透传组件能力。这里以函数式组件为例。

// 问题示例,这段代码是不正确的
import { useRef } from "react";function FunComp() {return <input />;
}function FunComp2() {return <div><input /></div>;
}function App() {const refFun = useRef(null);const handleClick = () => {console.log("click");};const handleClickFocus = () => {if (refFun.current) refFun.current?.focus();};return (<div><FunCompref={refFun}style={{ background: "red" }}onClick={handleClick}/><div onClick={handleClickFocus}>点我聚焦</div></div>);
}

假设我们想创建一个自定义组件(FunComp),里面封装了另外一个组件(例如这里的input),希望使用这个自定义组件增强原组件的能力,或者预先设定一些样式等等,自定义组件可能直接返回该组件,也可能被处理过(例如FunComp2被div包裹)。

虽然原组件被封装了,但是还希望原组件的能力直接被透传给自定义组件。例如我们在自定义组件上操作props, 事件,ref等,希望就像操作原组件一样。在Vue3中,很多能力可以直接使用Attributes继承特性,但是React却没有,需要我们自己实现。

函数式组件-透传Props和事件

在函数式组件中,prop实际上就是组件的入参,且所有prop是被包含在同一个参数中的,因此很容易透传给子组件。且事件本身实际上也是prop,可以一并透传。

function FunComp(props) {return <input {...props} />;
}function App() {const handleClick = () => {console.log("click");};return (<div><FunComp style={{ background: "red" }} onClick={handleClick} /></div>);
}

使用{...props}可以实现透传Props和事件。上述例子中,子组件可以接收到样式属性和事件。

函数式组件-透传子节点

React中有一个特殊的属性children,表示父组件中包含的子节点。这也是需要透传的。

直接渲染children属性

function FunComp(props) {return <div>{props.children}</div>;
}function App() {return (<div><FunComp>子节点</FunComp><FunComp /></div>);
}

可以看到,直接渲染props.children,即可透传子节点。即使没有子节点,这种透传也是没问题的。如果子组件本身已经透传了props,透传的对象又是

使用透传Props实现透传子节点

既然children也是Props之一,那么直接使用透传Props的方法是否可以呢? 我们试一下。

function FunComp(props) {return <div>{props.children}</div>;
}function FunComp1(props) {return <div {...props} />;
}function FunComp2(props) {return <FunComp {...props} />;
}function App() {return (<div><FunComp1>子节点</FunComp1><FunComp1 children="子节点" /><FunComp2>子节点</FunComp2><FunComp2 children="子节点" /></div>);
}/* 页面效果
子节点
子节点
左 子节点 右
左 子节点 右
*/

FunComp1包含的子组件是一个非自定义组件div,FunComp2包含的时自定义组件FunComp,可以看到我们使用{...props}进行Props透传,children实际上都被成功渲染了,甚至对父组件直接设置children属性也可以。

冲突场景

既然Props透传即可实现,那我们为什么还要强调一遍直接渲染props.children呢,因为有时候子组件不只渲染children,还有其它内容。如果Props和直接设置的子节点冲突,那么还是直接设置的子节点优先级更高。

function FunComp(props) {return <div {...props}>{props.children}</div>;
}function App() {return (<div><FunComp>子节点</FunComp><FunComp children="子节点" /></div>);
}/* 页面效果
左 子节点 右
左 子节点 右
*/

我们同时透传了Props,也直接设置了子节点(其中包含其它内容),最后直接设置的子元素生效了。

函数式组件-透传ref

使用ref可以操作访问DOM节点,获取DOM元素上的属性或者方法。ref也是可以透传的。

透传全部属性

import { forwardRef, useRef } from "react";const FunComp = forwardRef(function (props, ref) {return <input ref={ref} />;
});function App() {const inputRef = useRef(null);function handleClick() {console.log(inputRef.current?.style);inputRef.current?.focus();}return (<div><FunComp ref={inputRef} /><div onClick={handleClick}>点击聚焦</div></div>);
}

使用forwardRef,可以透传ref属性。我们尝试了聚焦输入框,以及console输出style属性,都是正常生效的。

仅暴露部分属性

有时候我们不想暴露全部属性,仅希望暴露我们希望用户使用的部分属性,使用useImperativeHandle可以做到。

import { forwardRef, useRef, useImperativeHandle } from "react";const FunComp = forwardRef(function (props, ref) {const inputRef = useRef(null);useImperativeHandle(ref, () => {return {focus() {inputRef.current?.focus();},};});return <input ref={inputRef} />;
});function App() {const inputRef = useRef(null);function handleClick() {// 无法输出console.log(inputRef.current?.style);inputRef.current?.focus();}return (<div><FunComp ref={inputRef} /><div onClick={handleClick}>点击聚焦</div></div>);
}

我们仅向外层的ref暴露了focus,因此外层组件focus可以正常调用,但是却拿不到style属性了。使用这种形式还可以对方法进行额外的包装,或者创建一些新的ref方法。

在React19中,不再需要forwardRef了,ref直接作为一个prop属性访问。可以看最后的参考文档。

类式组件-透传Props和事件

类式组件是另一种创建React组件的方法,被React标记为过时的API,但是在老代码中还经常被使用到。我们先来看一下,在类式组件中,如何Props和事件。

import { Component } from "react";
class ClassComp extends Component {render() {return <div {...this.props}>你好</div>;}
}class App extends Component {render() {const handleClick = () => {console.log("click");};return <ClassComp style={{ background: "red" }} onClick={handleClick} />;}
}

通过上述代码可以看到,在类式组件中透传Props和事件与函数式组件一致,使用{...props}可以实现透传Props和事件。

类式组件-透传子节点

来看看类式组件是如何透传子节点的。

直接渲染children属性

import { Component } from "react";
class ClassComp extends Component {render() {return <div>{this.props.children}</div>;}
}class App extends Component {render() {return (<div><ClassComp>子节点</ClassComp><ClassComp /></div>);}
}

代码依然与类式组件基本一致,直接渲染{this.props.children}即可。

使用透传Props实现透传子节点

上一节讲到的透传Props,同样可以实现透传子节点。

import { Component } from "react";
class ClassComp extends Component {render() {return <div>{this.props.children}</div>;}
}
class ClassComp1 extends Component {render() {return <div {...this.props} />;}
}
class ClassComp2 extends Component {render() {return <ClassComp {...this.props} />;}
}class App extends Component {render() {return (<div><ClassComp1>子节点</ClassComp1><ClassComp1 children="子节点" /><ClassComp2>子节点</ClassComp2><ClassComp2 children="子节点" /></div>);}
}/* 页面效果
子节点
子节点
左 子节点 右
左 子节点 右
*/

与函数式组件一致,Props透传时也会透传children,甚至对父组件直接设置children属性也可以透传。至于Props和直接设置的子节点冲突的场景也与函数式组件一致,这里就不举例了。

类式组件-透传ref

类式组件透传Ref的形式就与函数式组件不同了。具体类式组件有不同的实现方式,我们分别介绍下:

暴露部分属性

import { Component, createRef } from "react";class ClassComp extends Component {inputRef = createRef();focus() {this.inputRef.current?.focus();}render() {return <input ref={this.inputRef} />;}
}class App extends Component {classRef = createRef();handleClick() {console.log(this.classRef.current);this.classRef.current?.focus();}render() {return (<div><ClassComp ref={this.classRef} /><div onClick={() => this.handleClick()}>点击聚焦</div></div>);}
}

通过代码可以看到,在类式组件中,不需要通过forwardRef等方法就可以使用ref访问子组件,且能执行子组件类中的方法。所以我们只要把需要暴露的内容包装成一个方法,那么就可以让父组件获取到。

在这里插入图片描述

通过输出的图可以看到,不仅能拿到方法,还能拿到属性和其它很多东西。

拿到子组件内部的ref

既然可以拿到子组件类中和属性也能拿到。那么父组件可以直接拿到子组件内部的ref属性inputRef,父组件可以直接拿到它来执行内部的方法。

import { Component, createRef } from "react";class ClassComp extends Component {inputRef = createRef();render() {return <input ref={this.inputRef} />;}
}class App extends Component {classRef = createRef();handleClick() {this.classRef.current?.inputRef?.current?.focus();}render() {return (<div><ClassComp ref={this.classRef} /><div onClick={() => this.handleClick()}>点击聚焦</div></div>);}
}

通过代码可以看到,子组件不需要透出方法了,父组件直接拿到子组件的inputRef,想执行什么就执行什么,做到了真正的“透传”。绑定ref还有另一种方式,这里也介绍一下:

import { Component, createRef } from "react";class ClassComp extends Component {render() {return <input ref="inputRef" />;}
}class App extends Component {classRef = createRef();handleClick() {this.classRef.current?.refs?.inputRef?.focus();}render() {return (<div><ClassComp ref={this.classRef} /><div onClick={() => this.handleClick()}>点击聚焦</div></div>);}
}

ref属性的值可以直接是一个字符串,通过this.refs可以拿到使用字符串形式绑定的ref。

总结

函数式组件与类式组件在Props和事件透传的方式基本一致,但是ref透传的区别较大。直接对比的话,好像类式组件的透传能力更强一些,但是它把组件内部所有内容全暴露在外,违反了封装的原则,子组件内部的改动很容易影响父组件,不是一个好的设计。

在React19版本中,ref属性也变成了prop,仅通过透传Props,就能实现透传组件大部分能力了。

参考

  • 如何二次封装一个Vue3组件库?
    https://jzplp.github.io/2023/component-lib.html
  • Vue3 透传Attributes
    https://cn.vuejs.org/guide/components/attrs
  • React 使用ref操作DOM
    https://zh-hans.react.dev/learn/manipulating-the-dom-with-refs
  • React omponent
    https://zh-hans.react.dev/reference/react/Component
  • ref用法
    https://blog.csdn.net/qq_47305413/article/details/136059266
  • React v19 ref作为一个属性
    https://zh-hans.react.dev/blog/2024/12/05/react-19#ref-as-a-prop

相关文章:

如何使用React,透传各类组件能力/属性?

在23年的时候&#xff0c;我主要使用的框架还是Vue&#xff0c;当时写了一篇“如何二次封装一个Vue3组件库&#xff1f;”的文章&#xff0c;里面涉及了一些如何使用Vue透传组件能力的方法。在我24年接触React之后&#xff0c;我发现这种扩展组件能力的方式有一个专门的术语&am…...

汇编点灯练习

要求&#xff1a; 1、轮流将LED1、LED2、LED3及蜂鸣器点亮 2、基于STM32MP157AAA&#xff0c;阅读原理图和STM32MP157芯片手册 3、ARM汇编指令点灯 1、运行效果 汇编点灯 2、通过查询原理图和芯片手册&#xff0c;得到以下结论&#xff1a; 3、汇编源码 .text .global _start…...

数据结构与算法之动态规划: LeetCode 213. 打家劫舍 II (Ts版)

打家劫舍 II https://leetcode.cn/problems/house-robber-ii/description/ 描述 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋&#xff0c;每间房内都藏有一定的现金这个地方所有的房屋都 围成一圈 &#xff0c;这意味着第一个房屋和最后一个房屋是紧挨着的同时&#…...

Git工具

安装教程 详细安装教程 基本命令...

SpringBoot3.3.3+shardingsphere-jdbc5.5.0读写分离、自定义生成主键策略

最近在开发项目搭建框架时&#xff0c;考虑后期支付模块的订单数据量可能会比较大&#xff0c;于是使用现在主流的shardingsphere的读写分离、水平分表来解决后期数据量大影响查询效率的问题。 项目技术栈&#xff1a;jdk17Springboot3.3.3shardingsphere-jdbc5.5.0mybatis-pl…...

开发运维基本功:无需复杂配置快速实现本地Nginx的公网远程访问

文章目录 前言1. 本地连接测试2. 飞牛云安装Cpolar3. 配置公网连接地址4. 飞牛云APP连接测试5. 固定APP远程地址6. 固定APP地址测试 前言 现在生活和工作中的各种设备都变得越来越智能&#xff0c;而数据存储的需求也随之剧增。想象一下&#xff1a;你正在外地出差&#xff0c…...

金融租赁系统助力企业转型与市场竞争力提升

内容概要 在现代商业环境中&#xff0c;金融租赁系统不仅是一个简单的工具&#xff0c;而是企业转型的重要推动力。通过优化业务流程&#xff0c;提升自动化水平&#xff0c;它帮助企业在复杂的市场中找到自己的立足之地。想象一下&#xff0c;一个企业在使用传统方法时&#…...

【漫话机器学习系列】028.CP

Mallows’ Cp&#xff1a;标准化公式解析与应用 Mallows’ Cp 是一种常用的模型选择工具&#xff0c;用于在一系列候选模型中权衡拟合度和复杂性&#xff0c;帮助我们选择性能最优的模型。本文将基于其标准化公式展开详细解析&#xff0c;并探讨其应用场景、实现方法、优点与局…...

软件测试——面试八股文(入门篇)

今天给大家分享软件测试面试题入门篇&#xff0c;看看大家能答对几题 一、 请你说一说测试用例的边界 参考回答&#xff1a; 边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充&#xff0c;这种情况下&#xff…...

如何在不同工作场景下优化嵌入式系统的电源消耗

在不同工作场景下优化嵌入式系统的电源消耗是一个复杂但至关重要的任务&#xff0c;它涉及到硬件设计、软件编程以及系统级管理等多个方面。以下是一些具体的策略和方法&#xff1a; 1. 动态电压频率调节&#xff08;DVFS&#xff09; 原理&#xff1a;根据处理器的当前负载动…...

java - SpringBoot3.x接入Security6.x实现JWT认证

java - SpringBoot3.x接入Security6.x实现JWT认证 文章目录 java - SpringBoot3.x接入Security6.x实现JWT认证一、引言二、环境三、Maven依赖四、认识JWT1. JWT组成 五、认识Security6.x1. 和旧版本的区别&#xff08;Security5.7以前的版本&#xff09;2. Security6.x的默认筛…...

【每日学点鸿蒙知识】无障碍、getLastLocation、蓝牙问题、卡片大小、关系型数据库等

1、是否有类似无障碍辅助相关的API&#xff1f; 场景描述&#xff1a;锁机app&#xff0c;需要通过无障碍能力辅助检测当前正在打开的app&#xff0c;以及模拟用户操作&#xff0c; 关闭用户想要屏蔽的app 可参考&#xff1a;https://developer.huawei.com/consumer/cn/doc/h…...

[Linux] 服务器CPU信息

&#xff08;1&#xff09;查看CPU信息&#xff08;型号&#xff09; cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c输出&#xff1a;可以看到有128个虚拟CPU核心&#xff0c;型号是后面一串 128 Intel(R) Xeon(R) Platinum 8336C CPU 2.30GHz&#xff08;2&…...

MySQL——数据类型

一、常见的数据类型及分类 其中上述的数值类型包含了整形和浮点型&#xff0c;文本、二进制类型主要是字符串类型。 整数类型&#xff08;Integer Types&#xff09;&#xff1a; TINYINT&#xff1a;范围为-128到127或0到255&#xff08;无符号&#xff09;&#xff0c;用于…...

《AI赋能自由职业:开启竞争力提升新征程》

在当今数字化时代&#xff0c;AI技术为自由职业者带来了前所未有的机遇&#xff0c;使其能够在激烈的市场竞争中脱颖而出。以下是自由职业者借助AI提升自身竞争力的几种方法。 利用AI优化工作流程&#xff0c;提高效率 自动化任务处理&#xff1a;自由职业者可以借助自动化工具…...

Excel转Json编辑器工具

功能说明&#xff1a;根据 .xlsx 文件生成对应的 JSON 文件&#xff0c;并自动创建脚本 注意事项 Excel 读取依赖 本功能依赖 EPPlus 库&#xff0c;只能读取 .xlsx 文件。请确保将该脚本放置在 Assets 目录下的 Editor 文件夹中。同时&#xff0c;在 Editor 下再创建一个 Exc…...

创建型设计模式、结构型设计模式与行为型设计模式 上下文任务通用方案 设计模式 大全

设计模式&#xff08;Design Pattern&#xff09;是一种面向对象编程的思想&#xff0c;分为创建型模式、结构型模式与行为型模式三大类&#xff0c;它们提供了在特定上下文中解决常见任务的通用方案&#xff0c;旨在让程序&#xff08;软件&#xff09;具有更好的特点&#xf…...

Mac 环境 VVenC 编译与编码命令行工具使用教程

VVenC VVenC 是一个开源的高效视频编码器&#xff0c;专门用于支持 H.266/VVC (Versatile Video Coding) 标准的编码。H.266/VVC 是继 HEVC (H.265) 之后的新一代视频编码标准&#xff0c;主要目的是提供比 HEVC 更高的压缩效率&#xff0c;同时保持或提高视频质量。H.266/VVC…...

如何在 Ubuntu 22.04 上部署 Nginx 并优化以应对高流量网站教程

简介 本教程将教你如何优化 Nginx&#xff0c;使其能够高效地处理高流量网站。 Nginx 是一个强大且高性能的 Web 服务器&#xff0c;以其高效处理大量并发连接的能力而闻名&#xff0c;这使得它成为高流量网站的流行选择。 正确优化 Nginx 可以显著提高服务器的性能&#xff0…...

springcloud各个组件介绍

Spring Cloud 是一系列框架的集合&#xff0c;它基于 Spring Boot 提供了在分布式系统&#xff08;如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话和集群状态&#xff09;中快速构建一些常见模式的工具。下面是对 Sprin…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...