如何理解 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等来优化性能
  • 兄弟组件之间的状态变化不会相互影响
相关推荐
Light605 小时前
CSS逻辑革命:原生if()函数如何重塑我们的样式编写思维
前端·css·响应式设计·组件化开发·css if函数·声明式ui·现代css
蜡笔小嘟6 小时前
宝塔安装dify,更新最新版本--代码版
前端·ai编程·dify
ModyQyW7 小时前
HBuilderX 4.87 无法正常读取 macOS 环境配置的解决方案
前端·uni-app
bitbitDown7 小时前
我的2025年终总结
前端
五颜六色的黑7 小时前
vue3+elementPlus实现循环列表内容超出时展开收起功能
前端·javascript·vue.js
wscats8 小时前
Markdown 编辑器技术调研
前端·人工智能·markdown
EnoYao8 小时前
Markdown 编辑器技术调研
前端·javascript·人工智能
JIngJaneIL8 小时前
基于java+ vue医院管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
JIngJaneIL8 小时前
基于java + vue校园跑腿便利平台系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
前端要努力8 小时前
月哥创业3年,还活着!
前端·面试·全栈