手动修改Vue.js中的广告位后如何触发渲染?

时间:2019-03-13 08:39:18

标签: vue.js

可复制的简化示例:https://jsfiddle.net/nachocab/evc2374p/235/

更复杂的示例:https://jsfiddle.net/nachocab/evc2374p/125/(两次按向右箭头(不起作用),然后向左,然后向右(起作用)。More details

我知道这不是Vue方式,但是我需要修改从服务器接收到的HTML字符串(它表示带有幻灯片的幻灯片平台)。我遍历DOM树并将一些元素从data-hidden更改为true,我想触发幻灯片组件中的更新。

我已经尝试了几件事,包括更改组件键,发出事件和调用forceUpdate,但这是行不通的。

模板:

<div id="app">
  <wrapper></wrapper>
</div>


<template id="wrapper">
  <slide-deck>
    <slide>
      <p data-hidden="true">Hello</p>
    </slide>
  </slide-deck>
</template>

JS:

Vue.prototype.$eventBus = new Vue();
Vue.component('slide-deck', {
  created() {
    window.addEventListener('keydown', this.handleKeydown);
  },
  destroyed() {
    window.removeEventListener('keydown', this.handleKeydown);
  },
  methods: {
    handleKeydown(e) {
      this.$slots.default[0].componentOptions.children[0].data.attrs['data-hidden']="false"

      this.$eventBus.$emit('renderSlide')
    },
  },

    render(h) {
    console.log('render deck')
    return h('div',{}, this.$slots.default)
  }
})

Vue.component('slide', {
    created() {
      this.$eventBus.$on('renderSlide', () => {
        this.$forceUpdate()
      })
    },
    render(h) {
    console.log('render slide', this.$slots.default[0].data.attrs['data-hidden'])
    return h('div',{}, this.$slots.default)
  }
})


Vue.component('wrapper', {
    template: '#wrapper',
});

new Vue({
  el: '#app'
});

1 个答案:

答案 0 :(得分:2)

我认为这是因为您在子节点上更新了错误的属性。

您正在使用

child.data.attrs['data-hidden']="false"

但是您应该使用以下方法直接操作dom元素:

child.elm.dataset['hidden'] = false

话说回来,您还有其他问题。您将updateVisibilities方法触发到尚未创建DOM的created挂钩中。我建议您改用mounted钩子。

最后,由于在数据更新后触发了updateVisibilities方法,因此您还应该等待该方法,以便使用Vue nextTick帮助器来更新DOM:

updateVisibilities() { 
  this.$nextTick(() => {
    const validElements = this.currentSlide.componentOptions.children.filter(child => child.tag)
    validElements.forEach(child => {
      console.log(child, child.elm)
      child.elm.dataset['hidden'] = child.elm.dataset['order'] > this.currentFragmentIndex
      console.log('data-order', child.elm.dataset['order'], 'data-hidden', child.elm.dataset['hidden'])
    })
  })
}

在您的情况下,您也不需要任何$forceUpdate

这是您固定的两个小提琴:

我希望这会有所帮助!

编辑:这是完整的更新代码:

Vue.component('slide-deck', {
  data() {
    return {
      currentSlideIndex: 0,
      currentFragmentIndex: 0,
      numFragmentsPerSlide: [2,2]
    }
  },
  computed: {
    slideComponents() {
      return this.$slots.default.filter(slot => slot.tag)
    },
    currentSlide() {
      return this.slideComponents[this.currentSlideIndex]
    }
  },

  mounted () {
    window.addEventListener('keydown', this.handleKeydown);
    this.updateVisibilities();
  },
  destroyed() {
    window.removeEventListener('keydown', this.handleKeydown);
  },
  methods: {
    handleKeydown(e) {
      if (e.code === 'ArrowRight') {
        this.increaseFragmentOrSlide()
      } else if (e.code === 'ArrowLeft') {
        this.decreaseFragmentOrSlide()
      }
    },

    increaseFragmentOrSlide() {
      if (this.currentFragmentIndex < this.numFragmentsPerSlide[this.currentSlideIndex] - 1) {
        this.currentFragmentIndex +=1
        console.log('increased fragment index:', this.currentFragmentIndex)
      } else if (this.currentSlideIndex < this.numFragmentsPerSlide.length - 1){
        this.currentSlideIndex += 1
        this.currentFragmentIndex = 0
        console.log('increased slide index:', this.currentSlideIndex)
      }
      this.updateVisibilities()
    },

    decreaseFragmentOrSlide() {
      if (this.currentFragmentIndex > 0) {
        this.currentFragmentIndex -=1
        console.log('decreased fragment:', this.currentFragmentIndex)
      } else if (this.currentSlideIndex > 0) {
        this.currentSlideIndex -= 1
        this.currentFragmentIndex = 0
        console.log('decreased slide:', this.currentSlideIndex)
      }
      this.updateVisibilities()
    },

    updateVisibilities() {
      // this.$forceUpdate() // hack
      this.$nextTick(() => {
        const validElements = this.currentSlide.componentOptions.children.filter(child => child.tag)
        validElements.forEach(child => {
          console.log(child, child.elm)
          child.elm.dataset['hidden'] = child.elm.dataset['order'] > this.currentFragmentIndex
          console.log('data-order', child.elm.dataset['order'], 'data-hidden', child.elm.dataset['hidden'])
        })
      })
    }
  },

  render(h) {
    return h('div',{}, [this.currentSlide])
  }
})

Vue.component('slide', {
  render(h) {
    return h('div',{}, this.$slots.default)
  }
})


Vue.component('wrapper', {
  template: '#wrapper',
});

new Vue({
  el: '#app'
});