---
title: 合理使用 setData
summary: "介绍如何合理管理 React 状态更新以优化底层 setData 调用，减少不必要的重渲染。"
docType: default
tags: [setData优化, React状态更新优化, 通信数据精简, setState合并]
questions:
  - Ray 框架中 setState 和 setData 的关系是什么？
  - 为什么非渲染数据不应放在 state 中？
  - 如何使用 useRef 存储非渲染数据以避免不必要的重渲染？
  - React.memo 如何帮助减少子组件的不必要重渲染？
  - useMemo 在性能优化中起什么作用？
  - 高频事件导致的频繁 state 更新应该如何优化？
  - React 18+ 的批处理机制对 state 更新有什么影响？
  - 如何合并多个相关的 state 以减少渲染次数？
  - 页面不可见时为什么要停止 state 更新？
  - 如何利用页面生命周期控制后台页面的数据更新？
---

# 合理管理数据更新

## 底层工作原理

Ray 框架基于小程序双线程架构运行。当 React 组件的 state 发生变化时，底层流程为：

```
组件调用 setState / useState setter
    ↓
React 执行 reconciliation（diff 计算）
    ↓
Ray 将变化的数据通过 setData 发送到视图层
    ↓
视图层更新 DOM 并重新渲染
```

因此，React 的每次 state 更新最终都会触发底层的 `setData`。优化 state 更新方式，就是在优化 `setData` 的调用。

## 优化原则

### 1. state 中只存放渲染相关的数据

与渲染无关的数据不应放在 `state` 或 `useState` 中，否则会触发不必要的组件重渲染和底层 setData。

```tsx
// ❌ 避免：非渲染数据放在 state 中
import React, { useState } from 'react';

export default function Home() {
  const [list, setList] = useState([]);
  const [timer, setTimer] = useState(null);       // 非渲染数据
  const [cache, setCache] = useState({});          // 非渲染数据
  const [requestId, setRequestId] = useState('');  // 非渲染数据

  // ...
}
```

```tsx
// ✅ 推荐：使用 useRef 存储非渲染数据
import React, { useState, useRef } from 'react';

export default function Home() {
  const [list, setList] = useState([]);
  const timerRef = useRef(null);       // 不触发重渲染
  const cacheRef = useRef({});         // 不触发重渲染
  const requestIdRef = useRef('');     // 不触发重渲染

  // ...
}
```

### 2. 减少不必要的重渲染

React 组件的 state 变化会导致组件及其子组件重新渲染。应避免引起不必要的渲染。

**使用 `React.memo` 缓存子组件：**

```tsx
// ❌ 避免：父组件更新时，子组件每次都重渲染
function ListItem({ item }) {
  return <View><Text>{item.name}</Text></View>;
}
```

```tsx
// ✅ 推荐：使用 memo 避免不必要的重渲染
const ListItem = React.memo(function ListItem({ item }) {
  return <View><Text>{item.name}</Text></View>;
});
```

**使用 `useMemo` 缓存计算结果：**

```tsx
// ❌ 避免：每次渲染都重新计算
export default function Home() {
  const [list, setList] = useState([]);
  const sortedList = list.sort((a, b) => a.name.localeCompare(b.name)); // 每次渲染都排序

  return <View>...</View>;
}
```

```tsx
// ✅ 推荐：使用 useMemo 缓存
import React, { useState, useMemo } from 'react';

export default function Home() {
  const [list, setList] = useState([]);
  const sortedList = useMemo(
    () => [...list].sort((a, b) => a.name.localeCompare(b.name)),
    [list] // 只在 list 变化时重新计算
  );

  return <View>...</View>;
}
```

### 3. 控制 state 更新频率

高频的 state 更新（如滚动事件、输入事件）会导致频繁的重渲染和 setData 调用。

```tsx
// ❌ 避免：高频更新 state
export default function Home() {
  const [scrollTop, setScrollTop] = useState(0);

  const handleScroll = (e) => {
    setScrollTop(e.detail.scrollTop); // 每次滚动都更新
  };

  return <ScrollView onScroll={handleScroll}>...</ScrollView>;
}
```

```tsx
// ✅ 推荐：使用节流控制更新频率
import React, { useState, useRef, useCallback } from 'react';

export default function Home() {
  const [scrollTop, setScrollTop] = useState(0);
  const throttleRef = useRef(null);

  const handleScroll = useCallback((e) => {
    if (throttleRef.current) return;
    throttleRef.current = setTimeout(() => {
      setScrollTop(e.detail.scrollTop);
      throttleRef.current = null;
    }, 100);
  }, []);

  return <ScrollView onScroll={handleScroll}>...</ScrollView>;
}
```

### 4. 合并多次 state 更新

React 18+ 自动对多次 state 更新进行批处理（batching），但在异步回调中可能不会自动批处理：

```tsx
// React 会自动批处理同步更新
const handleClick = () => {
  setName('新名称');    // 不会立即触发渲染
  setAge(25);           // 不会立即触发渲染
  setStatus('active');  // 三次更新合并为一次渲染
};
```

```tsx
// 对于关联性强的状态，考虑使用 useReducer 或合并为一个 state
// ❌ 多个相关的 state
const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);
const [error, setError] = useState(null);

// ✅ 合并为一个 state
const [state, setState] = useState({
  loading: false,
  data: null,
  error: null,
});

const fetchData = async () => {
  setState(prev => ({ ...prev, loading: true }));
  try {
    const res = await api.getData();
    setState({ loading: false, data: res, error: null });
  } catch (err) {
    setState({ loading: false, data: null, error: err });
  }
};
```

### 5. 控制后台页面的更新

页面不可见时应停止 state 更新，避免浪费资源：

```tsx
import React, { useState, useEffect, useRef } from 'react';
import { usePageEvent } from '@ray-js/ray';

export default function Home() {
  const [data, setData] = useState(null);
  const isActiveRef = useRef(true);
  const timerRef = useRef(null);

  usePageEvent('onShow', () => {
    isActiveRef.current = true;
    startPolling();
  });

  usePageEvent('onHide', () => {
    isActiveRef.current = false;
    if (timerRef.current) {
      clearInterval(timerRef.current);
    }
  });

  const startPolling = () => {
    timerRef.current = setInterval(async () => {
      if (isActiveRef.current) {
        const res = await fetchLatestData();
        setData(res);
      }
    }, 3000);
  };

  useEffect(() => {
    startPolling();
    return () => {
      if (timerRef.current) {
        clearInterval(timerRef.current);
      }
    };
  }, []);

  return <View>...</View>;
}
```

## 检查清单

| 检查项 | 问题表现 | 优化方法 |
| ------ | -------- | -------- |
| state 中是否有非渲染数据 | 不必要的重渲染 | 使用 useRef 存储 |
| 子组件是否被不必要地重渲染 | 性能浪费 | 使用 React.memo |
| 是否有高频 state 更新 | 操作卡顿 | 使用节流/防抖 |
| 是否有重复的计算逻辑 | 渲染慢 | 使用 useMemo 缓存 |
| 多个相关 state 是否分散 | 多次渲染 | 合并为一个 state 或 useReducer |
| 后台页面是否有 state 更新 | 资源浪费 | 页面隐藏时停止更新 |

