需要避免的五個react的ref錯誤用法
前言
react是一個優(yōu)秀的框架,提供了我們很多的便利,但是在使用的過程中,我們也會遇到很多的問題,其中一個就是ref的使用,以下是我列出的5個使用ref的錯誤用法,并提供了正確的用法。
錯誤1: 當使用ref更好時,卻使用state
一個常見的錯誤就是明明使用ref更合適管理狀態(tài)的時候,但是卻使用state來存儲狀態(tài)。例如存儲定時器的間隔時間。
錯誤用法: 我們將定時器間隔時間存儲在狀態(tài)中從而觸發(fā)了不必要的重新渲染。
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有個好處就是不會觸發(fā)組件的重新渲染,從而避免了不必要的性能問題。
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 節(jié)點調用。這可能會導致 UI 中出現一些不必要的閃爍。解決方案?確保緩存(使用useCallback) ref 回調函數。
錯誤用法: 下面的代碼無法正常工作,因為每當inputValue或currentTime發(fā)生變化時,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 Hook useState useEffect componentD
這篇文章主要介紹了React Hook useState useEffect componentDidMount componentDidUpdate componentWillUnmount問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03

