#合理管理数据更新
#底层工作原理
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 更新 | 资源浪费 | 页面隐藏时停止更新 |