react navite避免频繁render、合理拆分组件
在 React Native 里,避免频繁 render + 合理拆分组件,核心不是“组件越小越好”,而是:
让状态尽可能靠近使用它的地方,让父组件不要因为无关状态变化而带着整棵子树一起 render。
1. 先搞清楚 React Native 为什么会频繁 render
例如:
function Home() {
const [count, setCount] = useState(0);
const [keyword, setKeyword] = useState('');
return (
<View>
<Header />
<SearchInput value={keyword} onChangeText={setKeyword} />
<Counter count={count} />
<ProductList />
</View>
);
}
count 改变时,Home 会重新执行,默认情况下:
Home render
├── Header render
├── SearchInput render
├── Counter render
└── ProductList render
即使 Header、SearchInput、ProductList 实际上都不关心 count。
2. 第一原则:状态放在“离使用它最近”的地方
不要一上来就:
function Home() {
const [search, setSearch] = useState('');
const [selectedTab, setSelectedTab] = useState(0);
const [modalVisible, setModalVisible] = useState(false);
const [count, setCount] = useState(0);
// ...
}
然后整个页面几十个组件都挂在 Home 下。
更好的方式:
function Home() {
return (
<View>
<Header />
<SearchSection />
<ProductSection />
<BottomTab />
</View>
);
}
让状态进入真正需要它的组件:
function SearchSection() {
const [keyword, setKeyword] = useState('');
return (
<>
<SearchInput
value={keyword}
onChangeText={setKeyword}
/>
<SearchResult keyword={keyword} />
</>
);
}
这样输入框变化时:
Home
├── Header 不动
├── SearchSection render
│ ├── Input
│ └── Result
└── BottomTab 不动
而不是整个 Home 都跟着变化。
这是比 useMemo / useCallback 更重要的优化。
3. 组件拆分的一个实用原则
不要按照“UI 看起来像一块”来拆。
应该按照:
A. 状态边界
有自己独立状态的区域,通常值得成为组件。
<SearchSection />
<FilterSection />
<CartSummary />
B. Render 边界
如果某一部分更新频率明显比其他部分高,可以拆出来。
例如:
商品详情页
├── ProductHeader 很少变化
├── ProductImages 很少变化
├── ProductInfo 很少变化
├── ProductOptions 用户频繁操作
├── QuantitySelector 用户频繁操作
└── BottomBar 用户频繁操作
QuantitySelector 不应该因为整个 Product 页面其他状态变化而重新 render。
C. 业务边界
例如:
<ProductCard />
<OrderSummary />
<PaymentMethod />
<AddressSelector />
这种拆分主要是为了维护性,同时也天然形成了比较好的优化边界。
4. React.memo 不要到处乱加
例如:
const ProductCard = React.memo(function ProductCard({
product,
}: Props) {
return (...);
});
适合:
-
列表中的大量重复组件
-
render 成本比较高
-
props 经常保持不变
-
父组件 render 很频繁
但不是:
const Everything = React.memo(...)
然后所有组件都 memo。
因为如果:
<ProductCard
product={product}
onPress={() => handlePress(product)}
/>
每次父组件 render:
onPress={() => ...}
都是一个新的函数引用。
所以:
React.memo(ProductCard)
可能依然挡不住 render。
5. useCallback 要和 memo 配合,而不是单独使用
例如:
const handlePress = useCallback(() => {
navigation.navigate('Detail', {
id: product.id,
});
}, [navigation, product.id]);
return (
<ProductCard
product={product}
onPress={handlePress}
/>
);
这样:
Parent render
↓
ProductCard
↓
props 是否变化?
↓
没有变化
↓
跳过 render
但要注意:
不要为了“优化”把所有函数都 useCallback。
如果:
function A() {
const fn = useCallback(() => {
console.log('hello');
}, []);
return <B fn={fn} />;
}
B 本身没有 memo,或者 render 成本很低,那么这个 useCallback 基本没有意义。
6. 对象同样容易破坏 memo
这个很常见:
<ProductCard
style={{ marginTop: 10 }}
config={{ showPrice: true }}
/>
每次 render 都产生新对象:
{} !== {}
所以即使:
const ProductCard = memo(...)
也可能照样 render。
可以:
const cardStyle = useMemo(
() => ({ marginTop: 10 }),
[]
);
const config = useMemo(
() => ({ showPrice: true }),
[]
);
但这里我更推荐直接:
<ProductCard
showPrice
/>
或者把静态对象放组件外:
const cardStyle = {
marginTop: 10,
};
不要为了 memo 而疯狂 useMemo。
7. Context 是隐藏的 render 来源
例如:
const AppContext = createContext(...);
function App() {
const [user, setUser] = useState();
const [theme, setTheme] = useState();
return (
<AppContext.Provider value={{ user, theme }}>
{children}
</AppContext.Provider>
);
}
这里:
setUser(...)
会导致:
Provider render
↓
value 改变
↓
所有使用 AppContext 的组件重新 render
如果 Context 很大,容易造成问题。
可以按领域拆:
<UserProvider>
<ThemeProvider>
<CartProvider>
{children}
</CartProvider>
</ThemeProvider>
</UserProvider>
或者至少拆 context:
AuthContext
ThemeContext
CartContext
SettingsContext
不要:
GlobalContext
{
user,
theme,
cart,
settings,
notifications,
...
}
8. React Native 列表尤其要注意
比如:
<FlatList
data={products}
renderItem={({ item }) => (
<ProductCard product={item} />
)}
/>
大量数据时,ProductCard 最好保持稳定:
const ProductCard = React.memo(function ProductCard({
product,
}: Props) {
...
});
同时避免:
renderItem={({ item }) => (
<ProductCard
product={item}
onPress={() => onPress(item)}
/>
)}
可以进一步考虑:
const renderItem = useCallback(
({ item }) => (
<ProductCard
product={item}
onPress={handleProductPress}
/>
),
[handleProductPress]
);
不过这里不要机械优化。真正应该优先检查的是 data 是否频繁创建、item 是否真的发生变化,以及列表本身是否合理。
9. 一个我比较推荐的页面结构
例如一个电商详情页:
ProductScreen
│
├── ProductHeader
│
├── ProductGallery
│
├── ProductInfo
│
├── ProductOptions
│ ├── ColorSelector
│ └── SizeSelector
│
├── ProductDescription
│
├── ReviewSection
│
└── ProductBottomBar
├── QuantitySelector
└── BuyButton
代码:
function ProductScreen() {
return (
<Screen>
<ProductHeader />
<ProductGallery />
<ProductInfo />
<ProductOptions />
<ProductDescription />
<ReviewSection />
<ProductBottomBar />
</Screen>
);
}
而不是:
function ProductScreen() {
const [color, setColor] = useState();
const [size, setSize] = useState();
const [quantity, setQuantity] = useState(1);
const [reviewVisible, setReviewVisible] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
// 还有几十个 state
return (
// 500 行 JSX
);
}
后者最容易出现一个小状态变化 → 整个页面重新执行。
10. 一个很实用的判断标准
我通常会用下面这个规则拆组件:
| 情况 | 是否建议拆 |
|---|---|
| 有独立 state | ✅ 强烈建议 |
| 更新频率明显不同 | ✅ 建议 |
| 业务逻辑独立 | ✅ 建议 |
| 列表 item | ✅ 建议 |
| 需要复用 | ✅ 建议 |
| JSX 超过一两百行 | ✅ 考虑 |
| 只是为了把 5 行 JSX 变成组件 | ❌ 不一定 |
为了性能把所有东西 memo |
❌ |
所有函数都 useCallback |
❌ |
所有对象都 useMemo |
❌ |
最后给你一个优化优先级
React Native 项目里,我建议按这个顺序处理:
① 状态放置位置
↓
② 合理组件边界
↓
③ 避免 Context 过大
↓
④ FlatList / SectionList 优化
↓
⑤ React.memo
↓
⑥ useCallback
↓
⑦ useMemo
↓
⑧ 最后才考虑更底层的性能优化
尤其记住一句:
useMemo/useCallback是“减少不必要 render”的工具,不是解决组件结构问题的工具。
如果你的页面现在已经比较复杂,我也可以直接按一个真实的 React Native 页面,给你演示 “错误组件拆分 → 优化后的组件树 → memo/useCallback 应该放在哪里”。
更多推荐

所有评论(0)