409 - 《React Tips》
发布于 2024年1月29日

题图:nadyeldems @ unsplash.com
1、避免在 useEffect 里获取数据。
因为,1)组件 unmount 后再 remount 时,数据会被重复获取,2)会导致请求瀑布流,3)通常不能做请求的预加载和缓存,4)不能兼容 SSR。
function App(props) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(props.userId).then((result) => {
setUser(result);
});
}, [props.userId]);
// ...
}
推荐的方法是用框架提供的方法,或者请求方案的库,比如 SWR,react-query 等。
function App(props) {
const { data } = useQuery(['user', props.userId], fetchUser);
}
2、避免 useEffect 里出现竞态条件。
一个例子带代码如下,先渲染 userId 1,再渲染 userId 2,假如 userId 1 的请求比 userId 2 的请求迟返回,那最后一次执行 setUser 的是 userId 1 的结果,而这并不符合预期。
function User(props) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${props.userId}`).then((result) => {
setUser(result);
});
}, [props.userId]);
if (!user) return null;
return <h1>{user.name}</h1>;
}
一个解法是,用 AbortController 来取消旧的请求。
function User(props) {
const [user, setUser] = useState(null);
useEffect(() => {
const abortController = new AbortController();
fetch(`/api/users/${props.userId}`, {
signal: abortController.signal,
}).then((result) => {
setUser(result);
});
return () => {
abortController.abort();
};
}, [props.userId]);
if (!user) return null;
return <h1>{user.name}</h1>;
}
3、如何监听 ref 变更?
一个常见的错误解法是用 useEffect 加 ref.current 依赖,而 ref.current 是不会触发变更的。
function App() {
const ref = useRef(null);
useEffect(() => {
const element = ref.current;
// ...
}, [ref.current]);
return <div ref={ref}>...</div>;
}
解法是用 ref 回调。每当 div 被挂载或卸载,ref 回调都会被调用。
function App() {
const ref = useCallback((node) => {
if (node !== null) {
// ...
}
}, []);
return <div ref={ref}>...</div>;
}
4、避免 useEffect 导致的无限循环。
比如下面的场景,setCount 更新 count 到触发 re-render,re-render 会生成新的 user,user 变更又触发 useEffect 里的 setCount,导致无限循环。
function App() {
const [count, setCount] = useState(0);
const user = { name: 'John', count: 5 };
useEffect(() => {
setCount((currCount) => currCount + user.count);
}, [user]);
return <div>{count}</div>;
}
解法很多,比如,1)用 useMemo 包一下 user,2)useEffect 的依赖用 user.count,3)等。
function App() {
const [count, setCount] = useState(0);
const user = useMemo(() => ({ name: 'John', count: 5 }), []);
useEffect(() => {
setCount((currCount) => currCount + user.count);
}, [user.count]);
return <div>{count}