中文

通过自定义 Hook 释放您 React 应用中可复用逻辑的强大功能。学习如何创建和利用自定义 Hook,以编写更清晰、更易于维护的代码。

自定义 Hook:React 中的可复用逻辑模式

React Hook 通过将状态和生命周期特性引入函数式组件,彻底改变了我们编写 React 组件的方式。在其众多优点中,自定义 Hook 作为一种跨多个组件提取和复用逻辑的强大机制脱颖而出。本篇博客将通过实例深入探讨自定义 Hook 的世界,探索其优势、创建方法和使用方式。

什么是自定义 Hook?

从本质上讲,自定义 Hook 是一个以“use”开头的 JavaScript 函数,它可以调用其他 Hook。它们允许您将组件逻辑提取到可复用的函数中。这是一种在组件之间共享有状态逻辑、副作用或其他复杂行为的强大方式,而无需依赖 render props、高阶组件或其他复杂模式。

自定义 Hook 的主要特点:

使用自定义 Hook 的好处

在 React 开发中,自定义 Hook 提供了几个显著的优势:

创建您的第一个自定义 Hook

让我们通过一个实际的例子来演示如何创建一个自定义 Hook:一个用于跟踪窗口大小的 Hook。

示例:useWindowSize

这个 Hook 将返回浏览器窗口的当前宽度和高度。当窗口大小调整时,它也会更新这些值。

import { useState, useEffect } from 'react';

function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }

    window.addEventListener('resize', handleResize);

    // 在清理时移除事件监听器
    return () => window.removeEventListener('resize', handleResize);
  }, []); // 空数组确保 effect 只在挂载时运行一次

  return windowSize;
}

export default useWindowSize;

解释:

  1. 导入必要的 Hook:我们从 React 中导入 useStateuseEffect
  2. 定义 Hook:我们创建一个名为 useWindowSize 的函数,遵循命名约定。
  3. 初始化状态:我们使用 useState 来初始化 windowSize 状态,其初始值为窗口的初始宽度和高度。
  4. 设置事件监听器:我们使用 useEffect 向窗口添加一个 resize 事件监听器。当窗口大小调整时,handleResize 函数会更新 windowSize 状态。
  5. 清理:我们从 useEffect 返回一个清理函数,在组件卸载时移除事件监听器。这可以防止内存泄漏。
  6. 返回值:该 Hook 返回 windowSize 对象,其中包含窗口的当前宽度和高度。

在组件中使用自定义 Hook

既然我们已经创建了自定义 Hook,让我们看看如何在 React 组件中使用它。

import React from 'react';
import useWindowSize from './useWindowSize';

function MyComponent() {
  const { width, height } = useWindowSize();

  return (
    

窗口宽度: {width}px

窗口高度: {height}px

); } export default MyComponent;

解释:

  1. 导入 Hook:我们导入 useWindowSize 自定义 Hook。
  2. 调用 Hook:我们在组件内部调用 useWindowSize Hook。
  3. 访问值:我们对返回的对象进行解构,以获取 widthheight 的值。
  4. 渲染值:我们在组件的 UI 中渲染宽度和高度的值。

任何使用 useWindowSize 的组件都会在窗口大小改变时自动更新。

更复杂的示例

让我们探讨一些更高级的自定义 Hook 用例。

示例:useLocalStorage

这个 Hook 允许您轻松地从本地存储中存储和检索数据。

import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  // 用来存储值的 state
  // 将初始值传递给 useState,这样逻辑只执行一次
  const [storedValue, setStoredValue] = useState(() => {
    try {
      // 通过 key 从 local storage 中获取
      const item = window.localStorage.getItem(key);
      // 解析存储的 json,如果没有则返回 initialValue
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      // 如果出错,也返回 initialValue
      console.log(error);
      return initialValue;
    }
  });

  // 返回一个 useState setter 函数的包装版本,它...
  // ...将新值持久化到 localStorage。
  const setValue = (value) => {
    try {
      // 允许 value 是一个函数,这样我们就有与 useState 相同的 API
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      // 保存到 local storage
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
      // 保存 state
      setStoredValue(valueToStore);
    } catch (error) {
      // 更高级的实现会处理错误情况
      console.log(error);
    }
  };

  useEffect(() => {
    try {
      const item = window.localStorage.getItem(key);
      setStoredValue(item ? JSON.parse(item) : initialValue);
    } catch (error) {
      console.log(error);
    }
  }, [key, initialValue]);

  return [storedValue, setValue];
}

export default useLocalStorage;

用法:

import React from 'react';
import useLocalStorage from './useLocalStorage';

function MyComponent() {
  const [name, setName] = useLocalStorage('name', 'Guest');

  return (
    

你好, {name}!

setName(e.target.value)} />
); } export default MyComponent;

示例:useFetch

这个 Hook 封装了从 API 获取数据的逻辑。

import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`HTTP 错误!状态: ${response.status}`);
        }
        const json = await response.json();
        setData(json);
        setLoading(false);
      } catch (error) {
        setError(error);
        setLoading(false);
      }
    }

    fetchData();
  }, [url]);

  return { data, loading, error };
}

export default useFetch;

用法:

import React from 'react';
import useFetch from './useFetch';

function MyComponent() {
  const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/todos/1');

  if (loading) return 

加载中...

; if (error) return

错误: {error.message}

; return (

标题: {data.title}

已完成: {data.completed ? '是' : '否'}

); } export default MyComponent;

自定义 Hook 的最佳实践

为确保您的自定义 Hook 高效且易于维护,请遵循以下最佳实践:

要避免的常见陷阱

高级模式

组合自定义 Hook

自定义 Hook 可以组合在一起以创建更复杂的逻辑。例如,您可以将 useLocalStorage Hook 与 useFetch Hook 结合起来,自动将获取的数据持久化到本地存储中。

在 Hook 之间共享逻辑

如果多个自定义 Hook 共享通用逻辑,您可以将该逻辑提取到一个单独的工具函数中,并在两个 Hook 中重复使用它。

将 Context 与自定义 Hook 结合使用

自定义 Hook 可以与 React Context 结合使用,以访问和更新全局状态。这使您能够创建可复用的组件,这些组件能够感知并与应用程序的全局状态进行交互。

真实世界示例

以下是一些自定义 Hook 在真实世界应用中如何使用的示例:

示例:用于地图或配送服务等跨文化应用的 useGeolocation Hook

import { useState, useEffect } from 'react';

function useGeolocation() {
  const [location, setLocation] = useState({
    latitude: null,
    longitude: null,
    error: null,
  });

  useEffect(() => {
    if (!navigator.geolocation) {
      setLocation({
        latitude: null,
        longitude: null,
        error: '此浏览器不支持地理定位。',
      });
      return;
    }

    const watchId = navigator.geolocation.watchPosition(
      (position) => {
        setLocation({
          latitude: position.coords.latitude,
          longitude: position.coords.longitude,
          error: null,
        });
      },
      (error) => {
        setLocation({
          latitude: null,
          longitude: null,
          error: error.message,
        });
      }
    );

    return () => navigator.geolocation.clearWatch(watchId);
  }, []);

  return location;
}

export default useGeolocation;

结论

自定义 Hook 是编写更清晰、更可复用、更易维护的 React 代码的强大工具。通过将复杂逻辑封装在自定义 Hook 中,您可以简化组件、减少代码重复,并改善应用程序的整体结构。拥抱自定义 Hook,释放其潜力,构建更健壮、更具可扩展性的 React 应用程序。

首先,在您现有的代码库中找出逻辑在多个组件间重复的地方。然后,将该逻辑重构为自定义 Hook。随着时间的推移,您将建立一个可复用的 Hook 库,这将加快您的开发进程并提高代码质量。

请记住遵循最佳实践,避免常见陷阱,并探索高级模式,以充分利用自定义 Hook。通过实践和经验,您将成为自定义 Hook 的大师和更高效的 React 开发人员。