在 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

即使 HeaderSearchInputProductList 实际上都不关心 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 应该放在哪里”

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐