跟着 ahooks 写 Hook 之 useFavicon,useTitle,useTimeout

253 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情

👨‍💻文档地址:ahooks.js.org/zh-CN/

👨‍💻github地址:github.com/alibaba/hoo…

useFavicon

Link type 属性type 属性可设置或者返回链接文档的类型(MIME 类型) ,比如MIME-types 实例: "text/css", "text/javascript", "image/gif", 等。

useEffect 中,获取传入 URL 的类型,将值赋值给 link.type。创建或者获取 link 标签,为 link.href 赋值,向 head 标签中添加即可。

import { useEffect } from 'react';

const ImgTypeMap = {
  SVG: 'image/svg+xml',
  ICO: 'image/x-icon',
  GIF: 'image/gif',
  PNG: 'image/png',
};

// 遍历某种类型的属性,并通过 keyof 操作符提取其属性的名称
type ImgTypes = keyof typeof ImgTypeMap;

const useFavicon = (href: string) => {
  useEffect(() => {
    if (!href) return;

    const cutUrl = href.split('.');
    const imgSuffix = cutUrl[cutUrl.length - 1].toLocaleUpperCase() as ImgTypes;

    const link: HTMLLinkElement =
      document.querySelector("link[rel*='icon']") || document.createElement('link');

    link.type = ImgTypeMap[imgSuffix];
    link.href = href;
    link.rel = 'shortcut icon';

    document.getElementsByTagName('head')[0].appendChild(link);
  }, [href]);
};

export default useFavicon;

Example

url 变化的时候,useFavicon 中的 useEffect 的依赖项 href 发生变化,重新执行一遍逻辑。

import React, { useState } from 'react';
import { useFavicon } from 'ahooks';

export const GOOGLE_FAVICON_URL = 'https://www.google.com/favicon.ico';

export default () => {
  const [url, setUrl] = useState<string>(DEFAULT_FAVICON_URL);

  useFavicon(url);

  return (
    <>
      <button
        style={{ marginRight: 16 }}
        onClick={() => {
          setUrl(GOOGLE_FAVICON_URL);
        }}
      >
        icon
      </button>
    </>
  );
};

useTitle

判断当前是否是浏览器。

const isBrowser = !!(
  typeof window !== 'undefined' &&
  window.document &&
  window.document.createElement
);

export default isBrowser;

如果是浏览器,则将传入的 title 赋值给 document.title

import { useEffect, useRef } from 'react';
import isBrowser from '../utils/isBrowser';

function useTitle(title: string) {
  const titleRef = useRef(isBrowser ? document.title : '');
  useEffect(() => {
    document.title = title;
  }, [title]);
}

export default useTitle;

Example

import React from 'react';
import { useTitle } from 'ahooks';

export default () => {
  useTitle('Page Title');
  return (
    <div>
      <p>Set title of the page.</p>
    </div>
  );
};

ahooks useTitlerestoreOnUnmount 参数,在这个例子中,我还是有点不太能理解。

useTimeout

useLatest:返回当前最新值的 Hook,可以避免闭包问题。

传入 fndelay。判断 delay 是否是数字,并且要大于 0。当 delay 发生改变的时候,使用 setTimeout。同时返回 clearTimeout,清除定时器。hooks 同时也返回 clearTimeout

export const isNumber = (value: unknown): value is number => typeof value === 'number';
import { useCallback, useEffect, useRef } from 'react';
import useLatest from '../useLatest';
import { isNumber } from '../utils';

function useTimeout(fn: () => void, delay: number | undefined) {
  const fnRef = useLatest(fn);
  const timerRef = useRef<number | NodeJS.Timer>();

  useEffect(() => {
    if (!isNumber(delay) || delay < 0) return;

    timerRef.current = setTimeout(() => {
      fnRef.current();
    }, delay);

    return () => {
      if (timerRef.current) {
        clearTimeout(timerRef.current as NodeJS.Timer);
      }
    };
  }, [delay]);

  const clear = useCallback(() => {
    if (timerRef.current) {
      clearTimeout(timerRef.current as NodeJS.Timer);
    }
  }, []);

  return clear;
}

export default useTimeout;

Example

import React, { useState } from 'react';
import { useTimeout } from 'ahooks';

export default () => {
  const [count, setCount] = useState(0);
  const [delay, setDelay] = useState<number | undefined>(1000);

  return (
    <div>
      <p> count: {count} </p>
      <p style={{ marginTop: 16 }}> 延迟秒数: {delay} </p>
      <button onClick={() => setDelay((t) => (!!t ? t + 1000 : 1000))}>
        延迟秒数 + 1000
      </button>
    </div>
  );
};

传入的 delay 变大,count 的增加明显变慢。

import React, { useState } from 'react';
import { useTimeout } from 'ahooks';

export default () => {
  const [count, setCount] = useState(0);
  const [delay, setDelay] = useState<number | undefined>(1000);

  const clear = useTimeout(() => {
    setCount(count + 1);
  }, delay);

  return (
    <div>
      <button onClick={clear}>clear</button>
    </div>
  );
};

调用 clear,清除页面的定时器。

Aug-09-2022 10-24-44.gif