合理管理数据更新

底层工作原理

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

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

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

优化原则

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

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

// ❌ 避免:非渲染数据放在 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('');  // 非渲染数据

  // ...
}
// ✅ 推荐:使用 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 缓存子组件:

// ❌ 避免:父组件更新时,子组件每次都重渲染
function ListItem({ item }) {
  return <View><Text>{item.name}</Text></View>;
}
// ✅ 推荐:使用 memo 避免不必要的重渲染
const ListItem = React.memo(function ListItem({ item }) {
  return <View><Text>{item.name}</Text></View>;
});

使用 useMemo 缓存计算结果:

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

  return <View>...</View>;
}
// ✅ 推荐:使用 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 调用。

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

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

  return <ScrollView onScroll={handleScroll}>...</ScrollView>;
}
// ✅ 推荐:使用节流控制更新频率
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),但在异步回调中可能不会自动批处理:

// React 会自动批处理同步更新
const handleClick = () => {
  setName('新名称');    // 不会立即触发渲染
  setAge(25);           // 不会立即触发渲染
  setStatus('active');  // 三次更新合并为一次渲染
};
// 对于关联性强的状态,考虑使用 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 更新,避免浪费资源:

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 更新资源浪费页面隐藏时停止更新