前言
這篇文章主要對react的setState函數(shù)使用過程中遇到的問題進行說明以及提供相應的解決辦法,我會用比較實際的例子展示使用setState會遇到的問題,當然這個問題并不是博主最先發(fā)現(xiàn)的,主要是想針對問題提供一些解決思路
1、setState到底有什么問題?
此處引用陳墨大佬的總結
(1).setState不會立刻改變React組件中state的值;
(2).setState通過引發(fā)一次組件的更新過程來引發(fā)重新繪制座云;
(3).多次setState函數(shù)調用產(chǎn)生的效果會合并
第一點主要是說,setState改變state的值是一個異步的過程付材,它并不會立馬修改state的值朦拖,如果你在setState后面要用到更新后的state的值,這樣是有問題的厌衔,比如:
state = {
a: 1
}
async handleClick = () => {
this.setState({
a: 2
})
const { a } = this.state
const reponse = await getData(`/api/getData?a={a}`)
}
最后其實調用的url實際上是/api/getData?a=1
第二點就是說調用setState方法相當于調用一次render方法進行頁面的重新繪制(所以說如果一個循環(huán)操作沒有操作完璧帝,也就是一次setState引發(fā)的重新繪制正在繪制當中的時候,又調用setState這種情況很有可能導致頁面進入渲染的死循環(huán)葵诈,博主在當時不是非常熟練react之前是經(jīng)歷過這種情況的裸弦,這種情況在數(shù)據(jù)量大做循環(huán)的時候很容易遇到)
第三點就是說如果你在短時間內多次調用setState函數(shù),它不會進行多次頁面渲染作喘,而是把這個時間內的所有改變計算出來進行一次渲染理疙,比如:
state = {
a: 1,
b: 2,
c: 3
}
this.setState({a:4})
this.setState({b:5})
this.setState({c:6})
其實這三次調用setState并不會進行三次頁面渲染泞坦,而是將這三次的改變合并為一次然后一次性進行頁面渲染窖贤,也就是相當于下面這樣的結果this.setState({a:4, b:5, c:6})
2、實際做什么贰锁?
我簡要描述一下這個界面的功能赃梧,首先進來就算根據(jù)環(huán)境和任務狀態(tài)藍色的兩種狀態(tài)獲取數(shù)據(jù)進行數(shù)據(jù)顯示(這里數(shù)據(jù)我是mock的,但是已經(jīng)對接過真實接口豌熄,接口數(shù)據(jù)沒有了授嘀,所以暫時只能用mock數(shù)據(jù)來展示),然后可以分頁查詢表格數(shù)據(jù)锣险,此時如果我將頁面切換到第6頁蹄皱,會展示第6頁的數(shù)據(jù)
但是切換過濾條件,這里我將任務狀態(tài)切換到
處理中
芯肤,處理中的數(shù)據(jù)只有3頁巷折,而此時如果我還是用page=6去請求數(shù)據(jù)的話,肯定會是空數(shù)據(jù)崖咨,所以這里我索性就拿page=1去請求處理中
狀態(tài)下第一頁的數(shù)據(jù)3锻拘、遇到的問題?
上面我主要描述了我的需求击蹲,為了盡量簡化代碼署拟,所有這些分頁婉宰,過濾其實調用的都是同一個接口,所以順利成章的我對整個調用過程進行了封裝芯丧,方便按環(huán)境芍阎、任務狀態(tài)、時間缨恒、搜索進行過濾。下面我展示過濾的代碼轮听,為了盡可能減少函數(shù)的參數(shù)我將動態(tài)的改變分頁配置的page和pageSize骗露,然后以此page和pageSize去請求接口,這樣可以最大程度的精簡代碼血巍。
constructor(props) {
super(props)
this.current = 1;
this.pageSize = 10;
this.showSizeChanger = true;
this.showQuickJumper = true;
this.state = {
pagination: { // 這是傳入分頁器的分頁器配置
current: this.current,
pageSize: this.pageSize,
showSizeChanger: this.showSizeChanger,
showQuickJumper: this.showQuickJumper,
},
};
}
// 下面是調用接口的函數(shù)
dispatchFetchTaskList = (search, accountCycleSn, environment, state) => {
const { dispatch } = this.props;
const { current, pageSize } = this.state.pagination // 從state中獲取分頁信息萧锉,這樣我只需要動態(tài)改變state中的分頁信息,然后我就不用每次調用接口都手動傳分頁信息了
const params = {
pageable: { // 分頁信息
page: current,
limit: pageSize,
},
search, // 其他參數(shù)
accountCycleSn, // 其他參數(shù)
environment, // 其他參數(shù)
state, // 其他參數(shù)
};
dispatch({
type: 'taskCenter/fetchTaskList',
payload: params,
})
}
下面我將展示我如何進行分頁處理述寡,進行分頁處理柿隙,比如我當前page=1,pageSize=10
鲫凶,如果我切換到第6頁禀崖,此時我肯定需要先將this.state.pagination.current = 6
是吧,所以邏輯上我肯定就這樣寫
// page參數(shù)為table最新的頁碼螟炫,pageSize參數(shù)為table當前頁的顯示條數(shù)
paging = (page, pageSize) => {
if (!page) return
this.setState({ // 先將this.state.pagination.current = page
pagination: {
current: page,
pageSize,
showSizeChanger: this.showSizeChanger,
showQuickJumper: this.showQuickJumper,
},
})
console.log('3333333', this.state.pagination)
const searchParams = { "maintainBy": this.currentUserId }
// 然后調用dispatchFetchTaskList函數(shù)波附,函數(shù)里面就可以取到page = 6了,多么完美的想法
this.dispatchFetchTaskList(searchParams, this.dateRange, this.environment, this.status)
}
這里你們不要先反駁我昼钻,明明可以手動將分頁的最新page和pageSize傳到dispatchFetchTaskList函數(shù)去掸屡,這里我先展示這么弄會出現(xiàn)的問題,關鍵是我這個頁面有很多處都要調用dispatchFetchTaskList函數(shù)然评,如果我每次都把從state獲取page和pageSize或者從分頁函數(shù)page獲取page和pageSize仅财,然后傳到dispatchFetchTaskList函數(shù)會很累贅,而且dispatchFetchTaskList的參數(shù)越多碗淌,人家閱讀代碼就越麻煩盏求,想想一個函數(shù),7贯莺,8個參數(shù)會是什么樣子风喇,當然你說可以把參數(shù)拼成對象再傳進入,這些都是后話了
然后問題就來了上面的代碼我先調用this.setState想把this.state.pagination.current = 6缕探,然后調用dispatchFetchTaskList函數(shù)魂莫,預期想的是dispatchFetchTaskList函數(shù)會執(zhí)行const { current, pageSize } = this.state.pagination
得到current = 6,然后再拿6去請求接口爹耗,還記得我當時說的第一條:setState不會立刻改變React組件中state的值耙考,所以在執(zhí)行const { current, pageSize } = this.state.pagination
會得到current = 1谜喊,所以請求的還是第一頁的數(shù)據(jù)。
同理我處理過濾條件時倦始,就像我上面描述的未開始
有6頁數(shù)據(jù)斗遏,我現(xiàn)在切換到處理中
狀態(tài)只有3頁數(shù)據(jù),所以我直接應該將state中的分頁器配置信息改成第一頁鞋邑, 然后我再拿分頁器配置中的current = 1去請求數(shù)據(jù)诵次,這樣肯定是最完美的,一是直接修改了分頁器的配置枚碗,可以直接讓分頁器顯示再第一頁逾一,然后我還可以拿這個current =1去請求接口數(shù)據(jù) (也就是調用dispatchFetchTaskList,里面的const { current, pageSize } = this.state.pagination
默認就得到current =1)肮雨,所以這里我就會這樣編寫代碼遵堵,這里我封裝了一個將current還原成1,pageSize還原成10的函數(shù)
revertPaginationConfig = (callback) => {
const { current, pageSize } = this.state.pagination
if (current !== this.current || pageSize !== this.pageSize) {
this.setState({
pagination: {
current: this.current, // this.current = 1
pageSize: this.pageSize, // // this.pageSize = 10
showSizeChanger: this.showSizeChanger,
showQuickJumper: this.showQuickJumper,
},
})
}
// 處理根據(jù)條件過濾
handleFilterStatus = (conditions) => {
if (conditions.length > 0) {
this.revertPaginationConfig()
console.log('444444', this.state.pagination) // 然而這里得到this.state.pagination.current = 6, 并沒有成功將this.state.pagination.current 設置成1
const searchParams = { "maintainBy": this.currentUserId }
this.dispatchFetchTaskList(searchParams, this.dateRange, this.environment, conditions[0].value)
this.status = conditions[0].value
}
}
4怨规、解決辦法陌宿?
(1).可以直接利用this.state.pagination.current = 6
來將分頁器的配置設置成6,因為使用this.state直接賦值這個操作會是同步的波丰,它會立馬觸發(fā)頁面的重繪過程壳坪,而且立即更改state
paging = (page, pageSize) => {
if (!page) return
this.state.pagination.current = page
this.state.pagination.pageSize = pageSize
console.log('888888', this.state.pagination)
const searchParams = { "maintainBy": this.currentUserId }
this.dispatchFetchTaskList(searchParams, this.dateRange, this.environment, this.status)
}
(2).setState函數(shù)可以接受第二個參數(shù),第二個參數(shù)接受一個回調函數(shù)呀舔,該回調函數(shù)會再setState成功更改state的數(shù)據(jù)后進行調用弥虐,所以上面的paging函數(shù)也可以調整成如下代碼:
paging = (page, pageSize) => {
if (!page) return
this.setState({
pagination: {
current: page,
pageSize,
showSizeChanger: this.showSizeChanger,
showQuickJumper: this.showQuickJumper,
},
}, () => {
console.log('999999', this.state.pagination)
const searchParams = { "maintainBy": this.currentUserId }
this.dispatchFetchTaskList(searchParams, this.dateRange, this.environment, this.status)
})
}
所以我上面的處理過濾的函數(shù)可以進行如下改寫:
revertPaginationConfig = (callback) => {
const { current, pageSize } = this.state.pagination
if (current !== this.current || pageSize !== this.pageSize) {
this.setState({
pagination: {
current: this.current,
pageSize: this.pageSize,
showSizeChanger: this.showSizeChanger,
showQuickJumper: this.showQuickJumper,
},
}, () => {
if (callback) callback()
})
} else callback()
}
下面是如何調用:
handleFilterStatus = (conditions) => {
if (conditions.length > 0) {
const callback = () => {
const searchParams = { "maintainBy": this.currentUserId }
this.dispatchFetchTaskList(searchParams, this.dateRange, this.environment, conditions[0].value, this.current, this.pageSize)
}
callback.bind(this)
this.revertPaginationConfig(callback)
this.status = conditions[0].value
}
}
補充:setState第一個參數(shù)除了可以接受一個對象,還可以接受一個函數(shù)
本文參考:
setState何時同步更新狀態(tài)
setState:這個API設計到底怎么樣