需要避免的五个react的ref错误用法
前言
react是一个优秀的框架,提供了我们很多的便利,但是在使用的过程中,我们也会遇到很多的问题,其中一个就是ref的使用,以下是我列出的5个使用ref的错误用法,并提供了正确的用法。
错误1: 当使用ref更好时,却使用state
一个常见的错误就是明明使用ref更合适管理状态的时候,但是却使用state来存储状态。例如存储定时器的间隔时间。
错误用法: 我们将定时器间隔时间存储在状态中从而触发了不必要的重新渲染。
import { useState, useEffect } from 'react'; export const CustomTimer = () => { const [intervalId, setIntervalId] = useState(); const [time, setTime] = useState(new Date()); useEffect(() => { const id = setInterval(() => { setTime(new Date()); }, 1000); setIntervalId(id); return () => clearInterval(id); }, []); const stopTimer = () => { intervalId && clearInterval(intervalId); } return ( <div> <p>{time.toLocaleString()}</p> <button onClick={stopTimer}>Stop Timer</button> </div> ); }
正确用法:将定时器间隔存储在ref中,从而避免了不必要的重新渲染。
ref有个好处就是不会触发组件的重新渲染,从而避免了不必要的性能问题。
import { useRef, useEffect } from'react'; export const CustomTimer = () => { const intervalIdRef = useRef(); const [time, setTime] = useState(new Date()); useEffect(() => { const id = setInterval(() => { setTime(new Date()); }, 1000); intervalIdRef.current = id; return () => clearInterval(id); }, []); const stopTimer = () => { intervalIdRef.current && clearInterval(intervalIdRef.current); } return ( <div> <p>{time.toLocaleString()}</p> <button onClick={stopTimer}>Stop Timer</button> </div> ); }
错误2: 在设置ref的值之前使用ref.current而不是ref
我们在使用ref传递值给某个函数或者子组件的时候,使用的是ref.current而不是ref本身,直接使用ref本身的情况下,ref本身就是一个变化的对象,我们可以在组件渲染时使用ref.current来获取当前的值,但是在设置ref的值之前,ref.current的值是undefined,这就会导致我们的代码出现错误。
错误用法: 下面的代码无法运行,因为ref.current最初为空, 因此,当代码运行时,element为空。
import { useRef } from'react'; const useHovered = (element) => { const [hovered, setHovered] = useState(false); useEffect(() => { if(element === null) return; element.addEventListener('mouseenter', () => setHovered(true)); element.addEventListener('mouseleave', () => setHovered(false)); return () => { element.removeEventListener('mouseenter', () => setHovered(true)); element.removeEventListener('mouseleave', () => setHovered(false)); }; }, [element]); return hovered; } export const CustomHoverDivElement = () => { const ref = useRef(); const isHoverd = useHovered(ref.current); return ( <div ref={ref}> Hoverd:{`${isHoverd}`} </div> ); }
正确用法: 我们需要在设置ref的值之前使用ref.current来获取当前的值。
import { useRef } from'react'; const useHovered = (ref) => { const [hovered, setHovered] = useState(false); useEffect(() => { if(ref.current === null) return; ref.current.addEventListener('mouseenter', () => setHovered(true)); ref.current.addEventListener('mouseleave', () => setHovered(false)); return () => { ref.current.removeEventListener('mouseenter', () => setHovered(true)); ref.current.removeEventListener('mouseleave', () => setHovered(false)); }; }, [ref]); return hovered; } export const CustomHoverDivElement = () => { const ref = useRef(); const isHoverd = useHovered(ref); return ( <div ref={ref}> Hoverd:{`${isHoverd}`} </div> ); }
错误3: 忘记使用fowardRef
在初学react时,我们可能都犯过这个错误,直接给组件传递ref参数。事实上,React 不允许你将 ref 传递给函数组件,除非它被forwardRef包装起来。解决办法是什么?只需将接收 ref 的组件包装在 forwardRef 中,或为 ref prop 使用另一个名称即可。
错误用法: 下面的代码无法运行,因为我们没有使用forwardRef来包装组件。
import { useRef } from'react'; const CustomInput = ({ ref,...rest }) => { const [value, setValue] = useState(''); useEffect(() => { if(ref.current === null) return; ref.current.focus(); }, [ref]); return ( <input ref={ref} {...rest} value={value} onChange={e => setValue(e.target.value)} /> ); } export const CustomInputElement = () => { const ref = useRef(); return ( <CustomInput ref={ref} /> ); }
正确用法: 我们需要使用forwardRef来包装组件。
import { useRef, forwardRef } from'react'; const CustomInput = forwardRef((props, ref) => { const [value, setValue] = useState(''); useEffect(() => { if(ref.current === null) return; ref.current.focus(); }, [ref]); return ( <input ref={ref} {...props} value={value} onChange={e => setValue(e.target.value)} /> ); }) export const CustomInputElement = () => { const ref = useRef(); return ( <CustomInput ref={ref} /> ); }
错误4: 调用函数来初始化ref的值
当你调用函数来设置 ref 的初始值时,该函数将在每次渲染时被调用,如果该函数开销很大,这将不必要地影响你的应用性能。解决方案是什么?缓存该函数或在渲染期间初始化 ref(在检查值尚未设置之后)。
错误用法: 下面的代码很浪费性能,因为我们在每次渲染时都调用了函数来设置 ref 的初始值。
import { useState, useRef, useEffect } from "react"; const useOnBeforeUnload = (callback) => { useEffect(() => { window.addEventListener("beforeunload", callback); return () => window.removeEventListener("beforeunload", callback); }, [callback]); } export const App = () => { const ref = useRef(window.localStorage.getItem("cache-date")); const [inputValue, setInputValue] = useState(""); useOnBeforeUnload(() => { const date = new Date().toUTCString(); console.log("Date", date); window.localStorage.setItem("cache-date", date); }); return ( <> <div> 缓存的时间: <strong>{ref.current}</strong> </div> 用户名:{" "} <input value={inputValue} onChange={(e) => setInputValue(e.target.value)} /> </> ); }
正确用法: 我们需要缓存该函数或在渲染期间初始化 ref(在检查值尚未设置之后)。
import { useState, useRef, useEffect } from "react"; const useOnBeforeUnload = (callback) => { useEffect(() => { window.addEventListener("beforeunload", callback); return () => window.removeEventListener("beforeunload", callback); }, [callback]); } export const App = () => { const ref = useRef(null); if (ref.current === null) { ref.current = window.localStorage.getItem("cache-date"); } const [inputValue, setInputValue] = useState(""); useOnBeforeUnload(() => { const date = new Date().toUTCString(); console.log("Date", date); window.localStorage.setItem("cache-date", date); }); return ( <> <div> 缓存的时间: <strong>{ref.current}</strong> </div> 用户名:{" "} <input value={inputValue} onChange={(e) => setInputValue(e.target.value)} /> </> ); }
错误5: 使用每次渲染都会改变的ref回调函数
ref 回调函数可以很好的管理你的代码,但是,请注意,每当更改时,React 都会调用 ref 回调。这意味着当组件重新渲染时,前一个函数将使用 null 作为参数调用,而下一个函数将使用 DOM 节点调用。这可能会导致 UI 中出现一些不必要的闪烁。解决方案?确保缓存(使用useCallback) ref 回调函数。
错误用法: 下面的代码无法正常工作,因为每当inputValue或currentTime发生变化时,ref 回调函数就会再次运行,并且输入将再次成为焦点。
import { useEffect, useState } from "react"; const useCurrentTime = () => { const [time, setTime] = useState(new Date()); useEffect(() => { const intervalId = setInterval(() => { setTime(new Date()); }, 1_000); return () => clearInterval(intervalId); }); return time.toString(); } export const App = () => { const ref = (node) => { node?.focus(); }; const [nameValue, setNameValue] = useState(""); const currentTime = useCurrentTime(); return ( <> <h2>当前时间: {currentTime}</h2> <label htmlFor="name">用户名: </label> <input id="name" ref={ref} value={nameValue} onChange={(e) => setNameValue(e.target.value)} /> </> ); }
正确用法: 我们需要确保缓存(使用useCallback) ref 回调函数。
import { useEffect, useState, useCallback } from "react"; const useCurrentTime = () => { const [time, setTime] = useState(new Date()); useEffect(() => { const intervalId = setInterval(() => { setTime(new Date()); }, 1_000); return () => clearInterval(intervalId); }); return time.toString(); } export const App = () => { const ref = useCallback((node) => { node?.focus(); }, []); const [nameValue, setNameValue] = useState(""); const currentTime = useCurrentTime(); return ( <> <h2>当前时间: {currentTime}</h2> <label htmlFor="name">用户名: </label> <input id="name" ref={ref} value={nameValue} onChange={(e) => setNameValue(e.target.value)} /> </> ); }
以上就是需要避免的五个react的ref错误用法的详细内容,更多关于react的ref错误用法的资料请关注脚本之家其它相关文章!
相关文章
React路由的history对象的插件history的使用解读
这篇文章主要介绍了React路由的history对象的插件history的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2023-10-10一文详解ReactNative状态管理redux-toolkit使用
这篇文章主要为大家介绍了ReactNative状态管理redux-toolkit使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-03-03
最新评论