Vue2+TypeScript+CompositionAPI+Vue Apollo

Vue2(+ts)でCompositionAPIを導入済み前提。

ライブラリをインストール

npm i @vue/apollo-composable --save

main.ts で読み込む

import { provide } from '@vue/composition-api'
import { DefaultApolloClient } from '@vue/apollo-composable'
import { ApolloClient, InMemoryCache, HttpLink, split } from '@apollo/client/core'
import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities";

ApolloクライアントをVueにつなぐ(main.ts)

// Create an http link:
const httpLink = new HttpLink({
  uri: "http://localhost:4000/"
});

// Cache implementation
const cache = new InMemoryCache({
  typePolicies: {
    Post:{
      keyFields: ["id"],
    },
    Query:{
      fields:{
        posts:{
          merge(existing, incoming) {
            return incoming;
          }
        }
      }
    }
  }
})

// Create a WebSocket link:
const wsLink = new WebSocketLink({
  uri: `ws://localhost:4000/graphql`,
  options: {
    reconnect: true
  }
});

// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const link = split(
  // split based on operation type
  (op) => {
    const definition = getMainDefinition(op.query);
    return (
      definition.kind === "OperationDefinition" &&
      definition.operation === "subscription"
    );
  },
  wsLink,
  httpLink
);

// client apollo client
// WebSocket 不要=subscription を使わないなら、link に
// GraphQLのサーバ文字列を渡すだけでよさそう。
const apolloClient = new ApolloClient({
  link,
  cache,
})

new Vue({
  router,
  store,
  setup () {
    provide(DefaultApolloClient, apolloClient)
  },
  render: h => h(App)
}).$mount('#app')

取得

// import 部分
import { useQuery, useResult } from "@vue/apollo-composable";
// setup 抜粋
setup() {
  const {result} = useQuery(ALL_POSTS); // ALL_POST は gql`~`
  const posts = useResult(result, null, data => data.posts);
  return {posts}
}

追加・更新・削除

// import 部分
import { useMutation, useResult } from "@vue/apollo-composable";
// setup 抜粋
setup() {
  const createPost = ():void => {
    mCreatePost({
      title:post.title,
      author:post.author,
    });
  }

  const updatePost = ():void => {
    mUpdatePost({
      id:post.id,
      title:post.title,
      author:post.author,
    });
  }

  const deletePost = (id:string):void => {
    mDeletePost({
      id
    });
  }

  // mutate を mCreatePost, mUpdatePost,名前を変えて受け取り
  const { mutate:mCreatePost } = useMutation(CREATE_POST,{ // CREATE_POST は gql`~`
    // data:{ createPost } は mutation で指定しているキー名
    update: (cache, { data: { createPost } }) => {
      const data:{posts:Post[]}|null = cache.readQuery({ query: ALL_POSTS })
      if(data !== null){
        const ret:{posts:Post[]} = {posts:data.posts.concat([createPost])}
        cache.writeQuery({ query: ALL_POSTS, data:ret })
      }
    },
  });

  const { mutate:mUpdatePost } = useMutation(UPDATE_POST,{ // UPDATE_POSTは gql`~`
    // data:{ updatePost } は mutation で指定しているキー名
    update: (cache, { data: { updatePost } }) => {
      const data:{posts:Post[]}|null = cache.readQuery({ query: ALL_POSTS })
      if(data !== null){
        const ret:{posts:Post[]} = {posts:data.posts.reduce( (ret:Post[], post:Post):Post[]=>{
          if(post.id === updatePost.id){
            ret.push({
              id:post.id,
              title:updatePost.title,
              author:updatePost.author
            })
          }else{
            ret.push(post)
          }
          return ret;
        },[])};
        cache.writeQuery({ query: ALL_POSTS, data:ret })
      }
    },
  });

  const { mutate:mDeletePost } = useMutation(DELETE_POST,{ // DELETE_POSTは gql`~`
    // data:{ createPost } は mutation で指定しているキー名
    update: (cache, { data: { deletePost } }) => {
      const data:{posts:Post[]}|null = cache.readQuery({ query: ALL_POSTS })
      if(data !== null) {
        const ret:{posts:Post[]} = {posts:data.posts.reduce( (ret:Post[], post:Post):Post[]=>{
          if(post.id !== deletePost.id){
            ret.push(post)
          }
          return ret;
        },[])};
        cache.writeQuery({ query: ALL_POSTS, data:ret })
      }
    },
  });
  return {createPost, updatePost, deletePost }
}

subscription

※ただし、データの反映を考えると、後述のsubscribeToMore の方が使い勝手がよさそうなので、ここの useSubscription をつかうことはあまりないのかも?…ということでデータがとれるところまでしか確認してない

// import 部分
import { useSubscription } from "@vue/apollo-composable";
// setup 抜粋
setup() {
  const {result} = useSubscription(
    SUBSCRIPTION_POST // SUBSCRIPTION_POST は gql`~`
  );

  watch(
    () => result,
    (data:any):void => {
      if(data !== null){
        if(data.value.post.mutation === 'DELETED'){
          console.log(data.value.post.data.id, '削除');
        }else if(data.value.post.mutation === 'UPDATED'){
          console.log(data.value.post.data.id, '更新');
        }else if(data.value.post.mutation === 'CREATED'){
          console.log(data.value.post.data.id, '追加');
        }
      }
    },
    {
      immediate:false,
      deep:true
    }
  );
}

subscribeToMore

// setup 抜粋
setup() {
  // まず、useQueryのところで subscribeToMore も受け取る
  const {result, subscribeToMore} = useQuery(ALL_POSTS);
  subscribeToMore(() => ({
    document: SUBSCRIPTION_POST,
    updateQuery: (previousResult, { subscriptionData }) => {
      const ret:{posts:Post[]} = {posts:[].concat(previousResult.posts)};
      if(subscriptionData.data.post.mutation === 'DELETED'){
        console.log(subscriptionData.data.post.data.id, '削除');
        const postIndex = ret.posts.findIndex((post) => post.id === subscriptionData.data.post.data.id);
        if(postIndex !== -1){
          ret.posts.splice(postIndex,1);
        }
      }else if(subscriptionData.data.post.mutation === 'UPDATED'){
        console.log(subscriptionData.data.post.data.id, '更新');
        ret.posts.forEach(post => {
          if(post.id === subscriptionData.data.post.data.id){
            post.title = subscriptionData.data.post.data.title;
            post.author = subscriptionData.data.post.data.author;
          }
        })
      }else if(subscriptionData.data.post.mutation === 'CREATED'){
        console.log(subscriptionData.data.post.data.id, '追加');
        ret.posts = previousResult.posts.concat([subscriptionData.data.post.data]);
      }
      return ret;
    }
  }))
  
  // また、関連して、追加・更新・削除 のところで更新後の処理が不要になる
  /* 中略 */
  const { mutate:mCreatePost } = useMutation(CREATE_POST)
  const { mutate:mUpdatePost } = useMutation(UPDATE_POST)
  const { mutate:mDeletePost } = useMutation(DELETE_POST)

}