关于javascript:你应该会喜欢的5个自定义-Hook

29次阅读

共计 12190 个字符,预计需要花费 31 分钟才能阅读完成。

作者:Grégory D’Angelo
译者:前端小智
起源: dev

点赞再看 ,微信搜寻【大迁世界】,B 站关注【前端小智】 这个没有大厂背景,但有着一股向上踊跃心态人。本文 GitHub https://github.com/qq44924588… 上曾经收录,文章的已分类,也整顿了很多我的文档,和教程材料。

最近开源了一个 Vue 组件,还不够欠缺,欢送大家来一起欠缺它,也心愿大家能给个 star 反对一下,谢谢各位了。

github 地址:https://github.com/qq44924588…

React hooks

React hooks 曾经在 16.8 版本引入到库中。它容许咱们在函数组件中应用状态和其余 React 个性,这样咱们甚至不须要再编写类组件。

实际上,Hooks 远不止于此。

Hooks 能够将组件内的逻辑组织成可重用的独立单元。

Hooks 非常适合 React 组件模型和构建应用程序的新办法。Hooks 能够笼罩类的所有用例,同时在整个应用程序中提供更多的提取、测试和重用代码的灵活性。

构建本人的自定义 React 钩子,能够轻松地在应用程序的所有组件甚至不同应用程序之间共享个性,这样咱们就不用反复本人的工作,从而进步构建 React 应用程序的效率。

当初,来看看我在开发中最罕用的 5 个自定义钩子,并头开始从新创立它们,这样你就可能真正了解它们的工作形式,并确切地理解如何应用它们来进步生产率和放慢开发过程。

咱们间接开始创立咱们的第一个自定义 React Hooks。

useFetch

获取数据是我每次创立 React 利用时都会做的事件。我甚至在一个应用程序中进行了好多个这样的反复获取。

不论咱们抉择哪种形式来获取数据,Axios、Fetch API,还是其余,咱们很有可能在 React 组件序中一次又一次地编写雷同的代码。

因而,咱们看看如何构建一个简略但有用的自定义 Hook,以便在须要在应用程序外部获取数据时调用该 Hook。

okk, 这个 Hook 咱们叫它 useFetch

这个 Hook 承受两个参数,一个是获取数据所需查问的URL,另一个是示意要利用于申请的选项的对象。

import {useState, useEffect} from 'react';

const useFetch = (url = '', options = null) => {};

export default useFetch;

获取数据是一个副作用。因而,咱们应该应用useEffect Hook 来执行查问。

在本例中,咱们应用 Fetch API 来发出请求。咱们会传递 URLoptions。一旦 Promise 被解决,咱们就通过解析响应体来检索数据。为此,咱们应用 json() 办法。

而后,咱们只须要将它存储在一个 React state 变量中。

import {useState, useEffect} from 'react';

const useFetch = (url = '', options = null) => {const [data, setData] = useState(null);

  useEffect(() => {fetch(url, options)
      .then(res => res.json())
      .then(data => setData(data));
  }, [url, options]);
};

export default useFetch;

这里,咱们还须要解决网络谬误,以防咱们的申请出错。所以咱们要用另一个 state 变量来存储谬误。这样咱们就能从 Hook 中返回它并可能判断是否产生了谬误。

import {useState, useEffect} from 'react';

const useFetch = (url = '', options = null) => {const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {fetch(url, options)
      .then(res => res.json())
      .then(data => {if (isMounted) {setData(data);
          setError(null);
        }
      })
      .catch(error => {if (isMounted) {setError(error);
          setData(null);
        }
      });
  }, [url, options]);
};

export default useFetch;

useFetch返回一个对象,其中蕴含从 URL 中获取的数据,如果产生了任何谬误,则返回谬误。

return {error, data};

最初,向用户表明异步申请的状态通常是一个好做法,比方在出现后果之前显示 loading。

因而,咱们增加第三个 state 变量来跟踪申请的状态。在申请之前,将 loading 设置为true,并在申请之后实现后设置为false

const useFetch = (url = '', options = null) => {const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {setLoading(true);

    fetch(url, options)
      .then(res => res.json())
      .then(data => {setData(data);
        setError(null);
      })
      .catch(error => {setError(error);
        setData(null);
      })
      .finally(() => setLoading(false));
  }, [url, options]);

  return {error, data};
};

当初,咱们能够返回 loading 变量,以便在申请运行时在组件中应用它来出现一个 loading,不便用户晓得咱们正在获取他们所申请的数据。

return {loading, error, data};

在应用 userFetch 之前,咱们还有一件事。

咱们须要查看应用咱们 Hook 的组件是否依然被挂载,以更新咱们的状态变量。否则,会有内存透露。

import {useState, useEffect} from 'react';

const useFetch = (url = '', options = null) => {const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    let isMounted = true;

    setLoading(true);

    fetch(url, options)
      .then(res => res.json())
      .then(data => {if (isMounted) {setData(data);
          setError(null);
        }
      })
      .catch(error => {if (isMounted) {setError(error);
          setData(null);
        }
      })
      .finally(() => isMounted && setLoading(false));

    return () => (isMounted = false);
  }, [url, options]);

  return {loading, error, data};
};

export default useFetch;

接下就是怎么用了?

咱们只须要传递咱们想要检索的资源的 URL。从那里,咱们失去一个对象,咱们能够应用它来渲染咱们的应用程序。

import useFetch from './useFetch';

const App = () => {const { loading, error, data = [] } = useFetch('https://hn.algolia.com/api/v1/search?query=react');

  if (error) return <p>Error!</p>;
  if (loading) return <p>Loading...</p>;

  return (
    <div>
      <ul>
        {data?.hits?.map(item => (<li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
};

useEventListener

这个 Hook 负责在组件外部设置和清理事件监听器。

这样,咱们就不须要每次增加事件监听器, 做反复的工作。

这个函数有几个参数,eventType 事件类型,listener 监听函数,target 监听对象,options 可选参数。

import {useEffect, useRef} from 'react';

const useEventListener = (
  eventType = '',
  listener = () => null,
  target = null,
  options = null
) => {};

export default useEventListener;

与前一个 Hook 一样,用 useEffect 来增加一个事件监听器。首先,咱们须要确保 target 是否反对addEventListener 办法。否则,咱们什么也不做。

import {useEffect, useRef} from 'react';

const useEventListener = (
  eventType = '',
  listener = () => null,
  target = null,
  options = null
) => {useEffect(() => {if (!target?.addEventListener) return;
  }, [target]);
};

export default useEventListener;

而后,咱们能够增加理论的事件监听器并在卸载函数中删除它。

import {useEffect, useRef} from 'react';

const useEventListener = (
  eventType = '',
  listener = () => null,
  target = null,
  options = null
) => {useEffect(() => {if (!target?.addEventListener) return;

    target.addEventListener(eventType, listener, options);

    return () => {target.removeEventListener(eventType, listener, options);
    };
  }, [eventType, target, options, listener]);
};

export default useEventListener;

实际上,咱们也会应用一个援用对象来存储和长久化监听器函数。只有当监听器函数发生变化并在事件监听器办法中应用该援用时,咱们才会更新该援用。

import {useEffect, useRef} from 'react';

const useEventListener = (
  eventType = '',
  listener = () => null,
  target = null,
  options = null
) => {const savedListener = useRef();

  useEffect(() => {savedListener.current = listener;}, [listener]);

  useEffect(() => {if (!target?.addEventListener) return;

    const eventListener = event => savedListener.current(event);

    target.addEventListener(eventType, eventListener, options);

    return () => {target.removeEventListener(eventType, eventListener, options);
    };
  }, [eventType, target, options]);
};

export default useEventListener;

咱们不须要从此 Hook 返回任何内容,因为咱们只是侦听事件并运行处理程序函数传入作为参数。

当初,很容易将事件侦听器增加到咱们的组件(例如以下组件)中,以检测 DOM 元素内部的点击。如果用户单击对话框组件,则在此处敞开对话框组件。

import {useRef} from 'react';
import ReactDOM from 'react-dom';
import {useEventListener} from './hooks';

const Dialog = ({show = false, onClose = () => null }) => {const dialogRef = useRef();

  // Event listener to close dialog on click outside element
  useEventListener(
    'mousedown',
    event => {if (event.defaultPrevented) {return; // Do nothing if the event was already processed}
      if (dialogRef.current && !dialogRef.current.contains(event.target)) {console.log('Click outside detected -> closing dialog...');
        onClose();}
    },
    window
  );

  return show
    ? ReactDOM.createPortal(
        <div className="fixed inset-0 z-9999 flex items-center justify-center p-4 md:p-12 bg-blurred">
          <div
            className="relative bg-white rounded-md shadow-card max-h-full max-w-screen-sm w-full animate-zoom-in px-6 py-20"
            ref={dialogRef}
          >
            <p className="text-center font-semibold text-4xl">
              What's up{' '}
              <span className="text-white bg-red-500 py-1 px-3 rounded-md mr-1">
                YouTube
              </span>
              ?
            </p>
          </div>
        </div>,
        document.body
      )
    : null;
};

export default Dialog;

useLocalStorage

这个 Hook 次要有两个参数,一个是 key,一个是 value

import {useState} from 'react';

const useLocalStorage = (key = '', initialValue ='') => {};

export default useLocalStorage; 

而后,返回一个数组,相似于应用 useState 取得的数组。因而,此数组将蕴含有状态值和在将其长久存储在 localStorage 中时对其进行更新的函数。

首先,咱们创立将与 localStorage 同步的 React 状态变量。

import {useState} from 'react';

const useLocalStorage = (key = '', initialValue ='') => {const [state, setState] = useState(() => {
    try {const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {console.log(error);
      return initialValue;
    }
  });
};

export default useLocalStorage;

在这里,咱们应用惰性初始化来读取 localStorage 以获取键的值,如果找到该值,则解析该值,否则返回传入的initialValue

如果在读取 localStorage 时呈现谬误,咱们只记录一个谬误并返回初始值。

最初,咱们须要创立 update 函数来返回它将在 localStorage 中存储任何状态的更新,而不是应用 useState 返回的默认更新。

import {useState} from 'react';

const useLocalStorage = (key = '', initialValue ='') => {const [state, setState] = useState(() => {
    try {const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {return initialValue;}
  });

  const setLocalStorageState = newState => {
    try {
      const newStateValue =
        typeof newState === 'function' ? newState(state) : newState;
      setState(newStateValue);
      window.localStorage.setItem(key, JSON.stringify(newStateValue));
    } catch (error) {console.error(`Unable to store new value for ${key} in localStorage.`);
    }
  };

  return [state, setLocalStorageState];
};

export default useLocalStorage;

此函数同时更新 React 状态和 localStorage 中的相应键 / 值。这里,咱们还能够反对函数更新,例如惯例的 useState hook。

最初,咱们返回状态值和咱们的自定义更新函数。

当初能够应用 useLocalStorage hook 将组件中的任何数据长久化到localStorage 中。

import {useLocalStorage} from './hooks';

const defaultSettings = {notifications: 'weekly',};

function App() {const [appSettings, setAppSettings] = useLocalStorage(
    'app-settings',
    defaultSettings
  );

  return (
    <div className="h-full w-full flex flex-col justify-center items-center">
      <div className="flex items-center mb-8">
        <p className="font-medium text-lg mr-4">Your application's settings:</p>

        <select
          value={appSettings.notifications}
          onChange={e =>
            setAppSettings(settings => ({
              ...settings,
              notifications: e.target.value,
            }))
          }
          className="border border-gray-900 rounded py-2 px-4"
        >
          <option value="daily">daily</option>
          <option value="weekly">weekly</option>
          <option value="monthly">monthly</option>
        </select>
      </div>

      <button
        onClick={() => setAppSettings(defaultSettings)}
        className="rounded-md shadow-md py-2 px-6 bg-red-500 text-white uppercase font-medium tracking-wide text-sm leading-8"
      >
        Reset settings
      </button>
    </div>
  );
}

export default App;

useMediaQuery

这个 Hook 帮忙咱们在性能组件中以编程形式测试和监控媒体查问。这是十分有用的,例如,当你须要渲染不同的 UI 取决于设施的类型或特定的特色。

咱们的 Hook 承受 3 个参数:

  • 首先,对应媒体查问的字符串数组
  • 而后,以与前一个数组雷同的程序匹配这些媒体查问的值数组
  • 最初,如果没有匹配的媒体查问,则应用默认值
import {useState, useCallback, useEffect} from 'react';

const useMediaQuery = (queries = [], values = [], defaultValue) => {};

export default useMediaQuery;

咱们在这个 Hook 中做的第一件事是为每个匹配的媒体查问构建一个媒体查问列表。应用这个数组通过匹配媒体查问来取得相应的值。

import {useState, useCallback, useEffect} from 'react';

const useMediaQuery = (queries = [], values = [], defaultValue) => {const mediaQueryList = queries.map(q => window.matchMedia(q));
};

export default useMediaQuery;

为此,咱们创立了一个包装在useCallback 中的回调函数。检索列表中第一个匹配的媒体查问的值,如果没有匹配则返回默认值。

import {useState, useCallback, useEffect} from 'react';

const useMediaQuery = (queries = [], values = [], defaultValue) => {const mediaQueryList = queries.map(q => window.matchMedia(q));

  const getValue = useCallback(() => {const index = mediaQueryList.findIndex(mql => mql.matches);
    return typeof values[index] !== 'undefined' ? values[index] : defaultValue;
  }, [mediaQueryList, values, defaultValue]);
};

export default useMediaQuery;

而后,咱们创立一个 React 状态来存储匹配的值,并应用下面定义的函数来初始化它。

import {useState, useCallback, useEffect} from 'react';

const useMediaQuery = (queries = [], values = [], defaultValue) => {const mediaQueryList = queries.map(q => window.matchMedia(q));

  const getValue = useCallback(() => {const index = mediaQueryList.findIndex(mql => mql.matches);
    return typeof values[index] !== 'undefined' ? values[index] : defaultValue;
  }, [mediaQueryList, values, defaultValue]);

  const [value, setValue] = useState(getValue);
};

export default useMediaQuery;

最初,咱们在 useEffect 中增加一个事件监听器来监听每个媒体查问的更改。当发生变化时,咱们运行更新函数。

mport {useState, useCallback, useEffect} from 'react';

const useMediaQuery = (queries = [], values = [], defaultValue) => {const mediaQueryList = queries.map(q => window.matchMedia(q));

  const getValue = useCallback(() => {const index = mediaQueryList.findIndex(mql => mql.matches);
    return typeof values[index] !== 'undefined' ? values[index] : defaultValue;
  }, [mediaQueryList, values, defaultValue]);

  const [value, setValue] = useState(getValue);

  useEffect(() => {const handler = () => setValue(getValue);
    mediaQueryList.forEach(mql => mql.addEventListener('change', handler));

    return () =>
      mediaQueryList.forEach(mql => mql.removeEventListener('change', handler));
  }, [getValue, mediaQueryList]);

  return value;
};

export default useMediaQuery;

我最近应用的一个简略的例子是增加一个媒体查问来查看设施是否容许用户悬停在元素上。这样,如果用户能够悬停或利用根本款式,我就能够增加特定的不通明款式。

import {useMediaQuery} from './hooks';

function App() {
  const canHover = useMediaQuery(
    // Media queries
    ['(hover: hover)'],
    // Values corresponding to the above media queries by array index
    [true],
    // Default value
    false
  );

  const canHoverClass = 'opacity-0 hover:opacity-100 transition-opacity';
  const defaultClass = 'opacity-100';

  return (<div className={canHover ? canHoverClass : defaultClass}>Hover me!</div>
  );
}

export default App;

useDarkMode

这个是我的最爱。它能轻松疾速地将暗模式性能利用于任何 React 应用程序。

这个 Hook 次要按需启用和禁用暗模式,将以后状态存储在 localStorage 中。

为此,咱们将应用咱们刚刚构建的两个钩子:useMediaQueryuseLocalStorage

而后,应用“useLocalStorage”,咱们能够在 localStorage 中初始化,存储和保留以后状态(暗或亮模式)。

import {useEffect} from 'react';
import useMediaQuery from './useMediaQuery';
import useLocalStorage from './useLocalStorage';

const useDarkMode = () => {
  const preferDarkMode = useMediaQuery(['(prefers-color-scheme: dark)'],
    [true],
    false
  );
};

export default useDarkMode;

最初一部分是触发副作用,以向 document.body 元素增加或删除 dark 类。这样,咱们能够简略地将 dark 款式利用于咱们的应用程序。

import {useEffect} from 'react';
import useMediaQuery from './useMediaQuery';
import useLocalStorage from './useLocalStorage';

const useDarkMode = () => {
  const preferDarkMode = useMediaQuery(['(prefers-color-scheme: dark)'],
    [true],
    false
  );

  const [enabled, setEnabled] = useLocalStorage('dark-mode', preferDarkMode);

  useEffect(() => {if (enabled) {document.body.classList.add('dark');
    } else {document.body.classList.remove('dark');
    }
  }, [enabled]);

  return [enabled, setEnabled];
};

export default useDarkMode;

~ 完,我是小智,我要去刷碗了。


代码部署后可能存在的 BUG 没法实时晓得,预先为了解决这些 BUG,花了大量的工夫进行 log 调试,这边顺便给大家举荐一个好用的 BUG 监控工具 Fundebug。

原文:https://dev.to/alterclass/5-r…

交换

文章每周继续更新,能够微信搜寻「大迁世界」第一工夫浏览和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 曾经收录,整顿了很多我的文档,欢送 Star 和欠缺,大家面试能够参照考点温习,另外关注公众号,后盾回复 福利,即可看到福利,你懂的。

正文完
 0