Решение проблемы с приведением типов при передаче данных из Laravel в Vue.js

При работе с Laravel и Vue.js типичным шаблоном инициализации наших компонентов Vue данными является передача данных контроллера из Laravel в реквизиты Vue. Это позволяет нам использовать контроллеры Laravel для управления нашими данными в стиле MVC.

Хотя это обычная практика, я столкнулся с сложной проблемой приведения типов, когда работаю с отфильтрованными данными. Я покажу вам, как воссоздать проблему и решить ее, чтобы вы могли избежать ее в своих проектах.

Настраивать

Начнем с обычного проекта Laravel. Чтобы узнать о том, как это настроить, вы можете ознакомиться с этим контрольным списком. Помимо базовой настройки, нам нужно будет подделать некоторые данные в нашем HomeController.php.

В этом файле мы добавим массив $results в функцию index.

public function index()
{
    $results = [
        [
            'id' => 1,
            'name' => 'Foo',
            'status' => 'active',
        ],
        [
            'id' => 2,
            'name' => 'Bar',
            'status' => 'inactive',
        ],
        [
            'id' => 3,
            'name' => 'Baz',
            'status' => 'active',
        ],
        [
            'id' => 4,
            'name' => 'Biz',
            'status' => 'inactive',
        ],
    ];
}

Затем мы превратим этот массив в коллекцию, чтобы мы могли применить преобразования, вызывающие проблему.

public function index()
{
    $results = collect([
        [
            'id' => 1,
            'name' => 'Foo',
            'status' => 'active',
        ],
        [
            'id' => 2,
            'name' => 'Bar',
            'status' => 'inactive',
        ],
        [
            'id' => 3,
            'name' => 'Baz',
            'status' => 'active',
        ],
        [
            'id' => 4,
            'name' => 'Biz',
            'status' => 'inactive',
        ],
    ]);
}

Затем мы вернем наше home представление с этими данными, чтобы мы могли использовать его в нашем компоненте Vue.

public function index()
{
    $results = collect([
        [
            'id' => 1,
            'name' => 'Foo',
            'status' => 'active',
        ],
        [
            'id' => 2,
            'name' => 'Bar',
            'status' => 'inactive',
        ],
        [
            'id' => 3,
            'name' => 'Baz',
            'status' => 'active',
        ],
        [
            'id' => 4,
            'name' => 'Biz',
            'status' => 'inactive',
        ],
    ]);
    
    return view('home', compact('results'));
}

Позаботившись о наших данных, мы можем сосредоточиться на добавлении нашего компонента Vue.

Внутри нашей папки компонентов мы можем добавить файл WhereExample.vue. Здесь мы просто примем опору results и отрендерим ее с помощью v-for.

<template>
    <div>
        <p v-for="result in results" :key="result.id">{{ result.name }}</p><br/>
    </div>
</template>
<script>
    export default {
        props: {
            results: {
               type: Array,
               required: true,
            },
        },
    }
</script>

Теперь мы можем зарегистрировать это в нашем app.js файле и добавить в home.blade.php.

// app.js
Vue.component('where-example', require('./components/WhereExample.vue'));
//home.blade.php
<where-example :results="{{ $results }}"></where-example>

Разобравшись с нашими настройками, давайте углубимся в проблему.

Эта проблема

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

Теперь о проблеме. Когда мы хотим отфильтровать наши данные в контроллере, мы увидим, что наша опора приводится как объект, а не как массив. Для этого мы добавим предложение ->where() в нашу $results коллекцию, чтобы отображались только активные результаты.

public function index()
{
    $results = collect([
        [
            'id' => 1,
            'name' => 'Foo',
            'status' => 'active',
        ],
        [
            'id' => 2,
            'name' => 'Bar',
            'status' => 'inactive',
        ],
        [
            'id' => 3,
            'name' => 'Baz',
            'status' => 'active',
        ],
        [
            'id' => 4,
            'name' => 'Biz',
            'status' => 'inactive',
        ],
    ])
    ->where('status', 'active');
    
    return view('home', compact('results'));
}

Вернувшись к нашим инструментам разработки Vue, мы видим, что наш массив превратился в объект! И мы видим, что Vue выдает полезную ошибку в консоли.

Это довольно большое дело. Представьте, что мы хотели показать сообщение, если у нас нет никаких результатов. С помощью массива мы могли бы легко использовать .length, чтобы увидеть, есть ли какие-либо результаты. Но мы не можем использовать .length с объектом. Внезапно наш компонент Vue сломался бы!

Это всего лишь один пример, но есть множество функций, которые мы могли бы создать, ожидая, что в качестве данных будет массив, который сломается, если получит объект. К счастью, есть довольно простое решение этой проблемы.

Решение

Предложение ->where() превращает наш массив в объект потому, что оно поддерживает индекс отфильтрованных результатов. Когда Vue получает эти данные с нелинейными индексами, он предполагает, что это должен быть объект.

Хотя это довольно разумно и даже желательно в большинстве случаев, здесь мы все же в недоумении. Но мы можем использовать предложение ->values() после ->where(), чтобы сбросить индексы до их линейных значений.

В результате наши результаты снова будут похожи на массив для Vue. Итак, в нашем примере мы можем добавить ->values() в конец нашей $results Коллекции, и все снова заработает.

public function index()
{
    $results = collect([
        [
            'id' => 1,
            'name' => 'Foo',
            'status' => 'active',
        ],
        [
            'id' => 2,
            'name' => 'Bar',
            'status' => 'inactive',
        ],
        [
            'id' => 3,
            'name' => 'Baz',
            'status' => 'active',
        ],
        [
            'id' => 4,
            'name' => 'Biz',
            'status' => 'inactive',
        ],
    ])
    ->where('status', 'active')
    ->values();
    
    return view('home', compact('results'));
}

Теперь у нас есть отфильтрованные результаты, работающие так, как мы и ожидали.

Заключение

Я определенно потратил некоторое время, пытаясь понять, почему мои компоненты Vue перестали работать из-за простого предложения ->where() в моем контроллере. Это часто бывает сложно отладить, потому что проблема так далеко удалена от того, где она появляется.

Когда я впервые столкнулся с этой проблемой, я, вероятно, не понял бы ее, если бы не использовал лучшие практики Vue, определяя свои определения опор. Это показывает, насколько они могут быть полезны в сложной ситуации.

Я надеюсь, что этот пост поможет вам избежать или, по крайней мере, отладить эту проблему в ваших проектах. Как всегда, не стесняйтесь задавать мне любые вопросы в Твиттере. И до следующего раза, удачных исследований!