React中的useImperativeHandle详解

React中的useImperativeHandle是一个自定义Hooks,它允许我们向子组件暴露一个特定的接口,使得我们可以在父组件中通过ref对象调用子组件中的方法或属性。其基本用法是:

import { forwardRef, useImperativeHandle } from 'react';

const Child = forwardRef((props, ref) => {
  const [value, setValue] = useState(0);

  useImperativeHandle(ref, () => ({
    increment() {
      setValue(value + 1);
    }
  }));

  return (
    <div>
      Value: {value}
    </div>
  );
});

const Parent = () => {
  const childRef = useRef();

  const handleClick = () => {
    childRef.current.increment();
  };

  return (
    <div>
      <Child ref={childRef} />
      <button onClick={handleClick}>Increment</button>
    </div>
  );
};

从上面的示例可以看出,useImperativeHandle接受两个参数:一个ref对象和一个函数,这个函数返回的对象会被合并到ref对象中。我们通常会把ref对象可调用的方法或就算是state值都传递给父组件所使用。

一、控制子组件暴露何种接口

useImperativeHandle的第二个参数是一个函数,它返回的对象会被合并到ref对象中。通过返回的对象不同,我们可以控制子组件暴露出何种接口。假设我们在父子组件间需要通过ref对象来控制子组件的2个按钮:“加1”和“减1”,那么我们可以在useImperativeHandle中返回一个对象,这个对象分别对应了“加1”和“减1”方法:

import { forwardRef, useImperativeHandle } from 'react';

const Child = forwardRef((props, ref) => {
  const [value, setValue] = useState(0);

  const increment = () => {
    setValue((val) => val + 1);
  };

  const decrement = () => {
    setValue((val) => val - 1);
  };

  useImperativeHandle(ref, () => ({
    increment,
    decrement
  }));

  return (
    <div>
      Value: {value}
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
    </div>
  );
});

const Parent = () => {
  const childRef = useRef();

  const handleIncrement = () => {
    childRef.current.increment();
  };

  const handleDecrement = () => {
    childRef.current.decrement();
  };

  return (
    <div>
      <Child ref={childRef} />
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleDecrement}>Decrement</button>
    </div>
  );
};

这样,我们就可以通过一个ref对象控制子组件内部的状态,而不必在子组件外再定义一个状态管理逻辑了。

二、仅在某些值发生改变时更新接口

useImperativeHandle的第三个参数是一个数组,它的元素是用来判断哪些依赖发生改变后再更新useImperativeHandle的返回值的。如果值发生变化,那么useImperativeHandle函数就会重新执行。举个例子:

import { forwardRef, useImperativeHandle, useState } from 'react';

const Child = forwardRef((props, ref) => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Child');

  useImperativeHandle(ref, () => ({
    increment() {
      console.log(count);
      setCount(count + 1);
    }
  }), [count]);

  console.log('Render Child');

  return <div>{name}: {count}</div>;
});

const Parent = () => {
  const childRef = useRef();

  const handleClick = () => {
    childRef.current.increment();
  };

  return (
    <div>
      <Child ref={childRef} />
      <button onClick={handleClick}>Increment</button>
    </div>
  );
};

在上面的例子中,在子组件中我们使用了count变量,这个变量是我们想让useImperativeHandle关注的状态。当count变化时,useImperativeHandle函数将重新执行,并更新ref所引用的值,这样我们就不需要再自己手动重新定义一下。

三、控制React组件工作流

在某些情况下,我们需要控制组件的生命周期,避免不必要的state更新。我们可以把所有控制代码封装到useImperativeHandle函数中。例如:

import { forwardRef, useImperativeHandle, useState } from 'react';

const Child = forwardRef((props, ref) => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Child');

  useImperativeHandle(ref, () => ({
    increment() {
      console.log(count);
      setCount(count + 1);
    }
  }), [count]);

  console.log('Render Child');

  return <div>{name}: {count}</div>;
});

const Parent = () => {
  const [showChild, setShowChild] = useState(false);
  const childRef = useRef();

  const handleClick = () => {
    childRef.current.increment();
  };

  return (
    <div>
      <button onClick={() => setShowChild(!showChild)}>Toggle Child</button>
      {showChild && <Child ref={childRef}/>}
      <button onClick={handleClick}>Increment</button>
    </div>
  );
};

在上述代码中,我们向子组件暴露了一个increment方法,这个方法被父组件控制。当我们点击“Toggle Child”按钮时,子组件重新出现在DOM中,并且再次渲染。但是,我们应该只在子组件出现时重新渲染一次子组件,而不是每次父组件中的任何状态发生变化就更新一次子组件。这个问题可以通过使用useImperativeHandle来解决。实现方法如下:

import { forwardRef, useImperativeHandle, useState, useMemo } from 'react';

const Child = forwardRef((props, ref) => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Child');

  useImperativeHandle(ref, () => ({
    increment() {
      console.log(count);
      setCount(count + 1);
    }
  }), [count]);

  console.log('Render Child');

  return <div>{name}: {count}</div>;
});

const MemoChild = memo(Child);

const Parent = () => {
  const [showChild, setShowChild] = useState(false);
  const childRef = useRef();

  const handleClick = () => {
    childRef.current.increment();
  };

  const child = useMemo(() => <MemoChild ref={childRef}/>, []);

  return (
    <div>
      <button onClick={() => setShowChild(!showChild)}>Toggle Child</button>
      {showChild && child}
      <button onClick={handleClick}>Increment</button>
    </div>
  );
};

在Parent组件中,我们使用了useMemo来 memorize Child组件,并通过 child变量渲染它。在父组件状态发生变化时,child不会被重新渲染,因为Child组件的props没有发生变化。只有在我们触发了“Toggle Child”按钮并且showChild和child之间的匹配状态发生变化时,child重新渲染。

原创文章,作者:小蓝,如若转载,请注明出处:https://www.506064.com/n/312861.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
小蓝小蓝
上一篇 2025-01-06 15:17
下一篇 2025-01-06 15:17

相关推荐

  • @uiw/react-amap介绍

    本文将详细阐述@uiw/react-amap的使用方法和参数配置,以及如何在React应用中集成高德地图组件。 一、@uiw/react-amap简介 @uiw/react-ama…

    编程 2025-04-29
  • Webrtc音视频开发React+Flutter+Go实战PDF

    本文将从多个方面介绍如何使用React、Flutter和Go来进行Webrtc音视频开发,并提供相应的代码示例。 一、Webrtc音视频开发介绍 Webrtc是Google开发的一…

    编程 2025-04-27
  • React简书项目

    本文将从以下几个方面介绍React简书项目: 项目概述 组件分析 路由配置 Redux状态管理 项目优化 一、项目概述 React简书项目是一个类似于博客的Web应用,提供用户撰写…

    编程 2025-04-27
  • Linux sync详解

    一、sync概述 sync是Linux中一个非常重要的命令,它可以将文件系统缓存中的内容,强制写入磁盘中。在执行sync之前,所有的文件系统更新将不会立即写入磁盘,而是先缓存在内存…

    编程 2025-04-25
  • 神经网络代码详解

    神经网络作为一种人工智能技术,被广泛应用于语音识别、图像识别、自然语言处理等领域。而神经网络的模型编写,离不开代码。本文将从多个方面详细阐述神经网络模型编写的代码技术。 一、神经网…

    编程 2025-04-25
  • Python输入输出详解

    一、文件读写 Python中文件的读写操作是必不可少的基本技能之一。读写文件分别使用open()函数中的’r’和’w’参数,读取文件…

    编程 2025-04-25
  • git config user.name的详解

    一、为什么要使用git config user.name? git是一个非常流行的分布式版本控制系统,很多程序员都会用到它。在使用git commit提交代码时,需要记录commi…

    编程 2025-04-25
  • Linux修改文件名命令详解

    在Linux系统中,修改文件名是一个很常见的操作。Linux提供了多种方式来修改文件名,这篇文章将介绍Linux修改文件名的详细操作。 一、mv命令 mv命令是Linux下的常用命…

    编程 2025-04-25
  • 详解eclipse设置

    一、安装与基础设置 1、下载eclipse并进行安装。 2、打开eclipse,选择对应的工作空间路径。 File -> Switch Workspace -> [选择…

    编程 2025-04-25
  • nginx与apache应用开发详解

    一、概述 nginx和apache都是常见的web服务器。nginx是一个高性能的反向代理web服务器,将负载均衡和缓存集成在了一起,可以动静分离。apache是一个可扩展的web…

    编程 2025-04-25

发表回复

登录后才能评论