清除 effect
通常,組件卸載時需要清除 effect 創建的諸如訂閱或計時器 ID 等資源。要實現這一點,useEffect
函數需返回一個清除函數。也就是說,要想在組件銷燬的時候搞一些事情,需要useEffect 末尾返回一個函數,在這個函數裏面可以寫具體銷燬的內容。
看下面的例子,在當前頁面裏面,頁面的標題是'
測試title',當切換到其他頁面時,頁面的標題變成‘前端精讀’
import React, { useEffect } from 'react';
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
return () => {
console.log('銷燬1————————————————');
document.title = '前端精讀';
};
}, [title]);
}
export default function CheckboxDemo() {
useDocumentTitle('測試title');
return <div />;
}
監聽頁面大小變化,網絡是否斷開
效果:在組件調用 useWindowSize
時,可以拿到頁面大小,並且在瀏覽器縮放時自動觸發組件更新。
import React, { useEffect, useState } from 'react';
function getSize() {
return {
innerHeight: window.innerHeight,
innerWidth: window.innerWidth,
outerHeight: window.outerHeight,
outerWidth: window.outerWidth,
};
}
function useWindowSize() {
const [windowSize, setWindowSize] = useState(getSize());
function handleResize() {
setWindowSize(getSize());
}
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return windowSize;
}
export default function Demo() {
const windowSize = useWindowSize();
return <div>頁面寬度{windowSize.innerWidth}</div>;
}
動態注入 css
效果:在頁面注入一段 class,並且當組件銷燬時,移除這個 class。
const className = useCss({
color: "red"
});
return <div className={className}>Text.</div>;
實現:可以看到,Hooks 方便的地方是在組件銷燬時移除副作用,所以我們可以安心的利用 Hooks 做一些副作用。注入 css 自然不必說了,而銷燬 css 只要找到注入的那段引用進行銷燬即可,具體可以看這個 代碼片段。
DOM 副作用修改 / 監聽場景有一些現成的庫了,從名字上就能看出來用法:document-visibility、network-status、online-status、window-scroll-position、window-size、document-title。
組件輔助
Hooks 還可以增強組件能力,比如拿到並監聽組件運行時寬高等。
獲取組件寬高
效果:通過調用 useComponentSize
拿到某個組件 ref 實例的寬高,並且在寬高變化時,rerender 並拿到最新的寬高。
import React, { useLayoutEffect, useState, useRef } from 'react';
function getSize(el) {
if (!el) {
return {};
}
return {
width: el.offsetWidth,
height: el.offsetHeight,
};
}
function useComponentSize(ref) {
const [ComponentSize, setComponentSize] = useState(getSize(ref.current));
function handleResize() {
if (ref && ref.current) {
setComponentSize(getSize(ref.current));
}
}
useLayoutEffect(() => {
handleResize();
let resizeObserver = new ResizeObserver(() => handleResize());
resizeObserver.observe(ref.current);
return () => {
resizeObserver.disconnect(ref.current);
resizeObserver = null;
};
}, []);
return ComponentSize;
}
export default function Demo() {
const ref = useRef(null);
const componentSize = useComponentSize(ref);
return (
<>
{componentSize.width}
<textarea ref={ref} />
</>
);
}
拿到組件 onChange 拋出的值
效果:通過 useInputValue()
拿到 Input 框當前用戶輸入的值,而不是手動監聽 onChange 再騰一個 otherInputValue
和一個回調函數把這一堆邏輯寫在無關的地方。
import React, { useState, useCallback } from 'react';
function useInputValue(initialValue) {
const [value, setValue] = useState(initialValue);
const onChange = useCallback(function(e) {
setValue(e.currentTarget.value);
}, []);
return {
value,
onChange,
};
}
export default function Demo() {
const name = useInputValue('jjsun');
return (
<>
{name.value}
<input {...name} />
</>
);
}