【现代声明式UI学与练】第9课 性能优化——渲染优化、列表优化、内存优化、启动优化

一、本课目标

学完这一课,你要建立七个认知:

  1. 性能优化的本质:减少不必要的函数重执行,减少不必要的 UI 描述对比。
  2. 渲染优化:纯渲染、稳定引用、细粒度更新------让函数只在必要时执行。
  3. 列表优化:Lazy 容器、稳定 key、避免内联------让列表只渲染可见项。
  4. 内存优化:清理副作用、避免泄漏、释放大对象------让内存只在必要时占用。
  5. 启动优化:减少首屏渲染、延迟加载、预加载------让首屏尽快可见。
  6. 性能监控与度量:量化指标、定位瓶颈、验证优化------用数据驱动优化。
  7. 实战能力:能定位性能瓶颈,能写出高性能的声明式 UI------从问题到方案的完整能力。

本课核心线索:

txt 复制代码
视图 = 函数(状态)
  ↓
性能问题 = 函数执行太频繁 + UI 描述对比太昂贵
  ↓
渲染优化:减少函数重执行
  ↓
列表优化:Lazy 容器 + 稳定 key
  ↓
内存优化:清理副作用 + 避免泄漏
  ↓
启动优化:减少首屏渲染 + 延迟加载
  ↓
性能监控:量化指标 + 定位瓶颈
  ↓
实战:高性能的声明式 UI

贯穿全文的核心问题:

txt 复制代码
如果 视图 = 函数(状态),
那么:
  函数执行太频繁怎么办?   → 第二节:渲染优化
  UI 描述对比太昂贵怎么办? → 第三节:列表优化
  内存占用太高怎么办?     → 第四节:内存优化
  首屏加载太慢怎么办?     → 第五节:启动优化
  怎么知道优化有没有效?   → 第六节:性能监控
  各框架怎么落地?         → 第七节:跨框架对照

二、渲染优化的本质

2.1 一句话说透

渲染优化的本质:减少不必要的函数重执行,减少不必要的 UI 描述对比。

txt 复制代码
视图 = 函数(状态)
  ↓
状态变化 → 函数重新执行 → 返回新 UI 描述 → 框架对比新旧描述
  ↓
性能问题:
  1. 函数执行太频繁(不必要的重渲染)
  2. UI 描述对比太昂贵(大列表、深层嵌套)

性能优化的两个方向:

txt 复制代码
方向一:减少函数重执行
  → 状态放在需要它的最近公共祖先
  → 用 memo/equatable 跳过不必要的重渲染
  → 稳定引用(useCallback、useMemo)

方向二:减少 UI 描述对比
  → 减少嵌套深度
  → 用 Lazy 容器
  → 用 Canvas 替代大量视图

2.2 渲染的完整流程

txt 复制代码
1. 状态变化
2. 框架标记需要重新渲染
3. 重新执行函数,读取状态        ← 瓶颈 1:函数执行太频繁
4. 得到新的 UI 描述              ← 瓶颈 2:UI 描述构建太昂贵
5. 框架对比新旧描述(diff)      ← 瓶颈 3:diff 太昂贵
6. 如果有差异,通知底层更新
7. 底层完成界面更新              ← 瓶颈 4:底层更新太频繁

性能瓶颈可能出现在:

瓶颈 场景 优化方向
第 3 步 不必要的重渲染 memo、细粒度状态
第 4 步 大列表、深层嵌套 Lazy 容器、减少嵌套
第 5 步 大列表、深层嵌套 稳定 key、减少嵌套
第 6-7 步 频繁 DOM 操作 批量更新、Canvas

2.3 渲染优化的通用原则

原则一:纯渲染。

tsx 复制代码
// TSX (React Native)
// 错误:渲染中执行副作用
function Bad() {
  fetch('/api/data').then(setData)  // 每次渲染都发请求
  return <Text>Loading</Text>
}

// 正确:副作用放 useEffect
function Good() {
  const [data, setData] = useState(null)
  useEffect(() => {
    fetch('/api/data').then(setData)
  }, [])
  return <Text>{data}</Text>
}

原则二:稳定引用。

tsx 复制代码
// TSX (React Native)
// 错误:每次渲染创建新对象/函数
<Child style={{ color: 'red' }} onPress={() => {}} />

// 正确:稳定引用
const styles = { color: 'red' }
const handlePress = useCallback(() => {}, [])
<Child style={styles} onPress={handlePress} />

原则三:细粒度更新。

tsx 复制代码
// TSX (React Native)
// 错误:状态提升太高,全部重渲染
function App() {
  const [input, setInput] = useState('')
  return (
    <View>
      <Header />        {/* 不需要 input,但重渲染 */}
      <Content />       {/* 不需要 input,但重渲染 */}
      <InputBox value={input} onChange={setInput} />
    </View>
  )
}

// 正确:状态放在需要它的组件
function App() {
  return (
    <View>
      <Header />
      <Content />
      <InputBox />  {/* 状态在内部 */}
    </View>
  )
}

2.4 各框架渲染优化对照

框架 跳过重渲染 稳定引用 细粒度更新
React Native React.memo useCallback、useMemo 状态就近
SwiftUI EquatableView 自动 状态就近
ArkUI @Reusable 手动 状态就近
Compose 自动跳过 remember 状态就近
Flutter const 手动 状态就近

三、渲染优化

3.1 React.memo 跳过重渲染

问题: 父组件重渲染时,子组件也跟着重渲染,即使 props 没变。

tsx 复制代码
// TSX (React Native)
function Parent() {
  const [count, setCount] = useState(0)

  return (
    <View>
      <Text>Count: {count}</Text>
      <Button title="+1" onPress={() => setCount(count + 1)} />
      <Child />  {/* count 变化时,Child 也重渲染 */}
    </View>
  )
}

function Child() {
  console.log('Child 渲染')
  return <Text>Child</Text>
}

优化: 用 React.memo 包裹子组件。

tsx 复制代码
// TSX (React Native)
const Child = React.memo(function Child() {
  console.log('Child 渲染')
  return <Text>Child</Text>
})

React.memo 的原理:

txt 复制代码
React.memo 对比新旧 props:
  → props 没变 → 跳过重渲染
  → props 变了 → 正常重渲染

注意: React.memo 只对比 props,不对比 state 和 context。

3.2 useCallback 稳定函数引用

问题: 每次渲染创建新函数,导致 React.memo 失效。

tsx 复制代码
// TSX (React Native)
function Parent() {
  const [count, setCount] = useState(0)

  return (
    <View>
      <Text>Count: {count}</Text>
      {/* 每次渲染创建新函数,Child 的 props 变了 */}
      <Child onPress={() => setCount(count + 1)} />
    </View>
  )
}

const Child = React.memo(function Child({ onPress }) {
  return <Button title="+1" onPress={onPress} />
})

优化: 用 useCallback 稳定函数引用。

tsx 复制代码
// TSX (React Native)
function Parent() {
  const [count, setCount] = useState(0)

  const handlePress = useCallback(() => {
    setCount(c => c + 1)
  }, [])  // 空依赖数组,函数引用不变

  return (
    <View>
      <Text>Count: {count}</Text>
      <Child onPress={handlePress} />
    </View>
  )
}

useCallback 的原理:

txt 复制代码
useCallback(fn, deps):
  → deps 不变 → 返回缓存的函数
  → deps 变化 → 返回新函数

空依赖数组:函数永远不变

3.3 useMemo 缓存计算结果

问题: 每次渲染都重新计算昂贵的派生数据。

tsx 复制代码
// TSX (React Native)
function TodoList({ todos, filter }) {
  // 每次渲染都重新过滤
  const filteredTodos = todos.filter(todo => todo.category === filter)

  return (
    <View>
      {filteredTodos.map(todo => <TodoItem key={todo.id} todo={todo} />)}
    </View>
  )
}

优化: 用 useMemo 缓存计算结果。

tsx 复制代码
// TSX (React Native)
function TodoList({ todos, filter }) {
  const filteredTodos = useMemo(() => {
    return todos.filter(todo => todo.category === filter)
  }, [todos, filter])

  return (
    <View>
      {filteredTodos.map(todo => <TodoItem key={todo.id} todo={todo} />)}
    </View>
  )
}

useMemo 的使用原则:

txt 复制代码
1. 简单计算不需要 useMemo
   → 字符串拼接、布尔判断、数组长度

2. 昂贵计算才需要 useMemo
   → 大数组过滤、复杂对象构造、递归计算

3. useMemo 本身有开销
   → 依赖比较、缓存管理
   → 不要过度使用

3.4 细粒度状态更新

问题: 状态提升太高,导致不必要的重渲染。

tsx 复制代码
// TSX (React Native)
// 错误:整个 App 重渲染
function App() {
  const [input, setInput] = useState('')
  const [theme, setTheme] = useState('light')

  return (
    <View>
      <Header theme={theme} />        {/* input 变化时重渲染 */}
      <Content theme={theme} />       {/* input 变化时重渲染 */}
      <InputBox value={input} onChange={setInput} />
    </View>
  )
}

优化: 把状态拆分为更细的粒度。

tsx 复制代码
// TSX (React Native)
// 正确:input 状态放在 InputBox 内部
function App() {
  const [theme, setTheme] = useState('light')

  return (
    <View>
      <Header theme={theme} />
      <Content theme={theme} />
      <InputBox />  {/* input 状态在内部 */}
    </View>
  )
}

function InputBox() {
  const [input, setInput] = useState('')
  return <TextInput value={input} onChangeText={setInput} />
}

3.5 避免不必要的 Context 重渲染

问题: Context 变化触发所有消费者重渲染。

tsx 复制代码
// TSX (React Native)
// 问题:theme 变化触发所有消费者重渲染
const AppContext = React.createContext({
  theme: 'light',
  user: null,
})

function App() {
  const [theme, setTheme] = useState('light')
  const [user, setUser] = useState(null)

  // 每次渲染创建新对象
  const value = { theme, user }

  return (
    <AppContext.Provider value={value}>
      <Content />
    </AppContext.Provider>
  )
}

优化: 拆分 Context,用 useMemo 稳定引用。

tsx 复制代码
// TSX (React Native)
const ThemeContext = React.createContext('light')
const UserContext = React.createContext(null)

function App() {
  const [theme, setTheme] = useState('light')
  const [user, setUser] = useState(null)

  return (
    <ThemeContext.Provider value={theme}>
      <UserContext.Provider value={user}>
        <Content />
      </UserContext.Provider>
    </ThemeContext.Provider>
  )
}

优化原则:

txt 复制代码
1. 按变化频率拆分 Context
   → 频繁变化和稳定的分开

2. 按使用范围拆分 Context
   → 不同组件用的分开

3. 用 useMemo 稳定 Context value
   → 避免每次渲染创建新对象

3.6 各框架渲染优化对照

框架 跳过重渲染 稳定函数 缓存计算 细粒度
React Native React.memo useCallback useMemo 状态就近
SwiftUI EquatableView 自动 自动 状态就近
ArkUI @Reusable 手动 手动 状态就近
Compose 自动 remember remember 状态就近
Flutter const 手动 手动 状态就近

四、列表优化

4.1 使用 Lazy 容器

问题: 普通容器一次性渲染所有子视图。

tsx 复制代码
// TSX (React Native)
// 错误:ScrollView 一次性渲染 1000 项
function BadList() {
  return (
    <ScrollView>
      {Array.from({ length: 1000 }, (_, i) => (
        <Text key={i}>Item {i}</Text>
      ))}
    </ScrollView>
  )
}

优化: 使用 FlatList(Lazy 容器)。

tsx 复制代码
// TSX (React Native)
// 正确:FlatList 只渲染可见项
function GoodList() {
  return (
    <FlatList
      data={Array.from({ length: 1000 }, (_, i) => ({ id: i, text: `Item ${i}` }))}
      renderItem={({ item }) => <Text>{item.text}</Text>}
      keyExtractor={item => item.id.toString()}
    />
  )
}

Lazy 容器的原理:

txt 复制代码
只渲染可见区域的子视图:
  → 初始渲染:只渲染可见的 ~10 项
  → 滚动时:回收离开屏幕的项,渲染新进入屏幕的项
  → 内存中始终只有少量项

各框架 Lazy 容器对照:

框架 Lazy 容器 普通容器
React Native FlatList、SectionList ScrollView
SwiftUI LazyVStack、LazyHStack、List VStack、HStack
ArkUI LazyForEach、List ForEach、Column
Compose LazyColumn、LazyRow Column、Row
Flutter ListView.builder Column

4.2 稳定 key

问题: 用 index 作为 key,列表重排时状态错乱。

tsx 复制代码
// TSX (React Native)
// 错误:用 index 作为 key
<FlatList
  data={todos}
  renderItem={({ item, index }) => <TodoItem key={index} todo={item} />}
/>

优化: 用稳定 id 作为 key。

tsx 复制代码
// TSX (React Native)
// 正确:用 id 作为 key
<FlatList
  data={todos}
  renderItem={({ item }) => <TodoItem todo={item} />}
  keyExtractor={item => item.id.toString()}
/>

稳定 key 的性能收益:

txt 复制代码
不稳定 key(index):
  列表重排 → 框架认为所有元素都变了 → 重建所有元素
  → 状态丢失 + 性能浪费

稳定 key(id):
  列表重排 → 框架知道哪些元素移动了 → 复用元素
  → 状态保留 + 性能优化

4.3 避免内联对象

问题: 每次渲染创建新对象,导致子组件不必要的重渲染。

tsx 复制代码
// TSX (React Native)
// 错误:每次渲染创建新对象
<FlatList
  data={todos}
  renderItem={({ item }) => <TodoItem todo={item} />}
  keyExtractor={item => item.id.toString()}
  contentContainerStyle={{ padding: 16 }}  // 新对象
  ItemSeparatorComponent={() => <View style={{ height: 8 }} />}  // 新组件
/>

优化: 提取到组件外部。

tsx 复制代码
// TSX (React Native)
const styles = {
  contentContainer: { padding: 16 }
}

const ItemSeparator = () => <View style={{ height: 8 }} />

const keyExtractor = (item: Todo) => item.id.toString()

function TodoListView({ todos }) {
  return (
    <FlatList
      data={todos}
      renderItem={({ item }) => <TodoItem todo={item} />}
      keyExtractor={keyExtractor}
      contentContainerStyle={styles.contentContainer}
      ItemSeparatorComponent={ItemSeparator}
    />
  )
}

4.4 避免内联函数

问题: 每次渲染创建新函数,导致子组件不必要的重渲染。

tsx 复制代码
// TSX (React Native)
// 错误:每次渲染创建新函数
<FlatList
  data={todos}
  renderItem={({ item }) => <TodoItem todo={item} />}
  keyExtractor={item => item.id.toString()}  // 新函数
  onEndReached={() => loadMore()}  // 新函数
/>

优化: 用 useCallback 稳定函数引用。

tsx 复制代码
// TSX (React Native)
const keyExtractor = useCallback((item: Todo) => item.id.toString(), [])
const renderItem = useCallback(({ item }) => <TodoItem todo={item} />, [])
const handleEndReached = useCallback(() => loadMore(), [loadMore])

function TodoListView({ todos }) {
  return (
    <FlatList
      data={todos}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      onEndReached={handleEndReached}
    />
  )
}

4.5 列表项组件 memo

问题: 列表项组件在父组件重渲染时也重渲染。

tsx 复制代码
// TSX (React Native)
function TodoItem({ todo }) {
  return <Text>{todo.text}</Text>
}

优化: 用 React.memo 包裹列表项组件。

tsx 复制代码
// TSX (React Native)
const TodoItem = React.memo(function TodoItem({ todo }) {
  return <Text>{todo.text}</Text>
})

4.6 列表性能检查清单

txt 复制代码
列表性能检查清单:

1. 是否使用了 Lazy 容器?
   → FlatList、LazyVStack、LazyColumn、ListView.builder

2. 是否使用了稳定 key?
   → 用 id,不用 index

3. 是否避免了内联对象?
   → 样式对象提取到组件外部

4. 是否避免了内联函数?
   → 用 useCallback 稳定函数引用

5. 列表项组件是否使用了 memo?
   → 用 React.memo 避免不必要的重渲染

6. 是否使用了 getItemLayout?
   → 固定高度的列表项可以优化滚动性能

7. 是否使用了 windowSize?
   → 控制渲染窗口大小

4.7 各框架列表优化对照

框架 Lazy 容器 key 优化 组件 memo 其他
React Native FlatList keyExtractor React.memo getItemLayout
SwiftUI LazyVStack、List Identifiable EquatableView ---
ArkUI LazyForEach、List keyGenerator @Reusable ---
Compose LazyColumn key() 自动跳过 contentType
Flutter ListView.builder ValueKey const itemExtent

五、内存优化

5.1 清理副作用

问题: 组件卸载后,副作用还在运行,导致内存泄漏。

tsx 复制代码
// TSX (React Native)
// 错误:定时器没有清理
function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(c => c + 1)
    }, 1000)
    // 没有清理
  }, [])

  return <Text>{count}</Text>
}

优化: 清理副作用。

tsx 复制代码
// TSX (React Native)
// 正确:定时器有清理
function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(c => c + 1)
    }, 1000)

    return () => clearInterval(timer)  // 清理
  }, [])

  return <Text>{count}</Text>
}

常见副作用清理:

txt 复制代码
1. 定时器:clearInterval / clearTimeout
2. 订阅:unsubscribe
3. 请求:AbortController.abort()
4. 事件监听:removeEventListener
5. WebSocket:close()
6. 动画:cancelAnimationFrame

5.2 避免内存泄漏

问题: 闭包引用导致对象无法释放。

tsx 复制代码
// TSX (React Native)
// 错误:闭包引用大对象
function Component() {
  const bigData = useMemo(() => new Array(1000000).fill(0), [])

  useEffect(() => {
    const handler = () => {
      console.log(bigData.length)  // 闭包引用 bigData
    }
    window.addEventListener('resize', handler)

    return () => window.removeEventListener('resize', handler)
  }, [bigData])

  return <Text>Component</Text>
}

优化: 避免闭包引用大对象。

tsx 复制代码
// TSX (React Native)
function Component() {
  useEffect(() => {
    const handler = () => {
      // 不引用大对象
    }
    window.addEventListener('resize', handler)

    return () => window.removeEventListener('resize', handler)
  }, [])

  return <Text>Component</Text>
}

5.3 释放大对象

问题: 大对象一直存在内存中,无法释放。

tsx 复制代码
// TSX (React Native)
// 错误:图片一直存在内存中
function ImageList({ images }) {
  return (
    <View>
      {images.map(img => (
        <Image key={img.id} source={{ uri: img.url }} />
      ))}
    </View>
  )
}

优化: 使用 Lazy 容器,只加载可见图片。

tsx 复制代码
// TSX (React Native)
function ImageList({ images }) {
  return (
    <FlatList
      data={images}
      renderItem={({ item }) => (
        <Image source={{ uri: item.url }} />
      )}
      keyExtractor={item => item.id.toString()}
    />
  )
}

5.4 避免频繁创建大对象

问题: 每次渲染创建大对象,导致内存抖动。

tsx 复制代码
// TSX (React Native)
// 错误:每次渲染创建大数组
function Bad() {
  const bigArray = new Array(10000).fill(0)
  return <Text>{bigArray.length}</Text>
}

优化: 用 useMemo 缓存大对象。

tsx 复制代码
// TSX (React Native)
function Good() {
  const bigArray = useMemo(() => new Array(10000).fill(0), [])
  return <Text>{bigArray.length}</Text>
}

5.5 内存优化检查清单

txt 复制代码
内存优化检查清单:

1. 是否清理了所有副作用?
   → 定时器、订阅、请求、事件监听

2. 是否避免了闭包引用大对象?
   → 闭包只引用必要的变量

3. 是否释放了大对象?
   → 用 Lazy 容器,只加载可见项

4. 是否避免了频繁创建大对象?
   → 用 useMemo 缓存

5. 是否有内存泄漏?
   → 用内存分析工具检查

6. 图片是否压缩?
   → 压缩图片,减少内存占用

7. 缓存是否有上限?
   → 缓存设置大小限制

5.6 各框架内存优化对照

框架 副作用清理 大对象释放 内存监控
React Native useEffect 清理 Lazy 容器 Flipper、Xcode Instruments
SwiftUI onDisappear LazyVStack Xcode Instruments
ArkUI aboutToDisappear LazyForEach DevEco Studio
Compose DisposableEffect LazyColumn Android Profiler
Flutter dispose ListView.builder DevTools

六、启动优化

6.1 减少首屏渲染

问题: 首屏渲染太多内容,启动慢。

tsx 复制代码
// TSX (React Native)
// 错误:首屏渲染所有内容
function App() {
  return (
    <ScrollView>
      <Header />
      <Banner />
      <FeaturedSection />
      <CategorySection />
      <ProductList />
      <Footer />
    </ScrollView>
  )
}

优化: 首屏只渲染可见内容,其余延迟加载。

tsx 复制代码
// TSX (React Native)
function App() {
  const [showMore, setShowMore] = useState(false)

  return (
    <ScrollView>
      <Header />
      <Banner />
      <FeaturedSection />
      {showMore && (
        <>
          <CategorySection />
          <ProductList />
          <Footer />
        </>
      )}
      {!showMore && (
        <Button title="加载更多" onPress={() => setShowMore(true)} />
      )}
    </ScrollView>
  )
}

6.2 延迟加载

问题: 所有代码打包在一起,启动时加载全部。

tsx 复制代码
// TSX (React Native)
// 错误:一次性导入所有组件
import HeavyComponent from './HeavyComponent'
import AnotherHeavyComponent from './AnotherHeavyComponent'

优化: 用 React.lazy 延迟加载。

tsx 复制代码
// TSX (React Native)
import React, { Suspense, lazy } from 'react'

const HeavyComponent = lazy(() => import('./HeavyComponent'))
const AnotherHeavyComponent = lazy(() => import('./AnotherHeavyComponent'))

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <HeavyComponent />
    </Suspense>
  )
}

6.3 预加载

问题: 用户点击后才加载,体验差。

tsx 复制代码
// TSX (React Native)
// 用户点击后才加载
function App() {
  const [showDetail, setShowDetail] = useState(false)

  return (
    <View>
      <Button title="查看详情" onPress={() => setShowDetail(true)} />
      {showDetail && <DetailScreen />}
    </View>
  )
}

优化: 提前预加载。

tsx 复制代码
// TSX (React Native)
function App() {
  const [showDetail, setShowDetail] = useState(false)

  // 预加载:组件挂载时就加载 DetailScreen 的代码
  useEffect(() => {
    import('./DetailScreen')
  }, [])

  return (
    <View>
      <Button title="查看详情" onPress={() => setShowDetail(true)} />
      {showDetail && <DetailScreen />}
    </View>
  )
}

6.4 启动优化检查清单

txt 复制代码
启动优化检查清单:

1. 首屏是否只渲染可见内容?
   → 延迟加载非首屏内容

2. 是否使用了代码分割?
   → React.lazy、动态 import

3. 是否预加载了关键资源?
   → 预加载下一个页面的代码

4. 是否优化了图片?
   → 压缩图片、懒加载图片

5. 是否减少了首屏请求?
   → 合并请求、缓存请求

6. 是否使用了启动屏?
   → 启动屏 + 后台加载

7. 是否分析了启动时间?
   → 用性能分析工具定位瓶颈

6.5 各框架启动优化对照

框架 代码分割 延迟加载 启动分析
React Native 动态 import React.lazy Flipper
SwiftUI --- @ViewBuilder Xcode Instruments
ArkUI 动态 import @Builder DevEco Studio
Compose --- @Composable Android Profiler
Flutter Deferred FutureBuilder DevTools

七、性能监控与度量

7.1 性能指标

txt 复制代码
核心指标:
  1. 帧率(FPS):60fps = 每帧 16ms
  2. 渲染时间:组件渲染耗时
  3. 布局时间:布局计算耗时
  4. 内存占用:应用内存使用量
  5. 启动时间:应用启动到首屏渲染

关键阈值:
  → 帧率低于 60fps 会卡顿
  → 渲染时间超过 16ms 会掉帧
  → 内存占用超过阈值会被系统杀死
  → 启动时间超过 2s 用户会感知

7.2 性能分析工具

React Native:

txt 复制代码
1. Flipper
   → 查看组件树、性能、网络

2. React DevTools
   → 查看组件渲染次数

3. Xcode Instruments
   → 查看 CPU、内存、GPU

4. Android Profiler
   → 查看 CPU、内存、网络

SwiftUI:

txt 复制代码
1. Xcode Instruments
   → 查看 CPU、内存、GPU

2. SwiftUI Inspector
   → 查看视图树

3. os_signpost
   → 自定义性能标记

ArkUI:

txt 复制代码
1. DevEco Studio
   → 查看 CPU、内存、GPU

2. ArkUI Inspector
   → 查看组件树

3. HiTrace
   → 分布式追踪

Compose:

txt 复制代码
1. Android Profiler
   → 查看 CPU、内存、网络

2. Layout Inspector
   → 查看组件树

3. Compose Compiler Metrics
   → 查看重组次数

Flutter:

txt 复制代码
1. DevTools
   → 查看 CPU、内存、网络

2. Flutter Inspector
   → 查看组件树

3. Performance Overlay
   → 查看帧率

7.3 性能优化流程

txt 复制代码
1. 度量
   → 用工具测量性能指标
   → 确定瓶颈在哪里

2. 定位
   → 找到具体的性能问题
   → 是渲染太频繁?还是 diff 太昂贵?

3. 优化
   → 根据问题选择优化策略
   → 减少重渲染、用 Lazy 容器、清理副作用

4. 验证
   → 重新测量性能指标
   → 确认优化有效

5. 监控
   → 持续监控性能
   → 防止性能退化

7.4 性能优化的优先级

txt 复制代码
优先级从高到低:

1. 消除明显的性能问题
   → 渲染中发请求、无限循环
   → 内存泄漏、大对象未释放

2. 列表优化
   → Lazy 容器、稳定 key
   → 避免内联对象和函数

3. 渲染优化
   → memo、useCallback、useMemo
   → 细粒度状态更新

4. 启动优化
   → 代码分割、延迟加载
   → 预加载关键资源

5. 微优化
   → 减少对象创建
   → 优化算法复杂度

7.5 性能监控检查清单

txt 复制代码
性能监控检查清单:

1. 是否定期测量性能指标?
   → 帧率、渲染时间、内存占用

2. 是否使用了性能分析工具?
   → Flipper、Xcode Instruments、DevTools

3. 是否监控线上性能?
   → 上报性能数据、异常监控

4. 是否设置了性能预算?
   → 帧率不低于 55fps
   → 启动时间不超过 2s

5. 是否定期做性能回归测试?
   → 每次发版前测试性能

6. 是否有性能优化文档?
   → 记录优化策略和注意事项

7.6 各框架性能监控对照

框架 性能分析工具 线上监控
React Native Flipper、React DevTools Firebase Performance
SwiftUI Xcode Instruments MetricKit
ArkUI DevEco Studio、HiTrace HiAppEvent
Compose Android Profiler Firebase Performance
Flutter DevTools、Performance Overlay Firebase Performance

八、常见误区

8.1 过早优化

tsx 复制代码
// TSX (React Native)
// 错误:过早优化,代码复杂但收益小
const MemoizedItem = React.memo(function Item({ item }) {
  const style = useMemo(() => ({ color: 'red' }), [])
  const handlePress = useCallback(() => {}, [])
  return <Text style={style} onPress={handlePress}>{item.text}</Text>
})

// 正确:先测量,确定瓶颈后再优化
function Item({ item }) {
  return <Text>{item.text}</Text>
}

原则: 先测量,再优化。不要凭感觉优化。

8.2 过度使用 useMemo

tsx 复制代码
// TSX (React Native)
// 错误:简单计算不需要 useMemo
const fullName = useMemo(() => firstName + ' ' + lastName, [firstName, lastName])

// 正确:直接算
const fullName = firstName + ' ' + lastName

原则: 只有昂贵的计算才用 useMemo。

8.3 过度使用 useCallback

tsx 复制代码
// TSX (React Native)
// 错误:不需要稳定的函数用了 useCallback
const handlePress = useCallback(() => {
  console.log('pressed')
}, [])

// 正确:简单函数直接定义
const handlePress = () => {
  console.log('pressed')
}

原则: 只有传递给 memo 组件的函数才需要 useCallback。

8.4 忽略列表 key

tsx 复制代码
// TSX (React Native)
// 错误:用 index 作为 key
{todos.map((todo, index) => (
  <TodoItem key={index} todo={todo} />
))}

// 正确:用稳定 id
{todos.map(todo => (
  <TodoItem key={todo.id} todo={todo} />
))}

8.5 不清理副作用

tsx 复制代码
// TSX (React Native)
// 错误:没有清理定时器
useEffect(() => {
  const timer = setInterval(() => {}, 1000)
}, [])

// 正确:有清理
useEffect(() => {
  const timer = setInterval(() => {}, 1000)
  return () => clearInterval(timer)
}, [])

8.6 首屏渲染太多

tsx 复制代码
// TSX (React Native)
// 错误:首屏渲染所有内容
function App() {
  return (
    <ScrollView>
      <Header />
      <Banner />
      {/* ... 很多内容 */}
    </ScrollView>
  )
}

// 正确:首屏只渲染可见内容
function App() {
  const [showMore, setShowMore] = useState(false)
  return (
    <ScrollView>
      <Header />
      <Banner />
      {showMore && <MoreContent />}
    </ScrollView>
  )
}

8.7 忽略性能监控

txt 复制代码
错误:不测量性能,凭感觉优化
正确:定期测量性能,定位瓶颈,针对性优化

8.8 性能优化反模式检查清单

txt 复制代码
性能优化反模式检查清单:

1. 是否过早优化?
   → 先测量,再优化

2. 是否过度使用 useMemo?
   → 只有昂贵计算才用

3. 是否过度使用 useCallback?
   → 只有传给 memo 组件的函数才用

4. 是否忽略了列表 key?
   → 用稳定 id

5. 是否不清理副作用?
   → 定时器、订阅、请求都要清理

6. 是否首屏渲染太多?
   → 首屏只渲染可见内容

7. 是否忽略性能监控?
   → 定期测量,持续监控

8. 是否只优化不验证?
   → 优化后重新测量

九、习题(15 道)

题目 1(选择题)

渲染优化的本质是什么?

A. 减少代码量

B. 减少不必要的函数重执行,减少不必要的 UI 描述对比

C. 增加缓存

D. 减少组件数量

参考答案:B

解读: 渲染优化的本质是减少不必要的函数重执行和 UI 描述对比。视图 = 函数(状态),性能问题来自函数执行太频繁和 UI 描述对比太昂贵。


题目 2(判断题)

React.memo 可以跳过所有不必要的重渲染。

参考答案:错误

解读: React.memo 只对比 props。如果 props 没变,跳过重渲染。但如果 props 中有每次渲染创建的新对象/函数,React.memo 会失效。需要配合 useCallback 和 useMemo 使用。


题目 3(填空题)

渲染优化的三个通用原则是:、、______。

参考答案:纯渲染;稳定引用;细粒度更新

解读: 纯渲染:渲染函数不做副作用;稳定引用:避免每次渲染创建新对象/函数;细粒度更新:状态放在需要它的最近公共祖先。


题目 4(简答题)

什么是 Lazy 容器?为什么 Lazy 容器能提升列表性能?

参考答案: Lazy 容器是只渲染可见区域子视图的列表容器。

Lazy 容器提升性能的原因:

  • 普通容器一次性渲染所有子视图,1000 项就渲染 1000 个
  • Lazy 容器只渲染可见的 ~10 项
  • 滚动时回收离开屏幕的项,渲染新进入屏幕的项
  • 内存中始终只有少量项

各框架的 Lazy 容器:FlatList(React Native)、LazyVStack(SwiftUI)、LazyForEach(ArkUI)、LazyColumn(Compose)、ListView.builder(Flutter)。


题目 5(代码分析题)

下面代码有什么问题?如何优化?

tsx 复制代码
// TSX (React Native)
function Parent() {
  const [count, setCount] = useState(0)
  return (
    <View>
      <Text>Count: {count}</Text>
      <Child onPress={() => setCount(count + 1)} />
    </View>
  )
}

const Child = React.memo(function Child({ onPress }) {
  return <Button title="+1" onPress={onPress} />
})

参考答案: 每次渲染创建新函数 () => setCount(count + 1),导致 Child 的 props 变化,React.memo 失效。

优化: 用 useCallback 稳定函数引用。

tsx 复制代码
// TSX (React Native)
function Parent() {
  const [count, setCount] = useState(0)
  const handlePress = useCallback(() => {
    setCount(c => c + 1)
  }, [])

  return (
    <View>
      <Text>Count: {count}</Text>
      <Child onPress={handlePress} />
    </View>
  )
}

题目 6(代码改错题)

下面代码有什么问题?如何修正?

tsx 复制代码
// TSX (React Native)
function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(c => c + 1)
    }, 1000)
  }, [])

  return <Text>{count}</Text>
}

参考答案: 没有清理定时器,组件卸载后定时器继续运行,导致内存泄漏。

修正: 加清理函数。

tsx 复制代码
// TSX (React Native)
function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(c => c + 1)
    }, 1000)
    return () => clearInterval(timer)
  }, [])

  return <Text>{count}</Text>
}

题目 7(跨框架对比题)

写出 React Native、SwiftUI、Compose 中列表优化的核心写法。

参考答案:

React Native:

tsx 复制代码
// TSX (React Native)
<FlatList
  data={items}
  renderItem={({ item }) => <Item item={item} />}
  keyExtractor={item => item.id.toString()}
/>

SwiftUI:

swift 复制代码
// Swift (SwiftUI)
LazyVStack {
    ForEach(items) { item in
        ItemView(item: item)
    }
}

Compose:

kotlin 复制代码
// Kotlin (Jetpack Compose)
LazyColumn {
    items(items, key = { it.id }) { item ->
        ItemView(item)
    }
}

解读: 三种框架的列表优化本质相同------用 Lazy 容器只渲染可见项,用稳定 key 标识元素身份。


题目 8(选择题)

以下哪种情况适合用 useMemo?

A. 字符串拼接

B. 布尔判断

C. 大数组过滤

D. 数组长度

参考答案:C

解读: useMemo 适合昂贵的计算,如大数组过滤、复杂对象构造。A、B、D 都是简单计算,直接算即可,不需要 useMemo。


题目 9(判断题)

useCallback 和 useMemo 可以随意使用,不会影响性能。

参考答案:错误

解读: useCallback 和 useMemo 本身有开销(依赖比较、缓存管理)。过度使用会增加性能开销,而不是优化性能。只有需要稳定引用或昂贵计算时才使用。


题目 10(填空题)

React Native 中跳过重渲染用 ______,稳定函数引用用 ______,缓存计算用 ______。

参考答案:React.memo;useCallback;useMemo

解读: React.memo 跳过 props 没变的组件重渲染;useCallback 稳定函数引用,配合 React.memo 使用;useMemo 缓存昂贵的计算结果。


题目 11(简答题)

什么是内存泄漏?如何避免内存泄漏?

参考答案: 内存泄漏是组件卸载后,副作用还在运行,导致内存无法释放。

常见内存泄漏:

  • 定时器没有清理
  • 订阅没有取消
  • 请求没有取消
  • 事件监听没有移除
  • WebSocket 没有关闭

避免内存泄漏的方法:

  1. 所有副作用都要清理
  2. 定时器:clearInterval / clearTimeout
  3. 订阅:unsubscribe
  4. 请求:AbortController.abort()
  5. 事件监听:removeEventListener
  6. WebSocket:close()

题目 12(代码分析题)

下面代码有什么问题?如何优化?

tsx 复制代码
// TSX (React Native)
function App() {
  const [input, setInput] = useState('')
  const [theme, setTheme] = useState('light')

  return (
    <View>
      <Header theme={theme} />
      <Content theme={theme} />
      <InputBox value={input} onChange={setInput} />
    </View>
  )
}

参考答案: input 状态放在 App,导致 Header 和 Content 在 input 变化时也重渲染,即使它们不需要 input。

优化: 把 input 状态放在 InputBox 内部。

tsx 复制代码
// TSX (React Native)
function App() {
  const [theme, setTheme] = useState('light')

  return (
    <View>
      <Header theme={theme} />
      <Content theme={theme} />
      <InputBox />
    </View>
  )
}

function InputBox() {
  const [input, setInput] = useState('')
  return <TextInput value={input} onChangeText={setInput} />
}

题目 13(设计题)

设计一个高性能的聊天消息列表:

  • 消息按时间排序
  • 新消息插入到顶部
  • 消息数量可能很大(1000+)
  • 每条消息有头像、文本、时间

写出列表优化、渲染优化、内存优化。

参考答案:

tsx 复制代码
// TSX (React Native)
// 1. 列表优化:FlatList
// 2. 渲染优化:memo + useCallback
// 3. 内存优化:Lazy 容器 + 清理副作用

const MessageItem = React.memo(function MessageItem({ message }) {
  return (
    <View>
      <Image source={{ uri: message.avatar }} />
      <Text>{message.text}</Text>
      <Text>{message.time}</Text>
    </View>
  )
})

const keyExtractor = (item: Message) => item.id

function ChatList({ messages }) {
  const renderItem = useCallback(({ item }) => (
    <MessageItem message={item} />
  ), [])

  return (
    <FlatList
      data={messages}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      inverted  // 新消息在底部
      initialNumToRender={20}
      maxToRenderPerBatch={10}
      windowSize={10}
    />
  )
}

优化点:

  • FlatList:Lazy 容器,只渲染可见项
  • React.memo:消息项组件跳过不必要的重渲染
  • useCallback:稳定 renderItem 引用
  • keyExtractor:稳定 key
  • inverted:新消息在底部,避免顶部插入导致的列表重排
  • initialNumToRender、maxToRenderPerBatch、windowSize:控制渲染窗口

题目 14(设计题)

设计一个启动优化的方案:

  • 首屏只渲染可见内容
  • 非首屏内容延迟加载
  • 关键资源预加载
  • 启动时间不超过 2s

写出首屏优化、代码分割、预加载策略。

参考答案:

tsx 复制代码
// TSX (React Native)
// 1. 首屏优化:只渲染可见内容
function App() {
  return (
    <Suspense fallback={<SplashScreen />}>
      <HomeScreen />
    </Suspense>
  )
}

function HomeScreen() {
  const [showMore, setShowMore] = useState(false)

  return (
    <ScrollView>
      <Header />
      <Banner />
      {showMore && (
        <Suspense fallback={<Spinner />}>
          <MoreContent />
        </Suspense>
      )}
    </ScrollView>
  )
}

// 2. 代码分割:React.lazy
const MoreContent = lazy(() => import('./MoreContent'))
const DetailScreen = lazy(() => import('./DetailScreen'))

// 3. 预加载:提前加载下一个页面
function HomeScreen() {
  useEffect(() => {
    // 预加载详情页代码
    import('./DetailScreen')
  }, [])

  return <View>...</View>
}

优化点:

  • React.lazy:代码分割,按需加载
  • Suspense:加载时显示 fallback
  • 首屏只渲染可见内容
  • useEffect 预加载下一个页面

题目 15(思考题)

为什么说"性能优化就是减少不必要的函数重执行"?请从 视图 = 函数(状态) 的角度分析。

参考答案: 视图 = 函数(状态) 的核心是"状态变化触发函数重新执行"。

从渲染管线角度:

txt 复制代码
1. 状态变化
2. 框架标记需要重新渲染
3. 重新执行函数,读取状态          ← 性能瓶颈 1
4. 得到新的 UI 描述                ← 性能瓶颈 2
5. 框架对比新旧描述(diff)        ← 性能瓶颈 3
6. 如果有差异,通知底层更新
7. 底层完成界面更新

性能优化就是减少第 3、4、5 步的开销:

减少函数重执行(第 3 步):

  • 状态放在需要它的最近公共祖先
  • React.memo 跳过 props 没变的组件
  • useCallback、useMemo 稳定引用
  • 细粒度状态更新

减少 UI 描述构建(第 4 步):

  • 减少嵌套深度
  • 用 Lazy 容器
  • 用 Canvas 替代大量视图

减少 diff 开销(第 5 步):

  • 稳定 key
  • 减少列表长度
  • 减少嵌套深度

终极结论: 性能优化就是减少不必要的函数重执行和 UI 描述对比。具体来说:

  • 能跳过的重渲染就跳过(memo、细粒度)
  • 能懒加载的就懒加载(Lazy 容器、代码分割)
  • 能复用的就复用(稳定 key、useMemo)
  • 能清理的就清理(副作用、大对象)

这就是 视图 = 函数(状态) 在性能优化上的体现------让函数只在必要时执行,让 UI 描述只在必要时对比。


十、下节预告

第 10 课:测试与调试------单元测试、组件测试、端到端测试、调试技巧。

相关推荐
传奇开心果编程1 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
旖旎夜光1 小时前
LeetCode 1576: 替换所有的问号(模拟) —— 题解
c++·学习·算法·leetcode·力控
mlidongfeng1 小时前
【学习】ParallelKittens(PK)学习与分析
学习
坤坤子吖2 小时前
Python基础语法学习:列表和元组
开发语言·笔记·python·学习
JWASX2 小时前
Java 转 go 学习 - 接口
学习·golang
I Am a robert girl2 小时前
从零读懂世界模型的持续学习:一份组合式基准的源码级拆解
学习·具身智能·持续学习·灾难性遗忘·机器人学习·世界模型·组合式基准
扶风ff2 小时前
练题簿在线免费刷题:刷题、背题、错题复习,一个小程序安排日常备考
学习·小程序
東隅已逝,桑榆非晚2 小时前
数据结构队列
数据结构·笔记·学习
老王爱玩车2 小时前
字符串和字符串函数
c语言·开发语言·数据结构·学习