83 lines
2.1 KiB
Markdown
83 lines
2.1 KiB
Markdown
---
|
|
title: 不要在组件内部定义组件
|
|
impact: HIGH
|
|
impactDescription: 阻止每次渲染时重新挂载
|
|
tags: rerender, components, remount, performance
|
|
---
|
|
|
|
## 不要在组件内部定义组件
|
|
|
|
**影响:高(阻止每次渲染时重新挂载)**
|
|
|
|
在另一个组件内部定义组件,会在每次渲染时创建一个新的组件类型。React 每次看到的是不同的组件,因此会完全重新挂载它,销毁所有状态和 DOM。
|
|
|
|
开发者这样做的一个常见原因是为了在不传递 props 的情况下访问父组件的变量。请始终改用传递 props 的方式。
|
|
|
|
**错误写法(每次渲染都重新挂载):**
|
|
|
|
```tsx
|
|
function UserProfile({ user, theme }) {
|
|
// 为了访问 `theme` 而在内部定义 - 错误
|
|
const Avatar = () => (
|
|
<img
|
|
src={user.avatarUrl}
|
|
className={theme === 'dark' ? 'avatar-dark' : 'avatar-light'}
|
|
/>
|
|
)
|
|
|
|
// 为了访问 `user` 而在内部定义 - 错误
|
|
const Stats = () => (
|
|
<div>
|
|
<span>{user.followers} followers</span>
|
|
<span>{user.posts} posts</span>
|
|
</div>
|
|
)
|
|
|
|
return (
|
|
<div>
|
|
<Avatar />
|
|
<Stats />
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
每次 `UserProfile` 渲染时,`Avatar` 和 `Stats` 都是新的组件类型。React 会卸载旧实例并挂载新实例,丢失所有内部状态,重新运行 effect,并重建 DOM 节点。
|
|
|
|
**正确写法(改为传递 props):**
|
|
|
|
```tsx
|
|
function Avatar({ src, theme }: { src: string; theme: string }) {
|
|
return (
|
|
<img
|
|
src={src}
|
|
className={theme === 'dark' ? 'avatar-dark' : 'avatar-light'}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function Stats({ followers, posts }: { followers: number; posts: number }) {
|
|
return (
|
|
<div>
|
|
<span>{followers} followers</span>
|
|
<span>{posts} posts</span>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function UserProfile({ user, theme }) {
|
|
return (
|
|
<div>
|
|
<Avatar src={user.avatarUrl} theme={theme} />
|
|
<Stats followers={user.followers} posts={user.posts} />
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
**此错误的症状:**
|
|
- 输入框在每次按键后失去焦点
|
|
- 动画意外重新开始
|
|
- `useEffect` 的清理/设置逻辑在每次父组件渲染时执行
|
|
- 组件内的滚动位置重置
|