React:将组件作为道具传递的方法
React: Ways of passing component as a props
这两种将组件作为 props 传递的方式有什么区别。
- 按原样传递
RepoMenu
:
<Fetch
url={`https://api.github.com/users/${login}/repos`}
renderSuccess={RepoMenu}
/>
- 将
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}><</button>
<p>{name}</p>
<button onClick={next}>></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;
在第一个选项中:
renderSuccess={({ data }) => <RepoMenu data={data} />}
您是说 Fetch
组件期望 renderSuccess
接收一个 returns 和 Component
的函数。如果您使用的是打字稿,Fetch
中 renderSuccess
的类型将类似于:
renderSuccess : (data: any) => JSX.Element
// OR return the component itself
renderSuccess : (data: any) => RepoMenu
这就是为什么当您在 Fetch
组件中调用 renderSuccess({ data })
时有效。
在第二个选项中:
renderSuccess={RepoMenu}
您是说 Fetch
组件期望 renderSuccess
接收 RepoMenu
组件(不是函数)。如果您使用的是打字稿,Fetch
中 renderSuccess
的类型将类似于:
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;
这两种将组件作为 props 传递的方式有什么区别。
- 按原样传递
RepoMenu
:
<Fetch
url={`https://api.github.com/users/${login}/repos`}
renderSuccess={RepoMenu}
/>
- 将
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}><</button>
<p>{name}</p>
<button onClick={next}>></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;
在第一个选项中:
renderSuccess={({ data }) => <RepoMenu data={data} />}
您是说 Fetch
组件期望 renderSuccess
接收一个 returns 和 Component
的函数。如果您使用的是打字稿,Fetch
中 renderSuccess
的类型将类似于:
renderSuccess : (data: any) => JSX.Element
// OR return the component itself
renderSuccess : (data: any) => RepoMenu
这就是为什么当您在 Fetch
组件中调用 renderSuccess({ data })
时有效。
在第二个选项中:
renderSuccess={RepoMenu}
您是说 Fetch
组件期望 renderSuccess
接收 RepoMenu
组件(不是函数)。如果您使用的是打字稿,Fetch
中 renderSuccess
的类型将类似于:
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;