将“检测外部点击”自定义指令从Vue 2迁移到Vue 3

7ajki6be  于 2023-04-07  发布在  Vue.js
关注(0)|答案(4)|浏览(172)

基于这个问题Detect click outside element和这个答案https://stackoverflow.com/a/42389266,我正在尝试将指令从Vue 2迁移到Vue 3。似乎binding.expressionvnode.context不再存在。我如何才能使其工作?

app.directive('click-outside', {
    beforeMount (el, binding, vnode) {
        el.clickOutsideEvent = function (event) {
            if (!(el === event.target || el.contains(event.target))) {
                vnode.context[binding.expression](event);
            }
        };
        document.body.addEventListener('click', el.clickOutsideEvent);
    },
    unmounted (el) {
        document.body.removeEventListener('click', el.clickOutsideEvent);
    }
});
xytpbqjk

xytpbqjk1#

你可以像这样使用binding.value

const { createApp } = Vue;

const highlightEl = (color ) => (event, el) => {
  if (el) {
    el.style.background = color;
  } else {
    event.target.style.background = color;
  }
}
const clearHighlightEl = (event, el) => {
  if (el) {
    el.style.background = '';
  } else {
    event.target.style.background = '';
  }
}

const app = Vue.createApp({
  setup() {
    return {
      highlightEl,
      clearHighlightEl
    }
  }
})

app.directive('click-outside', {
  mounted(el, binding, vnode) {
    el.clickOutsideEvent = function(event) {
      if (!(el === event.target || el.contains(event.target))) {
        binding.value(event, el);
      }
    };
    document.body.addEventListener('click', el.clickOutsideEvent);
  },
  unmounted(el) {
    document.body.removeEventListener('click', el.clickOutsideEvent);
  }
});

app.mount('#app')
<script src="https://unpkg.com/vue@3.0.0-rc.11/dist/vue.global.prod.js"></script>

<div id="app">
  <h1 v-click-outside="highlightEl('yellow')" @click="clearHighlightEl">Element 1</h1>
  <p v-click-outside="highlightEl('#FFCC77')" @click="clearHighlightEl">Element 2</p>
</div>
0x6upsns

0x6upsns2#

脱离上下文,在vue3中有一种更简单的方法。
Link to Vueuse ClickOutside (Vue 3)
Link to Vueuse ClickOutside(Vue 2)

<template>
  <div ref="target">
    Hello world
  </div>
  <div>
    Outside element
  </div>
</template>

<script>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'

export default {
  setup() {
    const target = ref(null)

    onClickOutside(target, (event) => console.log(event))

    return { target }
  }
}
</script>
fjnneemd

fjnneemd3#

挂钩解决方案:

<template>
  <p ref="myP">does click outside of p : {{ isClickOutside ? 'Yes' : 'No' }}</p>
</template>

<script lang="ts" setup>
import { useOnClickOutside } from './useOnClickOutside'

const myP = ref()
const isClickOutside = useOnClickOutside(myP, () => {
  console.log('click outside of p')
})
</script>

useOnClickOutside.js

import { onBeforeUnmount, onMounted, ref } from 'vue'
// DOMRef is dom by ref
export function useOnClickOutside(DOMRef = null, callback) {
  const isClickOutside = ref(false)
  function handleClick(event) {
    if (DOMRef?.value && !DOM.value.contains(event.target)) {
      callback()
      isClickOutside.value = true
      return
    }
    isClickOutside.value = false
  }

  onMounted(() => {
    document.addEventListener('mousedown', handleClick)
  })

  onBeforeUnmount(() => {
    document.removeEventListener('mousedown', handleClick)
  })
  return isClickOutside
}

组分溶液:
用途:

<template>
   <OnClickOutside :clickOutside="conosole.log('log when clickoutside of div')">
     <div>when clicked outside div</div>
   </OnClickOutside>
</template>

vue2解决方案:

<script>
  export default {
    name: 'OnClickOutside',
    props: ['clickOutside'],
    mounted() {
      const listener = e => {
        if (e.target === this.$el || this.$el.contains(e.target)) {
          return
        }
        this.clickOutside()
      }

      document.addEventListener('click', listener)
      this.$once('hook:beforeDestroy', () => document.removeEventListener('click', listener))
    },
    render() {
      return this.$slots.default[0]
    },
  }
</script>

vue3:

<script>
  import { getCurrentInstance, onMounted, onBeforeUnmount, ref, defineComponent } from 'vue'
  export default defineComponent({
    name: 'OnClickOutside',
    props: ['clickOutside'],
    setup(props, { emit, attrs, slots }) {
      const vm = getCurrentInstance()
      const listener = event => {
        const isClickInside = vm.subTree.children.some(element => {
          const el = element.el
          return event.target === el || el.contains(event.target)
        })
        if (isClickInside) {
          console.log('clickInside')
          return
        }
        props.clickOutside && props.clickOutside()
      }
      onMounted(() => {
        document.addEventListener('click', listener)
      })
      onBeforeUnmount(() => {
        document.removeEventListener('click', listener)
      })
      return () => slots.default()
    },
  })
</script>
pes8fvy9

pes8fvy94#

可以使用ref来确定元素是否包含单击的元素

<template>
    <div ref="myref">
        Hello world
    </div>
    <div>
        Outside element
    </div>
</template>

<script>

export default {

    data() {
        return {
            show=false
        }
    },
    mounted(){
        let self = this;
        document.addEventListener('click', (e)=> {
            if (self.$refs.myref !==undefined && self.$refs.myref.contains(e.target)===false) {
                //click outside!
                self.show = false;
            }
        })
    }
}
</script>

相关问题