Я использую GraphQL с первого выпуска Vue-Apollo для каждого проекта разработки API, и, как и в любой другой новой рабочей среде, у Vue-Apollo есть свои недостатки. Один из них заключается в том, что вы не можете просто обновить собственное хранилище, не вызывая мутацию (на практике, фактически, запрос также обновляет хранилище, но это не относится к этой теме).

Очевидно, с девелоперской стороны это вполне логично. Если вы хотите сохранить свою концепцию SSOT, то это правильный путь.

Но, и всегда есть "но", в некоторых случаях может потребоваться обновить полученные данные GraphQL (в данном контексте store) на экране клиента. В моем случае вот сценарий:

  1. На стороне клиента есть простой ввод, и пользователь вводит некоторые номера Id в базу данных,
  2. На той же странице есть список «Последние номера идентификаторов», чтобы пользователь мог увидеть некоторые результаты после отправки данных,
  3. Когда страница загружается в первый раз, последние 10 номеров идентификаторов, полученных с помощью запроса GraphQL,
  4. Когда пользователь отправляет новый номер идентификатора, срабатывает мутация и обновляет запрос новыми данными,
  5. На стороне сервера команда Laravel работает каждую минуту, чтобы проверить, не помечен ли номер идентификатора как «недействительный»,
  6. Для этих номеров идентификаторов существует сторонний сервер проверки, и эта команда запускает задание для отправки данных номера идентификатора для проверки,
  7. А пока, чтобы показать пользователю, какова ситуация с этим процессом проверки, не обновляя его страницу или не опрашивая сервер GraphQL каждую секунду, я использую службу Pusher и что задание запускает событие для обновления списка «Последние номера идентификаторов», и начинается самое интересное,
  8. Хотя пользователь все еще может вставлять новые номера Id в базу данных, он все еще может видеть, что его список заполняется данными, которые он вводит, и данными, которые Pusher обновляет.

Проблема с этой концепцией в том, что вы можете обновить данные Vue (полученные из запроса GraphQL), и ваша страница все равно будет в полном порядке. На номер 8 выше Pusher отправляет обновление по своему каналу и сообщает об обновленных данных:

mounted () {
   this.channel = this.$pusher.subscribe(`private-User.Entries.${this.user.id}`)
   this.channel.bind('pusher:subscription_succeeded', (e) => {
      console.warn('Subscribed to Entries channel.')
   })
   this.channel.bind('App\\Events\\EntryStatus', (data) => {
      this.onUpdateEntry(data)
   })
}

В этом коде вы можете видеть, что всякий раз, когда событие EntryStatus запускается на стороне Laravel, оно обновляет Pusher, а Pusher обновляет мое приложение Vue, а затем запускает метод onUpdateEntry с его обновленными данными.

data () {
    return {
        pendingUpdates: []
    }
},
methods: {
   onUpdateEntry (entryUpdated) {
      // First part
      const entryRendered = this.$apolloData.data.entriesPaginate.find(entry => entry.id === entryUpdated.id)
     if (entryRendered !== undefined) {
         entryRendered.status = entryUpdated.status
     }
     // Second part
     const index = this.pendingUpdates.findIndex(entry => entry.id === entryUpdated.id)
     if (index !== -1) {
         this.$set(this.pendingUpdates, index, entryUpdated)
     } else {
         this.pendingUpdates.push(entryUpdated)
     }
}

Этот код обновляет обработанные данные GraphQL и одновременно опрашивает обновленные данные записи. Обратите внимание на объект entryRendered. Вы можете задаться вопросом, зачем кому-то опрашивать данные entryUpdated, если можно обновить apolloData, верно? Дело в том, что apolloData - это не настоящий магазин Apollo. Это набор данных, который вы можете изменить в методе результатов вашего запроса.

data () {
    return {
        pendingUpdates: [],
        loading: false,
        total: 0,
        pagination: {
            age: 1,
            rowsPerPage: 10,
            sortBy: 'id',
            descending: true
        },
        entriesPaginate: []
    }
},
apollo: {
    entriesPaginate: () => ({
        query: ENTRY_PAGINATE,
        context: { uri: 'http://localhost/graphql' },
        variables () {
            return this.pagination
        },
        result ({ data: { entriesPaginate: { data, total } }, loading }) {
            if (!loading) {
                this.total = total
                this.entriesPaginate = data.slice().map((key, i) => ({
                   ...key,
                   __index: i
                }))
            }
        },
        watchLoading (isLoading) {
            this.loading = isLoading
        }
    })
}

С Vue-Apollo вы можете игнорировать метод результата в своих запросах (а также массив entryPaginate в данных) и позволить Apollo обрабатывать присвоение результата запроса данным Vue. Но в моем случае мне понадобились общие данные отдельно, и в наборе данных могут быть другие изменения. Поэтому здесь я использую метод результата. Но использовать это или нет, массив EntriesPaginate будет частью исходного результата запроса магазина Apollo.

Поэтому не имеет значения, обновляю ли я this. $ ApolloData.data.entriesPaginate или this.entriesPaginate в методе onUpdateEntry. Когда пользователь отправляет новый идентификатор и запускает мутацию, Apollo запускает сам метод результата запроса (определенный или нет), а массив this.entriesPaginate обновляется исходными данными хранилища Apollo. Это означает, что все обновления из метода onUpdateEntry будут отменены на экране пользователя. Он / она увидит новую запись идентификатора и данные старого запроса.

Чтобы решить эту проблему, мы бы использовали метод this. $ Apollo.store, но его нет. Так что я придумал обходной вариант опроса обновленных данных. Вы можете заметить, что в методе onUpdateEntry есть две части кода с комментариями, первая и вторая. Вторая часть вставляет данные updatedEntry в массив pendingUpdates. Я сохраняю эти записи, чтобы сначала обновить хранилище, когда пользователь отправляет новый идентификатор в базу данных через мутацию. Вот:

data () {
    return {
        graphQLErrors: [],
        form: {
            idNumber: null
        },
        pendingUpdates: [], // Empty here for definition, actual data filled with multiple entryUpdated data.
    }
},
methods: {
    submit () {
        this.loading = true
        this.$apollo.mutate({
             mutation: ENTRY_CREATE,
             variables: {
                 entry: {
                     id_number: this.form.idNumber
                 }
             },
             context: { uri: 'http://localhost/graphql' },
             update: (store, { data: { entryCreated } }) => {
                 const query = {
                     query: ENTRY_PAGINATE,
                     variables: this.pagination
                 }, data = store.readQuery(query)
             
                 if (data !== undefined) {
                      // First part
                      if (this.pendingUpdates.length) {
                         this.pendingUpdates.forEach(entryUpdated => {
                             const index = data.entriesPaginate.data.findIndex(entry => entry.id === entryUpdated.id)
                             if (index !== -1) {
                                 this.$set(data.entriesPaginate.data, index, entryUpdated)
                             }
                         })
                         this.pendingUpdates = [] // Reset
                     }
                     
                     // Second part
                     if (data.entriesPaginate.data.findIndex(entry => entry.id === entryCreated.id) === -1) {
                         data.entriesPaginate.data.unshift(entryCreated)
 
                         store.writeQuery({
                             ...query,
                             data: data
                         })
                     }
                 }
             }
        }).catch((e) => {
            this.graphQLErrors = e
        }).then(() => {
            this.loading = false
        })
    }
}

Вы можете заметить, что здесь метод update Мутации снова состоит из двух частей. Первая часть перед обновлением хранилища вновь созданными данными выполняет итерацию по массиву pendingUpdates и обновляет хранилище Apollo, затем вторая часть вставляет объект entryCreated в начало list и, наконец, store.writeQuery завершает обновление.

Самая важная часть из всего вышесказанного заключается в том, что вы должны опубликовать те же данные GraphQL в Pusher. Я не исследовал, способен ли мой пакет Laravel GraphQL на это, поэтому я просто имитирую то, как GraphQL обслуживает данные. Чтобы быть более понятным, это пример результатов запроса GraphQL для entriesPaginate:

{
  data: {
    entriesPaginate: [
      {id: 1, id_number: 12345678, created_at: SomeDate, validated_at: SomeDate, status: "queued", __typename: "Entry"},
      {id: 2, id_number: 34567890, created_at: SomeDate, validated_at: SomeDate, status: "validated", __typename: "Entry"},
      {id: 3, id_number: 45678901, created_at: SomeDate, validated_at: SomeDate, status: "validating", __typename: "Entry"},
      ...
    ],
    total: 41,
    __typename: "entriesPagination"
  }
}

И этот метод broadcastWith моего широковещательного события для отправки данных в Pusher:

public function broadcastWith()
{
    return [
        '__typename' => 'Entry',
        'id' => $this->entry->id,
        'id_number' => $this->entry->id_number,
        'status' => $this->entry->status,
        'created_at' => $this->entry->created_at,
        'validated_at' => $this->entry->validated_at
    ];
}

Сохранение данных в том же формате предотвращает ошибки обновления магазина Apollo.

В примерах кода Vue я использовал наиболее релевантные элементы данных в объекте данных. Поэтому, пожалуйста, не пытайтесь использовать приведенный выше код для простого копирования и вставки, поскольку в них могут отсутствовать определения / методы данных.