有 A 、B 两个 API ,其中 B 的结果需要依赖 A 的结果来组装数据(类似于 B 是用户表存了角色 ID ,界面显示的时候需要依赖 A 角色表里的角色名称)
之前的做法是在onMounted里先调用 A 然后再调用 B ,其中 A 被调用后我就把结果存起来了,只用调用这一次
现在我封装了一个分页组件,于是我不在onMounted里调用 B 了,直接watch页码和页大小有变化的时候调用 B ,但是这样会导致一个问题:就是页面一加载的时候 watch 就会生效,导致 B 比 A 先执行。但是我又不想把 A 也放在watch里,因为它的数据几乎是不会变的,类似于角色表,页面加装的时候调用一次即可
大佬们有什么思路吗?

把 A 放在父组件能调用?通过 props 传入结果到你的分页组件?

B 接口也是在父组件里调用的,分页组件它只处理页码或者页大小的变化,变化后会在父组件的 watch 里监控它 然后调用 B

再包一两层?把 A B 分别包成两个函数,onMounted 里面调用 C ,C 取判断如果有 A 就调用 B ,如果没有 A 就先调用 A 。这样你只需要找调用 B 的地方调用 C 即可。

有道理,这个方法可行

你可以考虑在你的分页组件中引入一个状态来表示 A 是否已经加载完成。这样,在组件加载时,你可以首先检查是否已经加载了 A 的数据。如果没有,那么首先加载 A ,然后再加载 B 。下面是一个简单的示例代码,说明了这个思路:import { ref, watch, onMounted } from 'vue';export default { setup() { // 用于存储 A 的数据 const dataA = ref(null); // 用于表示 A 是否已经加载完成 const isDataALoaded = ref(false); // 模拟加载 A 的数据 function fetchDataA() { // 这里假设 fetchDataA 是异步操作 setTimeout(() => { // 模拟从 API 获取到的 A 的数据 const result = / 调用 A 的 API /; // 存储 A 的数据 dataA.value = result; // 标记 A 已加载完成 isDataALoaded.value = true; }, 1000); // 假设加载 A 的数据需要 1 秒钟 } // 模拟加载 B 的数据 function fetchDataB(page, pageSize) { // 这里假设 fetchDataB 是异步操作 setTimeout(() => { // 只有在 A 的数据加载完成后才调用 B 的 API if (isDataALoaded.value) { // 这里可以使用 A 的数据来组装 B 的数据 const result = / 调用 B 的 API ,依赖于 A 的数据 /; console.log(result); } else { // 如果 A 的数据尚未加载完成,则等待 A 加载完成后再调用 B 的 API console.log("Waiting for A to load..."); } }, 500); // 假设加载 B 的数据需要 0.5 秒钟 } // 监听页码和页大小的变化,当它们变化时调用 fetchDataB watch([currentPage, pageSize], ([newPage, newPageSize], [oldPage, oldPageSize]) => { fetchDataB(newPage, newPageSize); }); // 在组件加载时,如果 A 的数据尚未加载,则先加载 A 的数据 onMounted(() => { if (!isDataALoaded.value) { fetchDataA(); } }); return { dataA, isDataALoaded, }; },};这样,无论在分页组件加载时还是在页码或页大小变化时,都会先检查 A 的数据是否已经加载完成,然后再决定是否加载 B 的数据。

A 返回数据再动态绑定 B 的属性?好像是 this.$set 啥的(但是好久不写 vue 了, 不知道是不是这么个思路

这个 A 接口是不是在系统中别的页面也需要用到的?就是这个角色是不是全局的,如果是这个就好办啦,直接放在入口( main.js 或者 router 钩子)请求后存 session 或 local storage ,这样你在别的页面直接取就行了。

提取一段公用代码,用来判断是否有 A 的数据,有就直接调用 B ,没有则先 A 后 B 。之前需要调用 B 方法的地方,都用这个公用代码就行

对的,A 返回数据再动态绑定 B 的属性。 我查查

就当前页面用得到。A 的数据虽然大概率不会改,但是还是有改的情况,所以想法还是在这个页面加装的时候请求一次最新的数据最好

嗯,有点类似于 3 楼的做法

上 tanstack query/vue ,你在任意地方调用一次,之后随便取就行了。想长时间有效不重新请求就设置失效时间,想提前失效重新加载也有对应的 API

AI 回答的吧?这个有个问题,假如 A 请求超过 1s 的话会导致 B 不会执行啊

分页组件 props 接收一个 beforeSearch 。在 watch 监听到变化执行你的 getData 方法(也就是 B )时 async 调用 beforeSearch 方法。

3.0 的写法好像是这样 cn.vuejs.org/api/component-instance.html#watch

看了下,这玩儿挺强大呀,好像还可以做限流?

  1. 用 watch 监控2. 用 vue-query

    vue-query 是不是和 12L 说的 tanstack query/vue 一个功能?

你可以在 onMounted 里调用 watch

b 执行的时候判断是否有 userid 就行,没有直接 return,不用搞那么复杂,watch 里添加对 userid 变化的监测

对的,感觉就是这么回事,然后就贴这儿了。

我可能会这样写:typescripttype Role = { id: string; name: string };function usePage<T>() { const role = ref<Role | null>(null) const loading = ref(false); const page = reactive({ current: 1, size: 10 }); const uri = computed(() => `/api/b?page=${page.current}&size=${page.size}`); const { data, error, execute } = useFetch(uri, { immediate: false }).json<T>() // 防抖避免快速点击翻页按钮发送无效请求 const onLoadPageData = debounce(async () => { try { loading.value = true; await initRole(); await execute(); } finally { loading.value = false; } }, 200) async function initRole() { if (role.value) return; const { data, error } = await useFetch("/api/a").json<Role>(); if (error.value) { /** error handler */ } else { role.value = data.value; onLoadPageData(); } } initRole(); watch(page, onLoadPageData, { immediate: true }); return { page, data, error, loading, onLoadPageData }}

watch 到变化 emit 一个事件让父组件件调用接口 B

是不是应该配置 immediate 为 false

我封装封装通用组件的思路,1. 传递一个获取数据的函数,而不是一个简单的路由,这个函数参数为页码条数据。 props: { auto: Boolean dataFunc: Function as PropType< (page: PaginationRequest) => Promise<XOR<PaginationResponse, Array>> >,}传递函数的原因是增加可控性,比如请求条件,数据过滤、转换,接口依赖等, 全部可以交给外部控制。 过滤条件,不同的业务可以做到随便控制。const getData = (page: PaginationRequest) => { // if (xxx) { // return [] // } return SupervisionService.load().filingList(page, unref(filter));};而如果使用传递条件,路由给通用组件方案,通用组件在业务变更、需求不同情况下,会越来越臃肿,相信我,后面代码没法维护的。。。。2. 提供了一个 auto 参数, 设置 auto ,会自动首次请求。 这个参数是因为业务上,首次请求时机 有时候会根据筛选条件决定, props: { auto: Boolean dataFunc: Function as PropType< (page: PaginationRequest) => Promise<XOR<PaginationResponse, Array>> >,}

你是不是给 watch 加了 immediate: true ? watch 默认是懒执行的:仅当数据源变化时,才会执行回调。

提供一个我们在用的一个思路 用 rxjs

按现有逻辑方案一,现有逻辑,类似 3 楼思路,合并 A ,B 调用为一个新函数 C ,里面对 A 做单例执行,如果 A 数据有值不重复请求 A 数据。方案二,把 onMounted 里触发的 A ,前置到 beforeRouteEnter ,保证 A 数据加载了之后,再 next 渲染页面,即把 页面 非表格数据的 loading ,前置到 路由的 loading 中,这也是一种常见的写法。方案三,一般个人写这种页面,不会去 watch 分页参数,或者即便 watch 也会去掉 immediate 触发,因为大概率 页面首次初始化逻辑是不同于切换分页组件的时候触发逻辑的(当然,对于简单的 CRUD 页面来说可能是一样的)。如果不 watch 分页参数,那逻辑就变成自然的,首次渲染 A + B ,切换分页组件的时候,触发 B 的逻辑。不 watch 分页的一些原因,主要因为 watch immediate 会在组件 created 的时候直接触发,加上如果修改 watch 参数的逻辑因为组件封装的复杂度,导致用户一次操作,同时修改了 watch 对象的 2 个值,并且 2 次修改有类似 nextTick 的分隔,会导致触发 2 次,如果是手动控制的 change ,就可以规避(依稀记得当年 element-ui 1.0 的时代,el-select 的 change 事件是直接类似 watch 的逻辑,每次修改 v-model 的值,都会触发 change ,写省市区级联选择框的时候,一言难尽,后面 v2 版本就直接改逻辑了,只有用户的操作才会触发 change )所以 OP 逻辑里面的 分页调用 B ,从封装组件逻辑角度出发,我是不建议直接 watch 触发逻辑的,应该是用户主动切换页码的时候,才触发 change ,可举个简单的例子,比如需要列表跳转 url 进入详情,详情返回列表还是在原来的页码,一般可通过 url 参数保留之前的页码,这时候 页面初始化会带有比如 ?page=2 这样的参数,如果内部分页组件直接 watch 不做特殊处理,那默认请求第 1 页,拿到 url 参数,再修改当前页为第 2 页,则会额外触发一次分页请求,但是如果不是 watch 实现的,分页触发只会在用户手动切换分页的时候触发,初始化的时候因为第一页逻辑自己控制,可以灵活的处理各类情况。

还有一个常见方案,即如果你这个 A 只是 B 表格数据显示的时候,比如某列是字典表,其他配置表,B 返回数据里面存的是 id ,显示的时候要显示成 A 返回里面对应的 name 。可以直接写一个计算属性,按照 A 返回的数据,生成新的显示数据get viewTableData() { // 如果能保证 roleData 一定有值的话,加这个可以让 loading 中的表格显示效果更好 if (this.roleData.length === 0) return [] const data = JSON.parse(JSON.stringify(this.tableData)) data.forEach(row => { row.roleName = this.roleData.find(v => v.id === row.roleId)?.name ?? '' }) return data}然后可以对表格加 loading ,A 数据未返回前,表格处于 loading 中,这样就可以不用关心 A ,B 数据的加载顺序,当然这个逻辑比较偏向固定这个逻辑的页面的写法,不适合通用组件封装的逻辑。

A 在路由中请求也行

典型的异步编程的需求,custom event ,callback ,promise 甚至轮询都可以做到。

确实,你说的有道理,如果有 url 带 page 的情况,确实会出现额外出发一次分页请求,不过目前这个页面的场景不会出现

等 A 执行完后放全局,再渲染分页 onMounted 中调 B ?

是的,immediate 设置为 false ,然后在 onMounted 里分别调用 A ,B 也能解决问题

最简单的做法就是把 A 放在 B 函数里面, 如果发现没有 A 数据就直接先 await A. mounted 不做任何操作

添加一个 A 的控制变量就行,watch B 的时候检查一下,有了就直接请求 B ,没了就请求一下 A 。或者既然 A 用户表不怎么改变,直接 created 的时候请求存一下不就可以了?

虽然数据上有依赖关系,但是请求可以各走各的,都返回了就正常显示,否则就等待,vue 的响应视图会自动处理好

rxjs 哪个页面依赖这个 A 数据 就订阅一下 这样可以保证拿到 A 数据后再执行后面的逻辑

const aData$ = new Subject()

最直接的,就是你在封装分页组件的时候,给这个组件添加一个属性 is_init,const is_init = ref(false)// watch 的时候判断一下if(is_init) return isInit.value = true//调用 B这样就可以避免第一次页面加载的时候调用 B 了,

题外话:本人 PHP 后端,我一般都是直接做好数据一个接口返给前端,即便是后续业务迭代,我也是这样,免得对接时候问来问去的,搞不好就出错。

这俩就是一个东西 vue-query: 'This package was migrated to be a part of github.com/TanStack/query'