watchEffect的flush属性你都用过不?

1.背景需求

现在有个需求需要当省份和城市都选择的时候,显示对应的人员信息。

2.逻辑实现

  1. 有一个省市的通用组件,支持选择省份联动显示城市,并且当切换省份的时候,之前选中的城市会自动清空。
  2. 利用这个组件,在父组件 通过 watchEffect方法监听当前 省和市 是否不为空,则发起接口请求,传入市的信息。
  3. 接口返回用户列表并展示。

3.对应代码

src/components/CommonSelect.vue

js 复制代码
<template>
    省:<select v-model="province">
      <option value="">请选择省份</option>
      <option v-for="p in provinces" :key="p" :value="p">{{ p }}</option>
    </select>
    > 
   市:<select v-model="city">
      <option value="">请选择城市</option>
      <option v-for="c in cities" :key="c" :value="c">{{ c }}</option>
    </select> 
</template>

<script setup>
import { ref, watch, defineProps, defineEmits } from 'vue'

const props = defineProps({
  provinces: {
    type: Array,
    default: () => ['广东', '江苏', '浙江']
  },
  mockData: {
    type: Object,
    required: true,
    default: { 
    广东: ['广州', '深圳', '珠海'],
    江苏: ['南京', '苏州', '无锡'],
    浙江: ['杭州', '宁波', '温州'],
    }
  }
})

const emit = defineEmits(['update:province', 'update:city', 'query'])

const province = ref('')
const city = ref('')
const cities = ref([]) 

watch(province, (newProvince) => {
  city.value = ''
  cities.value = props.mockData[newProvince] || []
  emit('update:province', newProvince)
})

watch(city, (newCity) => {
  emit('update:city', newCity)
})

</script>

<style scoped>
.province-city-selector {
  margin-bottom: 20px;
}
</style>

src/App.vue

js 复制代码
<template>
  <div>
    <ProvinceCitySelector
      :provinces="provinces"
      :mockData="mockData"
      @update:province="province = $event"
      @update:city="city = $event"
    />
    >
     <select v-model="user"  >
      <option value="">请选择人员</option>
      <option v-for="r in result" :key="r" :value="r">{{ r }}</option>
    </select> 
    <p>接口调用: {{ msg }}</p>
    查询结果:{{result}}
   
  </div>
</template>

<script setup>
import { ref, watchEffect } from 'vue'
import ProvinceCitySelector from './components/CommonSelect.vue'

const province = ref('')
const city = ref('')
const user = ref('')
const result = ref([])
const msg = ref('')

const handleQuery = () => {
  result.value = []
  msg.value = `参数: ${province.value},${city.value} > 查询中... `
  setTimeout(() => {
    msg.value = ''
    const mockData = { 
      广东:  ['天河伟', '越秀楠',  '罗湖琳'],
      江苏: ['玄武然', '鼓楼哲', '建邺然'],
      浙江: ['西湖舒', '余杭帆', '拱墅晨'],
    }
    result.value = mockData[province.value]
  }, 2000)
} 

watchEffect(() => {
  if (province.value && city.value) {
    handleQuery()
  } else {
    user.value = ''
  }
})
</script>
<style>
* {
  font-size: 30px;
}
</style>

4.问题

当选中广东和深圳后,再切换省份到江苏的时候,会触发接口请求,并且传递参数市江苏+深圳。

如下图所示

分析

由于触发网络请求的入口在watchEffect,同时清空市的逻辑也是在watchEffect,重点排查watchEffect

定位

  1. 由于通用select组件使用了watchEffect监听数据是否变化来重置市的信息如下
js 复制代码
watch(province, (newProvince) => {
  city.value = ''
  cities.value = props.mockData[newProvince] || []
  emit('update:province', newProvince)
})
  1. 而在父组件,我们也写了watchEffect去处理不为空时候的发起请求
js 复制代码
watchEffect(() => {
  if (province.value && city.value) {
    handleQuery()
  } else {
    user.value = ''
  }
})
  1. 这时候当响应式数据变化的时候,父子两个组件的watchEffect都会触发.
  2. 当前由于组件的创建实例化顺序是先父组件-> 子组件,所以watchEffect的触发顺序也是先父后子。
  3. 所以父组件的 watchEffect里的handleQuery会先执行。才出现江苏+深圳的请求参数
  4. 然后子组件的watchEffect才再进行设置市为空

5.解决思路

方案1 改造组件

select组件不是一个受控组件,有副作用,并且自身还生产数据,数据应该都在外部产生,并且通过单向数据流方式传递。

  • 优势:数据状态统一管理,所有逻辑也可以按需设计和有序执行。
  • 缺点:如果select组件是别人封装好的就不好改,自己也需要重新实现原有组件逻辑。

方案2 组件暴露方法

由于问题是watchEffect先后执行的问题,解决思路也是如果调整他们的顺序

  • 优势:可以精准控制触发时机,但是调整麻烦,
  • 缺点:如果select组件是别人封装好的就不好改,调整可能会影响其他业务。

方案3 settimeout延迟执行?

可以通过延迟执行实际的查询,在执行的时候再次判断条件是否满足如何

  • 缺点:代码看着就变扭
js 复制代码
watchEffect(() => {
  if (province.value && city.value) {
    setTimeout(() => {
      if (province.value && city.value) {
        handleQuery()
      } 
    }, 1000);
  } else {
    user.value = ''
  }
},{flush:'post'})

注意:watchEffect里面的异步代码是不能被依赖收集的

方案4 watchEffect的flush属性

在使用watchEffect,支持一个参数flush,先看看官方怎么描述:

默认情况下,侦听器将在组件渲染之前执行。设置 flush: 'post' 将会使侦听器延迟到组件渲染之后再执行。

所以如果要让上面父组件的watchEffect晚于子组件执行,只需要在父组件的watchEffect里加上{flush:'post'}即可。

在父组件src/App.vue 调整以下代码:

js 复制代码
watchEffect(() => {
  if (province.value && city.value) {
    handleQuery()
  } else {
    user.value = ''
  }
},{flush:'post'})

6.测试一把

nice~

总结

在实际开发中,往往会遇到渲染执行的顺序问题,我们可以先看看官方是否提供扩张的能力。找不到方案,再调整我们具体的代码逻辑,争取更多的时间摸鱼 哈。

源码

github.com/mjsong07/vu... 的 watcheffect_flush目录

相关推荐
FSHOW8 分钟前
【独立开发日记】MQ端到端类型安全
前端·javascript·后端
老华带你飞22 分钟前
社区互助|基于SSM+vue的社区互助平台的设计与实现(源码+数据库+文档)
java·前端·数据库·vue.js·小程序·毕设·社区互助平台
一支鱼33 分钟前
前端使用次数最多的工具封装
前端·typescript·编程语言
GIS之路36 分钟前
GDAL 简介
前端
前端工作日常1 小时前
单元测试与E2E测试中使用浏览器的原因及区别
前端·单元测试
chxii1 小时前
7.2elementplus的表单布局与模式
javascript·vue.js·elementui
Js_cold1 小时前
Notepad++使用技巧1
前端·javascript·fpga开发·notepad++
接着奏乐接着舞。2 小时前
前端RSA加密遇到Java后端解密失败的问题解决
java·开发语言·前端
dreams_dream2 小时前
vue中的与,或,非
前端·javascript·vue.js
柳杉3 小时前
使用three.js搭建3d隧道监测-3
前端·javascript·three.js