如何理解 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等来优化性能
  • 兄弟组件之间的状态变化不会相互影响
相关推荐
我太想进步了C~~1 小时前
Prompt Design(提示词工程)入门级了解
前端·人工智能·算法
苏打水com1 小时前
Day1-3 夯实基础:HTML 语义化 + CSS 布局实战(对标职场 “页面结构搭建” 核心需求)
前端·css·html·js
m0_740043731 小时前
mapState —— Vuex 语法糖
java·前端·javascript·vue.js
哟哟耶耶1 小时前
WebPage-postcss-px-to-viewport前端适配
前端·javascript·postcss
7澄11 小时前
Java Web 底层解析:Servlet 执行流程、Tomcat 工作原理与自定义 Tomcat 实现
java·前端·servlet·tomcat·自定义tomcat·tomcat执行流程·servlet执行流程
拾忆,想起1 小时前
Dubbo延迟加载全解:从延迟暴露到延迟连接的深度优化
前端·微服务·架构·dubbo·safari
IT_陈寒1 小时前
React 18并发渲染实战:这5个性能陷阱让我浪费了整整一周!
前端·人工智能·后端
xun_xin6661 小时前
QWidget for C++:ui资源使用
ui
EB_Coder1 小时前
前端面试题-JavaScript高级篇
前端·javascript·面试