【问题标题】:Avoid unnecessary http requests on identical images - vuejs避免对相同图像进行不必要的 http 请求 - vuejs
【发布时间】:2019-04-09 19:32:14
【问题描述】:

情况:

在一个页面中,有几个组件接收用户列表。收到列表后,会有一个 foreach 循环调用附加组件来获取用户的图像。多个组件可能包含相同的用户,这意味着重复完全相同的 http 请求以获取“重复图像”。为了避免这些不必要的请求,我在 vueX 的 store 中设置了一个用户有某个 base64 图像的信息,这样我就可以验证我是否已经得到了图像。

问题:当第一个组件请求获取图像并将其保存在商店中时,其余组件已经创建,因此商店仍然是空的,我无法检查是否有图片。

解决方案:创建组件时,我通过使用强制商店存在

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);
}

【问题讨论】:

    标签: image vue.js axios vue-component vuex


    【解决方案1】:

    我会这样做:

    首先,我会将所有请求逻辑移至 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 数据作为值。如果data的值为null,则表示图片已经在加载中。

    现在让我们看看我们如何编写动作来处理它:

    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]"。图片一加载就会显示出来。

    即使图像是loading,$store.state.images[user.avatar] 也会是虚假的(它具有 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')}`
                    })
                })
            }
        }
    }
    

    【讨论】:

    • 谢谢,这就是我想要的:D
    猜你喜欢
    • 2012-10-27
    • 1970-01-01
    • 1970-01-01
    • 2014-08-29
    • 1970-01-01
    • 2011-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多