Vue、Apollo、GraphQL:“类型错误:无法读取未定义的属性‘watchQuery’”

问题描述

我正在学习 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)

enter image description here

这是我的组件:

<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);

查询本身有效:

enter image description here

非常感谢您的帮助,因为您可以将我视为 Vue 菜鸟 :)

// 编辑

回复评论

@xadm

出现在 Posts 组件加载上的完整请求列表仅包含对 GraphQL 的一次重要调用,这是我的授权 - 这在此处有效且无关紧要。另一方面,返回 204 的那个在所有其他组件加载期间都存在,我对 Vue 不够熟悉,无法对其进行评论,但由于它在所有其他页面上,因此可以因与问题无关而被丢弃。

enter image description here

至于你的反应笑脸,我非常喜欢和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 请求都用正确的响应体解决,并且没有任何错误。然而,这是无关紧要的,因为正如我所说,这不是这篇文章内容

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)