首页 > 解决方案 > Vue、Apollo、GraphQL:“TypeError:无法读取未定义的属性‘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)

在此处输入图像描述

这是我的组件:

<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 Noob :)。

// 编辑

要回答评论:

@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 请求都使用正确的响应主体解决,并且没有任何错误。然而,这是题外话,因为正如我所说,这不是这篇文章的内容。

标签: javascriptvue.jsgraphqlapollo

解决方案


推荐阅读