Избегайте ненужных http запросов на идентичные изображения - vuejs - PullRequest
0 голосов
/ 06 ноября 2018

Положение:

На странице есть несколько компонентов, которые получают список пользователей. После получения списка есть цикл foreach, который вызывает дополнительный компонент для получения изображения пользователя. Возможно, что несколько компонентов могут содержать одного и того же пользователя, что будет означать повторение одного и того же http-запроса для получения «повторного изображения». Чтобы избежать этих ненужных запросов, я установил информацию о том, что у пользователя есть определенный образ base64 в хранилище vueX, чтобы я мог проверить, если я уже получил изображение.

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

Решение: при создании компонента я заставляю хранилище существовать, используя

this.images[this.user.id] = 'reserved'; 

Однако я не уверен, что это правильный подход к этой ситуации. Предложения приняты: 'D

Код:

родительский компонент

<template>
    <div class="info-cards">
        <div class="info-users">
            <div class="info-label">{{ $t('global.users') }}</div>
            <div class="info-images"  v-if="users.length > 0">
                <base-users-image
                    v-for="user in users"
                    :key="user.name"
                    :user="user"
                />
            </div>
            <div v-else class="message">{{ $t('global.noUsersRole') }}</div>
        </div>
    </div>
</template>

<script>
    // import components
    const baseUsersImage = () => System.import(/* webpackChunkName: 'usersImage' */ './../../users/baseUsersImage');

    export default {
        props: {
            users: Array,
            packages: Array
        },
        components: {
            baseUsersImage: baseUsersImage
        },
    }
</script>

компонент изображения

<template>
    <router-link to="user" class="anchor-image">
        <img v-if="show" :src="image" :alt="user.name" class="image">
        <div v-else class="image-default">t</div>
    </router-link>
</template>

<script>
    // import requests
    import requests from './../../../helpers/requests.js';

    // import store
    import { mapGetters, mapActions } from 'vuex';

    export default {
        props: {
            user: Object
        },
        data() {
            return {
                image: '',
                show: false
            }
        },
        created() {
            if (this.user.avatar) { // check if user has avatar
                if ( this.images[this.user.id] == null) { // check if it already exists in the store
                    this.images[this.user.id] = 'reserved'; // set as reserved in store
                    requests.get(this.user.avatar, { responseType: 'arraybuffer' }) // faz o pedido a API da image
                        .then( (response) => {
                            this.saveImage( { id: this.user.id, url: `data:${response.headers['content-type']};base64,${Buffer.from(response.data, 'binary').toString('base64')}` } );
                        }, error => {
                            console.log(error);
                        });
                }
            }
        },
        methods: {
            ...mapActions({
                saveImage: 'saveImage'
            })
        },
         computed: {
            ...mapGetters({
                images: 'images'
            })
        },
        watch: {
            images:  {
                immediate: true,
                deep: true, // so it detects changes to properties only
                handler(newVal, oldVal) {
                    if ( newVal[this.user.id] !=='reserved'
                        && this.user.avatar
                        && newVal[this.user.id] !== undefined
                    )  {
                        this.image = newVal[this.user.id];
                        this.show = true;
                    }
                }
            }
        }
    }
</script>

магазин

const state = {
    images: {}
}

const SAVE_IMAGE = (state, payload) => {
    state.images = {
        ...state.images,
        [payload.id] : payload.url
    }
}

const saveImage = ({commit}, payload) => {
    commit('SAVE_IMAGE', payload);
}

1 Ответ

0 голосов
/ 06 ноября 2018

Вот что я бы сделал:

Во-первых, я бы переместил всю логику запросов в VueX и сделал свой компонент максимально простым. Это должно быть достигнуто с помощью этого куска кода:

export default {
    props: {
        user: Object
    },
    created () {
        if (this.user.avatar) {
            this.$store.dispatch('fetchImage', this.user.avatar)
        }
    }
}

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

{
    images: {
        '/users/1/avatar': 'data:png:base64,....', // An image that have been loaded
        '/users/2/avatar': null // An image that is supposed to be loading
    }
}

Как видите, объект images использует URL-адреса изображений в качестве ключей и данные base64 в качестве значений. Если значение данных равно нулю, это означает, что изображение уже загружается.

Давайте теперь посмотрим, как написать действие для обработки этого:

const actions = {
    fetchImage ({state, commit}, url) {
        if (typeof state.images[url] !== 'undefined') {
            return null
        }

        commit('setImage', {
            url,
            payload: null
        })

        return requests.get(url, { responseType: 'arraybuffer'}).then(response => {
            commit('setImage', {
                url,
                payload: `data:${response.headers['content-type']};base64,${Buffer.from(response.data, 'binary').toString('base64')}`
            })
        })
    }
}

Посмотрите на первое условие. Если изображение не undefined в магазине, мы просто ничего не делаем. Потому что если изображение не undefined, это означает, что оно либо null (загрузка), либо имеет значение и загружено.

Сразу после этого условия мы устанавливаем изображение на null, чтобы другие компоненты не загружали его.

И в конце мы загружаем содержимое изображения и фиксируем его в состоянии.

Давайте теперь посмотрим на шаблон:

<template>
    <router-link to="user" class="anchor-image">
        <img v-if="$store.state.images[user.avatar]" :src="$store.state.images[user.avatar]" :alt="user.name" class="image">
        <div v-else class="image-default">t</div>
    </router-link>
</template>

Чтобы проверить, следует ли отображать изображение, вам просто нужно использовать v-if="$store.state.images[user.avatar]". Изображение появится, как только оно будет загружено.

$store.state.images[user.avatar] будет ложным, даже если изображение loading (имеет значение null.

Надеюсь, это поможет!

(Вот полный магазин:)

const store = {
    state: {
        images: {}
    },
    mutations: {
        setImage (state, image) {
            Vue.set(state.images, image.url, image.payload)
        }
    },
    actions: {
        fetchImage ({state, commit}, url) {
            if (state.images[url] !== undefined) {
                return null
            }

            commit('setImage', {
                url,
                payload: null
            })

            return requests.get(url, { responseType: 'arraybuffer'}).then(response => {
                commit('setImage', {
                    url,
                    payload: `data:${response.headers['content-type']};base64,${Buffer.from(response.data, 'binary').toString('base64')}`
                })
            })
        }
    }
}
...