VueJs

tips

  • 开发单页面网页(spa)
  • html不区分大小写,vue中的使用驼峰驼峰命名的属性,用到html中大写字母一般会变为-
  • css中的id/class区分大小,支持驼峰命名

框架与库

  • 框架:是一套完整的解决方案;对项目的侵入性较大,项目如果需要更换框架,则需要重新架构整个项目。
  • 库(插件):提供某一个小功能,对项目的侵入性较小,如果某个库无法完成某些需求,可以很容易切换到其它库实现需求。

MVC与MVVM

  • MVC是后端分层的概念,

  • MVVM(Model-View-ViewModel)是前端视图层的概念。将视图 UI 和业务逻辑分开主要目的是分离视图(View)和模型(Model)

引入Vue

1
2
3
4
5
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

插值表达式

数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值,Mustache 标签将会被替代为对应数据对象上属性的值。无论何时,绑定的数据对象上属性发生了改变,插值处的内容都会更新(响应式的)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<div id="app">
<!-- 渲染数据 -->
message: {{message}} <br>
number: {{number + 1}} <br>
{{ true ? 'YES' : 'NO' }}
</div>
</body>
<script src="js/vue.js"></script>
<script>
// 创建vue对象
var vm = new Vue({
el: '#app', // 表示该vue对象,要控制id为app的标签
data: {
message: 'hello vuejs',
number: 1
}
})
</script>

vue-devtools浏览器插件

安装插件后需要手动勾选允许访问文件系统

html文件应用的vue.js,必须是开发版的

VueJs指令

v-cloak指令

用于解决插值表达式闪烁的问题(就是在页面渲染前,显示插值表达式的问题)

1
2
3
4
5
6
7
8
9
<style>
[v-cloak] {display: none; }
</style>

<div id="app">
<p v-cloak>
message: {{message}}
</p>
</div>

v-text与v-html

用于渲染数据

  • v-text:将数据当作字符串显示在页面上
  • v-html:将数据当作html代码解析
  • 注意:它们都会覆盖标签体中的内容
1
2
3
<div v-html="message"></div>

<div v-text="message"></div>

v-on与修饰符

v-on指令用于事件绑定, 可简写成@

事件

  • keydown -> keypress -> keyup
  • mouseenter

事件修饰符

  • .stop 阻止事件传播
  • .prevent 阻止默认事件
  • .capture 添加事件侦听器时使用事件捕获模式
  • .self 只当事件在该元素本身(比如不是子元素)触发时触发回调
  • .once 事件只触发一次

按键修饰符: 监听键盘事件时添加按键修饰符

  • .enter
  • .tab
  • .delete (捕获 “删除” 和 “退格” 键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right
  • .ctrl
  • .alt
  • .shift
  • .meta

自定义按键修饰符

1
Vue.config.keyCodes.f2 = 113;	// value指定为对应得keyCode值
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
<body>
<div id="app">
<h3>v-on:click</h3>
<button v-on:click="fOnClick('v-on:click')">v-on:click</button>

<h3>v-on:keydown(只允许输入数字)</h3>
<input type="text" v-on:keydown="fKeyDown($event)"><!-- $event vuejs的event对象 跟js的event对象一样 -->

<h3>按键选择器(选择enter)</h3>
<input type="text" v-on:keydown.enter="fKeyEnter">

<h3>v-on:fMouseOver</h3>
<div @mouseover="divMouseOver" style="width:300px;height:220px;background-color: red">
<!-- stop属性: 相当于event.stopPropagation(),阻止事件传播 -->
<button @mouseover.stop="btnMouseOver($event)">btn</button>
</div>
</div>
</body>
<script src="js/vue.js"></script>
<script>
new Vue({
el: '#app',
methods: {
fOnClick: function (msg) {
alert(msg);
},
fKeyDown: function (event) {
var keycode = event.keyCode;
if (keycode < 48 || keycode > 57) {
// 不要执行与事件关联的默认动作
event.preventDefault();
}
},
fKeyEnter: function() {
alert("您按下了回车。。。")
},
divMouseOver: function () {
alert("鼠标移动到div上了。。。");
},
btnMouseOver: function (event) {
alert("鼠标移动到button上了。。。");

// 停止事件传播, 就是不在触发外层div的onmouseover事件
// event.stopPropagation();
}
}
})
</script>

v-model

可以实现表单元素Model中的数据的双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<div id="app">
<h2> {{msg}}</h2>
<input type="text" v-model="msg" style="width: 500px; height: 35px; font-size: 18px;">
</div>
</body>
<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
msg: "v-model实现数据的双向绑定"
}
});
</script>

v-bind

用于绑定属性,可简写成:

  1. 绑定属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <body>
    <div id="app">
    <a v-bind="{href: 'https://www.baidu.com/s?wd=' + word}">
    <input type="button" v-bind:value="'点击搜索' + word">
    </a>
    </div>
    </body>
    <script src="js/vue.js"></script>
    <script>
    var vm = new Vue({
    el: "#app",
    data: {
    word: "java"
    }
    });
    </script>
  2. 使用class样式

    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
    <head>
    <style>
    .pink {
    color: pink;
    }
    .thin {
    font-weight: 100;
    }
    #size {
    font-size: 40px;
    }
    </style>
    </head>
    <body>
    <div id="app">

    <h2 :class="'pink'" :id="'size'">引用单个样式</h2>
    <h2 :class="['pink', 'thin']">使用数组</h2>
    <h2 :class="flag ? 'pink' : ''">使用三元运算符</h2>
    <h2 :class="{'pink': true, 'thin': false}">使用对象</h2>

    </div>
    </body>
    <script src="js/vue.js"></script>
    <script>
    var vm = new Vue({
    el: "#app",
    data: {
    word: "java",
    flag: true,
    style: {color: 'pink', 'font-weight': '100'}
    }
    });
    </script>
  3. 使用内联样式

    1
    2
    3
    <!-- 属性名有'-'的必须使用引号 -->
    <h2 :style="{color: 'pink', 'font-weight': '100'}">使用内联样式</h2>
    <h2 :style="style">引用data中定义的样式</h2>

v-for和key

用于遍历数组、对象…,

在特殊情况下可能需要使用:key指定唯一的key

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
<body>
<div id="app">
<!-- fori -->
<span v-for="item,idx in 5"> {{idx}},{{item}} </span><br><br>
<!-- 遍历数组 -->
<span v-for="item in arr"> {{item}} </span><br><br>
<!-- 遍历对象 -->
<span v-for="prop in person"> {{prop}} </span><br><br>

<table border="0px">
<tr>
<td>checkbox</td>
<td>id</td>
<td>name</td>
<td>age</td>
<td>操作</td>
</tr>
<tr v-for="(person, idx) in persons" :key="person.id">
<!-- :key, 可以指定box的key(默认为第几个), 指定为person.id则可以将这个person和box绑定在一起 -->
<td><input type="checkbox"></td>
<td>{{person.id}}</td>
<td>{{person.name}}</td>
<td>{{person.age}}</td>
<td><input type="button" v-on:click="deleteByIdx(idx)" value="delete"></td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td><input type="text" v-model="id"></td>
<td><input type="text" v-model="name"></td>
<td><input type="text" v-model="age"></td>
<td><input type="button" v-on:click="add" value="add"></td>
</tr>
</table>
</div>
</body>
<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
arr: [1, 2, 3, 4, 5],
person: {id:0, name:'tt', age:18},
persons: [
{id:0, name:'tt', age:18},
{id:1, name:'tt', age:18},
],
id: null,
name: null,
age: null
},
methods: {
add() {
this.persons.push({id: this.id, name: this.name, age: this.age});
},
deleteByIdx(idx) { // ?: 方法名为delete不能成功执行
this.persons.splice(idx, 1);
}
}
})
</script>

v-if和v-show

  • v-if:每次都会删除重新创建元素,频繁操作性能浪费
  • v-show:只是切换了元素的display属性值,不会删除重建,但有初始消耗(不管显不显示都会创建元素)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<div id="app">

<button @click="flag = !flag">切换</button><br>

<span v-if="flag">hello</span>
<span v-show="flag">world</span>
</div>
<script src="js/vue.js"></script>
<script>
new Vue({
el:'#app',
data:{
flag:true
}
});
</script>

自定义指令

官方文档: https://cn.vuejs.org/v2/guide/custom-directive.html

钩子函数

  • bind: 指令第一次绑定到元素时调用
  • inserted:被绑定元素插入到dom中时调用
  • updated:当组件(VNode)更新时,会执行updated方法;可能会调用多次。

钩子函数的参数:

  • el:被绑定了指令得那个元素,是一个原生得js对象
  • binding:一个对象,包含执行相关信息
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值 ,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • expression:字符串形式的指令表达式

钩子函数使用原则

  • 和样式相关的操作,一般都可以在bind中设置
  • 和JS行为相关的操作,最好在inserted中执行

全局指令:聚焦输入框

1
2
3
4
5
6
7
8
9
// 参数1:指令名称,使用时需加上'v-'
// 参数2:是一个对象,该对象上有一些指令相关得函数,在特定得阶段执行特定得操作
Vue.directive("focus", {
inserted: function (el) {
el.focus();
}
})

// 使用<input type="text" v-focus>

私有指令

1
2
3
4
5
6
7
8
9
10
11
12
var vm = new Vue({
el: "#app",
directives: {
// 定义私有指令
"color": {
bind: function(el, binding) {
el.style.color = binding.value;
}
}
}
}
// 使用<input type="text" v-color="'green'">

函数简写

在很多时候,你可能想在 bindupdate 时触发相同行为,而不关心其它的钩子

1
2
3
4
5
6
7
8
9
10
// 全局
Vue.directive("color", function(el, binding) {
el.style.color = binding.value;
})
// 私有
directives: {
color (el, binding) {
el.style.color = binding.value;
}
}

过滤器

Vue.js 允许自定义过滤器,用来作一些常见的文本格式化

过滤器可以用在两个地方:mustache 插值和 v-bind 表达式

管道符:|

私有/局部过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="app">
{{new Date | dateFormat("yyyy-mm-dd")}}
</div>
</body>
<script src="js/vue.js"></script>
<script>
new Vue({
el: '#app',
filters: {
// 用于定义私有过滤器
dateFormat(dateStr, pattern) {
var date = new Date(dateStr);
var year = date.getFullYear();
var month = (date.getMonth() + 1).toString().padStart(2, "0");
var day = date.getDate().toString().padStart(2, "0");

if (pattern && pattern.toLowerCase() === "yyyy-mm-dd")
return `${year}-${month}-${day}`;
else
return "格式错误!";
}
}
})
</script>

全局过滤器

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
<div id="app">
{{new Date | dateFormat("yyyy-mm-dd")}}
</div>

</body>
<script src="js/vue.js"></script>
<script>

// 定义全局过滤器
Vue.filter('dateFormat', function(dateStr, pattern) {
var date = new Date(dateStr);
var year = date.getFullYear();
var month = (date.getMonth() + 1).toString().padStart(2, "0");
var day = date.getDate().toString().padStart(2, "0");

if (pattern && pattern.toLowerCase() === "yyyy-mm-dd")
return `${year}-${month}-${day}`;
else
return "格式错误!";
});

new Vue({
el: '#app'
})
</script>

注意当有局部和全局两个名称相同的过滤器时候,会以就近原则进行调用

Vue实例的生命周期

官网文档

组件Component

官网文档: https://cn.vuejs.org/v2/guide/components-registration.html

定义组件

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
<div id="app">
<my-com1></my-com1>
<my-com2></my-com2>
</div>

<template id="mytpl">
<h1>这是我的第二个component!</h1>
</template>

<script>
// 方式1
// 组件模板对象Vue.extend(),可以提到外部定义
Vue.component("myCom1", Vue.extend({
// template中只能由一个根标签
template: "<h1>这是我的第一个component!</h1>"
}));
// 方式2
Vue.component("myCom2", {
template: "#mytpl" // 直接引用html中定义的template
});

let vm = new Vue({
el: "#app",
});
</script>

其它拓展

  • data:是一个函数,并且必须返回一个对象,对象中的数据可读可写
  • props:是一个数组,可以指定组件拥有的属性,其属性值由父组件传递来
    • 尽量保证只读不写,因为父组件每次渲染都会改变该值
  • methods
  • directives
  • filters
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
<div id="app">
<count id="count1"></count>
<count id="count2"></count>
</div>

<template id="tpl">
<div>
<button v-on:click="increment">++count</button>
<h1>{{count}}</h1>
</div>
</template>

<script>
// 定义一个组件,实现点击按钮自增效果
Vue.component("count", {
props: ["id"],
template: "#tpl",
data: function() {
return { count: 0}
},
methods: {
increment() {
++ this.count;
}
},
});

let vm = new Vue({
el: "#app",
});
</script>

组件切换

VueJS提供了component标签,它的 :is属性可以用来指定要展示的组件

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

<button v-on:click="componentId='login'">登录组件</button>
<button v-on:click="componentId='register'">注册组件</button>

<component :is="componentId"></component>

</div>

<script src="js/vue.js"></script>
<script>

Vue.component("login", {
template: "<h2>这是一个登录组件</h2>",
});
Vue.component("register", {
template: "<h2>这是一个注册组件</h2>",
});

let vm = new Vue({
el: "#app",
data: {componentId: "login"}
});
</script>

父组件向子组件传值

通过属性绑定的方式, 使用props属性来定义父组件传递过来的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let vm = new Vue({
el: "#app",
data: {
msg: "msg!"
},
components: {
children: {
template: "<h2>{{myMsg}}</h2>",
props: ["parentMsg"],
data: function() {
return { myMsg: this.parentMsg }
}
}
}
});

// <children :parent-msg="msg"></children>

子组件向父组件传值

  1. 通过事件绑定机制,自定义一个事件,并父组件将方法的引用,传递到子组件内部

  2. 子组件在内部调用父组件传递过来的方法,并传递数据this.$emit('方法名', 要传递的数据)

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
<div id="app">
<!-- 通过事件绑定机制,自定义一个事件 -->
<children v-on:sunclick="show"></children>

<!-- 显示子组件的数据 -->
<h2>{{sunMsg}}</h2>
</div>

<script>
// 定义一个组件模板对象
let children = {
template: "<button @click='myclick'>获取子组件的Message</button>",
props: ["parentMsg"],
data: function() {
return {
sunMsg: "子组件Message!"
}
},
methods: {
myclick() {
this.$emit("sunclick", this.sunMsg);
}
}
};

let vm = new Vue({
el: "#app",
data: { sunMsg: "" },
methods: {
show(data) {
this.sunMsg = data;
console.log(data);
}
},
components: {
// "组件名: 组件模板对象", "children": children
children // 简写:默认组件名,为模板对象名
}
});
</script>

使用ref属性获取组件的引用

  • ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件
  • $refs 只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs
1
2
3
4
5
6
<h2 ref="h2">hello</h2>

<comm ref="comm"></comm> <!-- comm组件 -->

// vm.$refs.h2.innerText // h2标签的内容
// vm.$refs.comm.msg // comm组件中的msg属性

Vue中的动画

官网文档: https://cn.vuejs.org/v2/guide/transitions.html

transition单元素过度

注意:如果你使用一个没有名字的<transition>,则 v- 是这些类名的默认前缀。如果你使用了 <transition name="my">,那么 v-enter 会替换为 my-enter。

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
<head> 
<style>
/* 进入前,离开后 */
.v-enter, .v-leave-to {
opacity: 0;
transform: translateX(80px); /* 在x轴移动 */
}
/* 进入时,离开时 */
.v-enter-active, .v-leave-active {
transition: all 0.8s ease; /* 持续时间...*/
}
</style>
</head>
<body>

<div id="app">

<button v-on:click="flag = !flag">切换</button>

<!-- 将要设置过度效果的标签用transition标签包裹起来,用v-if/show控制显示与否 -->
<transition>
<h2 v-show="flag">我是一个用于展示的动画切换效果的h2标签!</h2>
</transition>
</div>

</body>
<script src="js/vue.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
flag: true
}
});
</script>

结合第三方css库

github: https://github.com/daneden/animate.css

特效展示:https://daneden.github.io/animate.css/

1
2
3
4
5
6
7
8
<button v-on:click="flag = !flag">切换</button>

<!-- enter/leave-active-class指定入场/离场时的特效类 -->
<!-- duration属性用于设置入场/离场的时间,:duration="{enter: 200, leave: 200}" -->
<transition enter-active-class="bounceIn"
leave-active-class="bounceOut" duration="2000">
<h2 v-show="flag">我是一个用于展示的动画切换效果的h2标签!</h2>
</transition>

结合钩子函数使用

官方文档: https://cn.vuejs.org/v2/guide/transitions.html#JavaScript-%E9%92%A9%E5%AD%90

  • 实现小球半场动画效果
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
<head>
<style>
/* 小球 */
.ball {
width: 20px; height: 20px;
border-radius: 50%;
background-color: red;
}
</style>
</head>
<body>
<div id="app">
<h2>模拟小球加入购物车效果</h2>
<button v-on:click="flag = !flag">加入购物车</button>

<transition name="ball"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter">
<div v-show="flag" class="ball" ></div>
</transition>
</div>
</body>
<script src="js/vue.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
flag: false // 默认不显示
},
methods: {
beforeEnter(el) {
// 设置动画开始时小球的位置,保证小球在多次动画前都回到起始位置
el.style.transform = "translate(0px, 0px)";
},
enter(el, done) {
// 没有实际意义,但是不写出不来动画效果
el.offsetWidth;
// 设置动画持续时间,以及结束时小球的位置
el.style.transform = "translate(300px, 600px)";
el.style.transition = "all 0.6s ease";

// done就是afterEnter的引用, 必须调用
done();
},
afterEnter(el) {
// 隐藏小球,并跳过后半场动画
this.flag = false;
}
},
});
</script>

列表过度

使用transition-group标签将li标签包裹起来,并将transition-group标签渲染为ul标签

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
<head>
<style>
li {
width: 100%; /* 消删除是,颜色显示不全的bug */
line-height: 50px;
font-size: 20px;
border: 1px dashed #999;
margin: 10px;
padding-left: 25px;
}
li:hover {
background-color: pink;
transition: all 0.5s ease;
}

.v-enter, .v-leave-to {
opacity: 0;
transform: translateY(100px);
}
.v-enter-active, .v-leave-active {
transition: all 0.6s ease;
}

/* .v-move和.v-leave-active配合使用,实现列表后续的元素,渐渐地漂上来的效果 */
.v-move { transition: all 0.5s ease;}
.v-leave-active { position: absolute;}
</style>
</head>
<body>
<div id="app">
<div style="padding: 15px 30px;">
<label for="">
ID: <input type="text" v-model="id">
</label>
<label for="">
name: <input type="text" v-model="name">
</label>
<label for="">
<input type="button" value="添加" v-on:click="add">
</label>
</div>

<!-- tag:设置渲染的标签,默认为span, appear:渐出效果 -->
<transition-group tag="ul" appear>
<li v-for="person, idx of persons"
:key="person.id"
@click="del(idx)">{{person.id}} ----- {{person.name}}</li>
</transition-group>
</div>
</body>
<script src="js/vue.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
id: null,
name: null,
persons: [
{id: 0, name: "tt1"},
{id: 1, name: "tt2"},
{id: 2, name: "tt3"},
{id: 3, name: "tt4"}
]
},
methods: {
add() {
this.persons.push({id: this.id, name: this.name});
this.id = this.name = null;
},
del(idx) {
this.persons.splice(idx, 1);
}
}
});
</script>
</html>

多个组件过渡

多个组件过渡,只需要用transition标签将动态组件标签包裹住,然后设置过度模式即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
v<style>
.v-enter, .v-leave-to {
opacity: 0;
transform: translateY(80px);
}
.v-enter-active, .v-leave-active {
transition: all 0.5s ease;
}
</style>


<!-- mode:out-in 设置组件切换模式先出后进-->
<transition mode="out-in">
<component :is="componentId"></component>
</transition>

路由Vue-router

Vue-router官方文档: https://router.vuejs.org/zh/

  • 后端路由:就是将url地址对应到服务器上的资源
  • 前端路由:在单页面应用程序中,通过改变url中的hash(#号后)来切换页面的方式,称作前端路由
    • url中hash的特点就是在发送http请求时,不会包含该hash相关内容。

入门案例

定义login、register两个组件,对应两条路线;实现两条路线间的切换。

  • route-view:用于展示匹配到的组件
  • router-link:用于链接到指定的路线
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
<style>
.myActive {
color: red;
font-size: 18px;
}
.v-enter, .v-leave-to {
opacity: 0;
transform: translateY(80px);
}
.v-enter-active, .v-leave-active {
transition: all 0.5s ease;
}
</style>

<div id="app">
<!-- 默认会渲染成一个a标签,也可用tag属性指定其它标签 -->
<router-link to="/login">登录</router-link> <!-- <a href="#/login"></a> -->
<router-link to="/register">注册</router-link>

<transition mode="out-in">
<!-- 用于展示匹配到的组件 -->
<router-view></router-view>
</transition>
</div>

<script src="js/vue.js"></script>
<script src="js/vue-router.js"></script>
<script>

let login = {
template: "<h2>这是一个登录组件!</h2>"
}
let register = {
template: "<h2>这是一个注册组件!</h2>"
}

// 创建一个VueRouter对象
const router = new VueRouter({
// routes 路由规则数组
routes: [
// path:请求路径, component:指定对应的组件模板对象,redirect前端重定向
{path: "/", redirect: "/login"},
{path: "/login", component: login},
{path: "/register", component: register}
],
// 指定高亮css类,默认为router-link-active
// 定位到对应的路由时,会将对应的route-link标签加上该类
linkActiveClass: "myActive"
})

let vm = new Vue({
el: "#app",
router: router // 指定VueRouter对象
});
</script>

使用请求参数

  • Vue/组件对象的$route.query中保存所有传统的请求参数, 如/getUser/?id=1

  • Vue/组件对象的$route.params中保存所有的路径参数(RESTful),如/user/1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let login = {
template: "<h2>这是一个登录组件! --- 参数:{{$route.query.temp}}</h2>",
}

let register = {
template: "<h2>这是一个注册组件! --- 参数:{{$route.params.temp}}</h2>"
}

const router = new VueRouter({
routes: [
// path:路线, component:指定路线对应的组件模板对象,redirect前端重定向
{path: "/", redirect: "/login"},
{path: "/login", component: login},
{path: "/register/:temp", component: register} // 使用路径参数
]
})

路由嵌套

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
<div id="app">
<!-- 展示用户模块 -->
<router-view></router-view>
</div>

<template id="account">
<div>
<h1>用户模块</h1>
<router-link to="/account/login">登录</router-link>
<router-link to="/account/register">注册</router-link>

<!-- 用于展示子路由 -->
<router-view></router-view>
</div>
</template>

<script>

let account = {
template: "#account"
}
let login = {
template: "<h2>这是一个登录组件! </h2>"
}
let register = {
template: "<h2>这是一个注册组件! </h2>"
}

let vm = new Vue({
el: "#app",
// 路由
router: new VueRouter({
routes: [
{path: "/", redirect: "/account"},
{
path: "/account",
component: account,
// 定义子路由
children: [
{path: "login", component: login},
{path: "register", component: register}
]
}
]
})
})
</script>

一路由多组件

通过指定router-view的name属性实现上、左、右经典布局

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
<style>
html, body {
margin: 0px; padding: 0px;
font-size: 24px;
text-align: center;
}
.header {
background-color: green;
height: 80px;
}
/* flex布局 */
.container {
display: flex;
}
.left {
background-color: lightpink;
height: 700px;
flex: 2;
}
.main {
background-color: pink;
height: 700px;
flex: 8;
}
</style>

<div id="app">
<router-view></router-view>
<div class="container">
<!-- router-view的name属性指定显示的组件名, 默认显示default-->
<router-view name="left"></router-view>
<router-view name="main"></router-view>
</div>
</div>

<script>

let headerBox = {
template: `<div class="header">这是头部</div>`
}
let leftBox = {
template: `<div class="left">这是左边部分</div>`
}
let mainBox = {
template: `<div class="main">这是主体</div>`
}

const router = new VueRouter({
routes: [
{
path: "/",
// 使用components指定该路由的所有组件
components: {
"default": headerBox, // 默认组件,不写引号vue会帮我们补全
left: leftBox, // 左侧组件
main: mainBox // 主体组件
}
}
]
})

let vm = new Vue({
el: "#app",
router: router
});
</script>
</html>

watch监控

watch是一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let vm = new Vue({
el: "#app",
router: router,
watch: {
"$route.path": function(newVal, oldVal) {
console.log(newVal + "---------------" + oldVal);
if (newVal === "/login") {
console.log("欢迎进入登录页面!");
} else if (newVal === "register") {
console.log("欢迎进入注册页面!");
}
}
}
});

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
<div id="app">

<input type="text" v-model="num1"> +
<input type="text" v-model="num2"> =
<input type="text" v-model="result">

{{result2}}

</div>
</body>
<script src="js/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
num1: 0,
num2: 0
},
computed: {
// 使用箭头函数,则 this 不会指向这个组件的实例
// 不过我们可以将实例作为函数的第一个参数来访问
result: vm => parseInt(vm.num1) + parseInt(vm.num2),
result2: function() {
return parseInt(this.num1) + parseInt(this.num2);
}
},
})
</script>

watch、computed和methods区别

  1. computed属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;

  2. methods方法表示一个具体的操作,主要书写业务逻辑;

  3. watch一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化从而进行某些具体的业务逻辑操作;可以看作是computedmethods的结合体;

Webpack4

webpack 是前端的一个项目构建工具,它是基于 Node.js 开发出来的一个前端工具。

可以处理js文件的依赖关系,js的建容问题

官方文档: https://www.webpackjs.com/concepts/

网页中的静态资源

常见的静态资源

  • JS:.js .jsx .coffee .ts

  • CSS:.css .less .sass .scss

  • Images:.jpg .png .gif .bmp .svg

  • Fonts:.svg .ttf .eot .woff .woff2

  • 模板文件:.ejs .jade .vue

网页中引入的静态资源多了以后有什么问题

  1. 网页加载速度慢, 因为 我们要发起很多的二次请求;
  2. 要处理错综复杂的依赖关系

如何解决上述两个问题

  1. 合并、压缩、精灵图、图片的Base64编码
  2. 可以使用之前学过的requireJS、也可以使用webpack可以解决各个包之间的复杂依赖关系

打包JS文件

在打包之前,需要安装这两个软件:npm i -g webpack webpack-cli

webpack4默认打包的入口时src/index.js,出口是dist/main.js

打包方式1

  • 直接命令行:webpack 入口 -o 出口
    • 参数:
      • --mode production:生产模式(默认),没有debug代码
      • --mode development:开发模式
    • 如:webpack src/main.js -o dist/bundle.js

打包方式2

  • 在项目根路径下创建webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let path = require("path"); // 导入路径处理库

    module.exports = {
    // 配置项目入口
    // 可以设置多个入口 entry: {home: 'home.js', about: 'about.js'}
    entry: path.join(__dirname, "src/main.js"),
    // 配置输出选项
    output: {
    path: path.join(__dirname, "dist"),
    filename: "bundle.js"
    },
    mode: "development"
    }
  • 然后再运行webpack命令

loader

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)

官网文档: https://www.webpackjs.com/concepts/loaders/

实现打包css文件

  1. 安装npm i style-loader css-loader .... --save-dev

  2. 修改webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    module.exports = {

    // ......

    // 配置第三方模块加载器
    module: {
    rules: [
    // 定义loader匹配规则
    // 匹配到后,从从右往左调用loader模块
    {test: /\.css$/, use: ["style-loader", "css-loader"]},
    {test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"]},
    {test: /\.scss$/, use: ["style-loader", "css-loader", "scss-loader"]}
    ]
    }
    }
  3. 在main.js使用import中引入css文件

    1
    import "./css/index.css"

处理css中的路径问题(图片字体)

  1. 运行npm i url-loader file-loader --save-dev

  2. webpack.config.js中添加处理url路径的loader模块

    1
    2
    3
    4
    5
    6
    7
    8
    // 图片
    {
    test: /\.(png|jpg|gif|bmp|jpeg)$/,
    // 图片大小 >= limit时,不转为base64格式, 其文件名按name指定的格式
    use: ["url-loader?limit=10240&name=[hash:8]-[name].[ext]"]
    },
    // 字体
    {test: /\.(ttf|eot|svg|woff|woff2)$/, use: "url-loader", }

使用babel处理高级JS语法

源码: https://github.com/babel/babel-loader

  1. 安装相关loader:cnpm install -D babel-loader @babel/core @babel/runtime @babel/plugin-transform-runtime

  2. 安装babel转换的语法:cnpm i -D @babel/preset-env

  3. webpack.config.js中添加相关loader模块,并排除node_modules

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // babel
    {
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
    loader: "babel-loader",
    options: {
    presets: ["@babel/preset-env"],
    plugins: ["@babel/plugin-transform-runtime"]
    }
    }
    }

webpack-dev-server实时打包

这种打包方式会将输出的js文件打包到内存中,当我们需要引用这个js文件的时候,直接把它当成在根路径下即可成功

步骤

  1. 安装:npm i -g webpack-dev-server

  2. webpack.config.js

  3. package.json中配置,webpack-dev-server命令参数

    1
    2
    3
    4
    5
    6
    7
    {
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    // 添加这一行dev配置
    "dev": "webpack-dev-server --open --contentBase src"
    }
    }
  4. 执行命令:npm run dev,此后我们修改文件后,就会自动打包

webpack-dev-server命令参数说明

  • --open:自动打开浏览器
  • --port:设置端口
  • --contentBase:设置根路径
  • --hot:热部署、局部刷新(异步)

也可以在webpack.config.js中指定webpack-dev-server命令参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let path = require("path");     // 导入路径处理库
let webpack = require("webpack");

module.exports = {
// 配置项目入口
entry: path.resolve(__dirname, "src/main.js"),
// 配置输出选项
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
// 配置webpack-dev-server的命令参数
devServer: {
open: true,
contentBase: "src",
hot: true
},
plugins: [
// 热更新模块对象
new webpack.HotModuleReplacementPlugin()
]
}

live-server

这是一款带有热加载功能的小型开发服务器。用它来展示你的HTML / JavaScript / CSS,但不能用于部署最终的网站。(用于多页面热部署

  • 安装:npm install -g live-server
  • 运行:live-server --port=80

html-webpack-plugin

这个插件可以帮我们在内存中生成html页面,并自动帮我们引用打包后的bundle.js文件

  1. 安装插件:npm i html-webpack-plugin -D

  2. 在webpack.config.js中配置插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    let path = require("path");     // 导入路径处理库
    let htmlWebpackPlugin = require("html-webpack-plugin");

    module.exports = {

    // .....

    plugins: [
    new htmlWebpackPlugin({
    // 模板页面、生成页面的名字
    template: path.join(__dirname, "src/index.html"),
    filename: "index.html"
    })
    ]
    }

使用vue

  • 安装依赖

    • cnpm i vue -S
    • cnpm i vue-loader vue-template-compiler -D
  • 修改配置文件webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const vueLoaderPlugin = require("vue-loader/lib/plugin");

    module.exports = {

    module: {
    rules: [
    // vue-loader
    {test: /\.vue/, use: "vue-loader"}
    ]
    },
    plugins: [
    // vueloader插件
    new vueLoaderPlugin()
    ],
    }
  • 创建一个vue单文件组件, app.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!-- 组件模板 -->
    <template>
    <h2>{{msg}}</h2>
    </template>

    <!-- 组件逻辑代码-->
    <script>
    export default {
    data() {
    return {msg: "hello world"}
    },
    methods: {}
    // .....
    }
    </script>

    <!-- 如果不使用scoped属性,则定义的是全局样式 -->
    <!-- lang属性可以指定语言,如scss,less -->
    <style scoped>

    </style>
  • 在入口js文件中引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import Vue from "vue"
    import app from "./component/app.vue"

    let vm = new Vue({
    el: "#app",
    // 用于渲染组件,这种渲染方式会把el指定的容器中的内容覆盖掉
    // createElement是一个回调函数,该函数返回指定组件渲染出来的html结果
    render: ce => ce(app)
    });

使用vue-router

  • 新增account,login,register单文件vue组件,分别对应三条路由

  • 安装vue-router,并在入口js文件中引入,然后在将其挂载到Vue上Vue.use(VueRouter)

  • 然后可以创建路由啦

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
import "./css/index.css"
import Vue from "vue"
import VueRouter from "vue-router"

import app from "./component/app.vue"
import account from "./component/account.vue"
import login from "./component/login.vue"
import register from "./component/register.vue"

Vue.use(VueRouter); // 将VueRouter挂载到Vue上


const router = new VueRouter({
routes: [
{path: "/", redirect: "/account"},
{
path: "/account",
component: account,
children: [
{path: "login", component: login},
{path: "register", component: register}
]
}
]
})

let vm = new Vue({
el: "#app",
// 用于渲染组件,这种渲染方式会把el指定的容器中的内容覆盖掉
// createElement是一个回调函数,该函数返回指定组件渲染出来的html结果
render: ce => ce(app),
router
});

import注意事项

  1. 如果是导入当前项目中我们自己的资源,则应使用./
  2. 没有./则会到node_modules目录中去找
    • node-modules/模块名 –> package.json –> 然后引入main属性指定的文件

vue脚手架

安装@vue/cli, @vue/cli-init

命令行工具:vue init webpack 项目名

图形工具:vue ui


 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×