这篇是 React Advanced London 2022, Using useEffect effectively 的学习笔记,原视频请走传送门。
首先他说,希望本分享能比最新的英国首相待的时间长一些。
使用 useEffect 获取远端数据
直接上代码,这是使用 class component 获取数据并渲染的标准流程。
class App extends Components {
constructor(props) {
super(props);
this.state = {
data: null,
};
}
componentDidMount() {
fetchData('/some/data').then((res) => {
this.setState({ data: res });
});
}
}
引入 hooks 以后,有以下要点。
- 作者想要使用
async
,await
,这显然有些不合适,因为 useEffect 需要的参数是一个返回 undefined 或者解构回调的 function,async
函数返回的是一个Promise
- 作者未传第二个参数,导致每次渲染的时候,都会重新 fetch 数据。
- 为了防止竞争条件,即先 fetch 的数据后来,需要使用
isCanceled
预防。 - React 18 strict mode 会在 mount 阶段执行两次 useEffect。请走传送门ensuring reusable state,为了防止 fetch 两次,需要把第一次的 fetch 取消掉。需要使用 AbortController.
导致的代码如下,然而这样的代码 React 18 已经不推荐使用。
const [data, setData] = useState(null);
useEffect(() => {
const abortController = new AbortController();
fetch('/some/data', {
signal: abortController.signal
})
.then(res => res.JSON())
.then((res) => {
setData(res);
})
.catch((err) => {
if (err.name === 'AbortError') {
/*
Most of the time there is nothing to do here
as the component is unmounted.
*/
} else {
/* Logic for other cases like request failing goes here. */
}
});
return () => abortController.abort();
}, [id]);
适用场景
Screen Shot 2022-10-29 at 19.20.36.png
Effect let you specify side effects that are caused by rendering, rather than by particular event.
推荐的使用是如下,useEffect 只用于响应活跃的事件(即持续的时间),不推荐使用于特定的事件(如 onClick, onSubmit 等只会离散触发的事件。)。
useEffect(() => {
const handler = event => {
// do something
};
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
不应该使用 useEffect
1. 有的场景,useEffect 可以用 useMemo 替换,甚至可能无需 useMemo;
function Cart() {
const [item, setItem] = useState(null);
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(items.reduce((currentTotal, item) => currentTotal + item.price, 0);
}, [items]);
}
2. 直接调用响应函数,而不是使用 useEffect;
3. 使用 useSyncExternalStore()
而不是使用 useEffect
.
不要使用 useEffect
监听外部的 store。 下面的代码不可取:
function Store() {
const [isConnected, setIsConnected] = useState(true);
useEffect(() => {
const sub = storeApi.subscribe(({ status }) => {
setIsConnected(status === 'connected');
});
return () => sub.unsubscribe();
}, []);
}
可以使用以下的代码替换:
const isConnected = useSyncExternalStore(
// subscribe
storeApi.subscribe,
// get snapshot
() => storeApi.getStatus === 'connected',
// get server snapshot
true
);
4. 不使用 useEffect
获取远端数据
useEffect
转为 renderAsYouFetch
改为使用第三方库,例如 remix
import { useLoaderData } from '@remix-run/react';
import { json } from '@remix-run/node';
import { getItems } from '../storeApi';
export const loader = async () => {
const items = await getItems();
return json(items);
}
export default function Store() {
const items = useLoaderData();
}
react-query
import { getItems } from './storeApi';
import { useQuery, useQueryClient } from 'react-query';
function Store() {
const queryClient = useQueryClient();
return (
<button onClick=(() => {
queryClient.prefetchQuery('items', getItems);
})>See Items</button>
);
}
function Items() {
const { data } = useQuery('items', getItems);
}
不使用 useEffect()
,使用 useQuery()
,或者 useSWR()
,
可能在未来,甚至直接适用 use()
。
const fetchPost = cache(await(id) => {
});
function Post({ id }) {
const post = use(fetchPost(id));
return (
<article>
<h1>{post.id}</h1>
<PostContent post={post}>
</article>
);
}
Screen Shot 2022-10-29 at 20.43.25.png
5. 不使用 useEffect
授权
下列代码不可接受
function Store() {
useEffect(() => {
// this will run twice
storeApi.authenticate();
}, []);
}
应使用以下代码
if (typeof window !== 'undefined') {
storeApi.authenticate();
}
function Store() {
}
或者
function renderApp() {
if (typeof window !== 'undefined') {
storeApi.authenticate();
}
appRoot.render(<Store />);
}
renderApp();
网友评论