logo头像
Snippet 博客主题

Vue新手进阶指南

1. Vue生命周期

1.1 什么是生命周期

  • 从Vue实例创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期!
  • 生命周期钩子:就是生命周期事件的别名而已;
  • 生命周期钩子 = 生命周期函数 = 生命周期事件

1.2 Vue实例的生命周期

  • 创建期间的生命周期函数:
    1. beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
    2. created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板
    3. beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
    4. mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
  • 运行期间的生命周期函数:
    1. beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点
    2. updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
  • 销毁期间的生命周期函数:
    1. beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
    2. destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

Vue实例生命周期

2. Vue组件

2.1 什么是组件

组件的出现,就是为了拆分Vue实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可;
组件化和模块化的不同:

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

2.2 组件类型及其定义

2.2.1 全局组件

  1. 基本的html代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <div id="app">
    <!-- 自定义的login登录组件 -->
    <login></login>
    <!-- 无属性的可以以单节点书写 -->
    <login/>
    </div>
    <script>
    // 创建 login 实例,得到 login
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {}
    });
    </script>
  2. 使用 Vue.extend 配合 Vue.component 方法

    1
    2
    3
    4
    5
    // 1.1 使用 Vue.extend 来创建全局的Vue组件
    var login = Vue.extend({
    template: '<h3>这是使用Vue.extend创建的登录组件</h3>' // 通过 template 属性,指定了组件要展示的HTML结构
    })
    Vue.component('login', login) // 注册为全局组件
  3. 直接使用 Vue.component方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 1.2 使用 Vue.component('组件的名称', 创建出来的组件模板对象)
    var login = "<h3>这是使用Vue.component创建的登录组件</h3>";
    // 注意: 组件中的DOM结构,最好有且只能有唯一的根元素(Root Element)来进行包裹!低版本的vue有可能报错
    Vue.component('login', {
    template : login // 注册为全局组件
    })
    // 如果使用 Vue.component 定义全局组件的时候,组件名称使用了驼峰命名,则在引用组件的时候,需要把 大写的驼峰改为小写的字母,同时,两个单词之前,使用 - 链接,如果不使用驼峰,则直接拿名称来使用即可;
    Vue.component('toLogin', {
    // 比如定义的全局组件为toLogin,页面<toLogin></toLogin>不可能显示组件,要想显示组件需要拆开这样写<to-
    template : login
    })
  4. 将模板字符串,定义到script标签中

    1
    2
    3
    4
    5
    6
    <!-- 通过script标签定义模板 -->
    <script id="login" type="x-template">
    <div>
    <a href="#">登录</a>
    </div>
    </script>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Vue.component('login', {  // 定义并注册为全局组件
    template: '#login' // 引用模板ID
    });

    // 注意这个必须在最后面,不然组件定义会失败
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {}
    });

2.2.3 私有组件

  1. 先定义两个组件模板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- 在 被控制的 #app 外面,使用 template 元素,定义组件的HTML模板结构  -->
    <template id="tmpl">
    <div>
    <h1>这是通过template元素,在外部定义的组件结构,这个方式,有代码的只能提示和高亮</h1>
    </div>
    </template>

    <template id="tmpl2">
    <h1>这是私有的login组件</h1>
    </template>
  2. 定义实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // 将tmpl定义为全局属性
    Vue.component('mycom3', {
    template: '#tmpl'
    })

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {}
    });


    var vm2 = new Vue({
    el: '#app2',
    data: {},
    methods: {},
    filters: {},
    directives: {},
    components: { // 将tmpl2定义实例内部私有组件的
    login: {
    template: '#tmpl2'
    }
    },

    beforeCreate() { },
    created() { },
    beforeMount() { },
    mounted() { },
    beforeUpdate() { },
    updated() { },
    beforeDestroy() { },
    destroyed() { }
    })
  3. 测试私有组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <div id="app">
    <mycom1></mycom1>
    <!-- 这是login是私有的组件,所以页面上不会显示出来 -->
    <!-- 除此之外,console会报错 Unknown custom element: <login> - did you register the component correctly? For recursive components, make sure to provide the "name" option. -->
    <login></login>
    </div>

    <div id="app2">
    <mycom1></mycom1>
    <!-- 这个本身就是app2的私有组件,可以正常显示 -->
    <login></login>
    </div>

2.3 组件中展示数据和响应事件

在组件中,data需要被定义为一个方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<body>
<div id="app">
<mycom1></mycom1>
</div>
<script>
// 1. 组件可以有自己的 data 数据
// 2. 组件的 data 和 实例的 data 有点不一样,实例中的 data 可以为一个对象,但是 组件中的 data 必须是一个方法
// 3. 组件中的 data 除了必须为一个方法之外,这个方法内部,还必须返回一个对象才行;
// 4. 组件中 的data 数据,使用方式,和实例中的 data 使用方式完全一样!!!
Vue.component('mycom1', {
template: '<h1 @click="sayHello">{{msg}},你好,欢迎学习Vue!</h1>',
data: function () {
return {
msg: '李彤'
}
},
methods: {
sayHello() {
// 当前this指的是vm这个实例
console.log(this)
// 获取data中的数据
alert(this.$data.msg + ', 你好,' + this.msg)
}
}
})
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {},
methods: {}
});
</script>
</body>

在子组件中,如果将模板字符串,定义到了script标签中,那么,要访问子组件身上的data属性中的值,需要使用this来访问;

2.4 探查组件中data属性为对象的原因

为什么组件中的data属性必须定义为一个方法并返回一个对象?我们通过计数器案例演示一个问题:

  1. 定义一个组件模板

    1
    2
    3
    4
    5
    6
    7
    8
    <template id="tmpl">
    <div>
    <!-- 定义一个按钮,它功能是点击一次按钮count+1的操作 -->
    <input type="button" value="+1" @click="increment">
    <!-- 显示计算后的结果 -->
    <h3>{{count}}</h3>
    </div>
    </template>
  1. 先定义一个可重用的counter计数器组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 定义一个全局的计数器
    var dataObj = { count: 0 }

    // 这是一个计数器的组件, 身上有个按钮,每当点击按钮,让 data 中的 count 值 +1
    Vue.component('counter', {
    template: '#tmpl',
    data: function () {
    return dataObj
    // return { count: 0 }
    },
    methods: {
    increment() {
    this.count++
    }
    }
    })

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {}
    });
  2. 书写页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <body>
    <div id="app">
    <counter></counter>
    <hr>
    <counter></counter>
    <hr>
    <counter></counter>
    </div>
    </body>
  3. 执行按钮结果,我们随便点击一个按钮,其他的按钮也会触发,这就是共用了全局变量的问题。

  4. 我们定义组件的时候,返回一个方法就可以实现正确的需求,原因是每个方法的count都是私有的,不会存在共享变量的问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 这是一个计数器的组件, 身上有个按钮,每当点击按钮,让 data 中的 count 值 +1
    Vue.component('counter', {
    template: '#tmpl',
    data: function () {
    // return dataObj
    return { count: 0 } // 定义这样就可以了
    },
    methods: {
    increment() {
    this.count++
    }
    }
    })

2.5 局部子组件

  1. 组件实例定义方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {},
    components: { // 定义子组件
    account: { // account 组件
    template: '<div><h1>这是Account组件{{name}}</h1><login></login></div>', // 在这里使用定义的子组件,组件嵌套
    components: { // 定义子组件的子组件
    login: { // login 组件
    template: "<h3>这是登录组件</h3>"
    }
    }
    }
    }
    });
    </script>
  2. 引用组件

    1
    2
    3
    <div id="app">
    <account></account>
    </div>

2.6 组件切换

2.6.1 使用flag标识符结合v-ifv-else切换组件

  1. 页面结构

    1
    2
    3
    4
    5
    <div id="app">
    <input type="button" value="toggle" @click="flag=!flag">
    <my-com1 v-if="flag"></my-com1>
    <my-com2 v-else="flag"></my-com2>
    </div>
  2. Vue实例定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <script>
    Vue.component('myCom1', {
    template: '<h3>很好</h3>'
    })

    Vue.component('myCom2', {
    template: '<h3>不好</h3>'
    })

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {
    flag: true
    },
    methods: {}
    });
    </script>

2.6.2 使用:is属性来切换不同的子组件

  1. 组件实例定义方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 登录组件
    const login = Vue.extend({
    template: `<div>
    <h3>登录组件</h3>
    </div>`
    });
    Vue.component('login', login);

    // 注册组件
    const register = Vue.extend({
    template: `<div>
    <h3>注册组件</h3>
    </div>`
    });
    Vue.component('register', register);

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: { comName: 'login' },
    methods: {}
    });
  2. 使用component标签,来引用组件,并通过:is属性来指定要加载的组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div id="app">
    <a href="#" @click.prevent="comName='login'">登录</a>
    <a href="#" @click.prevent="comName='register'">注册</a>
    <hr>
    <!-- Vue淡入淡出动画效果 -->
    <transition mode="out-in">
    <component :is="comName"></component>
    </transition>
    </div>
  3. 添加切换样式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <style>
    .v-enter,
    .v-leave-to {
    opacity: 0;
    transform: translateX(30px);
    }

    .v-enter-active,
    .v-leave-active {
    position: absolute;
    transition: all 0.3s ease;
    }

    h3{
    margin: 0;
    }
    </style>

2.7 组件间值传递和方法传递

2.7.1 父组件向子组件传值

  1. 组件实例定义方式,注意:一定要使用props属性来定义父组件传递过来的数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {
    msg: '123 啊-父组件中的数据'
    },
    methods: {},

    components: {
    // 结论:经过演示,发现,子组件中,默认无法访问到 父组件中的 data 上的数据 和 methods 中的方法
    com1: {
    data() { // 注意: 子组件中的 data 数据,并不是通过 父组件传递过来的,而是子组件自身私有的,比如: 子组件通过 Ajax ,请求回来的数据,都可以放到 data 身上;
    // data 上的数据,都是可读可写的;
    return {
    title: '123',
    content: 'qqq'
    }
    },
    template: '<h1 @click="change">这是子组件 --- {{ parentmsg }}</h1>',
    // 注意: 组件中的 所有 props 中的数据,都是通过 父组件传递给子组件的
    // props 中的数据,都是只读的,无法重新赋值
    props: ['parentmsg'], // 把父组件传递过来的 parentmsg 属性,先在 props 数组中,定义一下,这样,才能使用这个数据
    directives: {},
    filters: {},
    components: {},
    methods: {
    change() {
    this.parentmsg = '被修改了'
    }
    }
    }
    }
    });
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    <body>
    <div id="app">
    <div>
    {{pMsg}}
    </div>
    <!-- 加text属性传递给子组件 -->
    <!-- <son-Comp text="我是写死的数据"></son-Comp> -->
    <!-- 绑定父组件的pMsg -->
    <son-Comp :text="pMsg"></son-Comp>
    </div>
    </body>


    <script>
    Vue.component('sonComp', {
    data: function() {
    return {
    "sMsg": "我是子组件数据"
    }
    },
    template: "<h1>{{sMsg + '----' + text}}</h1>",
    // 只读属性
    props: ["text"]
    })
    new Vue({
    el: "#app",
    data: {
    "pMsg": "我是父组件数据"
    }
    })
    </script>
  1. 使用v-bind或简化指令,将数据传递到子组件中

    1
    2
    3
    4
    <div id="app">
    <!-- 父组件,可以在引用子组件的时候, 通过 属性绑定(v-bind:) 的形式, 把 需要传递给 子组件的数据,以属性绑定的形式,传递到子组件内部,供子组件使用 -->
    <com1 :parentmsg="msg"></com1>
    </div>

2.7.2 父组件向子组件传方法

  1. 组件定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <template id="tmpl">
    <div>
    <h1>这是子组件</h1>
    <input type="button" value="这是子组件中的按钮 - 点击它,触发 父组件传递过来的 func 方法" @click="myclick">
    </div>
    </template>

    <script>
    // 定义了一个字面量类型的 组件模板对象
    var com2 = {
    template: '#tmpl', // 通过指定了一个 Id, 表示 说,要去加载 这个指定Id的 template 元素中的内容,当作 组件的HTML结构
    data() {
    return {
    sonmsg: { name: '小头儿子', age: 6 }
    }
    },
    methods: {
    myclick() {
    // 当点击子组件的按钮的时候,如何 拿到 父组件传递过来的 func 方法,并调用这个方法???
    // emit 英文原意: 是触发,调用、发射的意思
    // this.$emit('func123', 123, 456)
    this.$emit('func', this.sonmsg)
    }
    }
    }


    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {
    datamsgFormSon: null
    },
    methods: {
    show(data) {
    console.log('调用了父组件身上的 show 方法: --- ' + data)
    console.log(data);
    this.datamsgFormSon = data
    }
    },

    components: {
    com2
    // com2: com2
    }
    });
    </script>
  2. 页面传递调用

    1
    2
    3
    4
    <div id="app">
    <!-- 父组件向子组件 传递 方法,使用的是 事件绑定机制; v-on, 当我们自定义了 一个 事件属性之后,那么,子组件就能够,通过某些方式,来调用 传递进去的 这个 方法了 -->
    <com2 @func="show"></com2>
    </div>

2.7.3 子组件向父组件传值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

<body>
<div id="app">
<div>
{{pMsg}}
</div>
<!-- 可以写为@say="handle($event)",$event接受参数 -->
<son-Comp @say="handle"></son-Comp>
</div>
</body>

<script>
/**
* 子组件传递值给父组件
* 1. 子组件通过$emit自定义事件向父组件传递数据
* 2. 父组件监听子组件的事件
*/
Vue.component('sonComp', {
data: function() {
return {
"sMsg": "我是子组件数据"
}
},
template: `
<button @click='$emit("say",sMsg)'>点击我一下试一试</button>
`,
// 只读属性,单向数据流,只允许父组件向子组件操作数据,不允许子组件直接操作props的数据
// 如果子组件直接操作prop的数据,逻辑比较麻烦,不好控制
})
new Vue({
el: "#app",
data: {
"pMsg": "我是父组件数据"
},
methods: {
handle: function(sMsg) {
console.log(sMsg)
// 将子组件的sMsg数据赋值给父组件的pMsg
this.pMsg = sMsg
}
}

})
</script>

2.7.4 兄弟组件传值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<body>
<div id="app">
<div>
<h1>{{pMsg}}</h1>
</div>
<comp1></comp1>
<comp2></comp2>
</div>
</body>


<script>
/**
* 兄弟之间传值
* 1. vue提供单独的事件中心管理组件进行通信
* 2. 单独new Vue()实例作为事件中心
* 3. 通过$on进行监听事件和$off进行销毁事件
* 4. 通过$emit触发事件
*/

// 提供事件中心
const hub = new Vue()
Vue.component('comp1', {
data: function() {
return {
msg1: "我是兄弟1数据",
data1: 11111111111
}
},
template: `<div><h2>{{data1}}</h2><button @click="handle">点击</button></div>`,
methods: {
handle: function(){
// 触发兄弟组件comp1的事件
hub.$emit("comp2-event",{
msg: this.msg1,
newVal: this.data1
})
}
},
mounted: function(){
// vue的生命周期,模板渲染完后执行,监听事件
hub.$on("comp1-event",(val) => {
console.log(val.msg);
console.log(val.newVal);
this.data1=val.newVal;
})
}
})
Vue.component('comp2', {
data: function() {
return {
msg2: "我是兄弟2数据",
data2: 22222222
}
},
template: `<div><h2>{{data2}}</h2><button @click="handle">点击</button></div>`,
methods: {
handle: function(){
// 触发兄弟组件comp2的事件
hub.$emit("comp1-event",{
msg:this.msg2,
newVal: this.data2
})
}
},
mounted: function(){
// vue的生命周期,模板渲染完后执行,监听事件
hub.$on("comp2-event",(val) => {
console.log(val.msg);
console.log(val.newVal);
this.data2=val.newVal;
})
}
})
new Vue({
el: "#app",
data: {
"pMsg": "我是父组件数据"
}

})
</script>

2.7.4 组件插槽

父组件向子组件传递模板内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<body>
<div id="app">
<comp1>中国必胜</comp1>
<comp2></comp2>
</div>
</body>


<script>

Vue.component('comp1', {
data: function() {
return {
}
},
template: `<div><strong>武汉加油:</strong><slot></slot></div>`,
})
Vue.component('comp2', {
data: function() {
return {

}
},
template: `<div><strong>来个默认值:</strong><slot>我是默认内容</slot></div>`,
})

new Vue({
el: "#app",
data: {

}

})
</script>

2.8 获取DOM元素和组件引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<body>
<div id="app">
<input type="button" value="获取元素" @click="getElement" ref="mybtn">
<h3 id="myh3" ref="myh3">哈哈哈, 今天天气太好了!!!</h3>
<hr>
<login ref="mylogin"></login>
</div>

<script>
var login = {
template: '<h1>登录组件</h1>',
data() {
return {
msg: 'son msg'
}
},
methods: {
show() {
console.log('调用了子组件的方法')
}
}
}

// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {},
methods: {
getElement() {
// console.log(document.getElementById('myh3').innerText)
// ref 是 英文单词 【reference】 值类型 和 引用类型 referenceError
console.log(this.$refs.myh3.innerText)
console.log(this.$refs.mylogin.msg)
this.$refs.mylogin.show()
}
},
components: {
login
}
});
</script>
</body>

2.9 组件综合练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>

<body>
<div id="app">
<cmt-box @func="loadComments"></cmt-box>

<ul class="list-group">
<li class="list-group-item" v-for="item in list" :key="item.id">
<span class="badge">评论人: {{ item.user }}</span>
{{ item.content }}
</li>
</ul>

</div>

<template id="tmpl">
<div>

<div class="form-group">
<label>评论人:</label>
<input type="text" class="form-control" v-model="user">
</div>

<div class="form-group">
<label>评论内容:</label>
<textarea class="form-control" v-model="content"></textarea>
</div>

<div class="form-group">
<input type="button" value="发表评论" class="btn btn-primary" @click="postComment">
</div>

</div>
</template>

<script>

var commentBox = {
data() {
return {
user: '',
content: ''
}
},
template: '#tmpl',
methods: {
postComment() { // 发表评论的方法
// 分析:发表评论的业务逻辑
// 1. 评论数据存到哪里去??? 存放到了 localStorage 中 localStorage.setItem('cmts', '')
// 2. 先组织出一个最新的评论数据对象
// 3. 想办法,把 第二步中,得到的评论对象,保存到 localStorage 中:
// 3.1 localStorage 只支持存放字符串数据, 要先调用 JSON.stringify
// 3.2 在保存 最新的 评论数据之前,要先从 localStorage 获取到之前的评论数据(string), 转换为 一个 数组对象, 然后,把最新的评论, push 到这个数组
// 3.3 如果获取到的 localStorage 中的 评论字符串,为空不存在, 则 可以 返回一个 '[]' 让 JSON.parse 去转换
// 3.4 把 最新的 评论列表数组,再次调用 JSON.stringify 转为 数组字符串,然后调用 localStorage.setItem()

var comment = { id: Date.now(), user: this.user, content: this.content }

// 从 localStorage 中获取所有的评论
var list = JSON.parse(localStorage.getItem('cmts') || '[]')
list.unshift(comment)
// 重新保存最新的 评论数据
localStorage.setItem('cmts', JSON.stringify(list))

this.user = this.content = ''

// this.loadComments() // ?????
this.$emit('func')
}
}
}

// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {
list: [
{ id: Date.now(), user: '李白', content: '天生我材必有用' },
{ id: Date.now(), user: '江小白', content: '劝君更尽一杯酒' },
{ id: Date.now(), user: '小马', content: '我姓马, 风吹草低见牛羊的马' }
]
},
beforeCreate(){ // 注意:这里不能调用 loadComments 方法,因为在执行这个钩子函数的时候,data 和 methods 都还没有被初始化好

},
created(){
this.loadComments()
},
methods: {
loadComments() { // 从本地的 localStorage 中,加载评论列表
var list = JSON.parse(localStorage.getItem('cmts') || '[]')
this.list = list
}
},
components: {
'cmt-box': commentBox
}
});
</script>
</body>

</html>

3. Vue Router

3.1 路由定义

  1. 后端路由:对于普通的网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源;
  2. 前端路由:对于单页面应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特点:HTTP请求中不会包含hash相关的内容;所以,单页面程序中的页面跳转主要用hash实现;
  3. 在单页面应用程序中,这种通过hash改变来切换页面的方式,称作前端路由(区别于后端路由);

3.2 vue-router基本用法

  1. 导入 vue-router 组件类库

    1
    2
    3
    4
    <!-- 0. 安装 vue由模块,路由的前置条件,如果在路由模块后面导入,则路由模块会失效 -->
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <!-- 1. 安装 vue-router 路由模块 -->
    <script src="https://cdn.bootcss.com/vue-router/3.1.3/vue-router.js"></script>
  2. 定义样式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <style>
    /* Vue路由自带的类 */
    .router-link-active,
    /* 自定义的类 */
    .myactive {
    color: red;
    font-weight: 800;
    font-style: italic;
    font-size: 80px;
    text-decoration: underline;
    background-color: green;
    }

    .v-enter,
    .v-leave-to {
    opacity: 0;
    transform: translateX(140px);
    }

    .v-enter-active,
    .v-leave-active {
    transition: all 0.5s ease;
    }
    </style>
  1. 使用 router-link 组件来导航

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <div id="app">

    <!-- <a href="#/login">登录</a> -->
    <!-- <a href="#/register">注册</a> -->

    <!-- router-link 默认渲染为一个a 标签 -->
    <router-link to="/login" tag="span">登录</router-link>
    <router-link to="/register">注册</router-link>

    <!-- 这是 vue-router 提供的元素,专门用来 当作占位符的,将来,路由规则,匹配到的组件,就会展示到这个 router-view 中去 -->
    <!-- 所以: 我们可以把 router-view 认为是一个占位符 -->
    <transition mode="out-in">
    <router-view></router-view>
    </transition>
    </div>
  2. 创建组件模板

    1
    2
    3
    4
    5
    6
    7
    8
    // 组件的模板对象
    var login = {
    template: '<h1>登录组件</h1>'
    }

    var register = {
    template: '<h1>注册组件</h1>'
    }
  3. 创建路由router实例,病定义路由规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 2. 创建一个路由对象, 当 导入 vue-router 包之后,在 window 全局对象中,就有了一个 路由的构造函数,叫做 VueRouter
    // 在 new 路由对象的时候,可以为 构造函数,传递一个配置对象
    var routerObj = new VueRouter({
    // route // 这个配置对象中的 route 表示 【路由匹配规则】 的意思
    routes: [ // 路由匹配规则
    // 每个路由规则,都是一个对象,这个规则对象,身上,有两个必须的属性:
    // 属性1 是 path, 表示监听 哪个路由链接地址;
    // 属性2 是 component, 表示,如果 路由是前面匹配到的 path ,则展示 component 属性对应的那个组件
    // 注意: component 的属性值,必须是一个 组件的模板对象, 不能是 组件的引用名称;
    // { path: '/', component: login },
    { path: '/', redirect: '/login' }, // 这里的 redirect 和 Node、Java中的 redirect 完全是两码事
    { path: '/login', component: login },
    { path: '/register', component: register }
    ],
    linkActiveClass: 'myactive' // 路由高亮,可以自定义,也可以用vue路由自带的类.router-link-active定义样式,激活相关的类
    })
  4. 使用router属性来使用路由规则

    1
    2
    3
    4
    5
    6
    7
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {},
    router: routerObj // 将路由规则对象,注册到 vm 实例上,用来监听 URL 地址的变化,然后展示对应的组件
    });

3.3 路由规则中定义参数

3.3.1 在规则中定义参数

1
{ path: '/register/:id', component: register }

3.3.2 this.$route.params获取路由中的参数

1
2
3
var register = Vue.extend({
template: '<h1>注册组件 --- {{this.$route.params.id}}</h1>'
});

3.3.3 使用查询字符串传递参数

  1. html代码

    1
    2
    3
    4
    5
    6
    <div id="app">
    <!-- 如果在路由中,使用 查询字符串,给路由传递参数,则 不需要修改 路由规则的 path 属性 -->
    <router-link to="/login?id=10&name=zs">登录</router-link>
    <router-link to="/register">注册</router-link>
    <router-view></router-view>
    </div>
  1. JavaScript代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <script>
    var login = {
    template: '<h1>登录 --- {{ $route.query.id }} --- {{ $route.query.name }}</h1>',
    data(){
    return {
    msg: '123'
    }
    },
    created(){ // 组件的生命周期钩子函数
    // console.log(this.$route)
    console.log(this.$route.query.id)
    }
    }

    var register = {
    template: '<h1>注册</h1>'
    }

    var router = new VueRouter({
    routes: [
    { path: '/login', component: login },
    { path: '/register', component: register }
    ]
    })

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {},
    // router: router
    router
    });
    </script>

3.3.4 使用Restful格式传参

  1. html代码

    1
    2
    3
    4
    5
    6
    <div id="app">
    <!-- 如果在路由中,使用 查询字符串,给路由传递参数,则 不需要修改 路由规则的 path 属性 -->
    <router-link to="/login?id=10&name=zs">登录</router-link>
    <router-link to="/register">注册</router-link>
    <router-view></router-view>
    </div>
  2. JavaScript代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <script>

    var login = {
    template: '<h1>登录 --- {{ $route.params.id }} --- {{ $route.params.name }}</h1>',
    data(){
    return {
    msg: '123'
    }
    },
    created(){ // 组件的生命周期钩子函数
    console.log(this.$route.params.id)
    }
    }

    var register = {
    template: '<h1>注册</h1>'
    }

    var router = new VueRouter({
    routes: [
    { path: '/login/:id/:name', component: login },
    { path: '/register', component: register }
    ]
    })

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {},
    // router: router
    router
    });
    </script>

3.4 路由嵌套

  1. html代码

    1
    2
    3
    4
    <div id="app">
    <router-link to="/account">Account</router-link>
    <router-view></router-view>
    </div>
  2. 定义父组件模板

    1
    2
    3
    4
    5
    6
    7
    8
    <template id="tmpl">
    <div>
    <h1>这是 Account 组件</h1>
    <router-link to="/account/login">登录</router-link>
    <router-link to="/account/register">注册</router-link>
    <router-view></router-view>
    </div>
    </template>
  3. JavaScript代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <script>

    // 组件的模板对象
    var account = {
    template: '#tmpl'
    }

    var login = {
    template: '<h3>登录</h3>'
    }

    var register = {
    template: '<h3>注册</h3>'
    }

    var router = new VueRouter({
    routes: [
    {
    path: '/account',
    component: account,
    // 使用 children 属性,实现子路由,同时,子路由的 path 前面,不要带 / ,否则永远以根路径开始请求,这样不方便我们用户去理解URL地址
    children: [
    { path: 'login', component: login },
    { path: 'register', component: register }
    ]
    }
    // { path: '/account/login', component: login },
    // { path: '/account/register', component: register }
    ]
    })

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {},
    router
    });
    </script>

3.5 命名视图

需求:命名视图实现经典布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdn.bootcss.com/vue-router/3.1.3/vue-router.js"></script>
<style>
html,
body {
margin: 0;
padding: 0;
}

.header {
background-color: orange;
height: 80px;
}

h1 {
margin: 0;
padding: 0;
font-size: 16px;
}

.container {
display: flex;
height: 600px;
}

.left {
background-color: lightgreen;
flex: 2;
}

.main {
background-color: lightpink;
flex: 8;
}
</style>
</head>

<body>
<div id="app">

<router-view></router-view>
<div class="container">
<!-- 指定name传递 -->
<router-view name="left"></router-view>
<router-view name="main"></router-view>
</div>

</div>

<script>

var header = {
template: '<h1 class="header">Header头部区域</h1>'
}

var leftBox = {
template: '<h1 class="left">Left侧边栏区域</h1>'
}

var mainBox = {
template: '<h1 class="main">mainBox主体区域</h1>'
}

// 创建路由对象
var router = new VueRouter({
routes: [
// 这个操作是不行的,三选一操作
/* { path: '/', component: header },
{ path: '/left', component: leftBox },
{ path: '/main', component: mainBox } */

{
path: '/', components: {
'default': header,
'left': leftBox,
'main': mainBox
}
}
]
})

// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {},
methods: {},
router
});
</script>
</body>
</html>

4. Vuex

4.1 Vuex是个什么鬼

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

上述摘抄来源于官方对Vuex的解释。眨眼一看,什么鬼?

后端的童鞋们,不要看了,前端技术太装逼了,看不下去。我来解释一下。

状态管理模式可以理解为对共用数据以及组件的管理,类似与我们Java Servlet的四大域对象,将数据放入域中管理,相同域的数据可以做到数据操控,前端不同的页面及组件修改了数据后都互相可见,并且是响应式的(数据与视图之间实时同步)。

总而言之,Vuex是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间的数据共享。

使用Vuex管理数据的好处:

  1. 能够在vuex中集中管理共享的数据,便于开发和后期进行维护;
  2. 能够高效的实现组件之间的数据共享,提高开发效率;
  3. 存储在vuex中的数据是响应式的,当数据发生改变时,页面中的数据也会同步更新;

4.2 “状态管理”又是个什么鬼

我们先来一个官方简单的Vue计数应用示例,见识一下这个Vuex。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
new Vue({
// state
data () {
return {
count: 0
}
},
// view
template: `
<div>{{ count }}</div>
`,
// actions
methods: {
increment () {
this.count++
}
}
})

这个状态自管理应用包含以下几个部分:

  • state,驱动应用的数据源;
  • view,以声明方式将 state 映射到视图;
  • actions,响应在 view 上的用户输入导致的状态变化。

以下是一个表示“单向数据流”理念的简单示意:

单向数据流

但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!

通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。

这就是 Vuex 背后的基本思想,借鉴了 FluxReduxThe Elm Architecture。与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。

如果你想交互式地学习 Vuex,可以看这个 Scrimba 上的 Vuex 课程,它将录屏和代码试验场混合在了一起,你可以随时暂停并尝试。

交互图

4.3 什么时候用Vuex

Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。

如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:

Flux 架构就像眼镜:您自会知道什么时候需要它。

4.4 开始使用Vuex

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex)
const store = new Vuex.Store({
// 状态(数据)
state: {
count: 0
},
// 状态变更操作
mutations: {
increment (state) {
state.count++
}
}
})
// store.commit 方法触发状态变更
store.commit('increment')
// 可以通过 store.state 来获取状态对象
console.log(store.state.count) // -> 1

我们通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,我们甚至可以实现如时间穿梭般的调试体验。

由于 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。触发变化也仅仅是在组件的 methods 中提交 mutation。

总而言之,只有组件共享数据的时候,才可以放入到vuex中,如果是组件内部的私有数据,只要放在组件的data中即可,不需要放入vuex进行数据管理。

4.5 用Vuex实现购物车的数据操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vuex</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script>
</head>

<body>
<div id="app">
<cart></cart>
<count></count>
<price></price>
</div>

<template id="cart">
<div>
<h1>购物车页面</h1>
</div>
</template>

<template id="count">
<div>
<!-- <p>索尼A7M3全画幅微单相机 &nbsp;&nbsp;&nbsp;&nbsp;¥{{$store.state.unitPrice}}</p> -->
<!-- 通过Vuex的Getter取值 -->
<p v-html="$store.getters.text"></p>
<input type="input" name="num" v-model="$store.state.num" >
<input type="button" value="再买一个" @click="add">
<input type="button" value="少买一个" @click="remove">
</div>
</template>

<template id="price">
<h2>当前总金额为 {{$store.state.num}} * {{$store.state.unitPrice}} = {{$store.state.num * $store.state.unitPrice}}</h2>
</template>


<script>
// 注册vuex到vue中
// Vue.use(Vuex)
// new Vuex.Store() 实例,得到一个 数据仓储对象
const store = new Vuex.Store({
// 大家可以把 state 想象成 组件中的 data ,专门用来存储数据的
// 如果在 组件中,想要访问,store 中的数据,只能通过 this.$store.state.*** 来访问
state: {
num: 2,
unitPrice: 13990
},
// 注意: 如果要操作 store 中的 state 值,只能通过 调用 mutations 提供的方法,才能操作对应的数据,不推荐直接操作 state 中的数据,因为 万一导致了数据的紊乱,不能快速定位到错误的原因,因为,每个组件都可能有操作数据的方法;
mutations: {
// 注意: 如果组件想要调用 mutations 中的方法,只能使用 this.$store.commit('方法名')
// 这种 调用 mutations 方法的格式,和 this.$emit('父组件中方法名')类似
// 注意: mutations 的 函数参数列表中,最多支持两个参数,其中,参数1: 是 state 状态; 参数2: 通过 commit 提交过来的参数;如果有多个参数,可将参数作为对象放入第2个参数即可
increase: (state)=> {
// 注意:箭头函数this在这里的作用域变了,如果不用箭头函数,那么默认的this就是vm实例
state.num++;
},
decrease: (state)=> {
if (state.num <= 1) {
return
}
state.num--;
}
},
getters: {
// 注意:这里的 getters, 只负责 对外提供数据,不负责 修改数据,如果想要修改 state 中的数据,请 去找 mutations
text: function (state) {
return '索尼A7M3全画幅微单相机 &nbsp;&nbsp;&nbsp;&nbsp;¥' + state.unitPrice
}
// 经过咱们回顾对比,发现 getters 中的方法, 和组件中的过滤器比较类似,因为 过滤器和 getters 都没有修改原数据, 都是把原数据做了一层包装,提供给了 调用者;
// 其次, getters 也和 computed 比较像, 只要 state 中的数据发生变化了,那么,如果 getters 正好也引用了这个数据,那么 就会立即触发 getters 的重新求值;
}
})

var vm = new Vue({
el: '#app',
data: {
// 我们可能在这里定义数据,如果组件多的情况下,尤其是组件多层嵌套的情况下,若最底层的数据发生变化,还得一层一层emit上去,很麻烦
// 如果用Vuex,只需要在最底层this.$store.commit(),然后再最外层组件用computer属性获取对应的值就可以做到实时更新
},
methods: {

},
components: {
cart: {
template: "#cart"
},
count: {
template: "#count",
data: function() {
return {
// 可以定义自己组件内部,那么问题来了,其他组件想用怎么办?传值太麻烦了
// num : 1
}
},
methods: {
add() {
// 千万不要这么用,不符合 vuex 的设计理念
// this.$store.state.num++;
this.$store.commit("increase");
},
remove () {
this.$store.commit("decrease");
}
},
},
price: {
template: "#price"
}

},
// 将 vuex 创建的 store 挂载到 VM 实例上, 只要挂载到了 vm 上,任何组件都能使用 store 来存取数据
store
})
// 总结:
// 1. state中的数据,不能直接修改,如果想要修改,必须通过 mutations
// 2. 如果组件想要直接 从 state 上获取数据: 需要 this.$store.state.***
// 3. 如果 组件,想要修改数据,必须使用 mutations 提供的方法,需要通过 this.$store.commit('方法的名称', 唯一的一个参数)
// 4. 如果 store 中 state 上的数据, 在对外提供的时候,需要做一层包装,那么 ,推荐使用 getters, 如果需要使用 getters ,则用 this.$store.getters.***
</script>

</body>

</html>

5. 自定义指令

5.1 自定义全局指令

  1. 指令定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    // 使用  Vue.directive() 定义全局的指令  v-focus
    // 其中:参数1 : 指令的名称,注意,在定义的时候,指令的名称前面,不需要加 v- 前缀,
    // 但是: 在调用的时候,必须 在指令名称前 加上 v- 前缀来进行调用
    // 参数2: 是一个对象,这个对象身上,有一些指令相关的函数,这些函数可以在特定的阶段,执行相关的操作
    Vue.directive('focus', {
    bind: function (el) { // 每当指令绑定到元素上的时候,会立即执行这个 bind 函数,只执行一次
    // 注意: 在每个 函数中,第一个参数,永远是 el ,表示 被绑定了指令的那个元素,这个 el 参数,是一个原生的JS对象
    // 在元素 刚绑定了指令的时候,还没有 插入到 DOM中去,这时候,调用 focus 方法没有作用
    // 因为,一个元素,只有插入DOM之后,才能获取焦点
    // el.focus()
    },
    inserted: function (el) { // inserted 表示元素 插入到DOM中的时候,会执行 inserted 函数【触发1次】
    el.focus()
    // 和JS行为有关的操作,最好在 inserted 中去执行,放置 JS行为不生效
    },
    updated: function (el) { // 当VNode更新的时候,会执行 updated, 可能会触发多次

    }
    })


    // 自定义一个 设置字体颜色的 指令
    Vue.directive('color', {
    // 样式,只要通过指令绑定给了元素,不管这个元素有没有被插入到页面中去,这个元素肯定有了一个内联的样式
    // 将来元素肯定会显示到页面中,这时候,浏览器的渲染引擎必然会解析样式,应用给这个元素
    bind: function (el, binding) {
    // el.style.color = 'red'
    // console.log(binding.name)
    // 和样式相关的操作,一般都可以在 bind 执行

    // console.log(binding.value)
    // console.log(binding.expression)

    el.style.color = binding.value
    }
    })
  2. 自定义全局指令使用方式

    1
    <input type="text" v-model="searchName" v-focus v-color="'red'" v-font-weight="900">

5.2 自定义局部指令

  1. 指令定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 自定义局部指令 v-color 和 v-font-weight,为绑定的元素设置指定的字体颜色 和 字体粗细:
    directives: {
    color: { // 为元素设置指定的字体颜色
    bind(el, binding) {
    el.style.color = binding.value;
    }
    },

    'font-weight': function (el, binding2) { // 自定义指令的简写形式,等同于定义了 bind 和 update 两个钩子函数
    el.style.fontWeight = binding2.value;
    }

    }
  2. 自定义局部指令使用方式

    1
    <input type="text" v-model="searchName" v-focus v-color="'blue'" v-font-weight="500">

6. 动画

作为后端的攻城狮,搞这些花里胡哨的功能干嘛,有这时间和兴趣多研究后端架构!

如果特别对动画有兴趣,请移步这里进行学习

7. watch监听和computed计算

7.1 watch属性

需求: 页面有三个文本输入框,分别是bt1、bt2、bt3,当bt1和bt2两个文本框数据改变的时候,自动监听填充到bt3中

7.1.1 传统实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<body>
<div id="app">
<!-- 分析: -->
<!-- 1. 我们要监听到 文本框数据的改变,这样才能知道 什么时候去拼接 出一个 fullname -->
<!-- 2. 如何监听到 文本框的数据改变呢??? -->
<input type="text" v-model="firstname" @keyup="getFullname"> +
<input type="text" v-model="lastname" @keyup="getFullname"> =
<input type="text" v-model="fullname">

</div>

<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {
firstname: '',
lastname: '',
fullname: ''
},
methods: {
getFullname() {
this.fullname = this.firstname + '-' + this.lastname
}
}
});
</script>
</body>

7.1.2 改进实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<body>
<div id="app">
<input type="text" v-model="firstname"> +
<input type="text" v-model="lastname"> =
<input type="text" v-model="fullname">
</div>

<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {
firstname: '',
lastname: '',
fullname: ''
},
methods: {},
watch: { // 使用这个 属性,可以监视 data 中指定数据的变化,然后触发这个 watch 中对应的 function 处理函数
'firstname': function (newVal, oldVal) {
// console.log('监视到了 firstname 的变化')
// this.fullname = this.firstname + '-' + this.lastname

// console.log(newVal + ' --- ' + oldVal)

this.fullname = newVal + '-' + this.lastname
},
'lastname': function (newVal) {
this.fullname = this.firstname + '+' + newVal
}
}
});
</script>
</body>

7.1.3 watch监听路由地址改变

请思考: keyup事件针对元素,那么请问路由是虚拟的,有keyup事件吗?答案是没有的,需求通过watch监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<body>
<div id="app">
<router-link to="/login">登录</router-link>
<router-link to="/register">注册</router-link>
<!-- 容器 -->
<router-view></router-view>
</div>

<script>
// 2. 创建子组件
var login = {
template: '<h3>这是登录子组件,这个组件是 李彤 开发的。</h3>'
}

var register = {
template: '<h3>这是注册子组件,这个组件是 李彤彤 开发的。</h3>'
}

// 3. 创建一个路由对象
var router = new VueRouter({
routes: [ // 路由规则数组
{ path: '/', redirect: '/login' },
{ path: '/login', component: login },
{ path: '/register', component: register }
],
linkActiveClass: 'myactive' // 激活相关的类
})

// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {},
methods: {},
// router: router
router,
watch: {
// this.$route.path
'$route.path': function (newVal, oldVal) {
// console.log(newVal + ' --- ' + oldVal)
if (newVal === '/login') {
console.log('欢迎进入登录页面')
} else if (newVal === '/register') {
console.log('欢迎进入注册页面')
}
}
}
});
</script>
</body>

7.2 computed属性

7.2.1 computed基本使用

  1. 默认只有getter的计算属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <div id="app">
    <input type="text" v-model="firstName"> +
    <input type="text" v-model="lastName"> =
    <span>{{fullName}}</span>
    </div>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {
    firstName: 'jack',
    lastName: 'chen'
    },
    methods: {},
    computed: { // 计算属性; 特点:当计算属性中所以来的任何一个 data 属性改变之后,都会重新触发 本计算属性 的重新计算,从而更新 fullName 的值
    fullName() {
    return this.firstName + ' - ' + this.lastName;
    }
    }
    });
    </script>
  2. 定义有gettersetter的计算属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <div id="app">
    <input type="text" v-model="firstName">
    <input type="text" v-model="lastName">
    <!-- 点击按钮重新为 计算属性 fullName 赋值 -->
    <input type="button" value="修改fullName" @click="changeName">

    <span>{{fullName}}</span>
    </div>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {
    firstName: 'jack',
    lastName: 'chen'
    },
    methods: {
    changeName() {
    this.fullName = 'TOM - chen2';
    }
    },
    computed: {
    fullName: {
    get: function () {
    return this.firstName + ' - ' + this.lastName;
    },
    set: function (newVal) {
    var parts = newVal.split(' - ');
    this.firstName = parts[0];
    this.lastName = parts[1];
    }
    }
    }
    });
    </script>

7.2.2 computed小练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<body>
<div id="app">

<input type="text" v-model="firstname"> +
<input type="text" v-model="middlename"> +
<input type="text" v-model="lastname"> =
<!-- 这里不要写成fullname() -->
<input type="text" v-model="fullname">
<!-- 引用fullname,ok只输出一次 -->
<p>{{ fullname }}</p>
<p>{{ fullname }}</p>
<p>{{ fullname }}</p>

</div>

<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {
firstname: '',
lastname: '',
middlename: ''
},
methods: {},
computed: { // 在 computed 中,可以定义一些 属性,这些属性,叫做 【计算属性】, 计算属性的,本质,就是 一个方法,只不过,我们在使用 这些计算属性的时候,是把 它们的 名称,直接当作 属性来使用的;并不会把 计算属性,当作方法去调用;

// 注意1: 计算属性,在引用的时候,一定不要加 () 去调用,直接把它 当作 普通 属性去使用就好了;
// 注意2: 只要 计算属性,这个 function 内部,所用到的 任何 data 中的数据发送了变化,就会 立即重新计算 这个 计算属性的值
// 注意3: 计算属性的求值结果,会被缓存起来,方便下次直接使用; 如果 计算属性方法中,所以来的任何数据,都没有发生过变化,则,不会重新对 计算属性求值;
'fullname': function () {
console.log('ok')
return this.firstname + '+' + this.middlename + '+' + this.lastname
}
}
});
</script>
</body>

7.3 watch、computed和methods区别

  1. computed属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;
  2. methods方法表示一个具体的操作,主要书写业务逻辑;
  3. watch一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;可以看作是computedmethods的结合体;

8. 渲染组件的另一种方式

8.1 组件定义标签渲染视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<body>
<div id="app">
<mv></mv>
</div>

</body>

<template id="tmpl">
<div><h1>这是用标签方式渲染的组件</h1></div>
</template>

<script>
var mv = {
template: '#tmpl'
}
new Vue({
el: "#app",
data: {

},
components: {
mv
}
})
</script>

8.2 使用render函数渲染视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<body>
<div id="app">
我是本身的内容,看一看是否覆盖
</div>

</body>

<template id="tmpl">
<div><h1>这是用render函数渲染的组件</h1></div>
</template>

<script>
var mv = {
template: '#tmpl'
}
new Vue({
el: "#app",
data: {

},
// render(createEl) {
// return createEl(mv)
// }
// 使用箭头函数简化书写,render函数渲染组件时候会覆盖当前实例的内容
render: createEl=>createEl(mv)
})
</script>

9. 相关文档

  1. URL中的hash(井号)
  2. Vue Router
  3. Vuex
支付宝打赏 微信打赏

请作者喝杯咖啡吧