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

时间:2021-03-11 22:17:52

标签: javascript vue.js graphql apollo

我正在学习 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 请求都用正确的响应体解决,并且没有任何错误。然而,这是无关紧要的,因为正如我所说,这不是这篇文章的内容。

0 个答案:

没有答案