前端技术

有效的使用 useEffect

2022-10-29  本文已影响0人  吴摩西

这篇是 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 以后,有以下要点。

  1. 作者想要使用 async, await,这显然有些不合适,因为 useEffect 需要的参数是一个返回 undefined 或者解构回调的 function,async 函数返回的是一个 Promise
  2. 作者未传第二个参数,导致每次渲染的时候,都会重新 fetch 数据。
  3. 为了防止竞争条件,即先 fetch 的数据后来,需要使用 isCanceled 预防。
  4. 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();
上一篇下一篇

猜你喜欢

热点阅读