跳至内容

Mutations

现在我们已经了解了 如何获取数据,下一步就是学习如何使用 **mutations** 更新数据。如果您需要复习 mutations 或 GraphQL 文档,请阅读 本指南

执行 mutation

useMutation 组合函数是 Vue 组件中设置 mutations 的主要方法。

首先在您的组件中导入它

vue
<script>
import { useMutation } from '@vue/apollo-composable'

export default {
  setup () {
    // Your data & logic here...
  },
}
</script>

您可以在您的 setup 选项中使用 useMutation,将 GraphQL 文档作为第一个参数传递给它,并检索 mutate 函数

vue
<script>
import { useMutation } from '@vue/apollo-composable'
import gql from 'graphql-tag'

export default {
  setup () {
    const { mutate } = useMutation(gql`
      mutation sendMessage ($input: SendMessageInput!) {
        sendMessage (input: $input) {
          id
        }
      }
    `)
  },
}
</script>

通常最好将 mutate 函数重命名为更明确的名称

vue
<script>
import { useMutation } from '@vue/apollo-composable'
import gql from 'graphql-tag'

export default {
  setup () {
    const { mutate: sendMessage } = useMutation(gql`
      mutation sendMessage ($input: SendMessageInput!) {
        sendMessage (input: $input) {
          id
        }
      }
    `)
  },
}
</script>

我们现在可以在模板中使用它,将变量传递给 mutate(现在称为 sendMessage

vue
<script>
import { useMutation } from '@vue/apollo-composable'
import gql from 'graphql-tag'

export default {
  setup () {
    const { mutate: sendMessage } = useMutation(gql`
      mutation sendMessage ($text: String!) {
        sendMessage (text: $text) {
          id
        }
      }
    `)

    return {
      sendMessage,
    }
  },
}
</script>

<template>
  <div>
    <button @click="sendMessage({ text: 'Hello' })">
      Send message
    </button>
  </div>
</template>

我们也可以在 JavaScript 代码中调用 sendMessage

选项

我们可以将选项作为第二个参数传递给 useMutation

js
const { mutate: sendMessage } = useMutation(gql`
  mutation sendMessage ($text: String!) {
    sendMessage (text: $text) {
      id
    }
  }
`, {
  fetchPolicy: 'no-cache',
})

它也可以是一个返回选项对象的函数。它将在调用 mutate(此处为 sendMessage)时自动调用,在 Apollo Client 上执行 mutation 之前。

js
const { mutate: sendMessage } = useMutation(gql`
  mutation sendMessage ($text: String!) {
    sendMessage (text: $text) {
      id
    }
  }
`, () => ({
  fetchPolicy: 'no-cache',
}))

请参阅 API 参考,了解所有可能的选项。

变量

有两种方法可以将变量对象传递给 mutation。

我们已经看到了第一个方法:我们可以在调用 mutate 函数时传递变量

js
const { mutate: sendMessage } = useMutation(gql`
  mutation sendMessage ($text: String!) {
    sendMessage (text: $text) {
      id
    }
  }
`)

sendMessage({
  text: 'Hello',
})

另一种方法是将它们放在 options

js
const { mutate: sendMessage } = useMutation(gql`
  mutation sendMessage ($text: String!) {
    sendMessage (text: $text) {
      id
    }
  }
`, {
  variables: {
    text: 'Hello',
  },
})

sendMessage()

大多数情况下,我们的变量将是动态的。假设我们有一个 text ref,它将由用户更新

js
const text = ref('')

const { mutate: sendMessage } = useMutation(gql`
  mutation sendMessage ($text: String!) {
    sendMessage (text: $text) {
      id
    }
  }
`, {
  variables: {
    // This will not work! See explanation below
    text: text.value,
  },
})

sendMessage()

这将不起作用!实际上,我们只设置了一次变量,因此即使 text ref 后来更改,variables.text 也将永远为空字符串。

解决方案是使用选项函数语法,以便每次执行 mutation 时都会调用它

js
const text = ref('')

const { mutate: sendMessage } = useMutation(gql`
  mutation sendMessage ($text: String!) {
    sendMessage (text: $text) {
      id
    }
  }
`, () => ({
  variables: {
    text: text.value,
  },
}))

sendMessage()

我们的组件现在看起来像这样

vue
<script>
import { ref } from 'vue'
import { useMutation } from '@vue/apollo-composable'
import gql from 'graphql-tag'

export default {
  setup () {
    const text = ref('')

    const { mutate: sendMessage } = useMutation(gql`
      mutation sendMessage ($text: String!) {
        sendMessage (text: $text) {
          id
        }
      }
    `, () => ({
      variables: {
        text: text.value,
      },
    }))

    return {
      text,
      sendMessage,
    }
  },
}
</script>

<template>
  <div>
    <input v-model="text" placeholder="Enter a message">

    <button @click="sendMessage()">
      Send message
    </button>
  </div>
</template>

如果您在选项中指定了一个变量对象,并在调用 mutate 时指定了一个变量对象,则这些变量对象将被合并。

js
const text = ref('')

const { mutate: sendMessage } = useMutation(gql`
  mutation sendMessage ($text: String!, $subject: String!) {
    sendMessage (text: $text, subject: $subject) {
      id
    }
  }
`, () => ({
  variables: {
    subject: 'Message sent from my app',
  },
}))

sendMessage({
  text: text.value,
})

在 mutation 后更新缓存

当您执行 mutation 时,您会在服务器上修改数据。在大多数情况下,这些数据也存在于您的客户端缓存中,因此您可能需要更新它以反映 mutation 所做的更改。这取决于 mutation 是否更新单个现有实体。

更新单个现有实体

在这种情况下,Apollo Client 将使用 mutation 执行返回的数据自动更新缓存中的实体。它将使用修改后的实体的 id 以及它的 __typename 在缓存中找到它并更新修改后的字段。

js
const { mutate: editMessage } = useMutation(gql`
  mutation editMessage ($id: ID!, $text: String!) {
    editMessage (id: $id, text: $text) {
      id
      text
    }
  }
`, () => ({
  variables: {
    id: 'abc',
    text: 'Hi! How is it going?',
  },
}))

editMessage()

在这个例子中,如果我们已经加载了包含 id 等于 abc 的消息列表,mutation 将在缓存中搜索它并自动更新它的 text 字段。

进行所有其他缓存更新

如果 mutation 修改了多个实体,或者它创建或删除了一个或多个实体,Apollo Client 不会自动更新缓存以反映 mutation 所做的更改。相反,您应该使用选项中的 update 函数来更新缓存。

update 函数的目的是修改您的缓存数据以匹配 mutation 在服务器上所做的更改。请注意,Apollo 缓存数据是不可变的,因此您需要使用 ...(展开)运算符或在修改它之前深度克隆缓存数据。

例如,我们的 sendMessage mutation 应该将新消息添加到我们的缓存中

vue
<script>
import { useQuery, useMutation } from '@vue/apollo-composable'
import gql from 'graphql-tag'

const MESSAGES = gql`
  query getMessages {
    messages {
      id
      text
    }
  }
`

export default {
  setup () {
    // Messages list
    const { result } = useQuery(MESSAGES)
    const messages = computed(() => result.value?.messages ?? [])

    // Send a new message
    const newMessage = ref('')
    const { mutate: sendMessage } = useMutation(gql`
      mutation sendMessage ($text: String!) {
        sendMessage (text: $text) {
          id
          text
        }
      }
    `, () => ({
      variables: {
        text: newMessage.value,
      },
      update: (cache, { data: { sendMessage } }) => {
        let data = cache.readQuery({ query: MESSAGES })
        data = {
          ...data,
          messages: [
            ...data.messages,
            sendMessage,
          ],
        }
        cache.writeQuery({ query: MESSAGES, data })
      },
    }))

    return {
      messages,
      newMessage,
      sendMessage,
    }
  },
}
</script>

<template>
  <div>
    <input v-model="newMessage" @keyup.enter="
      sendMessage()
      newMessage = ''
    ">

    <ul v-if="messages">
      <li v-for="message of messages" :key="message.id">
        {{ message.text }}
      </li>
    </ul>
  </div>
</template>

让我们关注 mutation

js
const { mutate: sendMessage } = useMutation(gql`
  mutation sendMessage ($text: String!) {
    sendMessage (text: $text) {
      id
      text
    }
  }
`, () => ({
  variables: {
    text: newMessage.value,
  },
  update: (cache, { data: { sendMessage } }) => {
    let data = cache.readQuery({ query: MESSAGES })
    data = {
      ...data,
      messages: [
        ...data.messages,
        sendMessage,
      ],
    }
    cache.writeQuery({ query: MESSAGES, data })
  },
}))

update 函数获取一个表示 Apollo Client 缓存的 cache 对象。它提供 readQuerywriteQuery 函数,使您能够在缓存上执行 GraphQL 操作并修改预期结果。

第二个参数是一个包含 mutation 结果数据的对象。这应该用于修改缓存数据并使用 cache.writeQuery 将其写回。

调用 update 函数后,缓存中数据已更改的组件将自动重新渲染。在我们的示例中,消息列表将自动更新为从 mutation 结果接收到的新消息。

或者,我们可以在调用 mutate 函数时在第二个参数中传递一个 update 函数

js
const { mutate: sendMessage } = useMutation(gql`
  mutation sendMessage ($text: String!) {
    sendMessage (text: $text) {
      id
      text
    }
  }
`)

sendMessage({
  text: text.value
},
{
  update: (cache, { data: { sendMessage } }) => {
    let data = cache.readQuery({ query: MESSAGES })
    data = {
      ...data,
      messages: [
        ...data.messages,
        sendMessage,
      ],
    }
    cache.writeQuery({ query: MESSAGES, data })
  }
})

Mutation 状态

加载

使用 useMutation 返回的 loading ref 来跟踪 mutation 是否正在进行

js
const { mutate: sendMessage, loading: sendMessageLoading } = useMutation(gql`
  mutation sendMessage ($text: String!) {
    sendMessage (text: $text) {
      id
      text
    }
  }
`)

通常最好将其重命名(此处为 sendMessageLoading)。

错误

使用 error ref 跟踪 mutation 期间是否发生任何错误

js
const { mutate: sendMessage, error: sendMessageError } = useMutation(gql`
  mutation sendMessage ($text: String!) {
    sendMessage (text: $text) {
      id
      text
    }
  }
`)

事件钩子

onDone

mutation 成功完成时调用此方法。

js
const { onDone } = useMutation(...)

onDone(result => {
  console.log(result.data)
})

在我们的示例中,这对于重置消息输入非常有用

vue
<script>
import { useQuery, useMutation } from '@vue/apollo-composable'
import gql from 'graphql-tag'

const MESSAGES = gql`
  query getMessages {
    messages {
      id
      text
    }
  }
`

export default {
  setup () {
    // Messages list
    const { result } = useQuery(MESSAGES)
    const messages = computed(() => result.value?.messages ?? [])

    // Send a new message
    const newMessage = ref('')
    const { mutate: sendMessage, onDone } = useMutation(gql`
      mutation sendMessage ($text: String!) {
        sendMessage (text: $text) {
          id
          text
        }
      }
    `, () => ({
      variables: {
        text: newMessage.value,
      },
      update: (cache, { data: { sendMessage } }) => {
        let data = cache.readQuery({ query: MESSAGES })
        data = {
          ...data,
          messages: [
            ...data.messages,
            sendMessage,
          ],
        }
        cache.writeQuery({ query: MESSAGES, data })
      },
    }))

    onDone(() => {
      newMessage.value = ''
    })

    return {
      messages,
      newMessage,
      sendMessage,
    }
  },
}
</script>

<template>
  <div>
    <input v-model="newMessage" @keyup.enter="sendMessage()">

    <ul v-if="messages">
      <li v-for="message of messages" :key="message.id">
        {{ message.text }}
      </li>
    </ul>
  </div>
</template>

onError

mutation 期间发生错误时触发此方法。

js
import { logErrorMessages } from '@vue/apollo-util'

const { onError } = useMutation(...)

onError(error => {
  logErrorMessages(error)
})