请教一个 Vue 请求后端 API 顺序问题

45 天前
 imherer

有 A 、B 两个 API ,其中 B 的结果需要依赖 A 的结果来组装数据(类似于 B 是用户表存了角色 ID ,界面显示的时候需要依赖 A 角色表里的角色名称)

之前的做法是在onMounted里先调用 A 然后再调用 B ,其中 A 被调用后我就把结果存起来了,只用调用这一次

现在我封装了一个分页组件,于是我不在onMounted里调用 B 了,直接watch页码和页大小有变化的时候调用 B ,但是这样会导致一个问题:就是页面一加载的时候 watch 就会生效,导致 B 比 A 先执行。但是我又不想把 A 也放在watch里,因为它的数据几乎是不会变的,类似于角色表,页面加装的时候调用一次即可

大佬们有什么思路吗?

3212 次点击
所在节点    程序员
42 条回复
shenleiyin
45 天前
@imherer 对的,感觉就是这么回事,然后就贴这儿了。
gitignore
45 天前
我可能会这样写:


```typescript
type 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) {
/** @todo error handler */
} else {
role.value = data.value;
onLoadPageData();
}
}

initRole();
watch(page, onLoadPageData, { immediate: true });

return { page, data, error, loading, onLoadPageData }
}
```
freezebreze
45 天前
watch 到变化 emit 一个事件让父组件件调用接口 B
M003
45 天前
是不是应该配置 immediate 为 false
mwjz
45 天前
我封装封装通用组件的思路,

1. 传递一个获取数据的函数,而不是一个简单的路由,这个函数参数为页码条数据。
props: {
auto: Boolean
dataFunc: Function as PropType<
(page: PaginationRequest) => Promise<XOR<PaginationResponse, Array<any>>>
>,
}

传递函数的原因是增加可控性,比如请求条件,数据过滤、转换,接口依赖等, 全部可以交给外部控制。

过滤条件,不同的业务可以做到随便控制。
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<any>>>
>,
}
oouz
45 天前
你是不是给 watch 加了 immediate: true ? watch 默认是懒执行的:仅当数据源变化时,才会执行回调。
ixixi
45 天前
提供一个我们在用的一个思路 用 rxjs
eurkidu
45 天前
按现有逻辑
方案一,现有逻辑,类似 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 实现的,分页触发只会在用户手动切换分页的时候触发,初始化的时候因为第一页逻辑自己控制,可以灵活的处理各类情况。
eurkidu
45 天前
@eurkidu 还有一个常见方案,即如果你这个 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 数据的加载顺序,当然这个逻辑比较偏向固定这个逻辑的页面的写法,不适合通用组件封装的逻辑。
Curtion
45 天前
A 在路由中请求也行
qiaobeier
45 天前
典型的异步编程的需求,custom event ,callback ,promise 甚至轮询都可以做到。
imherer
45 天前
@eurkidu 确实,你说的有道理,如果有 url 带 page 的情况,确实会出现额外出发一次分页请求,不过目前这个页面的场景不会出现
realJamespond
45 天前
等 A 执行完后放全局,再渲染分页 onMounted 中调 B ?
imherer
45 天前
@M003
@oouz 是的,immediate 设置为 false ,然后在 onMounted 里分别调用 A ,B 也能解决问题
a4854857
45 天前
最简单的做法就是把 A 放在 B 函数里面, 如果发现没有 A 数据就直接先 await A. mounted 不做任何操作
vialon17
45 天前
添加一个 A 的控制变量就行,watch B 的时候检查一下,
有了就直接请求 B ,没了就请求一下 A 。
或者既然 A 用户表不怎么改变,直接 created 的时候请求存一下不就可以了?
zhouS9
45 天前
虽然数据上有依赖关系,但是请求可以各走各的,都返回了就正常显示,否则就等待,vue 的响应视图会自动处理好
wzzx
45 天前
rxjs 哪个页面依赖这个 A 数据 就订阅一下 这样可以保证拿到 A 数据后再执行后面的逻辑
wzzx
45 天前
const aData$ = new Subject()
TangYuSen
45 天前
最直接的,就是你在封装分页组件的时候,给这个组件添加一个属性 is_init,

```
const is_init = ref(false)

// watch 的时候判断一下
if(is_init) return
isInit.value = true
//调用 B
```
这样就可以避免第一次页面加载的时候调用 B 了,

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1040134

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX