React:将组件作为道具传递的方法

React: Ways of passing component as a props

这两种将组件作为 props 传递的方式有什么区别。

  1. 按原样传递 RepoMenu
<Fetch
  url={`https://api.github.com/users/${login}/repos`}
  renderSuccess={RepoMenu}
/>
  1. RepoMenu 包装在一个函数中
<Fetch
  url={`https://api.github.com/users/${login}/repos`}
  renderSuccess={({ data }) => <RepoMenu data={data} />}
/>

RepoMenu 内部使用自定义挂钩。

function RepoMenu({ data, onSelect = (f) => f }) {
  const [{ name }, prev, next] = useIterator(data);

  return (
    <div style={{ display: "flex" }}>
      <button onClick={prev}>&lt;</button>
      <p>{name}</p>
      <button onClick={next}>&gt;</button>
    </div>
  );
}

使用一个(按原样传递 RepoMenu)开始失败,React 开始抛出异常 Rendered more hooks than during the previous render.

最初我认为可能无法像方法1那样使用组件。但是为了进一步测试它,我创建了一个小的纯组件,并试图渲染传递给它的数据。

function RepoMenuPure({ data = [], onSelect = (f) => f }) {
  return (
    <div style={{ display: "flex" }}>
      <h3>There are {data.length} repos for this user</h3>
    </div>
  );
}

当我尝试像下面那样使用它时,它在这种情况下有效。

const UserRepositories = ({ login, selectedRepo, onSelect = (f) => f }) => {
  return (
    <Fetch
      url={`https://api.github.com/users/${login}/repos`}
      renderSuccess={RepoMenuPure}
    />
  );
};

有人可以帮我解释一下发生了什么以及我错过了什么吗?

我在这里创建了一个小沙盒:https://codesandbox.io/s/Whosebug-example-bqfn3e?file=/src/App.js

return renderSuccess({ data });

您的问题是您像调用函数一样调用组件。每次调用 renderSuccess(即 RepoMenu - 现在它不被视为组件)时,它都会触发您的自定义挂钩 useIterator,这是无效的。

可以参考this document

Don’t call Hooks inside loops, conditions, or nested functions.

嵌套函数部分是你现在的情况。

对于纯组件,它可以正常工作,因为您没有使用任何挂钩,这就是为什么您看不到任何错误的原因。

对于修复,你可以这样做

import React from "react";
import { useFetch } from "./hooks/useFetch";

function Fetch({
  url,
  loadingFallback = <div>Loading....</div>,
  errorFallback = <div>Some thing went wrong</div>,
  renderSuccess: RenderSuccess //introduce it as a component
}) {
  const { data, error, loading } = useFetch(url);

  if (loading) {
    return loadingFallback;
  }

  if (error) {
    return errorFallback;
  }

  if (data) {
    return <RenderSuccess data={data} />; //render a component instead of executing a function
  }

  return null;
}

export default Fetch;

Sandbox

在第一个选项中:

renderSuccess={({ data }) => <RepoMenu data={data} />}

您是说 Fetch 组件期望 renderSuccess 接收一个 returns 和 Component 的函数。如果您使用的是打字稿,FetchrenderSuccess 的类型将类似于:

renderSuccess : (data: any) => JSX.Element

// OR return the component itself
renderSuccess : (data: any) => RepoMenu

这就是为什么当您在 Fetch 组件中调用 renderSuccess({ data }) 时有效。

在第二个选项中:

renderSuccess={RepoMenu}

您是说 Fetch 组件期望 renderSuccess 接收 RepoMenu 组件(不是函数)。如果您使用的是打字稿,FetchrenderSuccess 的类型将类似于:

renderSuccess : JSX.Element

// OR the component itself
renderSuccess : RepoMenu

要使此选项生效,您应该将 Fetch 组件更改为如下所示:

import React from "react";
import { useFetch } from "./hooks/useFetch";

function Fetch({
  url,
  loadingFallback = <div>Loading....</div>,
  errorFallback = <div>Some thing went wrong</div>,
  renderSuccess: Component
  //renderSuccess
}) {
  // console.log("renderSuccess", renderSuccess);

  const { data, error, loading } = useFetch(url);

  if (loading) {
    return loadingFallback;
  }

  if (error) {
    return errorFallback;
  }

  if (data) {
    // return renderSuccess({ data });
    return <Component data={data} />;
  }

  return null;
}

export default Fetch;