做者:Grégory D'Angelo
译者:前端小智
来源: dev
点赞再看,微信搜索
【大迁世界】,B站关注
【前端小智】这个没有大厂背景,但有着一股向上积极心态人。本文
GitHub
https://github.com/qq44924588... 上已经收录,文章的已分类,也整理了不少个人文档,和教程资料。
最近开源了一个 Vue 组件,还不够完善,欢迎你们来一块儿完善它,也但愿你们能给个 star 支持一下,谢谢各位了。前端
github 地址:https://github.com/qq44924588...vue
React hooks 已经在16.8
版本引入到库中。它容许咱们在函数组件中使用状态和其余React特性,这样咱们甚至不须要再编写类组件。react
实际上,Hooks 远不止于此。ios
Hooks 能够将组件内的逻辑组织成可重用的独立单元。git
Hooks 很是适合 React 组件模型和构建应用程序的新方法。Hooks 能够覆盖类的全部用例,同时在整个应用程序中提供更多的提取、测试和重用代码的灵活性。github
构建本身的自定义React钩子,能够轻松地在应用程序的全部组件甚至不一样应用程序之间共享特性,这样咱们就没必要重复本身的工做,从而提升构建React应用程序的效率。面试
如今,来看看我在开发中最经常使用的 5
个自定义钩子,并头开始从新建立它们,这样你就可以真正理解它们的工做方式,并确切地了解如何使用它们来提升生产率和加快开发过程。编程
咱们直接开始建立咱们的第一个自定义React Hooks。json
获取数据是我每次建立React应用时都会作的事情。我甚至在一个应用程序中进行了好多个这样的重复获取。api
无论咱们选择哪一种方式来获取数据,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来发出请求。咱们会传递URL
和 options
。一旦 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> ); };
这个 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;
这个 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;
这个 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;
这个是个人最爱。 它能轻松快速地将暗模式功能应用于任何React应用程序。
这个 Hook 主要按需启用和禁用暗模式,将当前状态存储在localStorage
中。
为此,咱们将使用咱们刚刚构建的两个钩子:useMediaQuery
和useLocalStorage
。
而后,使用“ 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和完善,你们面试能够参照考点复习,另外关注公众号,后台回复福利,便可看到福利,你懂的。