一文搞定常用的自定义 React Hooks
fengshi123 opened this issue · comments
前言
通过上一篇文章《一文归纳 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();
}, []);
};
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();
}, []);
};
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]);
}
export default useTimeout;
如下所示,我们只需要告诉 useTimeout 多少毫秒去调用哪个方法,不需要再去考虑移除定时器的事情了。
import React, { useState } from 'react';
import useTimeout from './useTimeout';
const MyPage = () => {
const [count, setCount] = useState(0);
useTimeout(() => {
setCount(count => 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]);
}
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]);
};
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 => count + 1);
}, []);
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]);
};
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 => count + 1);
}, []);
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 ~
useUnmount 处的代码为啥不是这样写呢
const useUnmount = (fn: () => void) => {
useEffect(() => {
// 👇
return fn();
}, []);
};
useUnmount 处的代码为啥不是这样写呢
const useUnmount = (fn: () => void) => { useEffect(() => { // 👇 return fn(); }, []); };
作者写错了~
useUnmount 处的代码为啥不是这样写呢
const useUnmount = (fn: () => void) => { useEffect(() => { // 👇 return fn(); }, []); };
这样写也戳啦,应该是酱紫啦
const useUnmount = (fn: () => void) => { useEffect(() => { // 👇 return ()=>fn(); // 或者 return fn }, []); };