React PureComponent中引用类型修改导致页面不更新的解决方案

 更新时间:2025年09月15日 09:25:00   作者:北辰alk  
React的PureComponent是React.Component的一个变体,它通过浅比较props和state来自动实现shouldComponentUpdate()方法,从而优化性能,本文详细解析React PureComponent中引用类型数据修改导致页面不更新的问题,并提供多种解决方案和最佳实践,需要的朋友可以参考下

PureComponent 的工作原理

什么是 PureComponent

PureComponent 是 React 提供的一个优化性能的组件基类,它通过浅比较(shallow comparison)来自动实现 shouldComponentUpdate 方法。

import React, { PureComponent } from 'react';

class MyComponent extends PureComponent {
  render() {
    return <div>{this.props.value}</div>;
  }
}

浅比较机制

PureComponent 的浅比较机制如下:

// 简化的浅比较实现
function shallowEqual(objA, objB) {
  if (objA === objB) {
    return true;
  }

  if (typeof objA !== 'object' || objA === null ||
      typeof objB !== 'object' || objB === null) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  for (let i = 0; i < keysA.length; i++) {
    const key = keysA[i];
    // 只比较第一层属性
    if (!objB.hasOwnProperty(key) || objA[key] !== objB[key]) {
      return false;
    }
  }

  return true;
}

PureComponent 与 Component 的区别

特性ComponentPureComponent
是否需要手动实现 shouldComponentUpdate
性能优化需要手动优化自动浅比较优化
适用场景需要精细控制更新的组件数据结构简单的展示组件

问题根源分析

引用类型的特点

JavaScript 中的引用类型(对象、数组)在赋值时传递的是引用(内存地址),而不是实际的值。

const obj1 = { count: 0 };
const obj2 = obj1; // obj2 和 obj1 指向同一个内存地址

obj2.count = 1;
console.log(obj1.count); // 输出 1,因为修改的是同一个对象

问题场景再现

class UserList extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      users: [
        { id: 1, name: 'Alice' },
        { id: 2, name: 'Bob' }
      ]
    };
  }

  // 错误的方式:直接修改引用类型
  updateUserName = (userId, newName) => {
    const users = this.state.users;
    const user = users.find(u => u.id === userId);
    user.name = newName; // 直接修改原对象
    
    this.setState({ users }); // users 引用未改变,PureComponent 不会重新渲染
  }

  render() {
    return (
      <div>
        {this.state.users.map(user => (
          <UserItem 
            key={user.id} 
            user={user} 
            onUpdate={this.updateUserName}
          />
        ))}
      </div>
    );
  }
}

问题流程图解

解决方案概览

解决方案对比表

解决方案优点缺点适用场景
不可变数据模式性能好,易于调试需要学习新概念大多数场景
状态管理库功能强大,生态丰富增加项目复杂度大型应用
forceUpdate简单直接违背 React 设计原则紧急修复
函数式子组件灵活可控需要手动优化性能简单组件

方案一:使用不可变数据模式

什么是不可变数据

不可变数据是指一旦创建就不能被修改的数据。任何修改都会返回一个新的数据副本。

使用扩展运算符

updateUserName = (userId, newName) => {
  this.setState(prevState => ({
    users: prevState.users.map(user => 
      user.id === userId 
        ? { ...user, name: newName } // 创建新对象
        : user
    )
  }));
}

使用数组的不可变方法

// 添加用户
addUser = (newUser) => {
  this.setState(prevState => ({
    users: [...prevState.users, newUser] // 创建新数组
  }));
}

// 删除用户
removeUser = (userId) => {
  this.setState(prevState => ({
    users: prevState.users.filter(user => user.id !== userId) // 创建新数组
  }));
}

// 更新用户
updateUser = (userId, updates) => {
  this.setState(prevState => ({
    users: prevState.users.map(user => 
      user.id === userId 
        ? { ...user, ...updates } // 创建新对象
        : user
    )
  }));
}

使用 Object.assign

updateUserName = (userId, newName) => {
  this.setState(prevState => ({
    users: prevState.users.map(user => 
      user.id === userId 
        ? Object.assign({}, user, { name: newName }) // 创建新对象
        : user
    )
  }));
}

处理嵌套对象

对于深层嵌套的对象,需要使用递归或专用库:

// 深层更新示例
updateUserProfile = (userId, field, value) => {
  this.setState(prevState => ({
    users: prevState.users.map(user => 
      user.id === userId 
        ? { 
            ...user, 
            profile: {
              ...user.profile,
              [field]: value
            }
          }
        : user
    )
  }));
}

方案二:使用状态管理库

使用 Immer 简化不可变更新

Immer 让你可以用可变的方式编写不可变更新逻辑。

npm install immer
import produce from 'immer';

class UserList extends PureComponent {
  // 使用 Immer 进行更新
  updateUserName = (userId, newName) => {
    this.setState(produce(draft => {
      const user = draft.users.find(u => u.id === userId);
      if (user) {
        user.name = newName; // 直接修改,Immer 会处理不可变性
      }
    }));
  }
}

使用 Redux 进行状态管理

// actions.js
export const updateUserName = (userId, name) => ({
  type: 'UPDATE_USER_NAME',
  payload: { userId, name }
});

// reducer.js
const initialState = {
  users: [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
  ]
};

export function userReducer(state = initialState, action) {
  switch (action.type) {
    case 'UPDATE_USER_NAME':
      return {
        ...state,
        users: state.users.map(user =>
          user.id === action.payload.userId
            ? { ...user, name: action.payload.name }
            : user
        )
      };
    default:
      return state;
  }
}

使用 MobX 进行状态管理

npm install mobx mobx-react
import { observable, action } from 'mobx';
import { observer } from 'mobx-react';

class UserStore {
  @observable users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
  ];

  @action
  updateUserName = (userId, newName) => {
    const user = this.users.find(u => u.id === userId);
    if (user) {
      user.name = newName; // MobX 会检测变化并触发更新
    }
  }
}

const userStore = new UserStore();

// 使用 observer 包装组件
const UserList = observer(({ store }) => (
  <div>
    {store.users.map(user => (
      <div key={user.id}>{user.name}</div>
    ))}
  </div>
));

方案三:使用 forceUpdate 方法

什么是 forceUpdate

forceUpdate() 是 React 组件的一个方法,它会强制组件重新渲染,跳过 shouldComponentUpdate

使用示例

class UserList extends PureComponent {
  updateUserName = (userId, newName) => {
    const users = this.state.users;
    const user = users.find(u => u.id === userId);
    user.name = newName; // 直接修改原对象
    
    this.forceUpdate(); // 强制重新渲染
  }
}

注意事项

  1. 不推荐常规使用forceUpdate 违背了 React 的数据流原则
  2. 性能影响:跳过 shouldComponentUpdate 可能导致不必要的渲染
  3. 使用场景:仅适用于无法通过正常数据流更新的特殊情况

替代方案:使用 key 属性

通过改变 key 值强制重新创建组件:

class UserList extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      users: [...],
      version: 0 // 用作 key 的值
    };
  }

  updateUserName = (userId, newName) => {
    const users = this.state.users;
    const user = users.find(u => u.id === userId);
    user.name = newName;
    
    // 通过改变 version 强制重新渲染
    this.setState(prevState => ({ version: prevState.version + 1 }));
  }

  render() {
    return (
      <div key={this.state.version}>
        {this.state.users.map(user => (
          <UserItem key={user.id} user={user} />
        ))}
      </div>
    );
  }
}

方案四:使用函数式子组件

将可变部分提取为独立组件

// UserItem.js - 使用普通 Component
class UserItem extends Component {
  shouldComponentUpdate(nextProps) {
    // 自定义比较逻辑
    return nextProps.user.name !== this.props.user.name;
  }

  render() {
    const { user } = this.props;
    return <div>{user.name}</div>;
  }
}

// UserList.js - 继续使用 PureComponent
class UserList extends PureComponent {
  // 仍然使用直接修改(不推荐,仅作示例)
  updateUserName = (userId, newName) => {
    const users = this.state.users;
    const user = users.find(u => u.id === userId);
    user.name = newName;
    
    this.setState({ users });
  }

  render() {
    return (
      <div>
        {this.state.users.map(user => (
          <UserItem 
            key={user.id} 
            user={user} 
            onUpdate={this.updateUserName}
          />
        ))}
      </div>
    );
  }
}

使用 React.memo 自定义比较

const UserItem = React.memo(({ user }) => {
  return <div>{user.name}</div>;
}, (prevProps, nextProps) => {
  // 自定义比较函数
  return prevProps.user.name === nextProps.user.name;
});

// 在父组件中
class UserList extends PureComponent {
  // 更新逻辑...
}

性能优化建议

使用不可变数据结构的性能考虑

  1. 结构共享:高级不可变库(如 Immutable.js)使用结构共享来减少内存使用
  2. 避免深层克隆:使用不可变更新时避免不必要的深层克隆
// 不好的做法:深层克隆整个对象
const newState = JSON.parse(JSON.stringify(prevState));

// 好的做法:浅层扩展
const newState = { ...prevState, users: updatedUsers };

使用 reselect 优化选择器

npm install reselect
import { createSelector } from 'reselect';

// 输入选择器
const getUsers = state => state.users;
const getFilter = state => state.filter;

// 记忆化选择器
export const getVisibleUsers = createSelector(
  [getUsers, getFilter],
  (users, filter) => {
    return users.filter(user => 
      user.name.toLowerCase().includes(filter.toLowerCase())
    );
  }
);

// 在组件中使用
const mapStateToProps = state => ({
  visibleUsers: getVisibleUsers(state)
});

使用 React Profiler 分析性能

import { Profiler } from 'react';

function onRenderCallback(
  id, // 发生提交的 Profiler 树的 "id"
  phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一
  actualDuration, // 本次更新 committed 花费的渲染时间
  baseDuration, // 估计不使用 memoization 的情况下渲染整颗子树需要的时间
  startTime, // 本次更新中 React 开始渲染的时间
  commitTime, // 本次更新中 React committed 的时间
  interactions // 属于本次更新的 interactions 的集合
) {
  // 合计或记录渲染时间...
}

<Profiler id="UserList" onRender={onRenderCallback}>
  <UserList {...props} />
</Profiler>

总结与最佳实践

问题解决总结

  1. 根本原因:PureComponent 的浅比较无法检测引用类型内部的变化
  2. 核心解决方案:使用不可变数据模式创建新对象/数组而不是修改原对象
  3. 辅助方案:状态管理库、forceUpdate、组件结构优化

最佳实践推荐

  1. 优先使用不可变数据模式:使用扩展运算符、map、filter 等方法
  2. 复杂场景使用 Immer:简化深层不可变更新的编写
  3. 大型应用使用状态管理库:Redux + 不可变更新或 MobX
  4. 避免使用 forceUpdate:除非在极其特殊的情况下
  5. 合理使用 PureComponent:在数据结构简单、渲染成本高的组件中使用

代码示例:完整解决方案

import React, { PureComponent } from 'react';

class UserManager extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      users: [
        { id: 1, name: 'Alice', profile: { age: 25, city: 'Beijing' } },
        { id: 2, name: 'Bob', profile: { age: 30, city: 'Shanghai' } }
      ]
    };
  }

  // 添加用户 - 使用不可变更新
  addUser = (newUser) => {
    this.setState(prevState => ({
      users: [...prevState.users, { ...newUser, id: Date.now() }]
    }));
  }

  // 更新用户信息 - 使用不可变更新
  updateUser = (userId, updates) => {
    this.setState(prevState => ({
      users: prevState.users.map(user =>
        user.id === userId
          ? { ...user, ...updates }
          : user
      )
    }));
  }

  // 更新用户配置 - 深层不可变更新
  updateUserProfile = (userId, profileUpdates) => {
    this.setState(prevState => ({
      users: prevState.users.map(user =>
        user.id === userId
          ? {
              ...user,
              profile: {
                ...user.profile,
                ...profileUpdates
              }
            }
          : user
      )
    }));
  }

  // 删除用户 - 使用不可变更新
  removeUser = (userId) => {
    this.setState(prevState => ({
      users: prevState.users.filter(user => user.id !== userId)
    }));
  }

  render() {
    return (
      <div>
        <UserForm onSubmit={this.addUser} />
        <UserList 
          users={this.state.users}
          onUpdate={this.updateUser}
          onUpdateProfile={this.updateUserProfile}
          onRemove={this.removeUser}
        />
      </div>
    );
  }
}

// 使用 React.memo 优化子组件
const UserList = React.memo(({ users, onUpdate, onUpdateProfile, onRemove }) => {
  return (
    <div>
      {users.map(user => (
        <UserItem
          key={user.id}
          user={user}
          onUpdate={onUpdate}
          onUpdateProfile={onUpdateProfile}
          onRemove={onRemove}
        />
      ))}
    </div>
  );
});

export default UserManager;

以上就是React PureComponent中引用类型修改导致页面不更新的解决方案的详细内容,更多关于React PureComponent类型修改页面不更新的资料请关注脚本之家其它相关文章!

相关文章

  • React+Antd 实现可增删改表格的示例

    React+Antd 实现可增删改表格的示例

    这篇文章主要介绍了React+Antd实现可增删改表格的示例,帮助大家更好的理解和学习使用React,感兴趣的朋友可以了解下
    2021-04-04
  • React从react-router路由上做登陆验证控制的方法

    React从react-router路由上做登陆验证控制的方法

    本篇文章主要介绍了React从react-router路由上做登陆验证控制的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • React中Provider组件详解(使用场景)

    React中Provider组件详解(使用场景)

    这篇文章主要介绍了React中Provider组件使用场景,使用Provider可以解决数据层层传递和每个组件都要传props的问题,本文结合示例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-02-02
  • create-react-app构建项目慢的解决方法

    create-react-app构建项目慢的解决方法

    这篇文章主要介绍了create-react-app构建项目慢的解决方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • React 实现车牌键盘的示例代码

    React 实现车牌键盘的示例代码

    这篇文章主要介绍了React 实现车牌键盘的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • React Hooks之usePolymerAction抽象代码结构设计理念

    React Hooks之usePolymerAction抽象代码结构设计理念

    这篇文章主要为大家介绍了React Hooks之usePolymerAction抽象代码结构设计理念,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • react-router实现跳转传值的方法示例

    react-router实现跳转传值的方法示例

    这篇文章主要给大家介绍了关于react-router实现跳转传值的相关资料,文中给出了详细的示例代码,对大家具有一定的参考学习价值,需要的朋友们下面跟着小编一起来学习学习吧。
    2017-05-05
  • React受控组件与非受控组件实例分析讲解

    React受控组件与非受控组件实例分析讲解

    具体来说这是一种react非受控组件,其状态是在input的react内部控制,不受调用者控制。可以使用受控组件来实现。下面就说说这个React中的受控组件与非受控组件的相关知识,感兴趣的朋友一起看看吧
    2023-01-01
  • React使用useEffect解决setState副作用详解

    React使用useEffect解决setState副作用详解

    这篇文章主要为大家介绍了React使用useEffect解决setState副作用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • react实现每隔60s刷新一次接口的示例代码

    react实现每隔60s刷新一次接口的示例代码

    本文主要介绍了react实现每隔60s刷新一次接口的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06

最新评论