家长不更新组件道具和数据

时间:2019-04-17 20:10:59

标签: javascript vue.js vue-component

我在这里https://codepen.io/stevemr/pen/VNQbYe对我的问题进行了笔误

我有一个根Vue实例,该实例维护一个组件VideoPlayer的道具。我的根实例有一个名为setVideo的方法,该方法现在仅分配一些虚拟值。

这是我在根实例的数据中使用的对象:

video: {
    drive: '',
    filename: '',
    mediaType: '',
},

这是setVideo函数:

setVideo: function() {
    // Get the drive, filename, and mediaType
    this.video.drive = 'hdd1';
    this.video.filename = 'game-of-thrones_s01e04.mp4';
    this.video.mediaType = 'show';

    // Hide all modals and trigger the display of the video player
    Event.trigger('hideModal');
    Event.trigger('displayVideoPlayer');
},

Event类只是Vue基本事件的包装:

window.Event = new class {
    constructor() {
        this.vue = new Vue();
    }

    trigger(event, data = null) {
        this.vue.$emit(event, data);
    }

    listen(event, callback) {
        this.vue.$on(event, callback);
    }
};

这是初始化我的VideoPlayer组件的DOM:

<video-player
    v-bind:drive="video.drive"
    v-bind:filename="video.filename"
    v-bind:media-type="video.mediaType"
></video-player>

最后,这是我的VideoPlayer组件:

<template>
    <div>
        <div id="movie-container">
            <div
                class="video-loader top-most"
                v-if="showVideoPlayer && !loaded"
            ></div>
            <video
                id="video-player"
                ref="video"
                v-if="showVideoPlayer && src !== ''"
                class="top-most"
                v-bind:class="{ hidden: !loaded }"
                v-on:click="togglePlay"
                controls
                autoplay
            >
                <source v-bind:src="src" v-bind:type="videoType"></source>
            </video>
        </div>

        <div id="time-range-container" v-if="showTimeRange">
            <input
                id="time-range"
                ref="timeRange"
                type="range"
                min="0"
                v-bind:max="duration"
                step="30"
                v-model:value="currentTime"
            />
        </div>
    </div>
</template>

<script>
    export default {
        props: [
            'drive',
            'filename',
            'mediaType',
        ],

        data() {
            return {
                currentTime: 0,
                duration: 0,
                loaded: false,
                showTimeRange: false,
                showVideoPlayer: false,
            }
        },

        computed: {
            src: function() {
                if(this.filename !== '') {
                    return
                        '/video/' + this.drive +
                        '/' + this.mediaType +
                        's/' + this.filename;
                }

                return '';
            },

            videoType: function() {
                var ext = this.filename.split('.')[1];
                var type = '';

                switch(ext) {
                    case 'mk4':
                    case 'm4v':
                        type = 'webm';
                        break;
                    case 'avi':
                        type = 'ogg';
                        break;
                    default:
                        type = ext;
                }

                return 'video/' + type;
            },
        },

        created() {
            Event.listen('displayVideoPlayer', this.display);
        },

        methods: {
            display: function() {
                if(this.src === '') {
                    return;
                }

                this.showVideoPlayer = true;
                this.loaded = false;

                var self = this;

                setTimeout(function() {
                    var interval = setInterval(function() {
                        var video = self.$refs.video;
                        if(video.readyState > 0) {
                            self.loaded = true;
                            self.duration = Math.round(video.duration);
                            self.currentTime = video.currentTime;
                            clearInterval(interval);
                        }
                    }, 500);
                }, 800);
            },

            togglePlay: function() {
                var video = this.$refs.video;

                if(video.paused) {
                    video.play();
                }

                if(!video.paused) {
                    video.pause();
                }
            },
        },
    }
</script>

调用setVideo时,应将VideoPlayer组件的prop设置为虚拟值,然后显示视频播放器。但是,当触发displayVideoPlayer事件时,组件props仍设置为其默认值(空字符串)。最重要的是,在调用display方法之前不会更新src计算的属性,因此display函数将立即返回而无需执行任何操作。

这就像我的组件的props和数据没有被更新,即使我可以通过开发工具看到它们。就像是发生的速度不够快。

我尝试将src用作组件数据的一部分,并使用另一个功能setSrc在显示功能中对其进行设置。但是发生了同样的事情。

我也尝试过移动Event.listen('displayVideoPlayer',this.display);进入mount()而不是created(),也没有解决任何问题。

如果您查看代码笔,则第一次单击按钮以触发setVideo功能时,应该显示视频播放器组件,而不是2次点击。

1 个答案:

答案 0 :(得分:0)

似乎问题在于Vue更新值与您调用display方法之间的竞争状态:

display: function() {
  console.log(this.src) // ""
  setTimeout(() => console.log(this.src)) // "/video/hdd1/shows/game-of-thrones_s01e04.mp4"
  if(this.src === '') {
    return
  }

这意味着您在更新值之前调用了display方法。

一种解决您问题的方法是在调用display方法之前增加一些延迟:

setVideo: function() {
  this.video.drive = 'hdd1'
  this.video.filename = 'game-of-thrones_s01e04.mp4'
  this.video.mediaType = 'show'
  setTimeout(() => {
    Event.trigger('displayVideoPlayer')
  })

但是我认为这将来可能会遇到更多问题。如果您想依靠道具,则应该改用观察者模式:

watch: {
  src (src) {
    if(src === '') {
      return
    }
    // ... display
  }
}

或者通过事件传递这些值,而不是像props这样的道具传递

Event.trigger('displayVideoPlayer', this.video)