React基础 第二十四章(DOM操作)

在React的世界里,我们通常不需要直接操作DOM,因为React为我们抽象了DOM操作。然而,有些场景下,我们仍然需要直接访问DOM节点,比如处理焦点、滚动或者测量元素的尺寸和位置。本文将详细探讨如何在React中使用refs来操作DOM,并提供一些实用的技巧和代码示例。

如何使用ref属性访问由React管理的DOM节点

ref是一种特殊的属性,它可以用来获取组件中的DOM元素。useRef是一个钩子(Hook),它可以创建一个ref对象,这个对象可以被附加到React元素上。当组件渲染时,这个ref对象的current属性就会指向对应的DOM元素,允许你直接访问和操作那个元素。

技巧

  • 使用useRef创建ref,并将其赋予JSX元素的ref属性。

示例

jsx 复制代码
// 1.导入`useRef`钩子
import { useRef } from 'react';

function TextInputWithFocusButton() {
  // 2.在组件中调用`useRef`,并传入初始值`null`。这会创建一个ref对象,你可以将它赋给任何你想要引用的DOM元素。
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // 4.一旦你有了指向DOM元素的ref,你就可以在组件的函数中使用它
    inputEl.current.focus();
  };
  return (
    <>
      <!--3.将创建的ref对象通过`ref`属性赋给一个JSX元素-->
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>聚焦输入框</button>
    </>
  );
}

注意事项

使用ref时,你需要确保在组件的整个生命周期内,ref指向的DOM元素不会发生变化。这意味着,你不应该在条件渲染的情况下使用ref,因为这可能会导致ref指向的DOM元素发生变化。

如果你在一个条件语句中渲染了一个元素,并且给它赋予了ref,当条件变化导致元素不再渲染时,ref的current属性将会变成null。这可能会导致你的代码在尝试访问current属性时出错。

确保ref始终指向一个稳定的DOM元素,可以避免这类问题。如果你确实需要在条件渲染中使用ref,你应该在每次渲染时都检查ref的current属性是否存在。

如何访问另一个组件的DOM节点

在React中,forwardRef是一个用于将ref自动传递给子组件的技术。通常情况下,refs并不会像props那样自动传递给子组件。这意味着如果你尝试像传递props一样传递ref,它将不会起作用。React.forwardRef解决了这个问题,它允许你将ref从父组件传递到子组件中的DOM元素。

技巧

  • 使用forwardRef来创建一个能够接收ref的组件。

示例

jsx 复制代码
// 1.导入`forwardRef`:
import React, { useRef, forwardRef } from 'react';

// 2.使用`forwardRef`来定义一个组件,这个组件能够接收`props`和`ref`作为参数,并将`ref`赋给`input`元素
const FancyInput = forwardRef((props, ref) => (
  <input ref={ref} {...props} />
));

function App() {
  // 3.在父组件中,你可以使用`useRef`钩子创建一个ref,并将其作为`ref`属性传递给`FancyInput`组件。
  const inputRef = useRef();
  return <FancyInput ref={inputRef} />;
}

通过以上3步inputRef.current将指向FancyInput内部的input元素

注意事项

在函数组件中,你通常不能直接使用ref,因为函数组件没有实例。这就是为什么你需要使用forwardRef来传递ref。如果你尝试在没有使用forwardRef的函数组件上直接设置ref,你将得到一个警告,并且ref将不会被正确传递。

使用forwardRef时,你应该确保它包裹的组件能够正确处理传递进来的ref。在FancyInput组件中,我们确保了传递给组件的ref被赋给了正确的DOM元素,这样父组件就可以通过ref来访问和控制这个DOM元素。

在哪些情况下修改React管理的DOM是安全的

何时可以安全地修改DOM

通常,React以声明式的方式管理DOM:你描述你的UI应该是什么样子,React则确保DOM匹配你的描述。这意味着你通常不需要直接修改DOM,因为React会为你做这些事情。然而,有些情况下,你可能需要直接与DOM交互,这时候就需要特别小心,以免破坏React的DOM更新逻辑。

你可以在React不会再次更新的DOM节点上进行修改。这通常发生在以下几种情况:

  • 组件已经被挂载,并且你知道React不会再次触及这个特定的DOM节点。
  • 你正在操作的DOM节点不是由React渲染的,比如通过第三方库创建的节点。

使用useEffect确保安全修改DOM

在React组件中,你可以使用useEffect钩子来执行副作用,包括操作DOM。你应该在useEffect中修改DOM,因为它提供了一个在组件渲染之后执行代码的时机,这时候所有的DOM节点都已经正确挂载。

在下面的例子中,我们使用useEffect来修改一个divinnerHTML

示例

jsx 复制代码
function MyComponent() {
  const divRef = useRef();

  useEffect(() => {
    // 这段代码会在组件挂载后执行,这时候React不会再次触及这个div的子节点
    const div = divRef.current;
    div.innerHTML = '<strong>Hi there!</strong>';
  }, []); // 空依赖数组意味着这个effect只会在组件挂载后运行一次

  return <div ref={divRef} />;
}

注意事项

当你直接修改由React管理的DOM时,你必须非常小心,因为这可能会导致React的内部状态与实际的DOM状态不一致,从而导致不可预测的行为。例如,如果你修改了React正在管理的DOM节点的子节点,然后React尝试更新这个节点,它可能会覆盖你的修改,或者在某些情况下,甚至可能导致错误。

为了避免这些问题,你应该:

  • 尽可能避免修改React管理的DOM。
  • 如果必须修改,确保你了解React的更新机制,并且确信React不会再次触及你正在修改的部分。

使用refs操作DOM的最佳实践

使用refs操作DOM是React中一个强大但需要谨慎使用的功能。Refs提供了一种逃生舱机制,让你能够直接访问DOM节点,但如果使用不当,可能会导致应用出现问题。以下是一些使用refs操作DOM的最佳实践,以及如何避免常见的陷阱。

技巧

  • 只在必要时使用refs操作DOM: 通常,你应该尽量避免使用refs来操作DOM,因为这可能会破坏React的声明式渲染模型。只有在没有声明式解决方案的情况下,比如需要集中管理焦点、触发动画或集成第三方DOM库时,才应该使用refs。

  • 使用refs进行非破坏性操作: 当你确实需要使用refs时,应该尽量进行非破坏性操作。这包括读取DOM的尺寸和位置、触发焦点或滚动等。这些操作通常不会对DOM结构造成改变,因此不太可能与React的更新机制发生冲突。

示例

jsx 复制代码
function ScrollToTopButton() {
  const topRef = useRef();

  const scrollToTop = () => {
    // 使用scrollIntoView方法来平滑滚动到ref所指向的div元素
    topRef.current.scrollIntoView({ behavior: 'smooth' });
  };

  return (
    <>
      <div ref={topRef} /> {/* 这个div作为滚动的目标 */}
      <button onClick={scrollToTop}>回到顶部</button>
    </>
  );
}

在这个例子中,topRef是一个ref,它被赋予了一个div元素。当按钮被点击时,scrollToTop函数会被调用,它使用scrollIntoView方法来平滑滚动到div元素的位置。这是一个非破坏性操作,因为它不会改变DOM结构,只是改变了页面的滚动位置。

注意事项

  • 避免使用refs来进行DOM修改: 你应该避免使用refs来直接修改DOM(如改变元素的innerHTML),因为这可能会与React的更新机制冲突。如果React试图更新一个你已经修改过的DOM节点,可能会导致不一致的渲染结果,甚至应用崩溃。

记住,refs是一个强大的工具,但也需要谨慎使用,以保持应用的稳定性和可维护性。

相关推荐
小白学习日记38 分钟前
【复习】HTML常用标签<table>
前端·html
丁总学Java1 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele1 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀2 小时前
CSS——属性值计算
前端·css
xgq2 小时前
使用File System Access API 直接读写本地文件
前端·javascript·面试
用户3157476081352 小时前
前端之路-了解原型和原型链
前端
永远不打烊2 小时前
librtmp 原生API做直播推流
前端
北极小狐2 小时前
浏览器事件处理机制:从硬件中断到事件驱动
前端
无咎.lsy2 小时前
vue之vuex的使用及举例
前端·javascript·vue.js
fishmemory7sec2 小时前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron