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}

内容预览已结束

此内容需要会员权限。请先登录以查看完整内容。