React PureComponent中引用類型修改導(dǎo)致頁面不更新的解決方案
PureComponent 的工作原理
什么是 PureComponent
PureComponent 是 React 提供的一個優(yōu)化性能的組件基類,它通過淺比較(shallow comparison)來自動實現(xiàn) shouldComponentUpdate 方法。
import React, { PureComponent } from 'react';
class MyComponent extends PureComponent {
render() {
return <div>{this.props.value}</div>;
}
}
淺比較機制
PureComponent 的淺比較機制如下:
// 簡化的淺比較實現(xiàn)
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 的區(qū)別
| 特性 | Component | PureComponent |
|---|---|---|
| 是否需要手動實現(xiàn) shouldComponentUpdate | 是 | 否 |
| 性能優(yōu)化 | 需要手動優(yōu)化 | 自動淺比較優(yōu)化 |
| 適用場景 | 需要精細控制更新的組件 | 數(shù)據(jù)結(jié)構(gòu)簡單的展示組件 |
問題根源分析
引用類型的特點
JavaScript 中的引用類型(對象、數(shù)組)在賦值時傳遞的是引用(內(nèi)存地址),而不是實際的值。
const obj1 = { count: 0 };
const obj2 = obj1; // obj2 和 obj1 指向同一個內(nèi)存地址
obj2.count = 1;
console.log(obj1.count); // 輸出 1,因為修改的是同一個對象
問題場景再現(xiàn)
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>
);
}
}
問題流程圖解
解決方案概覽
解決方案對比表
| 解決方案 | 優(yōu)點 | 缺點 | 適用場景 |
|---|---|---|---|
| 不可變數(shù)據(jù)模式 | 性能好,易于調(diào)試 | 需要學(xué)習(xí)新概念 | 大多數(shù)場景 |
| 狀態(tài)管理庫 | 功能強大,生態(tài)豐富 | 增加項目復(fù)雜度 | 大型應(yīng)用 |
| forceUpdate | 簡單直接 | 違背 React 設(shè)計原則 | 緊急修復(fù) |
| 函數(shù)式子組件 | 靈活可控 | 需要手動優(yōu)化性能 | 簡單組件 |
方案一:使用不可變數(shù)據(jù)模式
什么是不可變數(shù)據(jù)
不可變數(shù)據(jù)是指一旦創(chuàng)建就不能被修改的數(shù)據(jù)。任何修改都會返回一個新的數(shù)據(jù)副本。
使用擴展運算符
updateUserName = (userId, newName) => {
this.setState(prevState => ({
users: prevState.users.map(user =>
user.id === userId
? { ...user, name: newName } // 創(chuàng)建新對象
: user
)
}));
}
使用數(shù)組的不可變方法
// 添加用戶
addUser = (newUser) => {
this.setState(prevState => ({
users: [...prevState.users, newUser] // 創(chuàng)建新數(shù)組
}));
}
// 刪除用戶
removeUser = (userId) => {
this.setState(prevState => ({
users: prevState.users.filter(user => user.id !== userId) // 創(chuàng)建新數(shù)組
}));
}
// 更新用戶
updateUser = (userId, updates) => {
this.setState(prevState => ({
users: prevState.users.map(user =>
user.id === userId
? { ...user, ...updates } // 創(chuàng)建新對象
: user
)
}));
}
使用 Object.assign
updateUserName = (userId, newName) => {
this.setState(prevState => ({
users: prevState.users.map(user =>
user.id === userId
? Object.assign({}, user, { name: newName }) // 創(chuàng)建新對象
: user
)
}));
}
處理嵌套對象
對于深層嵌套的對象,需要使用遞歸或?qū)S脦欤?/p>
// 深層更新示例
updateUserProfile = (userId, field, value) => {
this.setState(prevState => ({
users: prevState.users.map(user =>
user.id === userId
? {
...user,
profile: {
...user.profile,
[field]: value
}
}
: user
)
}));
}
方案二:使用狀態(tài)管理庫
使用 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 進行狀態(tài)管理
// 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 進行狀態(tài)管理
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 會檢測變化并觸發(fā)更新
}
}
}
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(); // 強制重新渲染
}
}
注意事項
- 不推薦常規(guī)使用:
forceUpdate違背了 React 的數(shù)據(jù)流原則 - 性能影響:跳過 shouldComponentUpdate 可能導(dǎo)致不必要的渲染
- 使用場景:僅適用于無法通過正常數(shù)據(jù)流更新的特殊情況
替代方案:使用 key 屬性
通過改變 key 值強制重新創(chuàng)建組件:
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>
);
}
}
方案四:使用函數(shù)式子組件
將可變部分提取為獨立組件
// 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 - 繼續(xù)使用 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) => {
// 自定義比較函數(shù)
return prevProps.user.name === nextProps.user.name;
});
// 在父組件中
class UserList extends PureComponent {
// 更新邏輯...
}
性能優(yōu)化建議
使用不可變數(shù)據(jù)結(jié)構(gòu)的性能考慮
- 結(jié)構(gòu)共享:高級不可變庫(如 Immutable.js)使用結(jié)構(gòu)共享來減少內(nèi)存使用
- 避免深層克隆:使用不可變更新時避免不必要的深層克隆
// 不好的做法:深層克隆整個對象
const newState = JSON.parse(JSON.stringify(prevState));
// 好的做法:淺層擴展
const newState = { ...prevState, users: updatedUsers };
使用 reselect 優(yōu)化選擇器
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, // 發(fā)生提交的 Profiler 樹的 "id"
phase, // "mount" (如果組件樹剛加載) 或者 "update" (如果它重渲染了)之一
actualDuration, // 本次更新 committed 花費的渲染時間
baseDuration, // 估計不使用 memoization 的情況下渲染整顆子樹需要的時間
startTime, // 本次更新中 React 開始渲染的時間
commitTime, // 本次更新中 React committed 的時間
interactions // 屬于本次更新的 interactions 的集合
) {
// 合計或記錄渲染時間...
}
<Profiler id="UserList" onRender={onRenderCallback}>
<UserList {...props} />
</Profiler>
總結(jié)與最佳實踐
問題解決總結(jié)
- 根本原因:PureComponent 的淺比較無法檢測引用類型內(nèi)部的變化
- 核心解決方案:使用不可變數(shù)據(jù)模式創(chuàng)建新對象/數(shù)組而不是修改原對象
- 輔助方案:狀態(tài)管理庫、forceUpdate、組件結(jié)構(gòu)優(yōu)化
最佳實踐推薦
- 優(yōu)先使用不可變數(shù)據(jù)模式:使用擴展運算符、map、filter 等方法
- 復(fù)雜場景使用 Immer:簡化深層不可變更新的編寫
- 大型應(yīng)用使用狀態(tài)管理庫:Redux + 不可變更新或 MobX
- 避免使用 forceUpdate:除非在極其特殊的情況下
- 合理使用 PureComponent:在數(shù)據(jù)結(jié)構(gòu)簡單、渲染成本高的組件中使用
代碼示例:完整解決方案
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 優(yōu)化子組件
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中引用類型修改導(dǎo)致頁面不更新的解決方案的詳細內(nèi)容,更多關(guān)于React PureComponent類型修改頁面不更新的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React從react-router路由上做登陸驗證控制的方法
本篇文章主要介紹了React從react-router路由上做登陸驗證控制的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05
create-react-app構(gòu)建項目慢的解決方法
這篇文章主要介紹了create-react-app構(gòu)建項目慢的解決方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03
React?Hooks之usePolymerAction抽象代碼結(jié)構(gòu)設(shè)計理念
這篇文章主要為大家介紹了React?Hooks之usePolymerAction抽象代碼結(jié)構(gòu)設(shè)計理念,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09
react-router實現(xiàn)跳轉(zhuǎn)傳值的方法示例
這篇文章主要給大家介紹了關(guān)于react-router實現(xiàn)跳轉(zhuǎn)傳值的相關(guān)資料,文中給出了詳細的示例代碼,對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面跟著小編一起來學(xué)習(xí)學(xué)習(xí)吧。2017-05-05
React使用useEffect解決setState副作用詳解
這篇文章主要為大家介紹了React使用useEffect解決setState副作用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10

