react之自定义hooks

任何相对独立、复用性强的逻辑,都可以 extract 为自定义 Hook,自定义 Hook 是一种复用 React 的状态逻辑的函数。
自定义 Hook 的主要特点是:

  • 抽象组件间的状态逻辑,方便复用
  • 让功能组件更纯粹,更易于维护
  • 自定义 Hook 可以调用其他 Hook

为什么要用自定义 Hook?

  1. 提炼能复用的逻辑
    许多组件有相似的状态逻辑,使用自定义 Hook 可以很方便地提取出来复用。
  2. 解决复杂组件的可读性问题
    使用自定义 Hook 将复杂组件拆分为更小的功能独立的函数,有助于提高代码的可读性。
  3. 管理数据更新
    使用独立的 Hook 函数来管理数据请求、处理异步逻辑、数据缓存等,易于维护。
  4. 分离状态逻辑
    自定义 Hook 让函数组件更纯粹,只负责 UI,状态逻辑则交给 Hook。
  5. 调用其他 Hook
    自定义 Hook 本身还可以调用 useState、useEffect 等其他 React Hook。

以下是我总结的一些常用的hooks

1、useUpdateEffect

useUpdateEffect作用

useUpdateEffect 是一个自定义的 React Hook,用于在组件更新时执行副作用操作。它类似于 React 的 useEffect,但是会忽略组件的初始渲染阶段,只在组件更新时执行副作用操作。

在 React 中,useEffect 会在组件的每次渲染(包括初始渲染)完成后执行副作用操作。但有时候我们只想在组件更新时执行某些操作,而不关心初始渲染阶段的操作。这就是 useUpdateEffect 的用途。

以下是一个示例:

import { useEffect, useState } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('useEffect - Component has rendered');
  });

  useUpdateEffect(() => {
    console.log('useUpdateEffect - Component has updated');
  });

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

在上述示例中,当点击 “Increment” 按钮时,count 的值会增加并触发组件的重新渲染。useEffect 会在每次渲染后执行,而 useUpdateEffect 只会在组件更新时执行。

通过使用 useUpdateEffect,你可以在组件更新时执行一些特定的副作用操作,如请求数据、更新状态等,而不需要关心初始渲染阶段的操作。

为什么会需要用到useUpdateEffect

在某些情况下,我们希望在 React 组件更新时执行一些特定的副作用操作,而不在初始渲染阶段执行这些操作。这种情况下,我们可以使用类似于 useUpdateEffect 的自定义 Hook。

以下是一些使用 useUpdateEffect 的常见情况:

  1. 避免初始渲染时执行副作用:有些副作用操作可能只需要在组件更新时执行,例如发送网络请求、更新特定状态等。使用 useUpdateEffect 可以确保这些副作用操作在初始渲染时被跳过,只在组件更新时执行。

  2. 监听特定状态的变化:有时我们只关心特定状态的变化,并希望在状态发生变化时执行相应的操作。通过将状态值作为 useUpdateEffect 的依赖项,可以确保副作用操作只在这些状态发生变化时触发。

  3. 更新外部资源或库:有些第三方库或外部资源可能需要在组件更新时进行更新或重新初始化。使用 useUpdateEffect 可以确保在组件更新时调用相应的函数或方法,以便正确地更新这些外部资源。

通过使用 useUpdateEffect,我们可以更加精确地控制副作用操作的触发时机,避免不必要的重复执行,以及在需要时处理特定的更新逻辑。

需要注意的是,React 自带的 useEffect 可以处理大多数情况下的副作用操作,而 useUpdateEffect 是在某些特定场景下的补充工具。在大多数情况下,使用 useEffect 即可满足需求。

自定义useUpdateEffect

要自定义一个类似于 useUpdateEffect 的自定义 Hook,你可以借助 React 的 useEffectuseRef Hooks 来实现。以下是一个示例代码:

import { useEffect, useRef } from 'react';

function useUpdateEffect(effect, dependencies) {
  const isMounted = useRef(false);

  useEffect(() => {
    if (isMounted.current) {
      effect();
    } else {
      isMounted.current = true;
    }
  }, dependencies);
}

// 使用示例
function MyComponent() {
  const [count, setCount] = useState(0);

  useUpdateEffect(() => {
    console.log('Component has updated');
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

在上述示例中,我们创建了一个名为 useUpdateEffect 的自定义 Hook。它接受两个参数:effectdependencies。在内部,我们使用了 useRef 来创建一个标记是否已经完成初始渲染的变量 isMounted

useEffect 中,我们检查 isMounted 的值。如果 isMounted 的值为 true,则表示组件已经完成了初始渲染,此时执行传入的 effect 函数。否则,将 isMounted 的值设置为 true,表示组件已完成初始渲染。

在使用时,你可以像使用 useEffect 一样,传入 effect 函数和依赖项数组 dependencies,并且 effect 函数只会在组件更新时执行。

2、useTitle

useTitle 是一个相对经典的自定义 React Hook ,用来控制浏览器标题:

定义useTitle

import { useState, useEffect } from 'react';

function useTitle(initialTitle) {
  const [title, setTitle] = useState(initialTitle);
 
  useEffect(() => {
    document.title = title;
  }, [title]);

  return setTitle;
}

使用useTitle:

function Page() {
  const setTitle = useTitle('Default Title');
 
  return (
    <Button onClick={() => setTitle('New Title')}>
      Click me
    </Button>
  )
}

点击按钮后,浏览器标题会变成"New Title"。
它的工作原理是:

  • 保存标题的 state ,并记录修改 setTitle()
  • 用 useEffect 监测 title 变化,设置 document.title
    所以一旦我们调用 setTitle(‘New Title’) 改变 state ,useEffect 就会执行,设置新的浏览器标题。
    useTitle 的优点是:
  • 抽象出设置标题的逻辑,任何组件都可以共享
  • 让组件更纯粹,只需要调用 setTitle() 接口即可
    我们甚至可以抽象为更通用的 Hook:
js
function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
  }, [title]);
}

function Page() {
  useDocumentTitle('Default Title');
  // ...
}

通过自定义 Hook ,可以方便地在任何组件控制标题。

3、useForceUpdate

定义useForceUpdate

import { useState } from 'react';

function useForceUpdate() {
  const [, setValue] = useState(0); 
  
  return () => {
    setValue(value => !value); 
  };
}

useForceUpdate的使用

const forceUpdate = useForceUpdate();

// 模拟更新组件
forceUpdate();

这个 Hook 返回了一个更新函数。在调用这个函数时,使用useState强制组件重新渲染。
这是基于以下原理实现的:

  • useState()会触发组件重新渲染
  • state变化后,组件函数会重新执行
    函数式组件只有 state 或 props 变化时才会更新。
    使用此 Hook 我们可以主动触发组件更新。
    比如在使用过时数据时:
// 过时数据 
const { data } = useSomeHook();

// 更新组件
const forceUpdate = useForceUpdate();
setInterval(() => {
  forceUpdate();
}, 5000);

每5秒强制组件一次,保证拿到最新数据。

4、useDebounce

定义

import { useState, useEffect } from 'react';

const useDebounce = (value, delay) => {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    let handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
};

export default useDebounce;

使用

const inputValue = useDebounced(searchTerm, 500);

这里 每当searchTerm变化时,会设置一个 500ms 的定时器。只有500ms内没有再改变searchTerm,才会更新debouncedValue
这实现了防抖功能:在一定时间内停止触发, 只执行最后的动作。

5、useThrottle

定义

const useThrottle = (value, limit) => {
  const [throttledValue, setThrottledValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setThrottledValue(value);
    }, limit);
    return () => {
      clearTimeout(handler);
    };
  }, []); // 应设为空数组[]  
  
  useEffect(() => {
    clearTimeout(handler);
    handler = setTimeout(() => {
      setThrottledValue(value);    
    }, limit);
  }, [value, limit]); 
  
  return throttledValue;  
};

使用

const throttledValue = useThrottle(inputValue, 1000);

这里 每次inputValue变化时,会开始一个计时器。1s后才会更新throttledValue,实现了节流功能。

6、useInterval

定义

import { useRef, useEffect } from 'react';

const useInterval = (callback, delay) => {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
};

export default useInterval;

使用

useInterval(() => {
  // ...
}, 1000); 

这里每1000ms就会调用一次回调函数,实现了定时执行指定函数的功能。

7、useDeepEffect

如果你希望在使用useEffect时,能够正确地感知到复杂结构类型的更新,可以封装一个自定义的useEffect钩子,并在其中进行深度对比。

以下是一个可能的实现示例:

import { useEffect, useRef } from 'react';
import isEqual from 'lodash/isEqual';

function useDeepEffect(callback, dependencies) {
  const previousDependencies = useRef(dependencies);

  useEffect(() => {
    if (previousDependencies.current === null || !isEqual(previousDependencies.current, dependencies)) {
      callback();
    }

    previousDependencies.current = dependencies;
  }, [callback, dependencies]);
}

// 使用示例
function MyComponent({ data }) {
  useDeepEffect(() => {
    // 当 data 发生变化时执行的逻辑
    console.log('Data has changed:', data);
  }, [data]);

  // 组件的其余代码...
}

在上面的示例中,使用了useRef来保存上一次的依赖项。然后在每次useEffect回调执行时,我们使用lodash库的isEqual函数进行深度对比。只有当依赖项发生变化时,我们才调用实际的回调函数。

这样,当传递给useDeepEffect的复杂结构类型依赖项发生变化时,callback函数将被正确地调用。请确保在项目中安装了lodash库或者使用其他合适的深度对比工具。
有任何问题欢迎留言讨论学习

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
回答: React自定义Hooks是一种将组件逻辑提取到可重用的函数中的方式。它遵循一些规则,比如只能在最顶层使用Hooks,不能在循环、条件或嵌套中调用Hooks。同时,Hooks应该只在React函数(React函数组件自定义Hooks组件)中使用,而不是在普通的JavaScript函数中使用。通过定义自定义Hooks,我们可以方便地封装逻辑,并在多个组件中复用。一个例子是使用自定义Hooks来设置网页标题。通过在组件中调用useTitle自定义Hooks,我们可以将页面的标题设置为指定的值,并且在组件卸载时将标题重置为默认值。另一个例子是使用自定义Hooks实现倒计时功能。通过定义一个useCountDown自定义Hooks,我们可以实时返回剩余时间,并在指定时间到达时执行回调函数。自定义Hooks是React中非常强大和灵活的功能,可以帮助我们更好地组织和复用组件逻辑。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [react 自定义hook](https://blog.csdn.net/weixin_38318244/article/details/123908672)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [react 中如何自定义hooks](https://blog.csdn.net/DDAD9527/article/details/121341862)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值