优化useEffect中的网络请求

优化useEffect中的网络请求
Photo by Harley-Davidson / Unsplash

前一篇文章中我们提到 useEffect 需要在组建重新渲染的时候清除 SideEffect,对于网络请求或者其他IO操作我们同样需要清除 SideEffect 从而避免 Race Condition 的问题。

例如我们通常可能有下面的代码来获取用户的最新的个人信息后显示在页面上:

const Profile = ({uid}) => {
  const [name, setName] = useState('')
  useEffect(() => {
    fetch(name_url/${uid})
      .then(res => res.json())
      .then(setName)
  }, [uid])
  return (<div>{}</div>)
}

上面的代码在一般情况下并不会出现问题,但当 Profile 组件多次重新渲染然后用户通过其他方式修改了用户名,这时我们的页面显示的就有可能是旧的 name 也有可能是修改后的 name

让我们尝试引入一个状态值,来判断当前组件是否需要渲染这一份数据:

const Profile = ({uid}) => {
  const [name, setName] = useState('')
  useEffect(() => {
+   let ignore = false
    fetch(name_url/${uid})
      .then(res => res.json())
-     .then(setName)
+     .then(data => {
+       if (!ignore) {
+         setName(data)
+       }
+     })
+     return () => ignore = true
  }, [uid])
  return (<div>{}</div>)
}

我们引入了一个状态值 ignoreuseEffect 在清除 SideEffect 的时候我们将 ignore 设置为 true 。这时候如果我们从远程服务器获取到了数据,我们就是放弃这一份数据,如果是在 useEffect 清除之前那么页面处于之前的UI状态中,我们就可以渲染这一份数据。

但这种方式带来的问题就是就算我们成功的清除了 SideEffect ,我们的网络请求还是发出去了。对于用户和我们的服务器来说这都是一个不必要的请求,如果你使用的是 fetch 那么可以引入 Abort Controller

const Profile = ({uid}) => {
  const [name, setName] = useState('')
  useEffect(() => {
+    const controller = new AbortController();
-    fetch(name_url/${uid})
+    fetch(name_url/${uid}, { signal: controller.signal})
      .then(res => res.json())
      .then(setName)
+     return () => controller.abort()
  }, [uid])
  return (<div>{}</div>)
}

这时当我们的 SideEffect 被清除时我们会给当前的 HTTP请求 发一个 abort 的信号让浏览器去取消这个请求从而减少不要的网络请求。

Refs: