使用MutationObserver的自定义React Hooks指南

随着React Hooks的引入,React代码库中的可共享代码数量已经爆炸性增长。因为Hooks是React顶部的薄API,开发者可以通过将可重复使用的行为附加到组件上并将这些行为隔离到更小的模块中来进行协作。

虽然这类似于JavaScript开发人员在普通的JavaScript模块中抽象出商业逻辑,但Hooks提供了比纯JavaScript函数更多的东西。开发者可以扩展Hook内部发生的各种可能性,而不是将数据带入和带出。

例如,开发者可以。

  • 为一个特定的组件或整个应用程序突变和管理一段状态
  • 触发页面上的副作用,比如改变浏览器标签的标题
  • 通过使用Hooks进入React组件的生命周期来修正外部API

在这篇文章中,我们将探讨后者的可能性。作为一个案例,我们将在一个自定义的React Hook中抽象出MutationObserver API,展示我们如何在React代码库中建立强大的、可共享的逻辑片段。

我们将创建一个动态标签,它可以自我更新,以表明我们在一个列表中有多少个项目。我们不使用React提供的元素状态数组,而是使用MutationObserver API来检测添加的元素并相应地更新标签。

Update The Dynamic Label To Count Fruit

更新动态标签以计算列表中水果的数量。

实施概述

下面的代码是一个简单的组件,它渲染了我们的列表。它还更新一个计数器的值,代表当前列表中的水果数量。

export default function App() {
  const listRef = useRef();
  const [count, setCount] = useState(2);
  const [fruits, setFruits] = useState(["apple", "peach"]);
  const onListMutation = useCallback(
    (mutationList) => {
      setCount(mutationList[0].target.children.length);
    },
    [setCount]
  );

  useMutationObservable(listRef.current, onListMutation);

  return (
    <div>
      <span>{`Added ${count} fruits`}</span>
      <br />
      <button
        onClick={() => setFruits([...fruits, `random fruit ${fruits.length}`])}
      >
        Add random fruit
      </button>
      <ul ref={listRef}>
        {fruits.map((f) => (
          <li key={f}>{f}</li>
        ))}
      </ul>
    </div>
  );
}

复制代码

我们想在我们的list 元素发生变化时触发一个回调函数。在我们所指的回调中,元素的子代给我们列表中元素的数量。

实现useMutationObservable 自定义Hook

让我们来看看集成点。

useMutationObservable(listRef.current, onListMutation);

复制代码

上面的useMutationObservable 自定义Hook抽象了必要的操作来观察作为第一个参数传递的元素的变化。然后,只要目标元素发生变化,它就会运行作为第二个参数的回调。

现在,让我们来实现我们的useMutationObservable 自定义Hook。

在Hook中,有一些模板操作需要理解。首先,我们必须提供一组符合MutationObserver API的选项。

一旦一个MutationObserver 实例被创建,我们必须调用observe 来监听目标DOM元素的变化。

当我们不再需要监听这些变化时,我们必须在观察者上调用disconnect ,以清理我们的订阅。这必须在App 组件卸载时发生。

const DEFAULT_OPTIONS = {
  config: { attributes: true, childList: true, subtree: true },
};
function useMutationObservable(targetEl, cb, options = DEFAULT_OPTIONS) {
  const [observer, setObserver] = useState(null);

  useEffect(() => {
    const obs = new MutationObserver(cb);
    setObserver(obs);
  }, [cb, options, setObserver]);

  useEffect(() => {
    if (!observer) return;
    const { config } = options;
    observer.observe(targetEl, config);
    return () => {
      if (observer) {
        observer.disconnect();
      }
    };
  }, [observer, targetEl, options]);
}

复制代码

所有上述工作,包括用正确的参数初始化MutationObserver ,通过调用observer.observe 观察变化,以及用observer.disconnect 清理,都是从客户端抽象出来的。

我们不仅输出功能,而且还通过钩住React组件的生命周期和利用效果钩子上的清理回调来清理MutationObserver 实例。

现在我们有了一个功能性的基本版本的Hook,我们可以考虑通过迭代它的API来提高它的质量,并围绕这段可共享的代码增强开发者的体验。

输入验证和开发

在设计自定义React Hooks时,一个重要的方面是输入验证。我们必须能够在事情运行不顺畅或某个用例遇到边缘情况时向开发者传达。

通常情况下,开发日志可以帮助开发人员了解不熟悉的代码,以调整他们的实现。同样,我们可以通过增加运行时检查和全面的警告日志来加强上述实现,以验证和沟通问题给其他开发者。

function useMutationObservable(targetEl, cb, options = DEFAULT_OPTIONS) {
  const [observer, setObserver] = useState(null);

  useEffect(() => {
    // A)
    if (!cb || typeof cb !== "function") {
      console.warn(
        `You must provide a valid callback function, instead you've provided ${cb}`
      );
      return;
    }
    const { debounceTime } = options;
    const obs = new MutationObserver(cb);
    setObserver(obs);
  }, [cb, options, setObserver]);
  useEffect(() => {
    if (!observer) return;
    if (!targetEl) {
      // B)
      console.warn(
        `You must provide a valid DOM element to observe, instead you've provided ${targetEl}`
      );
    }
    const { config } = options;
    try {
      observer.observe(targetEl, config);
    } catch (e) {
      // C)
      console.error(e);
    }
    return () => {
      if (observer) {
        observer.disconnect();
      }
    };
  }, [observer, targetEl, options]);
}

复制代码

在这个例子中,我们要检查一个回调是否作为第二个参数被传递。这种运行时的API检查可以很容易地提醒开发者,在调用者一方出现了问题。

我们还可以看到提供的DOM元素在运行时提供给Hook的值是否有误,是否无效。这些都被记录在一起,以告知我们快速解决这个问题。

而且,如果observe 抛出一个错误,我们可以捕捉并报告它。我们必须尽可能避免破坏JavaScript运行时的流程,所以通过捕捉错误,我们可以根据环境选择记录它或报告它。

通过配置实现可扩展性

如果我们想给我们的Hook增加更多的功能,我们应该以一种追溯兼容的方式来做,比如一种选择能力,对它的采用几乎没有摩擦。

让我们来看看我们如何能够选择性地去掉所提供的回调函数,所以调用者可以指定一个时间间隔,当目标元素中没有其他变化触发时。这样就可以运行一次回调,而不是运行相同数量的元素或其子元素变异的次数。

import debounce from "lodash.debounce";

const DEFAULT_OPTIONS = {
  config: { attributes: true, childList: true, subtree: true },
  debounceTime: 0
};
function useMutationObservable(targetEl, cb, options = DEFAULT_OPTIONS) {
  const [observer, setObserver] = useState(null);
  useEffect(() => {
    if (!cb || typeof cb !== "function") {
      console.warn(
        `You must provide a valida callback function, instead you've provided ${cb}`
      );
      return;
    }
    const { debounceTime } = options;
    const obs = new MutationObserver(
      debounceTime > 0 ? debounce(cb, debounceTime) : cb
    );
    setObserver(obs);
  }, [cb, options, setObserver]);
  // ...

复制代码

如果我们必须运行一个繁重的操作,如触发一个网络请求,这很方便,确保它运行的次数尽可能少。

我们的debounceTime 选项现在可以传入我们的自定义Hook。如果一个大于0 的值传递到MutationObservable ,回调就会相应地延迟。

通过我们的Hook API中的简单配置,我们允许其他开发者对他们的回调进行去抖动,这可能会导致一个更高性能的实现,因为我们可能会大幅减少回调代码的执行次数。

当然,我们总是可以在客户端去掉回调,但这样我们就可以丰富我们的API,使调用方的实现更小,更具有声明性。

测试

测试是开发任何类型的共享能力的一个重要部分。当通用API被大量贡献和共享时,它可以帮助我们确保一定程度的质量。

测试React Hooks的指南中有大量关于测试的细节,可以在本教程中实施。

文档

文档可以提高自定义Hooks的质量,使其对开发者友好。

但即使在编写普通JavaScript时,也可以为自定义Hook API编写JSDoc文档,以确保Hook向开发者传递正确的信息。

让我们关注一下useMutationObservable 函数声明以及如何为其添加格式化的JSDoc文档。

/**
 * This custom hooks abstracts the usage of the Mutation Observer with React components.
 * Watch for changes being made to the DOM tree and trigger a custom callback.
 * @param {Element} targetEl DOM element to be observed
 * @param {Function} cb callback that will run when there's a change in targetEl or any
 * child element (depending on the provided options)
 * @param {Object} options 
 * @param {Object} options.config check \[options\](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe)
 * @param {number} [options.debounceTime=0] a number that represents the amount of time in ms
 * that you which to debounce the call to the provided callback function
 */
function useMutationObservable(targetEl, cb, options = DEFAULT_OPTIONS) {

复制代码

编写这个不仅对文档有用,而且还可以利用IntelliSense功能,自动完成Hook的使用,并为Hook的参数提供现货信息。这为开发者每次使用节省了几秒钟的时间,有可能增加到浪费在阅读代码和试图理解它的时间。

总结

通过我们可以实现的不同种类的自定义Hooks,我们看到它们是如何将外在的API集成到React世界中的。在Hooks中整合状态管理并根据使用Hook的组件的输入运行效果是很容易的。

请记住,要建立高质量的Hooks,重要的是。

  • 设计易于使用的、声明性的API
  • 通过检查正确的用法和记录警告和错误来增强开发经验
  • 通过配置暴露功能,如debounceTime 的例子
  • 通过编写JSDoc文档来简化Hook的使用

你可以在这里查看自定义React Hook的完整实现

The postGuide to custom React Hooks with MutationObserver appearedfirst on LogRocketBlog.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享