Vue生命周期函数

生命周期函数是指,在某一时刻会自动执行的函数。(生命周期也叫钩子函数)

学习记忆时可以成对进行记忆,也就是分为四组。

  1. beforeCreate( )
  • 在初始化 / 实例创建之前执行的函数
  1. Created( )
  • 在初始化 / 实例创建之后执行的函数
  1. beforeMount( )
  • 在组件内容被渲染到页面之前自动执行的函数
  • 注意:此时无法找到任何模板DOM节点
  1. Mounted( )
  • 在组件内容被渲染到页面之后自动执行的函数
  1. beforeUpdate( )
  • 在数据将要变化之前自动执行的函数
  1. updated( )
  • 在数据发生变化之后自动执行的函数
  1. beforeUnmount( )
  • 在VUE实例销毁之前自动执行的函数
  1. unmounted( )
  • 在VUE实例销毁之后自动执行的函数

通过下面案例帮我们学习整个调用周期运转。在控制台查看各函数的调用顺序,并调用destroy,再点改变title按钮,发现改变不了,因为已被销毁

  1. <html>
  2. <head>
  3. <meta charset="UTF-8">
  4. <title>生命周期</title>
  5. </head>
  6. <body>
  7. <div id="app1">
  8. {{title}}
  9. <button type="button" @click="changeTitle">change title</button>
  10. <button type="button" @click="destroy">destroy</button>
  11. </div>
  12. </body>
  13. <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.min.js"></script>
  14. <script>
  15. new Vue({
  16. el: "#app1",
  17. data: {
  18. title: "this is title"
  19. },
  20. methods: {
  21. changeTitle: function () {
  22. this.title = "new title";
  23. },
  24. destroy: function () {
  25. this.$destroy();
  26. }
  27. },
  28. beforeCreate() {
  29. console.log("beforeCreate")
  30. },
  31. created() {
  32. console.log("created")
  33. },
  34. beforeMount() {
  35. console.log("beforeMount")
  36. },
  37. mounted() {
  38. console.log("mounted")
  39. },
  40. beforeUpdate() {
  41. console.log("beforeUpdate")
  42. },
  43. updated() {
  44. console.log("updated")
  45. },
  46. beforeDestroy() {
  47. console.log("beforeDestory")
  48. },
  49. destroyed() {
  50. console.log("destory")
  51. }
  52. })
  53. </script>
  54. </html>

Vue进阶 - 图1

Vue实例知识点

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <div id="app">
            <h1>
                {{title}}
            </h1>
            <button id="btn" @click="btnclick">show</button>
            <p v-if="showFlag">显示段落</p>
            {{lowercasetitle}}
        </div>
    </body>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script type="text/javascript">
        //一个Vue实例
        var v1 = new Vue({
            el:'#app',
            data:{
                title:"测试标题",
                showFlag:false
            },
            methods:{
                btnclick:function(){
                    this.showFlag=true;
                    this.updateTitle("修改后的标题");
                },
                updateTitle:function(d){
                    this.title = d;
                }
            },
            computed:{
                lowercasetitle:function(){
                    return this.title.toLowerCase();
                }
            },
            watch:{
                title:function(newVal,oldVal){
                    console.log(newVal)
                }
            }
        })
    </script>
</html>

新的实例 ,可以创建多个实例对象

new Vue({
    el:"#app2"
})

假如我们要在实例一(#app1)中改变实例二(#app2)中的内容 需求如下:

在第一个Vue实例对象里面改变第二个Vue实例对象的 name 值

在第二个Vue实例对象里面改变第一个Vue实例对象的 name 值

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <div id="app">
            <h1>app里面的数据</h1>
            <h2>{{name}}</h2>
            <button type="button" @click="clickApp">转换app2</button>
        </div>
        <hr />
        <div id="app2">
            <h1>app2里面的数据</h1>
            <h2>{{name}}</h2>
            <button type="button" @click="clickApp2">转换app1</button>

        </div>
    </body>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script type="text/javascript">
        //第一个Vue实例
        var vm1 = new Vue({
            el:'#app',
            data:{
                name:'chen'
            },
            methods:{
                clickApp:function(){
                    vm2.name = "chen"
                }
            }
        })

        var vm2 = new Vue({
            el:'#app2',
            data:{
                name:'chenjia'
            },
            methods:{
                clickApp2:function(){
                    vm1.name = "chenjia"
                }
            }
        })
    </script>
</html>

在vue外面操作vue实例——操作属性

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <div id="app">
            <ul>
                <h2>{{name}}</h2>
            </ul>
        </div>
    </body>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm1 = new Vue({
            el:'#app',
            data:{
                name:'chen'
            }
        })

        setTimeout(function(){
            vm1.name="sttitle";
        },2000);
    </script>
</html>

调用vue实例中的方法——操作方法

<!--html代码-->
<div id="app">
    <h2>{{count}}</h2>
</div>

<!--Js代码-->
<script type="text/javascript">
    var vm1 = new Vue({
        el:'#app',
        data:{
            count:0
        },
        methods:{
            addcount:function(){
                this.count++
            }
        }
    })

    setTimeout(function(){
        vm1.addcount()
    },2000);
</script>

实例属性ref的用法:相当于是id

在vue里面,往往使用ref属性来代替id属性的使用。那么就可以快速的通过调用ref的值来获得页面中的某个元素

<div id="app">
    <button ref="btn1" id="btn" @click="btnclick">show</button>
    <button ref="btn2" id="btn" @click="btnclick">show</button>
</div>

<!--Js代码-->
<script type="text/javascript">
    var vm1 = new Vue({
        el:'#app',
        data:{
            count:0
        },
        methods:{
            btnclick:function(){
                this.$refs.btn1.innerText = "修改btn";
            }
        }
    })
</script>

动态绑定vue实例到页面中

实现了页面的元素和vue对象的动态绑定,之前都是通过el的方式来绑定,也可以通过mount实例属性进行

<div id="app3"></div>
<script>
    var v3 = new Vue({
        template:"<h1>hello</h1>"
    });

    v3.$mount("#app3");
</script>

Vue组件化开发

本章主要涉及的知识点如下。

  • 组件概念
  • 组件注册
  • 组件通信
  • 插槽
  • 特殊情况

什么是组件化开发

我们面对一个复杂问题时,不太可能一次性搞定所有的内容,我们可以将问题拆解,拆解成一个一个小问题,再将这些小问题一个一个解决。组件化开发就是类似的思想。

如果我们将一个页面中所有的逻辑都放到一起,处理起来会非常的复杂,而且不利于后续的管理和扩展。

但是如果我们将一个页面拆分成一个一个的小功能块,每个功能块完成属于自己的这部分独立的功能,那么之后整个页面管理和维护都变得非常容易。

组件化和模块化的不同:

  • 模块化: 是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一;
  • 组件化: 是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用;

总之,此功能是Vue.js框架中最精彩的部分之一,运用得当可以在很大程度上减少重复代码量,页面结构也会变得简洁

为什么要使用组件

为什么要使用组件?很多人在刚开始学习组件时可能都会问这个问题,因为组件是有学习成本的。若是学习成本很高,但实际效果令人也不满意,那么学这个东西就是不值得的。先举一个例子,如图下图所示。

Vue进阶 - 图2

一个很简单的搜索框,在很多项目中都会用到,而且有很大概率不止使用一次,可能几个页面都有相同的搜索框。那么问题来了,是为了方便每次都使用复制和粘贴功能,还是封装成一个组件呢?例如遇到搜索框的地方就使用复制、粘贴实现,其实是比较方便的,毕竟每次不用思考,可以很快地完成开发。但是会不会有这样一种情况,产品经理突然提了一个需求:给每个 <input> 标签都绑定上回车事件,按Enter键,<input> 标签中的内容会自动提交。这种情况可能有很多页面都需要改动,需要很长时间才能完成所有回车事件的触发。若是使用组件呢?这种问题就不是问题了,可以直接给组件绑定回车事件,如此便不用一个个修改了,便可以完成相应的需求了。

这就是为什么学习组件——让代码的复用性更强,同时对代码进行解耦。

什么是解耦?就是降低代码的耦合度。那耦合度又是什么?就是代码之间的联系。若是两个函数的联系很紧密,或者说一个函数只能为另外一个函数所调用,那么可以说,这两个函数的耦合度很高。反之,一个函数可以被很多个函数调用,那么这个函数的耦合性很低。

耦合度低有什么好处呢?首先就是代码量大大减少,其次就是函数和代码看起来会更加清晰,每个函数不相互依存,功能性更加突出。如此,不管是日后调试还是二次开发,都会十分方便,错误的定位也会更加简单。

这就是学习组件的目的,通过多种基础组件的组合形成完整的功能,同时功能的存在又不会增加多余的代码,如此修改,也会更加方便,牵一发而动全身。缺点就是对相应功能的规范要求较高,若是不符合规范,则会增加很多代码,所以,最好将常用的简单部分开发为组件,若是某个部分不常用,则无须组件化,增加工作强度。

Vue组件化思想

它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构建我们的应用。

任何的应用都会被抽象成一颗组件树。

Vue进阶 - 图3

有了组件化的思想,我们在之后的开发中就要充分的利用它。尽可能的将页面拆分成一个个小的、可复用的组件。这样让我们的代码,更加方便组织、管理、扩展。

组件基础练习示例

定义一个名为 button-counter 的新组件。每一次单击按钮,按钮上的数字都会加1。组件内部定义了count变量,之后在<button>标签上绑定了单击事件,每一次单击,count变量会自动增1。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <div id="counter-demo">
            <button-counter></button-counter>
        </div>
    </body>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script type="text/javascript">
        // 注册一个名为 button-counter 的新组件
        Vue.component('button-counter', {
            data: function () {
                return {
                    count: 0
                }
            },
            template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
        })
        new Vue({ el: '#counter-demo' })

    </script>
</html>

Vue进阶 - 图4

分析:

注册一个全局组件语法格式如下:

Vue.component(tagName, options)

tagName为组件名,options为配置选项。

注册后,我们可以使用以下方式来调用组件

<tagName></tagName>

组件命名的规则(tagName):

  • kebab-case
    需要注意的是组件的名字,这里给组件命名为button-counter,标签的名字也是如此。这里使用短横线分隔命名(kebab-case)。当使用短横线命名法命名组件时,调用组件时也必须使用短横线命名法,就像例子中的一样。
  • PascalCase
    另外一种更为常用的命名法,那就是驼峰命名法(PascalCase)。上面例子中的组件就可以命名为BttonCounter。调用的时候,可以使用驼峰命名法<BttonCounter>或者短横线命名法 <button-counter> 来命名。相对于短横线命名法来说,驼峰命名法使用范围更广。虽然可以使用两种调用方式,但是建议统一命名方式和调用方式,否则,在搜索组件名时,没有调用结果的情况,就十分尴尬了。

注意:还有一点需要注意,在新建组件的时候,data属性必须是一个函数,就像上面的例子一样,如果不是一个函数(如下所示):

Vue.component('button-counter', {
    data: function () {
        return {
            count: 0
        }
    },
    template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

在组件多次调用的时候,它们的值会相互覆盖,就是改变一个值,其他值都会受到影响。例如:

组件复用

这里多次引用button-counter这个组件,如果新建组件时,data属性是一个对象,而不是函数,单击任意一个按钮,其他按钮的值就会改变。若是data属性是一个函数,它们会维护自己data中的数据,不会互相影响。

Vue进阶 - 图5

<div id="components-demo">
    <button-counter></button-counter>
    <button-counter></button-counter>
    <button-counter></button-counter>
</div>

注意:当点击按钮时,每个组件都会各自独立维护它的 **count**。因为你每用一次组件,就会有一个它的新 **实例** 被创建。

组件注册

组件注册主要分为两部分:全局注册局部注册

可以通过全局注册和局部注册的方式来注册一个 Vue 组件,两种方式的区别在于,全局注册的组件能够在任何地方使用,其实就是所有的 Vue 实例化的时候都会去渲染这个全局组件;

而局部组件只是注册在某一个 Vue 实例化对象上,只能在这个 Vue 实例化的时候会渲染,其他地方使用会被当成普通的Html标签渲染。我们就先来了解下全局组件的注册。

全局注册

通过Vue.component(tagName , options)方法注册的都是全局组件,也就是他们再注册之后可以用在任何新创建的Vue 实例挂载的区域内。

tagName 为组件名,options 为配置选项。注册后,我们可以使用以下方式来调用组件:

<tagName></tagName>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <div id="app" style="border: 1px solid blue;">
          <my-con></my-con>
          <div style="border: 1px solid red;">
            <my-con></my-con>
          </div>
        </div>
        <div id="app1">
            <!-- 组件未注册 -->
            <my-con></my-con>
        </div>
      </body>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        Vue.component('my-con', {
          template: '<section><h2>组件标题</h2><p>组件内容</p></section>'
        })
        const vm = new Vue({
          el: '#app'
        })
      </script>
</html>

上面在<div id="app">...</div> 外的组件 my-con 没有替换成组件真正的页面结构,是因为 new Vue() 挂载在 id=app 的节点上,不在这个节点上标签,不会受到Vue的影响。

注意:全局注册必须在根Vue实例之前创建,需要在根实例之内使用

<div id="app">
    <p>这是p标签</p>
    <my-compoment></my-compoment>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    // 全局组件
    Vue.component('my-compoment',{
        template: '<div>第一个自己的全局组件</div>'
    });

    // 根实例
    new Vue({
        el:'#app',
        data: {}
    });
</script>

局部注册

Vue实例选项components包含了一个属性,键是组件的名称,值是一个对象,包含了组件的模板等属性。

  1. 定义局部组件: const Counter={…}以变量的形式定义局部组件。
  2. 使用时需要进行注册在Vue 实例中后才能使用,components:{xxx:xxx}
  3. 注册后即可在页面使用组件
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <div id="app">
            <my-con></my-con>
            <div>
                <my-con></my-con>
            </div>
        </div>
        <div id="app1">
            <my-con1></my-con1>
        </div>
    </body>
    <template id="template1">
        <section>
            <h3>组件标题</h3>
            <p>组件内容</p>
        </section>
    </template>
    <template id="template2">
        <section>
            <h3>组件标题B</h3>
            <p>组件内容B</p>
        </section>
    </template>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var componentA = {
            template: '#template1'
        }

        var componentB = {
            template: '#template2'
        }
        const vm = new Vue({
            el: '#app',
            components: {
                'my-con': componentA
            }
        })
        const vm1 = new Vue({
            el: '#app1',
            components: {
                'my-con1': componentB    
            }
        })
    </script>
</html>

组件之间的参数传递

Vue进阶 - 图6

Prop

prop是子组件用来接受父组件传递过来的数据的一个自定义属性。

父组件的数据需要通过props把数据传给子组件,子组件需要显式地用props选项声明 prop

下方案例是父组件通过props传递参数到子组件

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
    <div id="app">
        <child message="hello word!"></child>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    // 注册
    Vue.component('child', {
        // 声明 props
        props: ['message'],
        // 同样也可以在 vm 实例中像 "this.message" 这样使用
        template: '<span>{{ message }}</span>'
    })
    // 创建根实例
    new Vue({
        el: '#app'
    })
</script>
</html>

动态Prop

类似于用v-bind绑定 HTML 特性到一个表达式,也可以用 v-bind 动态绑定 props 的值到父组件的数据中。每当父组件的数据变化时,该变化也会传导给子组件:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
    <div id="app">
        <div>
            <input v-model="parentMsg">
            <hr>
            <child v-bind:message="parentMsg"></child>
        </div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    // 注册
    Vue.component('child', {
        // 声明 props
        props: ['message'],
        // 同样也可以在 vm 实例中像 "this.message" 这样使用
        template: '<span>{{ message }}</span>'
    })
    // 创建根实例
    new Vue({
        el: '#app',
        data: {
            parentMsg: '父组件内容'
        }
    })
</script>
</html>

注意: prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。

Prop 验证

组件可以为 props 进行验证。为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的 对象,而不是一个字符串数组。

Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。

自定义事件

父组件是使用props传递数据给子组件,但如果子组件要把数据传递回去,就需要使用自定义事件!

我们可以使用v-on绑定自定义事件, 每个 Vue 实例都实现了事件接口(Events interface),即:

  • 使用 $on(eventName) 监听事件
  • 使用 $emit(eventName) 触发事件
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
    <div id="app">
        <div id="counter-event-example">
            <p>{{ total }}</p>
            <button-counter v-on:increment="incrementTotal"></button-counter>
            <button-counter v-on:increment="incrementTotal"></button-counter>
        </div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    //定义组件
    Vue.component('button-counter', {
        template: '<button v-on:click="incrementHandler">{{ counter }}</button>',
        data: function () {
            return {
                counter: 0
            }
        },
        methods: {
            incrementHandler: function () {
                this.counter += 1
                this.$emit('increment')
            }
        },
    })
    new Vue({
        el: '#counter-event-example',
        data: {
            total: 0
        },
        methods: {
            incrementTotal: function () {
                this.total += 1
            }
        }
    })
</script>
</html>

注意:组件中的data 必须是一个函数,这样的好处就是每个实例可以维护一份被返回对象的独立的拷贝,如果 data 是一个对象则会影响到其他实例,如下所示:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
    <div id="components-demo3" class="demo">
        <button-counter2></button-counter2>
        <button-counter2></button-counter2>
        <button-counter2></button-counter2>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    var buttonCounter2Data = {
        count: 0
    }
    Vue.component('button-counter2', {
        /*
        data: function () {
            // data 选项是一个函数,组件不相互影响
            return {
                count: 0
            }
        },
        */
        data: function () {
            // data 选项是一个对象,会影响到其他实例
            return buttonCounter2Data
        },
        template: '<button v-on:click="count++">点击了 {{ count }} 次。</button>'
    })
    new Vue({ el: '#components-demo3' })

</script>
</html>

在组件上使用v-model

组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件。

<input v-model="parentData">

等价于:

<input 
    :value="parentData"
    @input="parentData = $event.target.value"
>

以下实例自定义组件 runoob-input,父组件的 num 的初始值是 100,更改子组件的值能实时更新父组件的 num:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
    <div id="app">
        <runoob-input v-model="num"></runoob-input>
        <p>输入的数字为:{{num}}</p>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    Vue.component('runoob-input', {
        template: `
            <p>   <!-- 包含了名为 input 的事件 -->
            <input
            ref="input"
            :value="value" 
            @input="$emit('input', $event.target.value)"
            >
            </p>`,
        props: ['value'], // 名为 value 的 prop
    })

    new Vue({
        el: '#app',
        data: {
            num: 100,
        }
    })

</script>
</html>

由于 v-model 默认传的是 value,不是 checked,所以对于复选框或者单选框的组件时,我们需要使用 model 选项,model 选项可以指定当前的事件类型和传入的 props。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title></title>
</head>

<body>
    <div id="app">
        <base-checkbox v-model="lovingVue"></base-checkbox>
        <div v-show="lovingVue">
            如果选择框打勾我就会显示。
        </div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    // 注册
    Vue.component('base-checkbox', {
        model: {
            prop: 'checked',
            event: 'change'  // onchange 事件
        },
        props: {
            checked: Boolean
        },

        template: `
                <input
                    type="checkbox"
                    v-bind:checked="checked"
                    v-on:change="$emit('change', $event.target.checked)"
                >`
    })
    // 创建根实例
    new Vue({
        el: '#app',
        data: {
            lovingVue: true
        }
    })

</script>

</html>

实例中 lovingVue 的值会传给 checked 的 prop,同时当 触发 change 事件时, lovingVue 的值也会更新。