插件窝 干货文章 掌握 React 中的 useImperativeHandle(使用 TypeScript)

掌握 React 中的 useImperativeHandle(使用 TypeScript)

组件 useimperativehan gt 使用 24    来源:    2024-10-20

使用 typescript 构建 react 应用程序时,开发人员经常遇到需要创建具有高级功能的自定义、可重用组件的场景。本文将探讨两个强大的概念:用于对引用管理进行细粒度控制的 useimperativehandle 挂钩,以及创建表单验证和模态组件等自定义组件。

我们将深入探讨:

  1. useimperativehandle 钩子:它的作用、何时使用它以及它如何允许您自定义父组件可以访问的 ref 值。
  2. 创建表单验证组件:使用 typescript 构建可重用组件进行表单验证的实际示例。
  3. 实现模态组件:另一个示例展示如何使用 typescript 创建交互式且可重用的模态。

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

什么是 useimperativehandle?

useimperativehandle 是 react 中的一个钩子,允许您自定义父组件可以访问的 ref 对象。当您想要向父组件公开自定义 api,而不是公开组件的内部实现细节时,这非常有用。

何时以及为何应该使用它

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

示例 1 - 拨动开关组件

此示例演示了如何使用 typescript 创建切换开关组件。组件使用 useimperativehandle 向父组件公开自定义 api,允许父组件控制开关状态。

  • 用例:创建可由父组件控制的自定义切换开关组件。
  • 实施:
    • 定义组件并使用 useimperativehandle 公开自定义 api。
    • 在父组件中创建一个 ref 并将其传递给 toggleswitch 组件。
    • 使用ref调用自定义api并控制开关状态。
import react, { forwardref, `useimperativehandle`, usestate } from "react";

interface toggleref {
  toggle: () => void;
  getstate: () => boolean;
}

type toggleswitchprops = {
  initialstate?: boolean;
};

const toggleswitch = forwardref<toggleref toggleswitchprops>((props, ref) =&gt; {
  const [istoggled, setistoggled] = usestate(props.initialstate ?? false);

  `useimperativehandle`(ref, () =&gt; ({
    toggle: () =&gt; setistoggled(!istoggled),
    getstate: () =&gt; 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 }}
    &gt;
      <motion.div classname="flex items-center justify-center w-5 h-5 bg-white rounded-full" animate="{{" x: istoggled : transition="{{" type: stiffness: damping:></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}"></toggleswitch><button onclick="{()"> toggleref.current?.toggle()}
          classname="px-4 py-2 text-white bg-blue-500 rounded-md"
        &gt;
          toggle switch
        </button>
      </section>
</div>
  );
}
</toggleref></toggleref>

示例 2 - 手风琴组件

此示例演示了如何使用 typescript 创建手风琴组件。该组件使用 useimperativehandle 向父组件公开自定义 api,允许父组件控制折叠状态。

  • 用例:创建可由父组件控制的自定义手风琴组件。
  • 实施:
    • 定义组件并使用 useimperativehandle 公开自定义 api。
    • 在父组件中创建一个 ref 并将其传递给 accordion 组件。
    • 使用ref调用自定义api并控制accordion状态。
interface AccordionRef {
  expand: () =&gt; void;
  collapse: () =&gt; void;
  isExpanded: () =&gt; boolean;
  toggle: () =&gt; void;
}

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

const Accordion = forwardRef<accordionref accordionprops>((props, ref) =&gt; {
  const [expanded, setExpanded] = useState(props.initialState ?? false);

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

  const handleToggle = () =&gt; {
    setExpanded((prev) =&gt; !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 :>
        {props.title}
      </motion.button><animatepresence initial="{false}">
        {expanded &amp;&amp; (
          <motion.div initial="collapsed" animate="expanded" exit="collapsed" variants="{{" expanded: opacity: height: collapsed: transition="{{" duration: ease:><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."></accordion></main><button onclick="{()"> {
          accordionRef.current?.expand();
        }}
        className="px-4 py-2 text-white bg-blue-500 rounded-md disabled:bg-gray-500"
      &gt;
        Expand Accordion
      </button>
      <button onclick="{()"> accordionRef.current?.collapse()}
        className="px-4 py-2 text-white bg-blue-500 rounded-md"
      &gt;
        Collapse Accordion
      </button>
      <button onclick="{()"> accordionRef.current?.toggle()}
        className="px-4 py-2 text-white bg-blue-500 rounded-md"
      &gt;
        Toggle Accordion
      </button>
    </div>
  );
}
</accordionref></accordionref>

使用 useimperativehandle 的优点

useimperativehandle 提供了一些关键的好处,特别是在构建可重用和交互式组件时:

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

  2. 细粒度控制
    它使您可以对 ref 对象进行细粒度控制。您无需公开整个组件实例或 dom 节点,而是可以决定哪些方法或值可用。当使用表单、切换或模态等复杂组件时,这一点至关重要。

  3. 提高可重用性
    通过抽象某些逻辑并控制向父级公开的内容,您的组件可以变得更加可重用。例如,表单验证组件或使用 useimperativehandle 构建的模式可以轻松地在具有不同配置的应用程序的多个部分中重用。

  4. 清除父组件的 api
    您可以为父组件创建一个干净、定义良好的 api,而不是提供对整个组件的直接访问。这会减少错误并提高组件行为的可预测性。

  5. typescript 中更好的类型安全
    借助 typescript,useimperativehandle 变得更加强大。您可以定义父级可以使用的确切方法和属性,从而提高类型安全性并确保开发人员在使用组件时遵循预期的 api。

结论

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

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

资源

  • react 文档 - useimperativehandle
  • react 文档 -forwardref
  • react 文档 - useref
  • typescript 文档