如何在 Vue 3 中的组件中多次挂钩
How to hook multiple times in component in Vue 3
我在一节课中看到我们可以使用组合创建 api hook usePromise 但问题是我有带有待办事项列表的简单 crud 应用程序,我在其中创建、删除、获取 API 调用,我不明白如何在一个组件中对所有 api 使用此挂钩。所有调用都正确但加载不正确,它仅在第一次调用 PostService.getAll() 时有效,然后加载程序未被触发。感谢回复。
usePromise.js
import { ref } from 'vue';
export default function usePromise(fn) {
const results = ref(null);
const error = ref(null);
const loading = ref(false);
const createPromise = async (...args) => {
loading.value = true;
error.value = null;
results.value = null;
try {
results.value = await fn(...args);
} catch (err) {
error.value = err;
} finally {
loading.value = false;
}
};
return { results, loading, error, createPromise };
}
apiClient.js
import axios from 'axios';
export default axios.create({
baseURL: 'https://jsonplaceholder.typicode.com/',
withCredentials: false,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
PostService.js
import apiClient from './apiClient';
const urlPath = '/posts';
export default {
getAll() {
return apiClient.get(urlPath);
},
add(post) {
return apiClient.post(urlPath, post);
},
delete(id) {
return apiClient.delete(`${urlPath}/${id}`);
},
};
List.vue
<template>
<div>
<VLoader v-if="loading" />
<template v-else>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Title</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="post in posts" :key="post.id">
<td>{{ post.id }}</td>
<td>{{ post.title }}</td>
<td>
<button class="btn btn-danger ml-1" @click="deletePost(post.id)">Delete</button>
</td>
</tr>
</tbody>
</table>
</template>
</div>
</template>
<script>
import { ref, computed, watch, unref } from 'vue';
import PostService from '@/services/PostService';
import usePromise from '@/use/usePromise';
export default {
setup() {
const posts = ref([]);
const post = ref({
title: '',
body: '',
});
const {
results: postsResultRef,
loading: postsLoadingRef,
createPromise: getAllPosts,
} = usePromise(() => PostService.getAll());
getAllPosts(); //get all posts by initialize component
const {
results: postDeleteResultRef,
loading: postDeleteLoadingRef,
createPromise: deletePost,
} = usePromise((id) => PostService.delete(id).then((result) => ({ ...result, removedId: id })));
watch(postsResultRef, (postsResult) => {
posts.value = postsResult.data;
});
watch(postDeleteResultRef, (postDeleteResult) => {
if (postDeleteResult.status === 200) {
posts.value = posts.value.filter((item) => item.id != postDeleteResult.removeId);
// unref(posts).splice(/* remove postDeleteResult.removedId */);
}
});
const loading = computed(() => [postsLoadingRef, postDeleteLoadingRef].map(unref).some(Boolean));
return { posts, post, loading };
},
};
</script>
问题只是第一个 loading
引用从 setup()
返回。其他的在每个方法中都是隐藏和未使用的。
一种解决方案是跟踪 state
中的活动 loading
ref,从 setup()
返回:
声明state.loading
.
export default {
setup() {
const state = reactive({
//...
loading: null,
})
//...
}
}
将 state.loading
设置为每个方法中的 loading
引用。
const fetchPosts = () => {
const { results, loading, createPromise } = usePromise(/*...*/)
state.loading = loading
//...
}
const deletePost = (id) => {
const { results, loading, createPromise } = usePromise(/*...*/)
state.loading = loading;
//...
}
const onSubmit = () => {
const { results, loading, createPromise } = usePromise(/*...*/)
state.loading = loading
//...
}
删除最初从 setup()
返回的 loading
ref,因为我们已经有了 state.loading
,而 toRefs(state)
会暴露 loading
已经添加到模板中:
export default {
setup() {
//...
//return { toRefs(state), loading }
// ^^^^^^^
return { toRefs(state) }
}
}
ref 保留对一个值的反应性引用,该值应该存在于整个组件生命周期中。它在组件的其他地方保持反应 - 模板、计算属性、观察者等。
像usePromise
这样的钩子应该在setup
函数中设置(因此得名):
const { results, loading, createPromise } = usePromise(() => PostService.getAll()
对于多个请求,可以组合多个hook结果:
const posts = ref([]);
const { results: postsResultRef, loading: postsLoadingRef, createPromise: getAllPosts } = usePromise(() =>
PostService.getAll()
);
const { results: postDeleteResultRef, loading: postDeleteLoadingRef, createPromise: deletePost } = usePromise(id =>
PostService.delete(id).then(result => ({...result, removedId: id }))
);
...
watch(postsResultRef, postsResult => {
posts.value = postsResult.data
});
watch(postDeleteResultRef, postDeleteResult => {
if (postDeleteResult.status === 200)
unref(posts).splice(/* remove postDeleteResult.removedId */)
});
...
const loading = computed(() => [postsLoadingRef, postDeleteLoadingRef, ...].map(unref).some(Boolean))
getAllPosts
等应该用作回调,例如在模板中,promise it returns 通常不需要显式处理和链接,因为它的当前状态已经反映在挂钩结果中。这表明挂钩中存在潜在缺陷,因为 createPromise
参数在结果可用时是未知的,这需要为删除结果明确提供参数。
我在一节课中看到我们可以使用组合创建 api hook usePromise 但问题是我有带有待办事项列表的简单 crud 应用程序,我在其中创建、删除、获取 API 调用,我不明白如何在一个组件中对所有 api 使用此挂钩。所有调用都正确但加载不正确,它仅在第一次调用 PostService.getAll() 时有效,然后加载程序未被触发。感谢回复。
usePromise.js
import { ref } from 'vue';
export default function usePromise(fn) {
const results = ref(null);
const error = ref(null);
const loading = ref(false);
const createPromise = async (...args) => {
loading.value = true;
error.value = null;
results.value = null;
try {
results.value = await fn(...args);
} catch (err) {
error.value = err;
} finally {
loading.value = false;
}
};
return { results, loading, error, createPromise };
}
apiClient.js
import axios from 'axios';
export default axios.create({
baseURL: 'https://jsonplaceholder.typicode.com/',
withCredentials: false,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
PostService.js
import apiClient from './apiClient';
const urlPath = '/posts';
export default {
getAll() {
return apiClient.get(urlPath);
},
add(post) {
return apiClient.post(urlPath, post);
},
delete(id) {
return apiClient.delete(`${urlPath}/${id}`);
},
};
List.vue
<template>
<div>
<VLoader v-if="loading" />
<template v-else>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Title</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="post in posts" :key="post.id">
<td>{{ post.id }}</td>
<td>{{ post.title }}</td>
<td>
<button class="btn btn-danger ml-1" @click="deletePost(post.id)">Delete</button>
</td>
</tr>
</tbody>
</table>
</template>
</div>
</template>
<script>
import { ref, computed, watch, unref } from 'vue';
import PostService from '@/services/PostService';
import usePromise from '@/use/usePromise';
export default {
setup() {
const posts = ref([]);
const post = ref({
title: '',
body: '',
});
const {
results: postsResultRef,
loading: postsLoadingRef,
createPromise: getAllPosts,
} = usePromise(() => PostService.getAll());
getAllPosts(); //get all posts by initialize component
const {
results: postDeleteResultRef,
loading: postDeleteLoadingRef,
createPromise: deletePost,
} = usePromise((id) => PostService.delete(id).then((result) => ({ ...result, removedId: id })));
watch(postsResultRef, (postsResult) => {
posts.value = postsResult.data;
});
watch(postDeleteResultRef, (postDeleteResult) => {
if (postDeleteResult.status === 200) {
posts.value = posts.value.filter((item) => item.id != postDeleteResult.removeId);
// unref(posts).splice(/* remove postDeleteResult.removedId */);
}
});
const loading = computed(() => [postsLoadingRef, postDeleteLoadingRef].map(unref).some(Boolean));
return { posts, post, loading };
},
};
</script>
问题只是第一个 loading
引用从 setup()
返回。其他的在每个方法中都是隐藏和未使用的。
一种解决方案是跟踪 state
中的活动 loading
ref,从 setup()
返回:
声明
state.loading
.export default { setup() { const state = reactive({ //... loading: null, }) //... } }
将
state.loading
设置为每个方法中的loading
引用。const fetchPosts = () => { const { results, loading, createPromise } = usePromise(/*...*/) state.loading = loading //... } const deletePost = (id) => { const { results, loading, createPromise } = usePromise(/*...*/) state.loading = loading; //... } const onSubmit = () => { const { results, loading, createPromise } = usePromise(/*...*/) state.loading = loading //... }
删除最初从
setup()
返回的loading
ref,因为我们已经有了state.loading
,而toRefs(state)
会暴露loading
已经添加到模板中:export default { setup() { //... //return { toRefs(state), loading } // ^^^^^^^ return { toRefs(state) } } }
ref 保留对一个值的反应性引用,该值应该存在于整个组件生命周期中。它在组件的其他地方保持反应 - 模板、计算属性、观察者等。
像usePromise
这样的钩子应该在setup
函数中设置(因此得名):
const { results, loading, createPromise } = usePromise(() => PostService.getAll()
对于多个请求,可以组合多个hook结果:
const posts = ref([]);
const { results: postsResultRef, loading: postsLoadingRef, createPromise: getAllPosts } = usePromise(() =>
PostService.getAll()
);
const { results: postDeleteResultRef, loading: postDeleteLoadingRef, createPromise: deletePost } = usePromise(id =>
PostService.delete(id).then(result => ({...result, removedId: id }))
);
...
watch(postsResultRef, postsResult => {
posts.value = postsResult.data
});
watch(postDeleteResultRef, postDeleteResult => {
if (postDeleteResult.status === 200)
unref(posts).splice(/* remove postDeleteResult.removedId */)
});
...
const loading = computed(() => [postsLoadingRef, postDeleteLoadingRef, ...].map(unref).some(Boolean))
getAllPosts
等应该用作回调,例如在模板中,promise it returns 通常不需要显式处理和链接,因为它的当前状态已经反映在挂钩结果中。这表明挂钩中存在潜在缺陷,因为 createPromise
参数在结果可用时是未知的,这需要为删除结果明确提供参数。