我正在学习 Vue
并在浏览器错误中挣扎,我不知道如何处理。该应用程序使用 Apollo 和 GraphQL。当应该解决 Posts.vue
查询时,在导航到 infiniteScrollPosts
组件期间抛出错误。
vue.runtime.esm.js:619 [Vue warn]: Error in created hook: "TypeError: Cannot read property 'watchQuery' of undefined"
vue.runtime.esm.js:1888 TypeError: Cannot read property 'watchQuery' of undefined
at DollarApollo.watchQuery (vue-apollo.esm.js:1256)
at SmartQuery.executeApollo (vue-apollo.esm.js:798)
at SmartQuery.start (vue-apollo.esm.js:561)
at SmartQuery.autostart (vue-apollo.esm.js:470)
at DollarApollo.addSmartQuery (vue-apollo.esm.js:1334)
at VueComponent.launch (vue-apollo.esm.js:1943)
at invokeWithErrorHandling (vue.runtime.esm.js:1854)
at callHook (vue.runtime.esm.js:4219)
at VueComponent.Vue._init (vue.runtime.esm.js:5008)
at new VueComponent (vue.runtime.esm.js:5154)
这是我的组件:
<template>
<v-container v-if="infiniteScrollPosts">
<div v-for="post in infiniteScrollPosts.posts" :key="post._id">
<img :src="post.imageUrl" height="100" />
<h3>{{ post.title }}</h3>
</div>
<v-btn @click="showMorePosts" v-if="showMoreEnabled">Fetch More</v-btn>
</v-container>
</template>
<script>
import { INFINITE_SCROLL_POSTS } from "../../queries";
const pageSize = 2;
export default {
name: "Posts",
data() {
return {
pageNum: 1,
showMoreEnabled: true
};
},
apollo: {
infiniteScrollPosts: {
query: INFINITE_SCROLL_POSTS,
variables: {
pageNum: 1,
pageSize
}
}
},
methods: {
showMorePosts() {
this.pageNum++;
this.$apollo.queries.infiniteScrollPosts.fetchMore({
variables: {
pageNum: this.pageNum,
pageSize
},
updateQuery: (prevResult, { fetchMoreResult }) => {
console.log("previous result", prevResult.infiniteScrollPosts.posts);
console.log("fetch more result", fetchMoreResult);
const newPosts = fetchMoreResult.infiniteScrollPosts.posts;
const hasMore = fetchMoreResult.infiniteScrollPosts.hasMore;
this.showMoreEnabled = hasMore;
return {
infiniteScrollPosts: {
__typename: prevResult.infiniteScrollPosts.__typename,
posts: [...prevResult.infiniteScrollPosts.posts, ...newPosts],
hasMore
}
};
}
});
}
}
};
</script>
GraphQL
查询:
export const INFINITE_SCROLL_POSTS = gql`
query(
$pageNum: Int!,
$pageSize: Int!
) {
infiniteScrollPosts(
pageNum: $pageNum,
pageSize: $pageSize
) {
hasMore
posts {
_id
title
imageUrl
categories
description
likes
createdDate
messages {
_id
}
createdBy {
_id
userName
avatar
}
}
}
}
`;
解析器:
module.exports = {
Query: {
(...)
infiniteScrollPosts: async (_, { pageNum, pageSize }, { postSchema }) => {
let posts;
if (pageNum === 1) {
posts = await postSchema.find({}).sort({ createdDate: "desc" }).populate({
path: "createdBy",
model: "User"
}).limit(pageSize);
} else {
const skips = pageSize * (pageNum - 1);
posts = await postSchema.find({}).sort({ createdDate: "desc" }).populate({
path: "createdBy",
model: "User"
}).skip(skips).limit(pageSize);
}
const totalPosts = await postSchema.countDocuments();
const hasMore = totalPosts > pageSize * pageNum;
return { hasMore, posts };
},
(...)
},
Mutation: {
(...)
}
};
类型定义:
(...)
type PostsPage {
posts: [Post]
hasMore: Boolean
}
type Query {
(...)
infiniteScrollPosts(pageNum: Int!, pageSize: Int!): PostsPage
}
(...)
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import Router from "vue-router";
import store from "./store";
import vuetify from "./plugins/vuetify";
import ApolloClient from "apollo-boost";
import VueApollo from "vue-apollo";
import FormAlert from "./components/Shared/FormAlert";
import "@babel/polyfill";
Vue.component("form-alert", FormAlert);
Vue.use(VueApollo);
export const apolloClient = new ApolloClient({
uri: "http://localhost:4000/graphql",
fetchOptions: {
credentials: "include"
},
request: operation => {
if (!localStorage.token) {
localStorage.setItem("token", "");
}
operation.setContext({
headers: {
authorization: localStorage.getItem("token")
}
});
},
onError: ({ graphQLErrors, networkError }) => {
if (networkError) {
console.log("[networkError]", networkError);
}
if (graphQLErrors) {
for (let err of graphQLErrors) {
console.dir(err);
if (err.name === "AuthenticationError") {
store.commit("setAuthError", err);
store.dispatch("signoutUser");
}
}
}
}
});
const apolloProvider = new VueApollo({ apolloClient });
Vue.config.productionTip = false;
new Vue({
apolloProvider,
router,
store,
vuetify,
render: h => h(App),
created() {
this.$store.dispatch("getCurrentUser");
}
}).$mount("#app");
const originalPush = Router.prototype.push;
Router.prototype.push = function(location) {
return originalPush.call(this, location).catch(ex => {
if (ex.name !== "NavigationDuplicated") {
throw ex;
}
});
};
Vue.use(router);
非常感谢您的帮助,因为您可以将我视为 Vue 菜鸟 :)
// 编辑
回复评论:
@xadm
出现在 Posts
组件加载上的完整请求列表仅包含对 GraphQL
的一次重要调用,这是我的授权 - 这在此处有效且无关紧要。另一方面,返回 204
的那个在所有其他组件加载期间都存在,我对 Vue
不够熟悉,无法对其进行评论,但由于它在所有其他页面上,因此可以因与问题无关而被丢弃。
至于你的反应笑脸,我非常喜欢和Blazor
一起工作。
@亚当奥尔洛夫
您可以在此处找到此类用法的示例:https://apollo.vuejs.org/guide/apollo/pagination.html,您以相同的方式访问 $store
。无论如何,它与问题无关,您可以将该部分注释掉,因为在显式单击按钮之前甚至没有调用它,而不是它,问题似乎在这里:
apollo: {
infiniteScrollPosts: {
query: INFINITE_SCROLL_POSTS,
variables: {
pageNum: 1,
pageSize
}
}
},
// 编辑 2
<块引用>这不是与 Vue 相关的错误。这是 Apollo 客户端错误。互联网上有许多关于 watchQuery 和 undefined 使用不同框架的类似错误。可能是您在页面加载时使用 apollo 来快速,并且它尚未初始化。 – 用户1093555
是的,我想,我在这里发帖之前浏览了这些,但没有发现任何可以帮助我确定问题的内容。我认为这在生命周期中也可能为时过早,但这段精确的代码在许多教程和文档页面中重复出现,包括我之前链接的官方页面。
<块引用>我不关心所有请求......只检查 POST /graphql 请求和响应细节(标题,正文) - 找到工作(?)首先和后续(fetchMore,下一页)或工作请求之间的差异操场......我假设你根本不知道反应,你看不到所有这些“模板化”,准[非真实]组件解决方案的差异/距离 – xadm
fetchMore
无关紧要,因为应用程序甚至还没有到达那里,它需要用户先点击一个按钮。您可以从我的代码中注释掉整个 methods
部分,它甚至没有达到那个点。我发布了请求的截图仅供参考,我告诉你我检查了标题和正文,唯一的请求是授权用户并且它有效(发送正确的数据,接收正确的响应)。甚至没有调用应该加载帖子的那个。不,我从来没有使用过 React,因为我从来不需要,我学习 Vue
来扩展我的知识,尽管我也不需要它。
我相信我遵循的教程的重点是表明可以将 apollo 直接与 apollo: (...)
一起使用。
我是 Vue 菜鸟,但我不是白痴,很容易通过以下修改使其工作:
Posts.vue
<template>
<v-container text-center v-if="posts && posts.length > 0">
<div v-for="post in posts" :key="post._id">
<img :src="post.imageUrl" height="100" />
<h3>{{ post.title }}</h3>
</div>
<v-btn class="mt-3 info" @click="showMorePosts" v-if="hasMore" info>Fetch More</v-btn>
</v-container>
</template>
<script>
import { mapGetters } from "vuex";
const pageSize = 2;
export default {
name: "Posts",
data() {
return {
pageNum: 1
};
},
created() {
this.handleGetInfiniteScrollPosts();
},
computed: {
...mapGetters(["posts", "hasMore"])
},
methods: {
handleGetInfiniteScrollPosts() {
this.$store.dispatch("getInfiniteScrollPosts", {
pageNum: this.pageNum,
pageSize
});
},
showMorePosts() {
this.pageNum++;
this.handleGetInfiniteScrollPosts();
}
}
};
</script>
store.js
:
(...)
Vue.use(Vuex);
(...)
export default new Vuex.Store({
state: {
posts: [],
hasMore: true
(...)
},
mutations: {
(...)
setPosts: (state, payload) => {
state.posts = payload;
},
updatePosts: (state, payload) => {
state.posts = [...state.posts, ...payload];
},
setHasMore: (state, payload) => {
state.hasMore = payload;
},
(...)
},
actions: {
(...)
getInfiniteScrollPosts: ({ commit }, payload) => {
if (payload.pageNum === 1) {
commit("setPosts", []);
}
apolloClient.query({
query: INFINITE_SCROLL_POSTS,
variables: payload
}).then(({ data }) => {
commit("updatePosts", data.infiniteScrollPosts.posts);
commit("setHasMore", data.infiniteScrollPosts.hasMore);
});
},
(...)
},
getters: {
posts: state => state.posts,
hasMore: state => state.hasMore,
(...)
}
});
证明: https://www.dropbox.com/s/r6ol4htbu5503hs/VueInfinitePosts.mp4?dl=0
在这种情况下,两个相关的 graphql 请求都用正确的响应体解决,并且没有任何错误。然而,这是无关紧要的,因为正如我所说,这不是这篇文章的内容。