如何理解 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等来优化性能
  • 兄弟组件之间的状态变化不会相互影响
相关推荐
_AaronWong1 小时前
Electron 实现仿豆包划词取词功能:从 AI 生成到落地踩坑记
前端·javascript·vue.js
cxxcode1 小时前
I/O 多路复用:从浏览器到 Linux 内核
前端
用户5433081441941 小时前
AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例
前端
JarvanMo1 小时前
Flutter 版本的 material_ui 已经上架 pub.dev 啦!快来抢先体验吧。
前端
恋猫de小郭2 小时前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
哀木2 小时前
给自己整一个 claude code,解锁编程新姿势
前端
程序员鱼皮2 小时前
GitHub 关注突破 2w,我总结了 10 个涨星涨粉技巧!
前端·后端·github
UrbanJazzerati2 小时前
Vue3 父子组件通信完全指南
前端·面试
是一碗螺丝粉2 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain
wuhen_n2 小时前
双端 Diff 算法详解
前端·javascript·vue.js