可以在组件内声明其他组件吗?



想象一下,我的反应应用程序上有一个卡片列表。所以我将有两个组件,<ListCard /><Card />。 我的怀疑是,在我工作的地方,当我们有这样的东西时,我们通常会在<ListCard />中声明一个名为Card的变量,它接收一个 JSX,但我不知道这是否是一件好事。 最近我在这种方法上遇到了一些问题,但我没有发现有人说要做或不做。

想象一下,我没有在应用程序的任何地方使用该<Card />

我们声明列表卡的方式

const ListCard = () => {
const cards = [
{ title: '1', id: 1 },
{ title: '2', id: 2 },
];
const Card = ({ title }) => <div>{title}</div>;
return (
<div>
<h1>List of Cards</h1>
{cards.map(card => (
<Card title={card.title} key={card.id} />
))}
</div>
);
};

我想知道像这样在列表卡之外声明是否更好。

const Card = ({ title }) => <div>{title}</div>;
const ListCard = () => {
const cards = [
{ title: '1', id: 1 },
{ title: '2', id: 2 },
];
return (
<div>
<h1>List of Cards</h1>
{cards.map(card => (
<Card title={card.title} key={card.id} />
))}
</div>
);
};

在同一文件中声明另一个Component是完全可以的,但是在另一个函数中声明它是低效的。想象一下,您的应用程序在每个render期间"重新创建"第二个Component

因此,可以随意在同一个文件中声明它,但不要在另一个Component函数中声明它。

在功能组件中,每个变量都会被销毁,并在每次渲染时重新创建。这就是useState钩子如此有价值的原因,因为它足够聪明,可以使用以前或更新的值重新创建它的变量。

通过在另一个组件中声明一个组件,您不仅可以重新呈现两个组件,还可以完全重新声明一个组件。这不会是很高的性能,尤其是在组件更复杂的情况下。

因此,您的问题的答案始终是将其分开声明。它可以在内部声明,但它没有好处,只有缺点。

与其他答案中所说的相反,在组件中声明组件可能是完全可以的。在某些情况下,它甚至非常有用,因为您需要从父范围访问函数,并且在渲染每个项目时成本高昂。唯一的事情是你需要了解你在做什么,小心,正确地做。但是你会了解到,使用 React 钩子,任何方法总是需要你对渲染过程有很好的理解。

在您的情况下,为了防止在每次渲染时重新挂载组件,您需要在正在创建的子组件上保留相同的引用。为此,我们使用useMemo钩。请注意,因为每次更新该钩子的依赖项时,您的引用都会更改,因此子项将重新挂载。有一些方法可以解决这个问题并始终保持相同的引用,但这可能有点偏离主题。

下面是一个常见示例:

const List = ({ items, onItemSelected }) => {
// If you don't use useMemo, your component will be recreated every time and
// React will destroy and recreate every item at every render.
const Item = React.useMemo(() => ({ index, item }) => {
const onClick = React.useCallback(() => onItemSelected(index), [index])
return <li onClick={onClick}>{item}</li>
}, [onItemSelected])
return (<ul>
{items.map((item, index) => <Item key={index} index={index} item={item} />)}
</ul>)
}

您可以在此处找到完整的工作示例

以下是完整的代码,其中包含一些您可能需要注意的注释。

import React from "react";
const items = ["apple", "banana", "orange"];
export default function App() {
const [selected, setSelected] = React.useState(-1);
const onItemSelected = React.useCallback((index) => setSelected(index), []);
return (
<div>
{/* Add some logs and see what happens if you add "selected={selected}" to the props of List */}
<List items={items} onItemSelected={onItemSelected} />
<p>
You pressed <b>{items[selected]}</b>
</p>
</div>
);
}
// We used React.memo so the list won't rerender when the parent state changes
// This is a good optimization, but nothing mandatory, here
const List = React.memo(({ items, onItemSelected }) => {
// If you don't use useMemo, your component will be recreated every time and
// React will destroy and recreate every item at every render.
const Item = React.useMemo(() => {
// We use this syntax only for eslint to detect that this is a component.
// There is nothing mandatory, here, neither.
const Component = ({ index, item }) => {
// The main interess here, is that "onItemSelected" is not a prop anymore.
// This way, we can make onClick a callback that won't change between renders
const onClick = React.useCallback(() => onItemSelected(index), [index]);
return <li onClick={onClick}>{item}</li>;
};
Component.displayName = "Item";
return Component;
// be very carefull with what you put here. The component should not depend on things
// That might change often, because every items will be destroyed and recreated when they change
// Try to add "selected" in this dependency list and see the impacts on rerender
}, [onItemSelected]);
return (
<ul>
{items.map((item, index) => (
<Item key={index} index={index} item={item} />
))}
</ul>
);
});

最新更新