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

在 React 中掌握 useImperativeHandle(使用 TypeScript)

当使用 TypeScript 构建 React 应用程序时,开发人员经常遇到需要创建具有高级功能的自定义、可重用组件的情况。本文将探讨两个强大的概念:使用 ImperativeHandle 钩子对 ref 管理进行细粒度控制;以及创建自定义组件,如表单验证和Modal组件。

我们将深入研究:useImperativeHandle 钩子:它做什么,何时使用它,以及它如何允许你自定义父组件可以访问的 ref 值。

这些示例将帮助初学者了解如何利用 TypeScript 构建交互式和可重用的组件,同时探索 ref 管理等高级概念。在本文结束时,您将为在 React 应用程序中创建强大的自定义组件打下坚实的基础。

什么是 useImperativeHandle

useImperativeHandle 是 React 中的一个钩子,它允许你自定义父组件可以访问的 ref 对象。当您想要向父组件公开自定义 API,而不是公开组件的内部实现详细信息时,这非常有用。

何时以及为何应该使用它

在大多数情况下,useRef 提供了足够的功能来访问 DOM 元素或组件实例。但是,当你需要更多控制时,useImperativeHandle 会介入,提供一种方法来仅向父组件公开你选择的方法或状态。这可确保您的组件保持模块化、封装状态并且更易于维护。该钩子还允许更好的抽象,这意味着您可以在应用程序中重用组件,同时最大限度地减少重复。

示例 1 - 切换开关组件

此示例演示如何使用 TypeScript 创建切换开关组件。该组件使用 useImperativeHandle 向父组件公开自定义 API,从而允许父组件控制开关状态。

  • 用例:创建可由父组件控制的自定义切换开关组件。
  • 在父组件中创建一个 ref 并将其传递给 ToggleSwitch 组件。
  • 使用 ref 调用自定义 API 并控制 switch 状态。
import React, { forwardRef, useImperativeHandle, useState } from "react";

interface ToggleRef {
  toggle: () => void;
  getState: () => boolean;
}

type ToggleSwitchProps = {
  initialState?: boolean;
};

const ToggleSwitch = forwardRef<ToggleRef, ToggleSwitchProps>((props, ref) => {
  const [isToggled, setIsToggled] = useState(props.initialState ?? false);

  useImperativeHandle(ref, () => ({
    toggle: () => setIsToggled(!isToggled),
    getState: () => isToggled,
  }));

  return (
    <motion.button
      onClick={() => setIsToggled(!isToggled)}
      className="flex items-center justify-start w-12 h-6 p-1 overflow-hidden bg-gray-300 rounded-full"
      animate={{
        backgroundColor: isToggled ? "#4CAF50" : "#f44336",
      }}
      transition={{ duration: 0.3 }}
    >
      <motion.div
        className="flex items-center justify-center w-5 h-5 bg-white rounded-full"
        animate={{
          x: isToggled ? "100%" : "0%",
        }}
        transition={{ type: "spring", stiffness: 700, damping: 100 }}
      ></motion.div>
    </motion.button>
  );
});

function Example() {
  const toggleRef = useRef<ToggleRef>(null);

  return (
    <div className="flex flex-col items-center justify-center h-screen gap-4">
      <section className="flex flex-row items-center justify-center w-full py-4 border border-gray-200 rounded-md gap-x-4">
        <ToggleSwitch ref={toggleRef} />
        <button
          onClick={() => toggleRef.current?.toggle()}
          className="px-4 py-2 text-white bg-blue-500 rounded-md"
        >
          Toggle Switch
        </button>
      </section>
    </div>
  );
}

示例 2 - 折叠组件 

此示例演示如何使用 TypeScript 创建折叠组件。该组件使用 useImperativeHandle 向父组件公开自定义 API,从而允许父组件控制折叠面板状态。

  • 用例:创建可由父组件控制的自定义折叠组件。
  • 在父组件中创建一个 ref 并将其传递给 Accordion 组件。
  • 使用 ref 调用自定义 API 并控制折叠面板状态。
interface AccordionRef {
  expand: () => void;
  collapse: () => void;
  isExpanded: () => boolean;
  toggle: () => void;
}

type AccordionProps = {
  initialState?: boolean;
  title: string;
  content: ReactNode;
};

const Accordion = forwardRef<AccordionRef, AccordionProps>((props, ref) => {
  const [expanded, setExpanded] = useState(props.initialState ?? false);

  useImperativeHandle(ref, () => ({
    expand: () => setExpanded(true),
    collapse: () => setExpanded(false),
    isExpanded: () => expanded,
    toggle: () => setExpanded((prev) => !prev),
  }));

  const handleToggle = () => {
    setExpanded((prev) => !prev);
  };

  return (
    <div className="overflow-hidden border border-gray-200 rounded-md w-ful">
      <motion.button
        className="w-full px-4 py-2 text-left bg-gray-100 hover:bg-gray-200"
        onClick={handleToggle}
        initial={false}
        animate={{ backgroundColor: expanded ? "#e5e7eb" : "#f3f4f6" }}
      >
        {props.title}
      </motion.button>
      <AnimatePresence initial={false}>
        {expanded && (
          <motion.div
            initial="collapsed"
            animate="expanded"
            exit="collapsed"
            variants={{
              expanded: { opacity: 1, height: "auto" },
              collapsed: { opacity: 0, height: 0 },
            }}
            transition={{ duration: 0.3, ease: "easeInOut" }}
          >
            <div className="p-4 bg-white">{props.content}</div>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
});

function Example() {
  const accordionRef = useRef<AccordionRef>(null);

  return (
    <div className="flex flex-col items-center justify-center h-screen gap-4">
      <main className="w-full px-4">
        <Accordion
          ref={accordionRef}
          title="Click to expand"
          content="This is the accordion content. It can contain any text or elements."
        />
      </main>
      <button
        onClick={() => {
          accordionRef.current?.expand();
        }}
        className="px-4 py-2 text-white bg-blue-500 rounded-md disabled:bg-gray-500"
      >
        Expand Accordion
      </button>
      <button
        onClick={() => accordionRef.current?.collapse()}
        className="px-4 py-2 text-white bg-blue-500 rounded-md"
      >
        Collapse Accordion
      </button>
      <button
        onClick={() => accordionRef.current?.toggle()}
        className="px-4 py-2 text-white bg-blue-500 rounded-md"
      >
        Toggle Accordion
      </button>
    </div>
  );
}

使用 useImperativeHandle 的优点

useImperativeHandle 提供了一些关键优势,尤其是在构建可重用和交互式组件时:

1、封装

通过使用 useImperativeHandle,您可以隐藏组件的内部实现细节,并仅公开您希望父组件与之交互的方法。这可确保您的组件维护其内部逻辑,而不受外部因素的影响,从而使其更加健壮。

2、精细控制

它让你对 ref 对象进行精细的控制。您不必公开整个组件实例或 DOM 节点,而是决定哪些方法或值可用。这在处理表单、切换或模态等复杂组件时至关重要。

3、提高可重用性

通过抽象某些 logic 并控制向 parent(父组件)公开的内容,您的组件可以变得更加可重用。例如,使用 useImperativeHandle 构建的表单验证组件或模态可以很容易地在具有不同配置的应用程序的多个部分之间重用。

4、父组件的干净 API

您可以为父组件创建一个干净、定义完善的 API,而不是提供对整个组件的直接访问。这会导致更少的 bug 和更可预测的组件行为。

5、TypeScript 中更好的类型安全性

使用 TypeScript,useImperativeHandle 变得更加强大。您可以定义父级可以使用的确切方法和属性,从而提高类型安全性并确保开发人员在使用您的组件时遵循预期的 API。

结论

useImperativeHandle 是一个强大的钩子,它允许你自定义父组件可以访问的 ref 对象。当您想要向父组件公开自定义 API,而不是公开组件的内部实现详细信息时,这非常有用。

通过使用 useImperativeHandle,您可以创建更灵活、更强大的自定义组件,这些组件可以很容易地被父组件重用和自定义。


http://www.kler.cn/a/308854.html

相关文章:

  • C#语言详解:从基础到进阶
  • C/C++语言基础--C++模板与元编程系列六,C++元编程相关库的讲解与使用
  • Tomcat 和 Netty 的区别及应用场景分析
  • 如何使用 Web Scraper API 高效采集 Facebook 用户帖子信息
  • 基于微信小程序的乡村研学游平台设计与实现,LW+源码+讲解
  • Autosar CP 基于CAN的时间同步规范导读
  • visual prompt tuning和visual instruction tuning
  • 白话:大型语言模型中的幻觉(Hallucinations)
  • react hooks--useState
  • Spring Boot基础
  • 【C#生态园】虚拟现实与增强现实:C#开发库全面评估
  • 【C++】—— list 的了解与使用
  • 一天认识一个硬件之显示器
  • squid代理及常见的代理上网(Squid Proxy and Common Proxy Internet Access)
  • 组件编译脚本(Component Compilation Script)
  • vue3 动态 svg 图标使用
  • 网络安全实训八(y0usef靶机渗透实例)
  • 深度学习之图像数据集增强(Data Augmentation)
  • Java代码审计篇 | ofcms系统审计思路讲解 - 篇4 | XXE漏洞审计
  • Vue.nextTick 的工作机制
  • 【乐企-业务篇】开票前置校验服务-规则链服务接口实现(纳税人基本信息)
  • 基于SpringBoot+Vue+MySQL的网上甜品蛋糕售卖店管理系统
  • android 老项目中用到的jar包不存在,通过离线的方法加载
  • 项目实战应用Redis分布式锁
  • wordpress不同网站 调用同一数据表
  • Mac虚拟机Parallels Desktop 20 for Mac破解版发布 完整支持 Windows 11