我的源码学习之路(七)---vue-2.6.14

89 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

前言

不甘于平庸又不努力

2023继续!!!


vue常见的一些面试问题

 ## vue组件的传递方式
 ## vue自定义指令如何实现

vue组件的传递方式

一般回答:

  • Event emitemit on 传递
    • 一起使用
    parent.vue
       export default{
           created(){
              Event.$emit('事件名', 数据)
           }
       }
    后代.vue // 后代组件都可以用
       export default{
           created(){
              Event.$on('事件名', ()=>{})
           }
       }
    
  • Vuex
    • 安装插件
  • attrsattrs listeners
    • 多级组件嵌套传递数据时
    • attrs:包含了父作用域中不被prop所识别的特性绑定(class\style除外)可以通过v-bind='$attrs'传入组件内部。配合interiAttrs选项一起使用。interiAttrs:false// 属性不显示在根元素
    • listeners包含了父作用域中(不含.naive修饰器的)von事件监听器,可以通过von=listeners 包含了父作用域中(不含.naive修饰器的)v-on事件监听器,可以通过v-on='listeners'传入内部组件,存放的父组件中绑定的非原生事件
    parent.vue
        <child ref='childData' :name='names' />
    child.vue
       <son v-bind='$attrs' />
    son.vue // 可以多层后代嵌套
       <div>
           {{$attrs}} 
       </div>
    
  • provide / inject // 祖先组件向后代组件注入变量
    • 需要一起使用
    parent.vue
       export default{
           provide: {name: ''}
       }
    后代.vue // 后代组件都可以用
       export default{
           inject: ['name']
       }
    
  • parentparent children ref
    • 可以直接得到组件实例,使用后可以直接调用组件的方法或者访问数据
    parent.vue
       <child ref='childData'/>
       <script>
           created(){
              this.$refs.childData.init()
           }
       </script>
    child.vue
       <div @click='init(name)'></div>
       <script>
           methods:{
               init(name){return name}
           }
       </script>
    
  • props $emit
    • 父传子:props
    parent.vue
        <child :name='names' /> 
    child.vue
        props: ['name']
    
    • 子传父: $emit <child @click='handleClick'/>
    parent.vue
        <child @parentClick='handleClicks'/>
        <script>
            methods:{
                handleClicks(name){console.log(name)}
            }
        </script>
    child.vue
        <div @click='handleClick(name)'></div>
        <script>
            methods:{
                handleClick(name){this.$emit('parentClick',name)}
            }
        </script>
    

总结

  • 父子通信:
    • 父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent/$children)ref 也可以访问组件实例;provide/inject API;$attrs/$listeners
  • 兄弟通信:
    • Bus;Vuex
  • 跨级通信:
    • Bus;Vuex;provide/inject API、$attrs/$listeners

vue自定义指令如何实现,实现过么

一般回答:

自定义指令分全局自定义和局部自定义。Vue.directive('指定名字',{inserted:function(el,binding){操作}}})。可以用于我们内置指定不够使用的时候自己定义。定义出来的使用方式就如同v-if v-for等等。

directive中可以使用以下几个可选钩子函数

  • bind: 只调用一次,指令第一次绑定到元素上时调用。可以进行一次性的初始化设置
  • inserted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)
  • update:所在组件的VNode更新时调用,但是可能发生在VNode更新之前,指令的值可能发生了该表,也可能没有
  • componentUpdate: 指令所在组件的VNode及其子VNode全部更新后调用
  • unbind: 只调用一次,指令与元素解绑时调用

钩子函数参数

  • el: 指令所绑定的元素。可以用来直接操作DOM
  • binding: 一个对象,包含以下property
    • name: 指令名,不包含v-前缀
    • value: 指令的绑定值。eg: v-demo='2' ,绑定值为2
    • oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子中可用。无论值是否改变都可用
    • expression:字符串形式的指令表达。v-demo='1+1'.表达式为1+1
    • arg:传给指令的参数。可选
    • modifiers: 一个包含修饰符的对象。eg:v-demo.foo.bar修饰符对象为{foo:true,bar:true}
  • vnode: Vue编译生成的虚拟节点
  • oldVnode: 上一个虚拟节点, 仅在update和componentUpdated钩子中可用。

示例:

<div id="app">
  <div v-color="direColor">定义v-color指令,根据变量变换颜色</div>
</div>
<script>
  Vue.directive('color', {
    inserted: function (el, binding) {
      console.log(el, binding)
      el.style.color = binding.value
    }
  })
  const vm = new Vue({
    el: '#app',
    data() {
      return {
        name: '张三',
        direColor: '#d117e6'
      }
    },
 })

image.png

实现过一些简单的操作指定:

吸顶
<div v-stickyed="{top:0}" style="width:1000px;height:40px;background:red;margin-left:200px;z-index:1001"></div>
 Vue.directive('stickyed', {
    inserted(el, binding) {
      el.style.position = 'sticky'   //position 的 属性  非常好用
      console.log(binding, '0000')
      function aaa() {
        const scroll_top = document.documentElement.scrollTop
        const offset_top = el.offsetTop
        console.log(scroll_top, '滚动条滚动得距离')
        console.log(offset_top, '元素距离顶部得距离')
        if (offset_top - scroll_top < 0) {
          el.style.top = binding.value.top + 'px'
        }
      }
      window.addEventListener('scroll', aaa, false)  //实施滚动条监听
    }
 })

image.png

nav的切换
<div id="app">
  <div v-color="direColor">定义v-color指令,根据变量变换颜色</div>
  <br>
  <ul class="nav-ul" v-nav-status="{currentIndex,className: 'nav-default', activeName: 'nav-active'}">
    <li v-for="(item, index) of list" :key="index" class="nav-default" @click="changeNav(index)">
      {{item}}
    </li>
  </ul>
</div>
<script>
Vue.directive('navStatus', {
  bind(el, binding) {
    const _value = binding.value
    const cn = el.getElementsByClassName(_value.className)
    console.log('el', el)
    cn[_value.currentIndex].className += ` ${_value.activeName}` // 初始化。默认第一个为默认状态 不一样的背景色
  },
  update(el, binding) {
    const _value = binding.value
    const _oldValue = binding.oldValue
    const cn = el.getElementsByClassName(_value.className)
    console.log('el', el)
    cn[_oldValue.currentIndex].className = `${_value.className}` // 取消前一个tab的背景色

    cn[_value.currentIndex].className += ` ${_value.activeName}` // 设置点击的tab为选中色
  }
})
const vm = new Vue({
  el: '#app',
  data() {
    return {
      currentIndex: 0,
      list: ['导航1', '导航2', '导航3', '导航4', '导航5']
    }
  },
  methods: {
    changeNav(i) {
      this.currentIndex = i
    },
  }
})
</script>

image.png

image.png

省略指令
<div class="ellipsis" v-ellipsis:200>一堆文字</div>
Vue.directive('ellipsis', {
  inserted: function (el, binding) {
    el.style.width = binding.arg || 100 + 'px'
    el.style.whiteSpace = 'nowrap'
    el.style.overflow = 'hidden';
    el.style.textOverflow = 'ellipsis';
  }
})

image.png

返回顶部按钮 隐藏消失
<div class="fixed-right" v-show="isShow.value" v-scroll-show="isShow"  @click="handleTop(0)">回到顶部</div>

> data:
isShow: {
  value: false
}
Vue.directive('scroll-show', {
  inserted(el, binding) {
    let scope = binding.arg || '200';
    window.addEventListener('scroll', function (e) {
      if (this.scrollY > Number(scope)) {
        binding.value.value = true
      } else {
        binding.value.value = false
      }
    })
  },
})

image.png

返回顶部
Vue.directive('back-top',{
  inserted(el,binding) {
    let e = binding.arg || 'click'
    el.addEventListener(e, function () {
      let scrollToptimer = setInterval(function () {
        let top = document.documentElement.scrollTop + document.body.scrollTop
        let speed = top / 4
        top -= speed
        document.documentElement.scrollTop = document.body.scrollTop = top
        if (top == 0) {
          clearInterval(scrollToptimer)
        }
      }, 30)
    })
  }
})

image.png

防抖
<button v-debounce="handleClick">点击登录按钮</button>

handleClick() {
  console.info('点击登录按钮')
},
Vue.directive('debounce', {
  inserted(el, binding) {
    let timer
    el.addEventListener('click', () => {
      if (timer) {
        clearTimeout(timer)
      }
      timer = setTimeout(() =>{
        binding.value()
      }, 1000)
    })
  },
})
权限操作指令

说明:可以根据后台返回的账号的状态进行限制是否显示,可以避免写太多的v-if v-else

<ul class="nav-ul">
  <li v-for="(item, index) of arr" :key="index" v-permission="item.status">
    {{item.name}}
  </li>
</ul>
> data:
    arr: [
      {name: 'admin1', status: 1},
      {name: 'admin2', status: 2},
      {name: 'user', status: 0},
      {name: 'admin4', status: 3},
    ]
Vue.directive('permission', {
  inserted(el, binding) {
    const status = binding.value
    if (parseInt(status) === 0) {
      el.parentNode && el.parentNode.removeChild(el) // 找到
    }
  },
})

后记

本文仅作为自己一个阅读记录,具体还是要看大佬们的文章

下一篇:我的源码学习之路(八)---vue-2.6.14