React中的自定义Hook

300 阅读3分钟

React 16.8 引入了 Hooks 这一重大特性,使得函数式组件也能使用 state 和其他 React 特性,极大地提升了代码的可复用性和开发效率。自定义 Hook 作为 Hooks 机制中的一大亮点,允许开发者将复杂的逻辑封装成可重用的函数,从而进一步提升开发效率和代码的可维护性。

一、自定义 Hook 的基本用法

1. 什么是自定义 Hook?

自定义 Hook 是一个以 use 开头的 JavaScript 函数,它可以在函数组件中调用其他 Hook,并将逻辑封装在函数中供组件使用。通过自定义 Hook,可以轻松复用状态逻辑和副作用逻辑。

2. 创建自定义 Hook

创建自定义 Hook 非常简单,只需编写一个以 use 开头的函数,并在函数内部调用其他 Hook。以下是一个创建计数器 Hook 的示例:

import { useState } from 'react';  
  
function useCounter(initialValue = 0) {  
    const [count, setCount] = useState(initialValue);  
    const increment = () => setCount(count + 1);  
    const decrement = () => setCount(count - 1);  
    return { count, increment, decrement };  
}

3. 使用自定义 Hook

创建好自定义 Hook 后,可以在组件中像使用内置 Hook 一样使用它。以下是使用自定义计数器 Hook 的示例:

import React from 'react';  
import useCounter from './useCounter';  
  
function CounterComponent() {  
    const { count, increment, decrement } = useCounter(10);  
    return (  
        <div>  
            <p>Count: {count}</p>  
            <button onClick={increment}>Increase</button>  
            <button onClick={decrement}>Decrease</button>  
        </div>  
    );  
}  
  
export default CounterComponent;

二、自定义 Hook 的进阶应用

1. 处理副作用

自定义 Hook 可以处理副作用,如数据获取、订阅和定时器等。使用 useEffect 可以将这些副作用逻辑封装在自定义 Hook 中。例如,创建一个用于数据获取的 Hook:

import { useState, useEffect } from 'react';  
  
function useFetch(url) {  
    const [data, setData] = useState(null);  
    const [loading, setLoading] = useState(true);  
  
    useEffect(() => {  
        fetch(url).then(response => response.json()).then(data => {  
            setData(data);  
            setLoading(false);  
        });  
  
        return () => {  
            // 清除逻辑  
        };  
    }, [url]);  
  
    return { data, loading };  
}

2. 组合多个 Hook

自定义 Hook 可以组合多个内置 Hook 来实现复杂的逻辑。例如,结合 useState 和 useEffect 实现一个带有计时功能的计数器:

import { useState, useEffect } from 'react';  
  
function useTimedCounter(initialValue = 0) {  
    const [count, setCount] = useState(initialValue);  
    const [isRunning, setIsRunning] = useState(false);  
  
    useEffect(() => {  
        let timer;  
        if (isRunning) {  
            timer = setInterval(() => {  
                setCount(prevCount => prevCount + 1);  
            }, 1000);  
        }  
        return () => {  
            clearInterval(timer);  
        };  
    }, [isRunning]);  
  
    const start = () => setIsRunning(true);  
    const stop = () => setIsRunning(false);  
  
    return { count, start, stop, isRunning };  
}

3. 参数化 Hook

自定义 Hook 可以接受参数,使其更加灵活和可重用。例如,创建一个可重用的表单输入 Hook:

import { useState } from 'react';  
  
function useFormInput(initialValue) {  
    const [value, setValue] = useState(initialValue);  
    const handleChange = (e) => {  
        setValue(e.target.value);  
    };  
    return { value, onChange: handleChange };  
}

4. 条件逻辑

自定义 Hook 可以包含条件逻辑,根据条件执行不同的操作。例如,一个带有条件逻辑的数据获取 Hook:

import { useState, useEffect } from 'react';  
  
function useConditionalFetch(url, shouldFetch) {  
    const [data, setData] = useState(null);  
    const [loading, setLoading] = useState(false);  
  
    useEffect(() => {  
        if (shouldFetch) {  
            setLoading(true);  
            fetch(url).then(response => response.json()).then(data => {  
                setData(data);  
                setLoading(false);  
            });  
        }  
    }, [url, shouldFetch]);  
  
    return { data, loading };  
}

三、面试题

笔者最近在面试中也碰到了实现自定义Hook,当请求数据的时候,显示数据的加载过程,包括加载中,成功,失败对应的UI,直接上代码

import React, { useEffect, useState } from 'react'

function useAsyncFn(fn) {
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState(null)
  const [data, setData] = useState(null)

  useEffect(() => {
    const fentchData = async () => {
      setIsLoading(true)
      setError(null)
      try {
        let data = await fn()
        setData(data)
      } catch (error) {
        setError(error)
      } finally {
        setIsLoading(false)
      }
    }
    fentchData()
  }, [])

  return [isLoading, error, data]
}

export default function App() {

  const fetchData = async () => {
    // 模拟 API 请求  
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const success = Math.random() > 0.5;
        if (success) {
          resolve('Data fetched successfully');
        } else {
          reject('Failed to fetch data');
        }
      }, 1000);
    });
  };

  const [isLoading, error, data] = useAsyncFn(fetchData)

  return (
    isLoading ? <div>Loading...</div> : error ? <div>Error: {error}</div> : <div>Data: {data}</div>
  )
}