如何理解 React的UI渲染

React UI 渲染的基本原理

React 的 UI 渲染基于虚拟 DOM(Virtual DOM)机制。当组件的状态(state)或属性(props)发生变化时,React 会重新计算虚拟 DOM 树,并通过 Diff 算法对比新旧虚拟 DOM 的差异,最终将变化的部分高效更新到真实 DOM 上。

触发重新渲染的条件

  1. 组件状态(state)变化 :调用 setStateuseState 的更新函数时,组件会重新渲染。
  2. 父组件重新渲染:父组件渲染会导致其所有子组件默认重新渲染。
  3. 属性(props)变化:父组件传递的 props 更新时,子组件会重新渲染。
  4. Context 变化:组件订阅的 Context 值发生变化时,会触发重新渲染。
  5. 强制更新 :调用 forceUpdate 方法(不推荐使用)。

理解React UI 渲染行为

1. React重新渲染的范围:组件级别的重新渲染

React的重新渲染是以组件为单位的,而不是整个页面:

javascript 复制代码
function ParentComponent() {
  const [count, setCount] = useState(0);
  
  // 当count变化时,只有ParentComponent会重新渲染
  // 但会影响其子组件
  return (
    <div>
      <ChildComponent1 />  {/* 会重新渲染 */}
      <ChildComponent2 />  {/* 会重新渲染 */}
    </div>
  );
}

function SiblingComponent() {
  // 这个组件不会因为ParentComponent的状态变化而重新渲染
  return <div>我不会重新渲染</div>;
}
2. 重新渲染时发生什么

当组件重新渲染时:

javascript 复制代码
function MyComponent() {
  const [count, setCount] = useState(0);
  
  // ✅ 每次渲染都会重新执行
  const expensiveValue = useMemo(() => {
    console.log('计算expensive value');
    return count * 1000;
  }, [count]);
  
  // ✅ 每次渲染都会重新创建
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);
  
  // ❌ 每次渲染都会重新创建(性能问题)
  const handleClickBad = () => {
    setCount(c => c + 1);
  };
  
  // ✅ 每次渲染都会重新执行函数体
  console.log('组件重新渲染了');
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={handleClick}>增加</button>
    </div>
  );
}
3. 如何避免不必要的重新渲染
javascript 复制代码
// 使用React.memo避免子组件不必要的重新渲染
const ChildComponent = React.memo(({ name }) => {
  console.log('ChildComponent渲染');
  return <div>{name}</div>;
});

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [name] = useState('张三');
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      {/* name没有变化,ChildComponent不会重新渲染 */}
      <ChildComponent name={name} />
    </div>
  );
}

//或者可以参考下面的优化策略:
function OptimizedComponent() {
  const [users, setUsers] = useState([]);
  const [filter, setFilter] = useState('');
  
  // ✅ 使用useMemo缓存计算结果
  const filteredUsers = useMemo(() => {
    console.log('过滤用户列表');
    return users.filter(user => 
      user.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [users, filter]);
  
  // ✅ 使用useCallback缓存函数
  const handleFilterChange = useCallback((e) => {
    setFilter(e.target.value);
  }, []);
  
  return (
    <div>
      <input onChange={handleFilterChange} value={filter} />
      <UserList users={filteredUsers} />
    </div>
  );
}

避免重复渲染的方法

使用 React.memo 优化子组件

React.memo 是一个高阶组件,用于对函数组件进行浅比较,避免不必要的渲染。仅当 props 变化时才会重新渲染。

jsx 复制代码
const MyComponent = React.memo(function MyComponent(props) {
  return <div>{props.value}</div>;
});

合理使用 useCallback 和 useMemo

useCallback 缓存函数,useMemo 缓存计算结果,避免因函数或值重新生成导致的子组件渲染。

jsx 复制代码
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

避免在渲染函数中直接创建对象或函数

在渲染函数中直接创建对象或函数会导致每次渲染时生成新的引用,可能触发子组件不必要的渲染。

jsx 复制代码
// 避免这样写
function BadExample() {
  return <ChildComponent style={{ color: 'red' }} onClick={() => {}} />;
}

使用 shouldComponentUpdate 或 PureComponent

类组件可以通过 shouldComponentUpdate 或继承 PureComponent 实现浅比较,减少渲染次数。

jsx 复制代码
class MyComponent extends React.PureComponent {
  render() {
    return <div>{this.props.value}</div>;
  }
}

合理拆分组件

将频繁变化的部分拆分为独立组件,减少其他部分的渲染影响。例如,将表单输入框拆分为单独组件。

使用 key 属性优化列表渲染

为列表项添加唯一的 key 属性,帮助 React 识别哪些项需要更新,避免整个列表重新渲染。

jsx 复制代码
{items.map(item => (
  <li key={item.id}>{item.name}</li>
))}

避免滥用 Context

Context 的变化会触发所有订阅组件的渲染,可以通过拆分 Context 或使用 useMemo 优化。

jsx 复制代码
const value = useMemo(() => ({ theme, toggleTheme }), [theme]);

调试重复渲染的工具

  1. React DevTools:通过高亮更新功能检查不必要的渲染。
  2. why-did-you-render:库用于检测潜在的不必要渲染原因。
  3. 性能分析工具:使用 Chrome DevTools 的 Performance 或 React Profiler 分析渲染性能。

总结

  • React重新渲染是组件级别的,不是整个页面
  • 当组件重新渲染时,会重新执行组件函数体内的所有代码
  • 子组件会跟着父组件一起重新渲染(除非使用优化手段)
  • 可以通过React.memouseMemouseCallback等来优化性能
  • 兄弟组件之间的状态变化不会相互影响
相关推荐
胡萝卜术18 小时前
当大模型遇上浏览器:用 React + WebGPU 在前端跑通 DeepSeek-R1 的实战笔记
前端·javascript·面试
不好听61318 小时前
Tailwind CSS 原子化 CSS 完全入门:为什么现代前端开发都在用?
前端·css
触底反弹18 小时前
🔥 React 零基础入门(上):环境搭建 + JSX 深度解析
前端·react.js·typescript
why技术18 小时前
分享一套我一直在使用的 AICoding 组合拳,小而美的典范。
前端·后端·ai编程
朦胧之19 小时前
AI应用-消费流式输出
前端·javascript·ai编程
小林ixn19 小时前
在浏览器跑通 15 亿参数大模型:我用 React + WebGPU 复刻了 DeepSeek-R1
前端·react.js·前端框架
Csvn19 小时前
容器查询 @container 实战:告别无休止的媒体查询
前端
稚南城才子,乌衣巷风流1 天前
函数:编程中的核心概念
开发语言·前端·javascript
IT_陈寒1 天前
为什么我的JavaScript异步代码总是不按顺序执行?
前端·人工智能·后端
索西引擎1 天前
【React】useState 状态更新机制:批量更新策略与“异步“错觉的深层解析
前端·react.js·前端框架