清除 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} />
</>
);
}