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

