3.21 useScript
import useAsync from "@hooks/useAsync"; export default function useScript(url: string) { return useAsync(() => { const script = document.createElement("script"); script.src = url; script.async = true; return new Promise<void>((resolve, reject) => { script.addEventListener("load", () => resolve()); script.addEventListener("error", () => reject()); document.body.appendChild(script); }); }, [url]); }
useScript
它具备异步处理脚本加载的能力。通过将脚本的async
属性设置为true
,确保它不会阻塞应用程序的渲染。特别是在处理较大的脚本或较慢的网络连接时,有很大用处。
使用场景
useScript
可以用于各种情景。例如,我们可以加载外部库,如jQuery
,从而能够利用其强大的功能,而不会增加捆绑文件的体积。此外,我们还可以加载分析脚本或应用程序动态行为所需的任何其他脚本。
import useScript from "@hooks/useScript"; export default function ScriptComponent() { const { loading, error } = useScript( "https://code.jquery.com/jquery-3.6.0.min.js" ); if (loading) return <div>资源加载中...</div>; if (error) return <div>资源加载失败😡</div>; return <div>显示当前视图的宽度{window.$(window).width()}</div>; }
注意:在使用$
处会有一个TS错误。我们需要在项目中弄一个index.d.ts
然后需要对$
进行定义
declare interface Window { $: any; }
该钩子返回加载状态和错误状态,可以用于相应地显示加载中的旋转图标或错误消息。一旦脚本成功加载,组件将使用jQuery
显示当前窗口宽度。
3.22 useStateWithHistory
import { useCallback, useRef, useState, Dispatch, SetStateAction } from "react"; type HistoryAction<T> = { history: T[]; pointer: number; back: () => void; forward: () => void; go: (index: number) => void; }; type StateWithHistoryReturn<T> = [ T, Dispatch<SetStateAction<T>>, HistoryAction<T> ]; function useStateWithHistory<T>( defaultValue: T, capacity: number = 10 ): StateWithHistoryReturn<T> { const [value, setValue] = useState<T>(defaultValue); const historyRef = useRef<T[]>([value]); const pointerRef = useRef<number>(0); const set = useCallback( (v: SetStateAction<T>) => { const resolvedValue = typeof v === "function" ? (v as (prevState: T) => T)(value) : v; if (historyRef.current[pointerRef.current] !== resolvedValue) { if (pointerRef.current < historyRef.current.length - 1) { historyRef.current.splice(pointerRef.current + 1); } historyRef.current.push(resolvedValue); while (historyRef.current.length > capacity) { historyRef.current.shift(); } pointerRef.current = historyRef.current.length - 1; } setValue(resolvedValue); }, [capacity, value] ); const back = useCallback(() => { if (pointerRef.current <= 0) return; pointerRef.current--; setValue(historyRef.current[pointerRef.current]); }, []); const forward = useCallback(() => { if (pointerRef.current >= historyRef.current.length - 1) return; pointerRef.current++; setValue(historyRef.current[pointerRef.current]); }, []); const go = useCallback((index: number) => { if (index < 0 || index > historyRef.current.length - 1) return; pointerRef.current = index; setValue(historyRef.current[pointerRef.current]); }, []); const historyAction: HistoryAction<T> = { history: historyRef.current, pointer: pointerRef.current, back, forward, go, }; return [value, set, historyAction]; } export default useStateWithHistory;
useStateWithHistory的优势
- 自动历史跟踪:
useStateWithHistory
自动跟踪我们设置的值,允许我们在需要时访问完整的历史记录。 - 高效的内存使用:该钩子利用容量参数(支持动态传人),确保历史记录不会无限增长。我们可以定义要保留的历史值的最大数量,防止过多的内存消耗。
- 时间旅行功能:通过
back()
、forward()
和go()
函数,我们可以轻松地浏览记录的历史。在以前的状态之间来回切换,或直接跳到特定索引,实现强大的撤销/重做或逐步操作功能。
何时使用useStateWithHistory
- 表单管理:通过提供一种简化处理表单输入的方式,可以跟踪更改,还原以前的值或重做修改,从而简化处理表单输入的过程。
- 撤销/重做功能:轻松实现应用程序中的撤销/重做功能。跟踪状态更改,允许用户轻松地在其操作之间来回导航。
- 逐步导航:使用
useStateWithHistory
构建交互式指南或教程,用户可以在不同步骤之间导航,同时保留其进度。
使用场景
import useStateWithHistory from "@hooks/useStateWithHistory"; export default function StateWithHistoryComponent() { const [count, setCount, { history, pointer, back, forward, go }] = useStateWithHistory(1); return ( <div> <div>当前指针所指位置的数值:{count}</div> <div>History的所有值{history.join(", ")}</div> <div>指针指向的Index(从0开始):{pointer}</div> <button onClick={() => setCount((currentCount) => currentCount * 2)}> 将之前的数据数值翻倍后,插入到History </button> <button onClick={() => setCount((currentCount) => currentCount + 1)}> 将之前的数据数值+1后,插入到History </button> <button onClick={back}>回退</button> <button onClick={forward}>前进</button> <button onClick={() => go(2)}>指向第二步</button> </div> ); }
3.23 useStateWithValidation
import { useState, useCallback } from "react"; export default function useStateWithValidation<T>( validationFunc: (value: T) => boolean, initialValue: T ): [T, (nextState: T | ((prevState: T) => T)) => void, boolean] { const [state, setState] = useState<T>(initialValue); const [isValid, setIsValid] = useState(() => validationFunc(state)); const onChange = useCallback( (nextState: T | ((prevState: T) => T)) => { const value = typeof nextState === "function" ? (nextState as (prevState: T) => T)(state) : nextState; setState(value); setIsValid(validationFunc(value)); }, [validationFunc, state] ); return [state, onChange, isValid]; }
useStateWithValidation
钩子结合了React的useState
和useCallback
钩子,它接受两个参数:
- 一个验证函数(用于确定当前状态是否被视为有效。)
- 一个初始值
使用场景
我们可以传递适合我们特定需求的任何验证函数。无论是检查字符串的长度,确保数字值在特定范围内,还是执行更复杂的验证,useStateWithValidation
都可以满足我们的需求。
import useStateWithValidation from "@hooks/useStateWithValidation"; export default function StateWithValidationComponent() { const [username, setUsername, isValid] = useStateWithValidation<string>( (name) => name.length > 5, "前端柒八九" ); return ( <> <div>输入框内容是否大于5: {isValid.toString()}</div> <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} /> </> ); }
在这个示例中,使用useStateWithValidation
钩子来管理用户名的状态。验证函数检查用户名的长度是否大于5个字符,isValid
变量反映了当前输入的有效性。
3.24 useTranslation
import { useLocalStorage } from "@hooks/useStorage"; import * as translations from "./translations"; type TranslationFunction = (key: string) => string | undefined; export default function useTranslation(lang: string, fallbackLang: string) { const [language, setLanguage] = useLocalStorage<string>("language", lang); const [fallbackLanguage, setFallbackLanguage] = useLocalStorage<string>( "fallbackLanguage", fallbackLang ); const translate: TranslationFunction = (key) => { const keys = key.split("."); return ( getNestedTranslation(language, keys) ?? getNestedTranslation(fallbackLanguage, keys) ?? key ); }; return { language, setLanguage, fallbackLanguage, setFallbackLanguage, t: translate, }; } function getNestedTranslation( language: string, keys: string[] ): string | undefined { return keys.reduce((obj, key) => { return obj?.[key]; }, translations[language]); }
我们可以定义一组语言集合(translations
文件夹中)
en.json
{ "hi": "Hello", "bye": "Goodbye", "nested": { "value": "front789" } }
zh.json
{ "hi": "你好", "bye": "再见👋", "nested": { "value": "前端柒八九" } }
并在index.js
中导出
export * as en from "./en.json"; export * as zh from "./zh.json";
它会自动保存用户选择的语言和回退语言,因此用户每次访问我们的应用时都会看到他们喜好的语言内容。
该钩子利用了 useStorage
库的 useLocalStorage
钩子来持久保存语言设置。这确保即使用户刷新页面或导航离开并返回,他们的语言偏好也将得以保留。
当然,市面上也有很多优秀的库。例如react-i18next
。这个就看大家的实际情况,酌情使用了。
使用场景
我们将能够访问当前语言、设置语言、回退语言以及设置回退语言的功能。此外,该钩子还提供了一个便捷的翻译函数 t
,它以key
作为输入并返回相应的翻译值。
无论我们正在构建多语言网站、国际化应用程序,还是仅需要支持 UI 组件的翻译,该钩子都将简化流程并使我们的代码更易维护。
import useTranslation from "@hooks/useTranslation"; export default function TranslationComponent() { const { language, setLanguage, fallbackLanguage, setFallbackLanguage, t } = useTranslation("zh", "en"); return ( <> <div>使用{language}</div> <div>{t("hi")}</div> <div>{t("bye")}</div> <div>{t("nested.value")}</div> <button onClick={() => setLanguage("zh")}>切换中文</button> <button onClick={() => setLanguage("en")}>切换英文</button> <div>喜好的语音{fallbackLanguage}</div> <button onClick={() => setFallbackLanguage("zh")}> 切换到喜好的语言 </button> </> ); }
3.25 useUpdateEffect
import { useEffect, useRef } from "react"; type EffectHookType = typeof useEffect; const createUpdateEffect: (effect: EffectHookType) => EffectHookType = (effect) => (callback, deps) => { const isMounted = useRef(false); // 处理刷新 effect(() => { return () => { isMounted.current = false; }; }, []); effect(() => { if (!isMounted.current) { isMounted.current = true; } else { return callback(); } }, deps); }; export default createUpdateEffect(useEffect);
useUpdateEffect
钩子旨在仅在初始渲染后执行回调函数。这种行为在我们希望基于状态更改执行操作,同时跳过初始执行时特别有用。通过利用 useRef
钩子,useUpdateEffect
跟踪首次渲染,并在该阶段跳过回调。
使用场景
这个自定义钩子可以在各种场景中使用。例如,我们有一个计数器组件,每当计数更改时需要显示警报,但要排除初始渲染。
import { useState } from "react"; import useUpdateEffect from "@hooks/useUpdateEffect"; export default function UpdateEffectComponent() { const [count, setCount] = useState(10); useUpdateEffect(() => alert(count), [count]); return ( <div> <div>{count}</div> <button onClick={() => setCount((c) => c + 1)}>数字+1</button> </div> ); }
3.26 useWindowSize
import { useState } from "react"; import useEventListener from "@hooks/useEventListener"; type WindowSize = { width: number; height: number; }; export default function useWindowSize(): WindowSize { const [windowSize, setWindowSize] = useState<WindowSize>({ width: window.innerWidth, height: window.innerHeight, }); useEventListener("resize", () => { setWindowSize({ width: window.innerWidth, height: window.innerHeight }); }); return windowSize; }
使用useWindowSize
我们就可以访问包含窗口当前宽度和高度的对象,使我们能够专注于构建动态和响应式界面。
此包还包括 useEventListener
钩子,它智能地侦听窗口调整大小事件。每当窗口大小更改时,useWindowSize
更新状态以反映最新的尺寸,触发消耗组件的重新渲染。
使用场景
useWindowSize
钩子可以用于各种场景。在构建适应不同屏幕尺寸的响应式布局时,它特别有用。借助此钩子,我们可以根据可用的窗口空间轻松调整组件的样式、布局或内容。此外,它使我们能够根据窗口尺寸动态渲染或隐藏元素,优化图像加载或执行依赖于窗口尺寸的任何其他行为。
import useWindowSize from "@hooks/useWindowSize"; export default function WindowSizeComponent() { const { width, height } = useWindowSize(); return ( <div> {width} x {height} </div> ); }
3.27 useDeepCompareEffect
import { useRef, useEffect, DependencyList } from "react"; import isEqual from "lodash/fp/isEqual"; type EffectHookType = typeof useEffect; type CreateUpdateEffect = (hook: EffectHookType) => EffectHookType; export const createDeepCompareEffect: CreateUpdateEffect = (effect) => (callback, deps) => { const ref = useRef<DependencyList>(); const signalRef = useRef<number>(0); if (deps === undefined || !isEqual(deps, ref.current)) { ref.current = deps; signalRef.current += 1; } effect(callback, [signalRef.current]); }; export default createDeepCompareEffect(useEffect);
在React中管理依赖关系是一件很棘手的事情,尤其是在处理复杂的数据结构或嵌套对象时。为了解决默认useEffect
钩子的限制,useDeepCompareEffect
确保仅当依赖关系发生深层更改时才触发效果回调,它使用lodash
的isEqual
函数进行准确的比较。
它能够防止不必要的重新渲染。通过在当前依赖项和先前依赖项之间执行深层比较,该钩子智能地确定是否应触发效果,从而在浅层比较
无法胜任的情况下实现了性能优化。
使用场景
这个自定义钩子在处理复杂的状态对象时特别有用,比如当你有深层嵌套的数据结构或需要跟踪多个相互关联的状态时。它使你能够定义准确反映你想要跟踪的特定更改的依赖关系,确保只有在绝对必要时才执行效果。
import React, { useEffect, useState, useRef } from "react"; import useDeepCompareEffect from "@hooks/useDeepCompareEffect"; export default function DeepCompareEffectComponent() { const [age, setAge] = useState<number>(0); const [otherCount, setOtherCount] = useState<number>(0); const useEffectCountRef = useRef<HTMLSpanElement>(null); const useDeepCompareEffectCountRef = useRef<HTMLSpanElement>(null); const person = { age: age, name: "Sergey" }; useEffect(() => { if (useEffectCountRef.current) { useEffectCountRef.current.textContent = ( parseInt(useEffectCountRef.current.textContent || "0") + 1 ).toString(); } }, [person]); useDeepCompareEffect(() => { if (useDeepCompareEffectCountRef.current) { useDeepCompareEffectCountRef.current.textContent = ( parseInt(useDeepCompareEffectCountRef.current.textContent || "0") + 1 ).toString(); } }, [person]); return ( <div> <div> useEffect被触发的次数: <span ref={useEffectCountRef}>0</span> </div> <div> useDeepCompareEffect被触发的次数: <span ref={useDeepCompareEffectCountRef}>0</span> </div> <div>不相干的值: {otherCount}</div> <div>{JSON.stringify(person)}</div> <button onClick={() => setAge((currentAge) => currentAge + 1)}> 修改监听对象中的值 </button> <button onClick={() => setOtherCount((count) => count + 1)}> 修改和监听对象无关的值 </button> </div> ); }
后记
分享是一种态度。
全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。