最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 一文搞定常用的自定义 React Hooks

    正文概述 掘金(我是你的超级英雄)   2021-01-25   537

    前言

    通过上一篇文章《一文归纳 React Hooks 常用场景》,我们根据使用场景分别进行举例说明,帮助你认识理解并可以熟练运用 React Hooks 大部分特性了。本文则对 hooks 进一步加深,让我们通过自定义一些 hooks,解决我们在平时项目中非常常用的需求场景,做到代码高复用低耦合,从而加深对 hooks 的理解和运用。

    辛苦整理良久,还望手动点赞鼓励~ 博客 github地址为:github.com/fengshi123/… ,汇总了作者的所有博客,欢迎关注及 star ~

    1、实现自定义的 useMount

    首先我们自定义一个 useMount hook,其功能为在 Dom 渲染之后执行相关函数,即类似于 class 组件写法中的 componentDidMount 生命周期钩子的功能。 我么基于以下原理实现:如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。如果在函数组件中实现该功能,即代码如下所示

    useEffect(() => {
      console.log('mount');
    }, []);
    

    现在我们将这个功能进行抽取,封装成为 useMount hook,则可以如下实现,其中该钩子支持传入一个回调执行函数 fn 作为参数。

    import { useEffect } from 'react';
    
    const useMount = (fn: () => void) => {
      useEffect(() => {
        fn();
      }, [fn]);
    };
    
    export default useMount;
    

    现在我们就可以在相关业务场景中使用这个 useMount hook 了,如下所示,只会在 MyPage 初次渲染时执行一次 fun,即使我们多次点击 button,使 count 不断增加,页面不断更新,也不会再执行 fun。

    import React, { useCallback, useState } from 'react';
    import useMount from './useMount';
    
    const MyPage = () => {
      const [count, setCount] = useState(0);
      const fun = useCallback(() => {
        console.log('mount');
      }, []);
    
      useMount(fun);
    
      return (
        <div >
          <button type="button" onClick={() => { setCount(count + 1); }}>
            增加 {count}
          </button>
        </div>
      );
    };
    
    export default MyPage;
    

    2、实现自定义的 useUnmount

    本节我们自定义一个 useUnmount hook,其功能为在 Dom 卸载之前执行相关函数,即类似于 class 组件写法中的 componentWillUnmount 生命周期钩子的功能。 我么基于以下原理实现:如果 effect 有返回一个函数,React 将会在执行清除操作时调用它。如果在函数组件中实现该功能,即代码如下所示

    useEffect(() => () => {
      console.log('unmount');
    });
    

    现在我们将这个功能进行抽取,封装成为 useUnmount hook,则可以如下实现,其中该钩子支持传入一个回调执行函数 fn 作为参数。

    import { useEffect } from 'react';
    
    const useUnmount = (fn: () => void) => {
      useEffect(() => {
        fn();
      }, [fn]);
    };
    
    export default useUnmount;
    

    现在我们就可以在相关业务场景中使用这个 useUnmount hook 了,如下所示,只会在 MyComponet 卸载时执行一次 fun。

    import React, { useCallback, useState } from 'react';
    import useUnmount from './useUnmount';
    
    const MyComponent = () => {
      const fun = useCallback(() => {
        console.log('unmount');
      }, []);
    
      useUnmount(fun);
    
      return <div>Hello World</div>;
    };
    
    
    const MyPage = () => {
      const [state, setState] = useState(true);
    
      return (
        <div >
          {state && <MyComponent />}
          <button type="button" onClick={() => { setState(!state); }}>
            切换
          </button>
        </div>
      );
    };
    
    export default MyPage;
    

    3、实现自定义的 useUpdate

    我们都知道如果想让 function 组件重新渲染,我们不得不更新 state,但是有时候业务需要的 state 是没必要更新的,我们不能仅仅为了让组件会重新渲染而强制让一个 state 做无意义的更新,所以这个时候我们就可以自定义一个更新的 hook 来优雅的实现组件的强制更新,类似于 class 组件的 forceUpdate 的功能,实现代码如下

    import { useCallback, useState } from 'react';
    
    const useUpdate = () => {
      const [, setState] = useState({});
    
      return useCallback(() => setState({}), []);
    };
    
    export default useUpdate;
    
    

    useUpdate 的使用实例如下所示,点击按钮时,调用 update,会看到 Time 的值在变化,说明组件已经强制更新了。

    import React from 'react';
    import useUpdate from './useUpdate';
    
    const MyPage = () => {
      const update = useUpdate();
    
      return (
        <div >
          <button type="button" onClick={update}>
          Time: {Date.now()}
          </button>
        </div>
      );
    };
    
    export default MyPage;
    

    4、实现自定义的 usePrevious

    平时在实现需求时,经常需要保存上一次渲染时 state 的值,so 这个 hook 就是用来保存上一次渲染状态的。如下所示为实现逻辑,主要用到 useRef.current 来存放变量。

    import { useRef } from 'react';
    
    function usePrevious<T> (state: T): T|undefined {
      const prevRef = useRef<T>();
      const curRef = useRef<T>();
    
      prevRef.current = curRef.current;
      curRef.current = state;
    
      return prevRef.current;
    }
    
    export default usePrevious;
    

    usePrevious 的使用实例如下所示,当点击按钮使 count 增加时,previous 会保留 count 的上一个值。

    import React, { useState } from 'react';
    import usePrevious from './usePrevious';
    
    const MyPage = () => {
      const [count, setCount] = useState(0);
      const previous = usePrevious(count);
    
      return (
        <div >
          <div>新值:{count}</div>
          <div>旧值:{previous}</div>
          <button type="button" onClick={() => { setCount(count + 1); }}>
            增加
          </button>
        </div>
      );
    };
    
    export default MyPage;
    

    5、实现自定义的 useTimeout

    在 hook 中,我们使用 setTimeout 之后,需要在 dom 卸载时,手动进行 clearTimeout 将定时器移除,否则可能造成内存泄漏。假设我们在项目中多次用到,那我们则需要多次重复写移除代码,并且有时候可能由于疏忽,将其遗忘。so,为什么不能将它封装成 hook,在需要的时候调用即可。

    import { useEffect } from 'react';
    
    function useTimeout (fn: () => void, delay: number) {
      useEffect(() => {
        const timer = setTimeout(() => {
          fn();
        }, delay);
        return () => {
          clearTimeout(timer); // 移除定时器
        };
      }, [delay, fn]);
    }
    
    export default useTimeout;
    

    如下所示,我们只需要告诉 useTimeout 多少毫秒去调用哪个方法,不需要再去考虑移除定时器的事情了。

    import React, { useState } from 'react';
    import useTimeout from './useTimeout';
    
    const MyPage = () => {
      const [count, setCount] = useState(0);
    
      useTimeout(() => {
        setCount(count + 1);
      }, 3000);
    
      return (
        <div >
          <button type="button">
            增加 {count}
          </button>
        </div>
      );
    };
    
    export default MyPage;
    

    6、实现自定义的 useInterval

    useInterval 封装 setInterval 功能,其原因和用法跟 useTimeout 一样,这里不再赘述。

    import { useEffect } from 'react';
    
    function useInterval (fn: () => void, delay: number) {
      useEffect(() => {
        const timer = setInterval(() => {
          fn();
        }, delay);
        return () => {
          clearInterval(timer); // 移除定时器
        };
      }, [delay, fn]);
    }
    
    export default useInterval;
    

    7、实现自定义的 useDebounce

    防抖在我们日常开发中是非常常见的,比如:按钮点击、文本编辑保存等,为防止用户过于频繁操作,需要进行防抖处理。**防抖的定义:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时间,才执行代码一次。**类比于生活中的场景就例如坐公交,在一定时间内,如果有乘客陆续刷卡上车,司机就不会开车,当乘客没有刷卡了,司机才开车。 防抖功能的基本实现和相关注释如下所示

    function debounce(fn,wait){
        let timeout1;
        return function(){
            clearTimeout(timeout1);  // 重新清零
            let context = this;  // 保存上下文
            let args = arguments; // 获取传入的参数
            timeout1 = setTimeout(()=> {
                fn.apply(context, args);
            },wait)
        }
    }
    

    我们将以上的实现用 hooks 自定义的方式来写,useDebounce hook 相关代码如下,其中传入的两个参数为:fn(要执行的回调方法)和 delay(防抖时间),然后该 hook 返回一个执行方法

    import { useCallback, useRef } from 'react';
    
    const useDebounce = (fn: Function, delay = 100) => {
      const time1 = useRef<any>();
    
      return useCallback((...args) => {
        if (time1.current) {
          clearTimeout(time1.current);
        }
        time1.current = setTimeout(() => {
          fn(...args);
        }, delay);
      }, [delay, fn]);
    };
    
    export default useDebounce;
    

    现在我们就可以在相关业务场景中使用这个 useDebounce hook 了,如下所示,我们不断点击 button,count 也不会增加,只有点击间隔超过 3000ms,count 数才会增加。

    import React, { useCallback, useState } from 'react';
    import useDebounce from './useDebounce';
    
    const MyPage = () => {
      const [count, setCount] = useState(0);
      const fun = useCallback(() => {
        setCount(count + 1);
      }, [count]);
    
      const run = useDebounce(fun, 3000);
    
      return (
        <div >
          <button type="button" onClick={() => { run(); }}>
            增加 {count}
          </button>
        </div>
      );
    };
    
    export default MyPage;
    

    8、实现自定义的 useThrottle

    节流在我们日常开发中是非常常见的,比如:滚动条监听、图片放大镜效果功能等,我们不必每次鼠标滚动都触发,这样可以降低计算的频率,而不必去浪费资源。节流的定义:函数节流是指一定时间内 js 方法只跑一次。类比于生活中的场景就例如人眨眼睛,就是一定时间内眨一次。 节流功能的基本实现和相关注释如下所示,跟防抖很类似

    function throttle(fn, wait){
      let timeout;
      return function(){
          if(timeout) return; // 如果已经触发,则不再触发
          let args = arguments;
          let context = this;
          timeout = setTimeout(()=>{
            fn.apply(context,args); // 执行
            timeout = null; // 执行后,将标志设置为未触发
          },wait)
      }
    }
    

    我们将以上的实现用 hooks 自定义的方式来写,useThrottle hook 相关代码如下,其中传入的两个参数为:fn(要执行的回调方法)和 delay(节流时间),然后该 hook 返回一个执行方法

    import { useCallback, useRef } from 'react';
    
    const useThrottle = (fn: Function, delay = 100) => {
      const time1 = useRef<any>();
    
      return useCallback((...args) => {
        if (time1.current) {
          return;
        }
        time1.current = setTimeout(() => {
          fn(...args);
          time1.current = null;
        }, delay);
      }, [delay, fn]);
    };
    
    export default useThrottle;
    

    现在我们就可以在相关业务场景中使用这个 useThrottle hook 了,如下所示,我们不断点击 button,count 只会在连续间隔 3000ms 增加一次,不会每次点击都会增加一次。

    import React, { useCallback, useState } from 'react';
    import useThrottle from './useThrottle';
    
    const MyPage = () => {
      const [count, setCount] = useState(0);
      const fun = useCallback(() => {
        setCount(count + 1);
      }, [count]);
    
      const run = useThrottle(fun, 3000);
    
      return (
        <div >
          <button type="button" onClick={() => { run(); }}>
            增加 {count}
          </button>
        </div>
      );
    };
    
    export default MyPage;
    

    总结

    本文是 react hooks 三部曲中的第二篇,按照预期,后续我们会写 react hooks 三部曲中的第三篇,敬请期待。

    辛苦整理良久,还望手动点赞鼓励~ 博客 github地址为:github.com/fengshi123/… ,汇总了作者的所有博客,欢迎关注及 star ~


    起源地下载网 » 一文搞定常用的自定义 React Hooks

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元