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

news2025/2/5 0:52:38

在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>
      <FunComp
        ref={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

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

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

相关文章

使用Excel制作通达信自定义“序列数据“

序列数据的视频教程演示 Excel制作通达信自定义序列数据 1.序列数据的制作方法&#xff1a;删掉没有用的数据&#xff08;行与列&#xff09;和股代码格式处理&#xff0c;是和外部数据的制作方法是相同&#xff0c;自己上面看历史博文。只需要判断一下&#xff0c;股代码跟随的…

VuePress搭建个人博客

VuePress搭建个人博客 官网地址: https://v2.vuepress.vuejs.org/zh/ 相关链接: https://theme-hope.vuejs.press/zh/get-started/ 快速上手 pnpm create vuepress vuepress-starter# 选择简体中文、pnpm等, 具体如下 .../19347d7670a-1fd8 | 69 .../19…

《机器学习》从入门到实战——线性回归

目录 一、什么是线性回归 二、一元线性回归模型 三、多元线性回归模型 四、误差项分析 1、误差项是否可以忽略 2、误差项的特点 3、误差项满足高斯分布&#xff08;正太分布&#xff09; &#xff08;1&#xff09;、高斯分布公式 &#xff08;2&#xff09;、公式推导 …

Java - 日志体系_Apache Commons Logging(JCL)日志接口库_适配Log4j2 及 源码分析

文章目录 PreApache CommonsApache Commons ProperLogging &#xff08;Apache Commons Logging &#xff09; JCL 集成Log4j2添加 Maven 依赖配置 Log4j2验证集成 源码分析1. Log4j-jcl 的背景2. log4j-jcl 的工作原理2.1 替换默认的 LogFactoryImpl2.2 LogFactoryImpl 的实现…

brupsuite的基础用法常用模块(1)

proxy模块&#xff1a; Options: 设置代理端口&#xff0c;默认为8080端口&#xff0c;若8080端口被占用可在该界面更改代理端口. HTTP history: 拦截的历史请求&#xff0c;右键可做更多操作&#xff0c;很多操作与其他模块有关。&#xff08;清除历史的话右键选择clear p…

概率统计与随机过程--作业7

编程题 分别使用混合高斯聚类算法&#xff08;GMM&#xff09;和K-Means聚类算法将agricultural_economy.xlsx文件中的9个样本聚成2类&#xff08;可以使用sklearn的GaussianMixture和KMeans&#xff09;&#xff0c;用不同颜色绘图显示各类样本点&#xff0c;并与“最短距离聚…

【Unity3D】ECS入门学习(十二)IJob、IJobFor、IJobParallelFor

IJob&#xff1a;开启单个线程进行计算&#xff0c;线程内不允许对同一个数据进行操作&#xff0c;也就是如果你想用多个IJob分别计算&#xff0c;将其结果存储到同一个NativeArray<int>数组是不允许的&#xff0c;所以不要这样做&#xff0c;如下例子就是反面教材&#…

MicroDiffusion——采用新的掩码方法和改进的 Transformer 架构,实现了低预算的扩散模型

介绍 论文地址&#xff1a;https://arxiv.org/abs/2407.15811 现代图像生成模型擅长创建自然、高质量的内容&#xff0c;每年生成的图像超过十亿幅。然而&#xff0c;从头开始训练这些模型极其昂贵和耗时。文本到图像&#xff08;T2I&#xff09;扩散模型降低了部分计算成本&a…

Java - 日志体系_Simple Logging Facade for Java (SLF4J)日志门面_SLF4J集成Log4j1.x 及 原理分析

文章目录 Pre官网集成Log4j1.x步骤POM依赖使用第一步&#xff1a;编写 Log4j 配置文件第二步&#xff1a;代码 原理分析1. 获取对应的 ILoggerFactory2. 根据 ILoggerFactory 获取 Logger 实例3. 日志记录过程 小结 Pre Java - 日志体系_Apache Commons Logging&#xff08;JC…

详解VHDL如何编写Testbench

1.概述 仿真测试平台文件(Testbench)是可以用来验证所设计的硬件模型正确性的 VHDL模型&#xff0c;它为所测试的元件提供了激励信号&#xff0c;可以以波形的方式显示仿真结果或把测试结果存储到文件中。这里所说的激励信号可以直接集成在测试平台文件中&#xff0c;也可以从…

Linux day 11 28

一.Linux简介 1.1 Linux介绍 Linux 是一套免费使用和自由传播的操作系统。说到操作系 统&#xff0c;大家比较熟知的应该就是 Windows 和 MacOS 操作系统&#xff0c; 我们今天所学习的 Linux 也是一款操作系统。 1.2 Linux发展历史 时间&#xff1a; 1991 年 地点&#xf…

【深度学习环境】NVIDIA Driver、Cuda和Pytorch(centos9机器,要用到显示器)

文章目录 一 、Anaconda install二、 NIVIDIA driver install三、 Cuda install四、Pytorch install 一 、Anaconda install Step 1 Go to the official website: https://www.anaconda.com/download Input your email and submit. Step 2 Select your version, and click i…

Lecture 17

10’s Complement Representation 主要内容&#xff1a; 1. 10’s 补码表示: • 10’s 补码表示法需要指定表示的数字位数&#xff08;用 n 表示&#xff09;。 • 表示的数字取决于 n 的位数&#xff0c;这会影响具体数值的解释。 2. 举例: • 如果采用 3 位补码&…

惠州市政数局局长杨伟斌:惠州市公共数据授权运营模式探索

近期&#xff0c;2024数字资产管理大会召开。会上&#xff0c;惠州市政务服务和数据管理局局长杨伟斌在会上做了题为基于“隐私计算区块链”的惠州市公共数据授权运营模式探索主旨演讲&#xff0c;从三个方面展开&#xff0c;一是建制度汇数据&#xff0c;二是夯基础保安全&…

C#编写的金鱼趣味小应用 - 开源研究系列文章

今天逛网&#xff0c;在GitHub中文网上发现一个源码&#xff0c;里面有这个金鱼小应用&#xff0c;于是就下载下来&#xff0c;根据自己的C#架构模板进行了更改&#xff0c;最终形成了这个例子。 1、 项目目录&#xff1b; 2、 源码介绍&#xff1b; 1) 初始化&#xff1b; 将样…

探索贝叶斯魔法和误差的秘密

引言 今天我们要一起学习两个神秘的魔法概念&#xff1a;贝叶斯魔法和误差的秘密。这些概念听起来可能有点复杂&#xff0c;但别担心&#xff0c;我会用最简单的方式来解释它们。 一、贝叶斯魔法 贝叶斯魔法是一种预测的魔法&#xff0c;它帮助我们理解在不确定的情况下事情…

深度学习blog-卷积神经网络(CNN)

卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种广泛应用于计算机视觉领域&#xff0c;如图像分类、目标检测和图像分割等任务中的深度学习模型。 1. 结构 卷积神经网络一般由以下几个主要层组成&#xff1a; 输入层&#xff1a;接收…

深度学习笔记(6)——循环神经网络RNN

循环神经网络 RNN 核心思想:RNN内部有一个“内部状态”,随着序列处理而更新 h t f W ( h t − 1 , x t ) h_tf_W(h_{t-1},x_t) ht​fW​(ht−1​,xt​) 一般来说 h t t a n h ( W h h h t − 1 W x h x t ) h_ttanh(W_{hh}h_{t-1}W_{xh}x_t) ht​tanh(Whh​ht−1​Wxh​xt…

最新版Edge浏览器加载ActiveX控件技术——alWebPlugin中间件V2.0.28-迎春版发布

allWebPlugin简介 allWebPlugin中间件是一款为用户提供安全、可靠、便捷的浏览器插件服务的中间件产品&#xff0c;致力于将浏览器插件重新应用到所有浏览器。它将现有ActiveX控件直接嵌入浏览器&#xff0c;实现插件加载、界面显示、接口调用、事件回调等。支持Chrome、Firefo…