【问题标题】:Why does useQuery call cause a re-render in my component?为什么 useQuery 调用会导致我的组件重新渲染?
【发布时间】:2021-05-11 09:05:38
【问题描述】:

正如我们所知,当它的 props 或 state 发生变化时,react 组件会重新渲染。

现在我正在使用来自react-apollo 包的useQuery,如下所示:

import { gql, useQuery } from '@apollo/client';

const getBookQuery = gql`
  {
    books {
      name
    }
  }
`;

function BookList() {
    const { loading, error, data} = useQuery(getBookQuery);

    if(loading) return <p>Loading....</p>
    if(error) return <p>Ops! Something went wrong</p>

    return (
      <>
        <ul>
          {data.books.map(book => (
            <li key={book.name}>{book.name}</li>
          ))}
        </ul>
      </>
    )
}

export default BookList;

当我运行上面的代码时,我们首先在 DOM 中获得Loading...,然后更新为包含查询数据的列表(一旦到达)。但是一旦从查询中接收到数据,react 如何知道重新渲染我的组件。

这些dataloadingerror 属性是否映射到组件道具并且它们正在更新?如果是这样,为什么 chrome 开发工具不显示此 BookList 组件的任何道具?

有人能解释一下这个useQuery 自定义钩子在这里是如何工作的吗?

【问题讨论】:

  • 文档是为了这些目的,对吧? apollographql.com/docs/react/data/queries
  • 看不到道具,但可以看到道具下方的挂钩。顺便说一句,Apollo 有一个非常好的开发工具。你也可以使用它

标签: javascript reactjs react-hooks apollo react-apollo


【解决方案1】:

了解(大致)useQuery 中发生的事情的一个好方法是考虑你自己会如何做,例如

const MyComponent = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(async () => {
    try {
      setLoading(true);
      const data = await GraphQL.request(getBookQuery);
      setData(data);
    } catch (ex) {
      setError(ex);
    } finally {
      setLoading(false);
    }
  }, []);

  if(loading) return <p>Loading....</p>
  if(error) return <p>Ops! Something went wrong</p>

  return (
    <>
      <ul>
        {data.books.map(book => (
          <li key={book.name}>{book.name}</li>
        ))}
      </ul>
    </>
  );
};

在上面你可以看到你的组件有 state(不是 props)dataloadingerror,这会导致你的组件重新渲染。

然后你可以想象这个逻辑被包裹在你自己的useQuery钩子里:

const useQuery = (query, variables) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(async () => {
    try {
      setLoading(true);
      const data = await GraphQL.request(query, variables);
      setData(data);
    } catch (ex) {
      setError(ex);
    } finally {
      setLoading(false);
    }
  }, []);

  return { data, loading, error };
}

const MyComponent = () => {
  const { data, loading, error } = useQuery(getBookQuery);

  if(loading) return <p>Loading....</p>
  if(error) return <p>Ops! Something went wrong</p>

  return (
    <>
      <ul>
        {data.books.map(book => (
          <li key={book.name}>{book.name}</li>
        ))}
      </ul>
    </>
  );
};

所以最终你的组件会重新渲染,因为它确实有dataloadingerror 保持在MyComponent 状态,它只是被抽象掉了。

【讨论】:

  • 啊,所以基本上钩子的状态变量也可以作为使用钩子的组件的状态(或道具?)?如果钩子的状态发生变化,组件渲染也会被触发。使用一个非常基本的自定义钩子对其进行了测试,似乎实际上是这种情况https://codesandbox.io/s/modest-sea-ghqjm
  • 是的,实际上没有“钩子”状态之类的东西,只有组件状态,自定义钩子仅定义与调用它的组件相关联的状态。在不同的函数 (useQuery) 中调用 useState 的事实在很大程度上没有意义,它都与调用它的组件有关。
  • 绝妙的答案!
【解决方案2】:

钩子可以有自己的状态,当改变时可以触发重新渲染。例如加载、错误、数据。

我在这个沙箱https://codesandbox.io/s/determined-wave-dd1wd?file=/src/BookList2.js中创建了apollo的useQuery hook的示例实现

在假实现中,可以看到钩子具有状态,并且每当状态更改时,组件都会重新渲染。

还创建了另一个组件 BookList2,在该组件中我没有使用钩子,而是在组件中粘贴了钩子的假实现 - 这样可能会更清楚地触发重新渲染的方式

TLDR:检查https://codesandbox.io/s/determined-wave-dd1wd?file=/src/BookList2.js - 看看钩子的状态在改变时会导致重新渲染

【讨论】:

    猜你喜欢
    • 2020-05-17
    • 2020-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多