一起玩react 你不知道的setState

前言

这篇文章主要对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设计到底怎么样

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • 个人笔记, 转载请注明转载自 szhshp的第三边境研究所 Refs and the DOM In the t...
    szhielelp阅读 1,475评论 0 1
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,638评论 18 399
  • 一. Java基础部分.................................................
    wy_sure阅读 3,811评论 0 11
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,798评论 0 38
  • 凌晨,才过四点,黄山顶上,天海的白云宾馆,就开始喧闹起来。 我们还在床上躺着。 昨天一天,西海的跋涉,倦意还是酸涨...
    强哥_2018阅读 654评论 2 4