前言
这篇文章主要对react的setState函数使用过程中遇到的问题进行说明以及提供相应的解决办法,我会用比较实际的例子展示使用setState会遇到的问题,当然这个问题并不是博主最先发现的,主要是想针对问题提供一些解决思路
1、setState到底有什么问题?
此处引用陈墨大佬的总结
(1).setState不会立刻改变React组件中state的值;
(2).setState通过引发一次组件的更新过程来引发重新绘制;
(3).多次setState函数调用产生的效果会合并
第一点主要是说,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方法进行页面的重新绘制(所以说如果一个循环操作没有操作完,也就是一次setState引发的重新绘制正在绘制当中的时候,又调用setState这种情况很有可能导致页面进入渲染的死循环,博主在当时不是非常熟练react之前是经历过这种情况的,这种情况在数据量大做循环的时候很容易遇到)
第三点就是说如果你在短时间内多次调用setState函数,它不会进行多次页面渲染,而是把这个时间内的所有改变计算出来进行一次渲染,比如:
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、实际做什么?
我简要描述一下这个界面的功能,首先进来就算根据环境和任务状态蓝色的两种状态获取数据进行数据显示(这里数据我是mock的,但是已经对接过真实接口,接口数据没有了,所以暂时只能用mock数据来展示),然后可以分页查询表格数据,此时如果我将页面切换到第6页,会展示第6页的数据
但是切换过滤条件,这里我将任务状态切换到处理中
,处理中的数据只有3页,而此时如果我还是用page=6去请求数据的话,肯定会是空数据,所以这里我索性就拿page=1去请求处理中
状态下第一页的数据
3、遇到的问题?
上面我主要描述了我的需求,为了尽量简化代码,所有这些分页,过滤其实调用的都是同一个接口,所以顺利成章的我对整个调用过程进行了封装,方便按环境、任务状态、时间、搜索进行过滤。下面我展示过滤的代码,为了尽可能减少函数的参数我将动态的改变分页配置的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,
},
};
}
// 下面是调用接口的函数
dispatchFetchTaskList = (search, accountCycleSn, environment, state) => {
const { dispatch } = this.props;
const { current, pageSize } = this.state.pagination // 从state中获取分页信息,这样我只需要动态改变state中的分页信息,然后我就不用每次调用接口都手动传分页信息了
const params = {
pageable: { // 分页信息
page: current,
limit: pageSize,
},
search, // 其他参数
accountCycleSn, // 其他参数
environment, // 其他参数
state, // 其他参数
};
dispatch({
type: 'taskCenter/fetchTaskList',
payload: params,
})
}
下面我将展示我如何进行分页处理,进行分页处理,比如我当前page=1,pageSize=10
,如果我切换到第6页,此时我肯定需要先将this.state.pagination.current = 6
是吧,所以逻辑上我肯定就这样写
// page参数为table最新的页码,pageSize参数为table当前页的显示条数
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函数,函数里面就可以取到page = 6了,多么完美的想法
this.dispatchFetchTaskList(searchParams, this.dateRange, this.environment, this.status)
}
这里你们不要先反驳我,明明可以手动将分页的最新page和pageSize传到dispatchFetchTaskList函数去,这里我先展示这么弄会出现的问题,关键是我这个页面有很多处都要调用dispatchFetchTaskList函数,如果我每次都把从state获取page和pageSize或者从分页函数page获取page和pageSize,然后传到dispatchFetchTaskList函数会很累赘,而且dispatchFetchTaskList的参数越多,人家阅读代码就越麻烦,想想一个函数,7,8个参数会是什么样子,当然你说可以把参数拼成对象再传进入,这些都是后话了
然后问题就来了上面的代码我先调用this.setState想把this.state.pagination.current = 6,然后调用dispatchFetchTaskList函数,预期想的是dispatchFetchTaskList函数会执行const { current, pageSize } = this.state.pagination
得到current = 6,然后再拿6去请求接口,还记得我当时说的第一条:setState不会立刻改变React组件中state的值,所以在执行const { current, pageSize } = this.state.pagination
会得到current = 1,所以请求的还是第一页的数据。
同理我处理过滤条件时,就像我上面描述的未开始
有6页数据,我现在切换到处理中
状态只有3页数据,所以我直接应该将state中的分页器配置信息改成第一页, 然后我再拿分页器配置中的current = 1去请求数据,这样肯定是最完美的,一是直接修改了分页器的配置,可以直接让分页器显示再第一页,然后我还可以拿这个current =1去请求接口数据 (也就是调用dispatchFetchTaskList,里面的const { current, pageSize } = this.state.pagination
默认就得到current =1),所以这里我就会这样编写代码,这里我封装了一个将current还原成1,pageSize还原成10的函数
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,
},
})
}
// 处理根据条件过滤
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直接赋值这个操作会是同步的,它会立马触发页面的重绘过程,而且立即更改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函数可以接受第二个参数,第二个参数接受一个回调函数,该回调函数会再setState成功更改state的数据后进行调用,所以上面的paging函数也可以调整成如下代码:
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)
})
}
所以我上面的处理过滤的函数可以进行如下改写:
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第一个参数除了可以接受一个对象,还可以接受一个函数
本文参考:
setState何时同步更新状态 setState:这个API设计到底怎么样