vue----3.混入,插槽,自定义指令,自定义过滤器,过渡动画,响应式原理

文章目录

  • 混入
  • 插槽
  • 自定义指令
  • 自定义过滤器
  • 过渡动画
  • 响应式原理(来源于官方文档)

混入

混入(mixins)提供了一种非常灵活的方式来分发vue组件中的可复用功能,一个混入对象可以包含任意组件选项,当组件使用混入对象时,所有混入对象的选项将被混合进入该组件本身的选项。
例如:

    let mixins = {
        data: function () {
            return {
                message1: "HelloHH",
                message3: "123"
            }
        },
        methods: {
            fun1: function () {
                console.log("我是f1");
            },
            fun2: function () {
                console.log("我是f2");
            }
        },
        created: function () {
            console.log("我是混入对象");
        }
    };
    const vm = new Vue({
        el: "#app",
        mixins: [mixins],
        data: function(){
            return {
                message1: "Hello",
                message2: "World"
            }
        },
        methods:{
            fun2: function () {
                console.log("我是vm中的f2");
            },
            fun3: function () {
                console.log("我是vm中的f3");
            }
        },
        created: function () {
            //钩子函数
            console.log("我是vm");
          document.getElementById("p1").innerText = this.$data.message1;
        }
    });
    vm.fun1();
    vm.fun2();
    vm.fun3();

全局混入:混入也可以全局注册,一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。

// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

new Vue({
  myOption: 'hello!'
})
// => "hello!"

自定义选项合并策略:自定义选项将适用默认策略,简单覆盖已有的值,如果想自定义合并逻辑,可以向Vue.config.optionMergeStrategies中添加一个函数:

Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
  // 合并逻辑
}
对于多数值为对象的选项,可以使用与methods相同的合并策略(直接覆盖):
var strategies = Vue.config.optionMergeStrategies
strategies.myOption = strategies.methods

插槽

vue实现了一套内容分发的API,将slot元素作为承载分发内容的出口。
当你想在一个插槽中使用数据时,例如;

<navigation-link url="/profile">
   Logged in as {
    { user.name }}
navigation-link>

该插槽和模板的其他地方一样可以访问相同的实例属性(相同的作用域),但是不能访问navigation-link的作用域,例如url就是访问不到的。
tips:父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

单个插槽使用:

    <t1>
        <h1>{
    {name}}h1> 
    t1>

        components: {
            "t1": {
                template: "<div>" +
                    "<h1>测试数据h1>" +
                    "<slot>slot>" +
                    "div>"
            }
        }

具名插槽:

<t2>
    <template v-slot:header>
        <h1>Hello Header Messageh1>
    template>
    <template v-slot:default>
        <h1>主要内容h1>
    template>
    <template v-slot:footer>
        <h1>Hello Footer Messageh1>
    template>
t2>

            "t2": {
                template:   "<div class='container'>" +
                    "<header><slot name='header'>slot>header>" +
                    "<main><slot name='default'>slot>main>" +
                    "<footer><slot name='footer'>slot>footer>"  +
                    "div>"
            }

v-slot只能在template标签上使用,slot则不一样。

作用域插槽:有时候让插槽内容能够访问子组件中才有的数据是很有用的,作用域插槽在解决需要动态生成字符串模板时非常有用。

        
    <t3 v-bind:list="contentList">

    <template v-slot:thead>
        <tr>
            <td>编号td>
            <td>内容td>
            <td>状态td>
        tr>
    template>
    <template slot-scope="{item}" slot="tbody"> 
        <td>{
    {item.id}}td>
        <td>{
    {item.text}}td>
        <td>{
    {item.isALive}}td>
    template>
    t3>

            "t3": {
                props: ["list"],
                template:  "<table border='1px'>" +
                    "<thead>" +
                    "<slot name='thead'>slot>" +
                    "thead>" +
                    "<tbody>" +
                    "<tr v-for='item in list'>" +
                    "<slot name='tbody' v-bind:item='item'>slot>" +
                    "tr>" +
                    "tbody>" +
                        "table>"
            }

在父组件中只需要在thead中指定具体内容,对应每个tbody中tr每个td的字段绑定,其他交给组件处理即可,其中数据源是list属性,与slot通信是通过slot-scope来实现数据传递,组件中 v-bind:item=“item” 与父组件 slot-scope="{item}" 完成数据访问的传递。(注意v-slot不能同时和slot-scope一起使用,因为v-slot是属于2.6.0开始的新语法,而slot-scope是旧语法)。
和v-on、v-bind一样,v-slot也有缩写,例如:v-slot:header就可以携程#header,插槽没有name的话可以写为:#default。

自定义指令

除了核心功能默认内置的指令(v-model和v-show),vue也允许注册自定义指令,但是在有的情况下需要对普通dom元素进行操作,这时我们就可以使用自定义指令,例如:
一打开页面就聚焦输入框:

请输入:<input type="text" v-focus> 

        directives: {
            //自定义指令
            focus: {
                //inserted:在被绑定元素插入父节点时调用
                inserted: function (el) {
                    el.focus();
                }
            }
        }

钩子函数

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

钩子函数参数

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

简单使用:

<div id="app" v-demo:foo.a.b="message">
div>

            demo: {
                //只调用一次
                bind: function (el,binding,vnode) {
                    let str = JSON.stringify;
                    el.innerHTML =
                        'name: '       + str(binding.name) + '<br>' +
                        'value: '      + str(binding.value) + '<br>' +
                        'expression: ' + str(binding.expression) + '<br>' +
                        'argument: '   + str(binding.arg) + '<br>' +
                        'modifiers: '  + str(binding.modifiers) + '<br>' +
                        'vnode keys: ' + Object.keys(vnode).join(', ')
                }
            }

动态指令参数:指令的参数可以是动态的。
例如:

<p v-internal="200">我是一段文本p>

            internal: {
                bind: function (el,binding,vnode) {
                    el.style.position = "fixed"; //固定
                    el.style.top = binding.value + "px";
                }
            }
<p v-internal1:[direction]="300">我也是一段文本p>

            internal1: {
                bind: function (el,binding,vnode) {
                    el.style.position = "fixed";
                    let str = binding.arg === "left"?"left":"top";
                    el.style[str] = binding.value + "px";
                }
            }

函数简写:

//在bind和update时触发相同行为
Vue.directive('color-swatch', function (el, binding) {
   el.style.backgroundColor = binding.value
})

传入多个参数:(指令函数能够接受所有合法的JavaScript表达式)

<p v-test1="{a: 'red',b: 'blue'}">p>

    Vue.directive('test1', function (el, binding) {
        console.log(binding.value.a) // => "white"
        console.log(binding.value.b)  // => "hello!"
    })

自定义过滤器

vue允许我们自定义过滤器,可被用于一些常见的文本格式化,过滤器可以用在两个地方:双花括号赋值和v-bind表达式:

{
    { message | capitalize }}
<div v-bind:id="rawId | formatId">div>

自定义过滤器例子:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue学习title>
head>
<body>

<div id="app">

    <h1>
        {
    {time | MyFilter1("yyyy-mm-dd")}}
    h1>
    <h2>
        {
    {"hello" | toUpper()}}
    h2>
    

div>

body>
<script src="../js/vue.js">script>
<script type="text/javascript">
    Vue.filter("MyFilter1",function (input,pattern="") {
      
        let date = new Date(input);
        let year = date.getFullYear();
        let month = (date.getMonth() + 1).toString().padStart(2,"0");
        //padStart(arg0,arg1),第一个参数指定字符串的最小长度
        //第二个参数是字符串长度不足时添加的字符串,padStart添加在首部,而padEnd添加在尾部
        let day = (date.getDay() + 1).toString().padStart(2,"0");
        if(pattern === "yyyy-mm-dd"){
      
            return year+"-"+month+"-"+day;
        }else{
      
            let hours = date.getHours().toString().padStart(2,"0");
            let minutes = date.getMinutes().toString().padStart(2,"0");
            let seconds = date.getSeconds().toString().padStart(2,"0");
            return year+"-"+month+"-"+day+" "+hours+":"+minutes+":"+seconds;
        }
        // console.log(year+','+month+','+day);
    });
    const vm = new Vue({
      
        el: "#app",
        data: {
      
            time: 1575182777000 //毫秒
        },
        filters: {
      
            toUpper: function (str) {
      
                if(str === "" || str ==null) return "";
                return str.charAt(0).toUpperCase() + str.slice(1); //首字母大写
            }
        }
    });
script>
html>

过滤器函数总接收表达式的值(之前的操作链的结果)作为第一个参数,例如:下面的例子,filterA会将message作为第一个参数,然后filterB会将filterA的结果作为第一个参数,arg0和arg1作为第二个和第三个参数。

{
    {message | filterA | filterB(arg0,arg1)}}

过渡动画

vue在插入、更新和移除dom时,提供多种不同方式的应用过渡效果。
包括:

  • 在css过渡和动画中自动应用class
  • 可以配合使用第三方css动画库,例如Anitmate.css
  • 在过渡钩子函数中使用JavaScript直接操作DOM
  • 可以配合使用第三方JavaScript动画库,例如Velicity.js

vue提供了transition的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡。

  • 条件渲染(使用v-if)
  • 条件展示(使用v-show)
  • 动态组件
  • 组件根节点

例如:

<div id="app">

    <button @click="isShow=!isShow">点我button>

    <transition name="fade">
        <p v-if="isShow">Hellop>
    transition>
div>

body>
<script src="../js/vue.js">script>
<script type="text/javascript">
    const vm = new Vue({
      
        el: "#app",
        data: {
      
            isShow: true
        }
    });
script>
<style>
    /* 定义开始和结束状态 */
    .fade-enter,.fade-leave-to{
      
        opacity: 0;
    }
    /* 定义进入和离开过渡过程中需要的时间 */
    .fade-enter-active,.fade-leave-active{
      
        transition: opacity 5s;
    }
style>

当插入和删除包含在transition组件中的元素时,vue将会做以下处理:

  1. 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。
  2. 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用。
  3. 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和 Vue 的 nextTick 概念不同

过渡的类名:
在进入/离开的过渡中,会有6个class切换:
1.v-enter:定义进入过渡的开始状态,在元素被插入之前生效,在元素被插入之后的下一帧移除。
2.v-enter-active:定义进入过渡生效时的状态,在整合进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除,这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数
3.v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
4.v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
5.v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
6.v-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。
vue----3.混入,插槽,自定义指令,自定义过滤器,过渡动画,响应式原理_第1张图片

对于这些在过渡中切换的类名来说,如果你使用一个没有name的transition,则v是默认的前缀,有name的话,name作为默认前缀。
css过渡:

    <transition name="side-fade">
        <p v-if="isShow">Worldp>
    transition>

    .side-fade-enter,.side-fade-leave-to{
        transform: translateX(10px); /* 移动10px */
        opacity: 0;
    }
    /* 进入动画 */
    .side-fade-enter-active{
        transition: all .3s ease;
    }
    /* 离开动画 */
    .side-fade-leave-active{
        transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
    }

css动画:css动画用法同css过渡区别是在动画中v-enter类名在节点插入DOM后不会立即删除,而是在animationend事件触发后删除。

    <transition name="bounce">
        <p v-if="isShow">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.p>
    transition>

    .bounce-enter-active {
        animation: bounce-in .5s;
    }
    .bounce-leave-active {
        animation: bounce-in .5s reverse;
    }
    @keyframes bounce-in {
        0% {
            transform: scale(0);
        }
        50% {
            transform: scale(1.5);
        }
        100% {
            transform: scale(1);
        }
    }

自定义过渡的类名:
我们可以通过以下特性来自定义过渡类名:

  • enter-class
  • enter-active-class
  • enter-to-class (2.1.8+)
  • leave-class
  • leave-active-class
  • leave-to-class (2.1.8+)

他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用,例如:

    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/[email protected]">

    <transition
            name="custom-classes-transition"
            enter-active-class="animated tada"
            leave-active-class="animated bounceOutRight"
    >
        <p v-if="isShow">hellop>
    transition>

javascript过渡钩子函数:

<transition
      v-on:before-enter="beforeEnter"
      v-on:enter="enter"
      v-on:after-enter="afterEnter"
      v-on:enter-cancelled="enterCancelled"
      
      v-on:before-leave="beforeLeave"
      v-on:leave="leave"
      v-on:after-leave="afterLeave"
      v-on:leave-cancelled="leaveCancelled"
  >
  // html
  transition>
  <script>
  // ...
  methods: {
      
    // --------
    // 进入中
    // --------
  
    beforeEnter: function (el) {
      
      // ...
    },
    // 当与 CSS 结合使用时
    // 回调函数 done 是可选的
    enter: function (el, done) {
      
      // ...
      done()
    },
    afterEnter: function (el) {
      
      // ...
    },
    enterCancelled: function (el) {
      
      // ...
    },
  
    // --------
    // 离开时
    // --------
  
    beforeLeave: function (el) {
      
      // ...
    },
    // 当与 CSS 结合使用时
    // 回调函数 done 是可选的
    leave: function (el, done) {
      
      // ...
      done()
    },
    afterLeave: function (el) {
      
      // ...
    },
    // leaveCancelled 只用于 v-show 中
    leaveCancelled: function (el) {
      
      // ...
    }
  }
  script>

tips:当只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成;推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css=“false”,Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。

响应式原理(来源于官方文档)

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
vue----3.混入,插槽,自定义指令,自定义过滤器,过渡动画,响应式原理_第2张图片
tips:受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:

var vm = new Vue({
  data:{
       a:1
  }
})
// `vm.a` 是响应式的

vm.b = 2
// `vm.b` 是非响应式的

对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。例如:

Vue.set(vm.someObject, 'b', 2)
同:
this.$set(this.someObject,'b',2)

有时你可能需要为已有对象赋值多个新属性,比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新属性不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的属性一起创建一个新的对象。

// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

声明响应式属性:由于 Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有根级响应式属性,哪怕只是一个空值:

var vm = new Vue({
data: {
  // 声明 message 为一个空值字符串
  message: ''
},
template: '<div>{
    { message }}div>'
})
// 之后设置 `message`
vm.message = 'Hello!'
//这样的限制在背后是有其技术原因的,它消除了在依赖项跟踪系统中的一类边界情况,也使 Vue 实例能更好地配合类型检查系统工作。但与此同时在代码可维护性方面也有一点重要的考虑:data 对象就像组件状态的结构 (schema)。提前声明所有的响应式属性,可以让组件代码在未来修改或给其他开发人员阅读时更易于理解。

异步更新队列:
可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
例如,当你设置 vm.someData = ‘new value’,该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick” 中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。例如:

<div id="example">{
    {message}}div>
<script>
var vm = new Vue({
      
  el: '#example',
  data: {
      
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
      
  vm.$el.textContent === 'new message' // true
})
script>

在组件内使用 vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:

Vue.component('example', {
  template: '<span>{
    { message }}span>',
  data: function () {
       return {
         message: '未更新'
       }
  },
  methods: {
     updateMessage: function () {
         this.message = '已更新'
         console.log(this.$el.textContent) // => '未更新'
         this.$nextTick(function () {
            console.log(this.$el.textContent) // => '已更新'
         })
     }
  }
})

因为 $nextTick() 返回一个 Promise 对象,所以你可以使用新的 ES2017 async/await 语法完成相同的事情:

methods: {
   updateMessage: async function () {
       this.message = '已更新'
       console.log(this.$el.textContent) // => '未更新'
       await this.$nextTick()
       console.log(this.$el.textContent) // => '已更新'
   }
}

你可能感兴趣的:(vue)