2021.11.28 vue的全局自定义指令(10)

vue的全局自定义指令

除了核心功能默认内置的指令(v-model , v-show等等),Vue 也允许注册自定义指令。

Vue.directive( “eventName”, options)

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  1. bind:只调用一次,。在这里可以进行一次性的初始化设置。指令第一次绑定到元素时调用
  2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
  3. update:所在组件的 VNode ( 抽象DOM )更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
  4. componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  5. unbind:只调用一次,指令与元素解绑时调用。
<body>
  <div id='app'>
    <!-- <div v-dd>你好,世界</div> -->
    <cpn></cpn>
  </div>
  <script src='./js/vue.js'></script>
  <script>
    Vue.directive('dd', {
      bind() {
        console.log('bind:初始化设置只会执行一次,第一次被绑定到元素时调用');
        //console.log(document.getElementById('div').innerText); //获取不到,此时模板还没有被挂载到页面
      },
      inserted() {
        console.log('inserted:被绑定元素插入父节点时调用');
        console.log(document.getElementById('div').innerText);
      },
      update() {
        console.log('update:所在组件更新时调用');
        console.log(document.getElementById('div').innerText); //页面还没有更新
      },
      componentUpdated() {
        console.log('页面已经更新');
        console.log(document.getElementById('div').innerText); //页面已经更新
      },
      unbind() {
        console.log('指令断开链接');
      }
    })
    const app = new Vue({
      el: '#app',
      data: {

      },
      components: {
        cpn: {
          data() {
            return { msg: '哈哈哈' }
          },
          template: '<div v-dd id="div">{{msg}}<button @click="fn">btn</button><button @click="cle">cle</button></div>',
          methods: {
            fn() {
              // console.log('ok');
              this.msg += '嘿嘿嘿'
            },
            cle() {
              this.$destroy()
            }
          }
        }
      }
    })
  </script>
</body>

vue的局部自定义指令

如果想注册局部指令,组件中也接受一个 directives的选项:

<body>
  <div id='app'>
    <cpn></cpn>
  </div>

  <script src='./js/vue.js'></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {

      },
      components: {
        cpn: {
          template: '<div v-dd>我是cpn</div>',
          directives: {
            dd: {
              inserted() {
                console.log('hello world');
              }
            }
          }
        }
      }
    })
  </script>
</body>

vue的自定义指令的钩子函数参数

指令钩子函数会被传入以下参数:

  1. el:指令所绑定的元素,可以用来直接操作 DOM 。
  2. binding:一个对象,包含以下属性:
    1. name:指令名,不包括 v- 前缀。
    2. value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。
    3. oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    4. expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
    5. arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
    6. modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
  3. vnode:Vue 编译生成的虚拟节点。
  4. oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
<body>
  <div id='app'>
    <cpn></cpn>
  </div>

  <template id="cpn">
    <div v-dd.foo.bar="1+2">
      我是cpn{{msg}}
      <button @click="btn">更新</button>
      <button @click="boom">销毁</button>
    </div>
  </template>
  <script src='./js/vue.js'></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {

      },
      components: {
        cpn: {
          data() {
            return {
              msg: '你好,世界'
            }
          },
          template: '#cpn',
          methods: {
            btn() {
              this.msg += '你也好'
            },
            boom() {
              this.$destroy()
            }
          },
          directives: {
            dd: {
              bind(el, binding, vnode, oldVnode) {
                console.log('%c%s', 'color:red', 'bind_el:', el);
                console.log('%c%s', 'color:red', 'bind_binding:', binding);
                console.log('%c%s', 'color:red', 'bind_vnode:', vnode);
                console.log('%c%s', 'color:red', 'bind_oldVnode:', oldVnode);
              },
              inserted(el, binding, vnode, oldVnode) {
                console.log('%c%s', 'color:red', 'inserte_del:', el);
                console.log('%c%s', 'color:red', 'inserted_binding:', binding);
                console.log('%c%s', 'color:red', 'inserted_vnode:', vnode);
                console.log('%c%s', 'color:red', 'inserted_oldVnode:', oldVnode);
              },
              update(el, binding, vnode, oldVnode) {
                console.log('%c%s', 'color:red', '更新1el:', el);
                console.log('%c%s', 'color:red', '更新1binding:', binding);
                console.log('%c%s', 'color:red', 'update_vnode:', vnode);
                console.log('%c%s', 'color:red', 'update_oldVnode:', oldVnode);
              },
              componentUpdated(el, binding, vnode, oldVnode) {
                console.log('%c%s', 'color:red', '更新2el:', el);
                console.log('%c%s', 'color:red', '更新2binding:', binding);
                console.log('%c%s', 'color:red', 'componentUpdated_vnode:', vnode);
                console.log('%c%s', 'color:red', 'componentUpdated_oldVnode:', oldVnode);
              },
              unbind(el, binding, vnode, oldVnode) {
                console.log('%c%s', 'color:red', '销毁el:', el);
                console.log('%c%s', 'color:red', '销毁binding:', binding);
                console.log('%c%s', 'color:red', 'unbind_vnode:', vnode);
                console.log('%c%s', 'color:red', 'unbind_oldVnode:', oldVnode);
              }
            }
          }
        }
      }
    })
  </script>
</body>

vue的动态指令参数

<body>
  <div id='app'>
    <h1>自定义指令动态参数</h1>
    <cpn></cpn>
  </div>

  <!-- <template id='cpn'>
    <div>
      <div v-for='(item,index) in items' :key='item' @click='change_index(index)'
        :class="['nav_item',{'nav_active':index === current_index}]">{{item}}</div>
    </div>
  </template> -->

  <template id='cpn'>
    <!-- <div v-change_class> -->
    <div v-change_class="{
      class_name:'nav_item',
      active_class:'nav_active',
      current_index
    }">
      <!-- <div v-for='(item,index) in items' :key='item' @click='change_index(index)'
        :class="['nav_item',{'nav_active':index === current_index}]">{{item}}</div> -->
      <div v-for='(item,index) in items' :key='item' @click='change_index(index)' class="nav_item">{{item}}</div>
    </div>
  </template>
  <script src='./js/vue.js'></script>
  <script>

    const app = new Vue({
      el: '#app',
      data: {

      },
      components: {
        cpn: {
          template: '#cpn',
          directives: {
            change_class: {
              bind(el, binding) {
                console.log('bind');
                console.log(binding);
                const options = binding.value;
                const { class_name, active_class, current_index } = options
                const children = el.getElementsByClassName(class_name)
                // children[current_index].className += ' active_class'
                children[current_index].className += ` ${active_class}`
              },
              update(el, binding) {
                // console.log('update');
                // console.log(binding);
                // 拿到旧的元素
                const old_options = binding.oldValue
                const options = binding.value;
                const { class_name, active_class, current_index } = options
                const { current_index: old_indx } = old_options
                const children = el.getElementsByClassName(class_name)
                children[current_index].className += ` ${active_class}`
                children[old_indx].className = class_name
              }
            }
          },
          data() {
            return {
              items: ['项目1', '项目2', '项目3'],
              current_index: 0
            }
          },
          methods: {
            change_index(index) {
              this.current_index = index
            }
          }
        }
      }
    })
  </script>
</body>

vue的自定义指令的函数简写

在很多时候,你可能想在bind 和 update时触发相同行为,而不关心其它的钩子。比如这样写:

change_class(el, binding) {
  const old_options = binding.oldValue
  const options = binding.value;
  if (old_options) {
    const { current_index: old_indx } = old_options
    const { class_name, active_class, current_index } = options
    const children = el.getElementsByClassName(class_name)
    children[old_indx].className = class_name
  }
  const { class_name, active_class, current_index } = options
  const children = el.getElementsByClassName(class_name)
  children[current_index].className += ` ${active_class}`

}

渲染函数

Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。

节点、树以及虚拟 DOM

在深入渲染函数之前,回顾一些浏览器的工作原理

<div id='app'>
  <h1>My title</h1>
  <!-- TODO: Add tagline -->
</div>

以这段 HTML 为例:

当浏览器读到这些代码时,它会建立一个“DOM 节点”树来保持追踪所有内容,如同你会画一张家谱树来追踪家庭成员的发展一样。
请添加图片描述

每个元素都是一个节点。每段文字也是一个节点。甚至注释也都是节点。

高效地更新所有这些节点会是比较困难的,不过所幸你不必手动完成这个工作。你只需要告诉Vue 你希望页面上的HTML 是什么

  1. 这可以是在一个模板里:
<body>
  <div id='app'>
    {{msg}}
  </div>

  <script src='./js/vue.js'></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        msg: '你好,世界'
      }
    })
  </script>
</body>

或者一个渲染函数里:

render(createElement) {
  return createElement('h2', this.msg)
}

在这两种情况下,Vue 都会自动保持页面的更新

render渲染函数详解

render函数就是用来渲染一个DOM的, 函数里面会有一个形参叫做createElement, createElement是专门用来渲染出DOM的函数,这个函数返回的不是一个实际的DOM.它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个VNode 树的称呼。

createElement函数的参数

render(createElement) {
  return createElement(
    'div', //第一个参数是标签的名称,可以是字符串,组件对象
    // 向模板中添加属性
    {
      attrs: { id: 111, class: 'main' },
      style: {
        color: 'red',
        fontSize: '30px'
      },
    },
    // 文本节点内容
    //'aaa' //Strign
    [
      createElement('h1', this.name),
      createElement('h2', this.num),
      createElement('h3', '爱老虎油'),
    ]
    // this.msg
  )
}

有一点要注意:正如v-bind:class 和 v-bind:style在模板语法中会被特别对待一样,createElement函数的object参数在 VNode数据对象中也有对应的顶层字段。该对象也允许你绑定普通的HTML 特性,也允许绑定如innerHTML 这样的 DOM 属性 (这会覆盖 v-html指令)。

{
  //接收普通特性
  attrs: { id: 111, class: 'main' },
  //接收对象
  style: {
    color: 'red',
    fontSize: '30px'
  },
},
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值