0619-关于mixin&自定义指令

105 阅读6分钟

Mixin相关

  1. 第一种是使用 组合式 API 使用渲染函数指令 来操作VNode组件.
  2. 使用 template 选项来进行 VNode的生成.

如果你使用 组合式的API 那么完全可以使用 ECMAScript6 模块语法,来进行 选项、方法的重用。

如果说选择使用 template 选项来进行VNode 的生成,那么Mixin 就显的比较重要了。

其实这个 JSX 语法 其实它并不好用。为什么还会有人用它啊,就是因为 组合式的 API 在 setup 函数当中去创建方法、变量、ref对象、reactive对象 很方便。很直观且易于管理,在于对方法、配置的重用性上,也更加灵活。

template 模板这个形式,对于代码定义块 各各作用域 比较 复杂 且 不太好理解,和 setup 比 具有一定的局限性 且 重复的方法、变量、对象 重用性也比 setup 更低。

子组件 和 Vue.app 实例之间的区别.

Vue.createApp() 创建一个 app 的实例

app的实例 mount 挂载以后将创建一个 vm 实例

在使用 模板 ref 的时候, 返回来的这个属性 不是Element 对象,它就是一个 vm 实例

返回来的已经是一个 app 实例 mount 挂载后的 vm 对象,所以它直接跳过了createApp 这个步骤,所以我们暂时不知道 怎么在 vm 对象当中获取 app 实例

没有办法直接给一个子组件进行全局挂载。

子组件是 Vue默认给我们 挂载上的一个 app 实例。

在 Main.js 入口文件,慢慢向下查找,就会有一个 Vue 整个项目的 总的 app 挂载点, 对于这个总的 app 挂载点,使用方法来注册 全局子组件、全局定义指令、全局Mixin....

对于 局部使用 Mixin 我们先要搞清楚 $option 是个啥东西. 可以将它用来 控制 子组件中的一些配置管理,当然也不一定非要使用这个 option 也可以使用 data。

Mixin 有以下特点:

  1. 默认情况下,option 会被 配置项中的 同名选项所替代。
  2. data中的同名 属性 也将被 配置项中的 data 数据所替代。
  3. 生命周期钩子,将依次被调用,按注册顺序先运行 Mixin 中的,最后运行 配置项中的。
  4. methods、computed、 directives 都将被 配置项中 同名的属性所替代。

html部分

<p :style="{color}">测试Mixin中的值.</p>
<p>{{ testString }}</p>

js部分

import Mixin from '../../Mixin/Mixin.js'
import * as Vue from 'vue'
export default {
name: "mixin-main.vue",
mixins: [Mixin.OptionMixin, Mixin.DataMixin],
}

Mixin.js

//使用es6的模块语法,向外暴露Mixin 的这个配置对象
import {positionGeometry} from "three/examples/jsm/nodes/shadernode/ShaderNodeBaseElements";
import * as Vue from 'vue'
const OptionMixin = {
  created(){
      console.log('OptionMixin 加载成功!')
  },
  colorOption:'red'
}

const DataMixin = {
  setup(){
      const test_setup_string = Vue.ref('Mixin中的setup返回值!')
  },
  //DataMixin 当中仅允许生命周期钩子,但不允许使用setup
  //在mixin 中不允许使用组合式API
  created(){
      console.log('DataMixin 加载成功!')
  },
  data(){
    return{
        color:'red',
        testString:'Mixin中的测试字符串'
    }
  }
}

const globalSetup = {
  variable:'red',
  testFunc()
  {
      console.log('mixin 函数setup 函数')
  }
}
const export_object = {OptionMixin,DataMixin,globalSetup};
export default export_object;

自定义指令

一定要注意 自定义指令 不是 v-bind 它不能被当作一个 变量、一个对象。

我要在某一个 Element 元素上执行某些操作.这个操作也并不是说 什么时候都能执行的,而是具有严格 的生命周期钩子管理的。

一种批量化 抽像的 模板 ref 管理。

ref 是 现在要找到我这个组件上的 某一个VNode节点 Element 对象,然后再对其进行 某些 操作。

如果说对这个 ref 的管理,我们可以仅需要一个 在这个节点上 使用 ref="custom-ref-name", 在元素挂载后使用 this.$ref['custom-ref-name'] 来获取这个 element 或者 子组件中的 vm 来解决这个问题.

ref 要处理的节点是在 for 循环中批量产生的。

自定义指令demo1-判断奇偶数

html部分

 <p>现在给你一个数:<span ref="number1">10</span> 在后边判断奇偶数! 这个数字为: <span ref="number-show1"></span></p>
<ul>
  <li v-num:test.up.left="index" v-for="index in 20">{{ index }}</li>
</ul>
<p>是否为奇偶数 在每一个节点上,我们需要添加一个 奇偶数的 class 或者 文本标识.</p>
<p>使用 ref 取页面 element 元素,然后 使用 侦听函数 来侦听 Vue 响应式变量,来更改 元素的 style 属性,达成这个目的。</p>

js部分

 directives: {
  num: {
    // 填生命周期钩子了  注意 this 不能使用 使用binding.instance 来代替 生命周期钩子中的 this
    // mounted(el, binding)
    // {
    //   console.log('自定义指令 num 被运行!', el, binding);
    //   // instance  使用指令的组件实例
    //   console.log(binding.instance.color);
    //   // value  指的是 指令 值 既然是指令,那么后面的这个值 就是一个 javascript 表达式。
    //   console.log(binding.value);
    //   // oldValue 像 computed 里边一样,旧的值是什么?
    //   console.log(binding.oldValue); // undefined  如果你在下一次更新的时候,再去使用这个值它就变更了.
    //   // arg
    //   // 指的是传递的参数  Vue 指令的格式  v-命令:参数 = "值"
    //   console.log(binding.arg);
    //   // modifiers  指的 命令后缀 会以一个对象的形式返回
    //   console.log(binding.modifiers);
    //   // dir
    //   // 是一个对象在注册指令时作为参数进行传递
    //   console.log(binding.dir);
    //   // 去写一些这个通用方法
    //   binding.dir.testFunc();
    // },
    testFunc()
    {
      // 不同周期钩子所使用的 共有方法
      console.log('测试函数被运行!');
    }
  }
},
 mounted()
{
  console.log(this.$refs.number1);
  this.$refs["number-show1"].innerHTML = parseInt(this.$refs.number1.innerText) % 2 === 0?'偶数':'奇数';
 }

自定义指令demo2-移动块

html部分

<!--  <div id="test-area">-->
<!--    <div id="child-area" ref="move-element" data-direction='right'></div>-->
<!--  </div>-->
<!--  <p><input type="range" min="0" max="550" v-model="range"/> {{ range }}</p>-->
<div id="test-area">
  <div v-move:right.once.test="range" id="child-area"></div>
</div>
<p v-test="range"></p>
<p><input type="range" min="0" max="550" v-model="range"/> {{ range }}</p>

js部分

import * as Vue from 'vue'
export default {
  name: "mixin-main",
  directives: {
    move: {
   
      // 自定义指令的解释: 在组件的 mounted 期间,如果某一个 VNode 节点 具有 v-move 指令,则解析这个指令,解析这个指令,将运行当前的 mounted 函数.
      // 像这个 watch 函数一样,添加自定义 格式是差不多的
      // 生命周期钩子 或者 公用的 方法
      mounted(el, { value, instance, arg, modifiers })
      {
        // directives 里边是不允许 使用this的
        // this 指针,被 binding 中的 instance 替代了
        // 元素肯定要渲染以后,才会读取它这个自定义指令吧? 通过对这个 模板 ref 我们可以知道,元素的渲染在 mounted 期间了.
        // console.log(instance.direction);  // left
        // 看上去这个 move 这个自定义指令的 value 它是不具备响应性的,但是 它真的不备响应性么?
        // 什么是响应性? 就是某一个值变化的时候,同时通知和它相关的 对象来进行更新。 这就叫响应性
        // 页面上,如论是使用 v-model 绑定的 对象  还是使用 v-bind 绑定的对象,它都是具有 响应性的。
        // 为什么我们自定义的这个 指令,它就不具备响应性了呢?
        // 根本没有关系到 响应不响应的问题  而是 这个值为什么只赋值、动态更新了一次呢? 是因为 组件的 mounted 只运行了一次.

        // console.log(value);
        // 这个 mounted 是组件初始化,包括 组件中的 VNode 显示 每一次更新页面的时候 使用的是
        // beforeUpdate
        // update
        // 自定义指令的后缀
        console.log(modifiers.once?'once':'no once');
        // 如果有这个后缀在的情况下,那么我们就执行某些操作
        if(modifiers.test) {console.log('test')} else {console.log('no test')}
        // 我们需要 对这个元素设置 定位 还需要设置 方向的定位点
        el.style.position = 'absolute';
        const direction = arg || instance.direction;
        el.style[direction] = (value || instance.range) + 'px';
        // 当前 自定义指令只会使用 1 次
        Vue.watch(() => instance.range, newValue => el.style[direction] = newValue + 'px');
      },
      beforeUpdate(el, { value })
      {
        // 在 组件的 beforeUpdate 更新之前的 生命钩子执行期间,对于具有 v-move 指令的 VNode 节点 将对这个指令进行解析。
        // 因为 beforeUpdate、updated 这个钩子 也好,它都是在 页面进行响应的时候 就进行更新.
        // 你只要在页面中 使用 {{ }}  模板语法 或着任何 v-xx指令,所绑定的 ref 对象 或者 reactive 对象,值进行更新,都将触发 组件的 updated 更新。
        // console.log(value);
      },
      updated(el, { value })
      {
        console.log(value);
      }
      // 和 Vue 响应式对象相关的 组件中的数据,最常见的 有两个 生命周期钩子:  元素挂载时 元素更新时

    },
    test(el, { value })
    {
      console.log(value);  // 300
      // 其实我并不推荐这么写  直接这种简写的方式,如果说对 某一个 组件涉及到 仅初始化、更新时的变更
      // el.style.position = 'absolute';  // 反复设置无用的属性,这是没有意义的 浪费开销.
      // 从整个钩子函数,我们也应该明白两个问题:
      // 1、 Vue 背后的 响应式 各个数据之间的 更新触发  是Vue 的一个很大特点.
      // 2、 这个特点背后所服务的 就页面上的 VNode节点 生命周期钩子 初始化、更新 都是以  最小是 组件为单位 进行更新的。
      // 提示出一个 如何划分子组件的 思路 按功能性去划分  当你某一个值发生变动 将 相关 页面上要变更的 VNode 集合在一起 作为 一个组件。
      // 因为这样,我们可以尽最小的限度去更新 VNode。
    }
  },
  setup()
  {
    // setup 从这个生命周期钩子来看,它的期间在 beforeCreate 和 create 期间
    return { range: Vue.ref(300), direction: Vue.ref('left') }
    /*
    // 使用 ref 来获取 页面对象 Element元素  也可能是一个 子组件
    // ===========================================================================
    // 特别的方便
    const range = Vue.ref(0);
    const moveElement = Vue.ref(null);
    // 挂载以后才运行的 组合式API确实比 之前配置项式的 创建 app 要灵活许多.
    Vue.onMounted(() => {
      console.log(moveElement.value);
      // 挂载成功之后 这个 ref 才会有效
      // 添加上一个 position 的 style 样式
      moveElement.value.style.position = 'absolute';
      // 元素初始化之后,我就可以使用 当前元素上 attributes 来获取可移动方向
      const direction = moveElement.value.dataset.direction;
      // 对元素的移动方向进行初始化
      moveElement.value.style[direction] = range.value + 'px';
      Vue.watch(range, newValue => moveElement.value.style[direction] = range.value + 'px');
    });

    return {
      range,
      "move-element": moveElement
    }