B站 - Vue 学习笔记
vue全家桶:https://www.bilibili.com/video/BV15741177Eh?from=search&seid=8341611554359525751
0 课程介绍
- 邂逅Vue.js
- Vue基础语法
- 组件化开发
- Vue CLI详解
- vue-router
- vuex详解
- 网络封装
- 项目实战
1 邂逅Vue.js
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。
1.1 MVVM架构
-
MVVM架构
- MVC与MVP
- MVVM
-
Vue.js的定位
- Vue.js在MVVM架构中的定位
- Vue.js的功能定位
-
Vue.js的核心思想与特点
- Reactive Components for Modern Web Interfaces(数据驱动的组件,为现代的 Web 界面而生)
- 数据驱动
- 组件化
- 特点:侵入性低、鼓励模块化、轻量、高性能
- Reactive Components for Modern Web Interfaces(数据驱动的组件,为现代的 Web 界面而生)
-
数据驱动的 Web 开发
-
Vue.js 安装
-
下载Vue.js,通过
-
-
1.2 Vue初体验
-
HelloVue.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>TestVue</title> </head> <body> <div id="app">{{message}}</div> <script src="../js/vue.js"></script> <script> // 一、普通js写法(编程范式:命令式编程) //1. 创建div元素,设置id属性 //2. 定义一个变量叫message //3. 将message变量放在前面的div元素中显示 //4. 修改message数据 //5. 将message数据替换原数据 // 二、Vue写法(编程范式:声明式编程) // let变量、const常量 // new Vue()就是ViewModel const app = new Vue({ //el、data属于Vue的options el: '#app', //挂载管理的元素 data: { // 定义数据 message: 'zfcer' } }) // Vue可以直接在浏览器中修改message:app.message = 'hello' </script> </body> </html>
-
Vue列表展示
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue列表展示</title> </head> <body> <!-- Vue 声明式 + 响应式 --> <!-- 可以在浏览器中直接app.movies.push('d'),向数组movies中添加元素d --> <div id="app"> <ul> <li v-for="item in movies">{{item}}</li> </ul> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ movies: ['a', 'b', 'c'] } }) </script> </body> </html>
-
Vue案例-计数器
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>计数器</title> </head> <body> <div id="app"> <h1>当前计数:{{counter}}</h1> <!-- <button v-on:click="counter++">+</button> <button v-on:click="counter--">-</button> --> <button @click="add">+</button> <button @click="sub">-</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { counter: 0 }, methods: { add: function(){ console.log("add执行了") // 要用this. this.counter++; }, sub: function(){ this.counter--; } } }) </script> </body> </html>
1.3 Vue生命周期
在github中下载vue源码(tag2.6.14)后,用vscode打开src --> core --> index.js,发现import Vue from './instance/index'
打开./instance/index后就可以看到Vue函数,options表示new Vue({})传入的el、data、methods
import { initMixin } from './init'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
进入./init后,发现生命周期
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
- Vue官网生命周期流程
- 理解Vue生命周期
2 Vue基础语法
- vscode前端代码规范缩进设置为2
2.1 Mustache语法
- Mustache中可以写简单的表达式,如果是复杂的表达式推荐使用computed计算属性。Mustache也是响应式的。Mustache作用在显示内容本身。
<div id="app">
<h2>{{message}}</h2>
<!-- Mystache语法适用于标签内容上,不使用与标签属性 -->
<!-- Mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式 -->
<h2>{{firstName + lastName}}</h2>
<h2>{{firstName + ' ' + lastName}}</h2>
<h2>{{firstName}} {{lastName}}</h2>
<h2>{{counter*2}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
firstName: "zfcer",
lastName: "520",
counter: 1,
},
});
</script>
2.2 v-once语法
- v-once是非响应式的,不希望随意更改数据或界面。v-once作用在标签上。
<div id="app">
<h2>{{message}}</h2>
<!-- 使用v-once后,下面的h2标签不会随着message改变而改变 -- 非响应式 -->
<!-- v-once直接使用 -->
<h2 v-once>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "zfcer",
},
});
</script>
2.3 v-html语法
- v-html用来按照html格式进行解析显示,v-html作用在标签上。
- 历史遗留问题: {{{}}}以前可以使用三大括号来处理html格式解析,现在不再支持了。
<div id="app">
<!-- 以前可以使用{{{}}}来代替,但是由于跟{{}}容易弄混,现在使用v-html -->
<!-- v-html后面跟url,作用在标签上 -->
<h2 v-html="url"></h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
url: "<a href='http://ww.baidu.com'>百度一下</a>",
},
});
</script>
2.4 v-text语法
- v-text与Mustache比较相似,但是v-text作用在标签上,并且会覆盖掉原来的内容。不推荐使用
<div id="app">
<h2>{{message}}, zfcer</h2>
<!-- 使用v-text会用message覆盖掉‘,zfcer’ -->
<!-- 不推荐使用 -->
<h2 v-text="message">, zfcer</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello Vue",
},
});
</script>
2.5 v-pre语法
- v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
<div id="app">
<h2>{{message}}</h2>
<!-- 使用v-pre最开始编译过程中会显示{{message}} -->
<h2 v-pre>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello Vue",
},
});
</script>
2.6 v-cloak语法
- 当Vue加载前,v-cloak存在;当Vue加载后,v-cloak被移除。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>v-cloak语法</title>
<style>
/* 隐藏元素信息{{message}} */
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app" v-cloak>{{message}}</div>
<script src="../js/vue.js"></script>
<script>
// 当Vue加载前,v-cloak存在
// 当Vue加载后,v-cloak被移除
setTimeout(
// 延时加载
function () {
const app = new Vue({
el: "#app",
data: {
message: "hello Vue",
},
});
},
2000
);
</script>
</body>
</html>
2.7 v-bind语法
- v-bind基本用法:作用于标签属性上,语法糖
:
<div id="app">
<!-- Mustache语法不适用于标签属性上 -->
<a href="{{url}}">百度一下</a>
<br />
<!-- v-bind作用与标签属性上 -->
<a v-bind:href="url">百度一下</a>
<!-- v-bind语法糖: -->
<a :href="url">百度一下</a>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
url: "http://www.baidu.com",
},
});
</script>
- v-bind动态绑定class对象:通过v-bind:class绑定标签上的class属性
- 在v-bind:class属性中传入对象{style1: boolean1, style2: boolean2},可以做到自动根据boolean来进行拼接
- 在普通class上定义公共style,最终会实现将普通class与v-bind:class进行拼接
- v-bind:class也可以通过函数的方式传入对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>v-bind动态绑定class(对象语法)</title>
<style>
.active {
color: red;
}
.line {
padding: 100px;
}
</style>
</head>
<body>
<div id="app">
<!-- 用普通class定义固定样式,v-bind绑定的class定义动态样式对象(会根据boolean值自动选择对应的样式) -->
<!-- 最终会将普通class与v-bind绑定的class拼接起来 -->
<h2 class="title" :class="{active: isActive, line: isLine}">
Hello Vue (对象)
</h2>
<!-- 补充: 绑定函数 -->
<h2 class="title" :class="getClasses()">Hello Vue (对象)</h2>
<button @click="btnClick">点击变色</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
isActive: true,
isLine: true,
},
methods: {
btnClick: function () {
this.isActive = !this.isActive;
},
getClasses: function () {
return { active: this.isActive, line: this.isLine };
},
},
});
</script>
</body>
</html>
- v-bind绑定class数组:与v-bind绑定class对象类似
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>v-bind动态绑定class(数组语法) - 不推荐</title>
<style>
.active {
color: red;
}
.line {
padding: 100px;
}
</style>
</head>
<body>
<div id="app">
<!-- 用普通class定义固定样式,v-bind绑定的class定义动态样式数组(会根据boolean值自动选择对应的样式) -->
<!-- 最终会将普通class与v-bind绑定的class拼接起来 -->
<!-- 不推荐 -->
<h2 class="title" :class="[active, line]">Hello Vue (数组)</h2>
<!-- 补充: 绑定函数 -->
<h2 class="title" :class="getClasses()">Hello Vue (数组)</h2>
<button @click="btnClick">点击变色</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
active: "active",
line: "line",
},
methods: {
btnClick: function () {
this.isActive = !this.isActive;
},
getClasses: function () {
return [this.active, this.line];
},
},
});
</script>
</body>
</html>
- 练习:v-for与v-bind结合
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>作业 - v-for和v-bind</title>
<style>
.active {
color: red;
}
</style>
</head>
<body>
<div id="app">
<!-- 方案一 -->
<ul>
<li :class="{active: isActive[index]}" v-for="(m, index) in movies">
{{index}} - {{m}} <button @click="btnClick(index)">点击</button>
</li>
</ul>
<br>
<br>
<br>
<!-- 方案二:推荐 -->
<ul>
<li :class="{active: index == curIdx }" v-for="(m, index) in movies" @click="liClick(index)">
{{index}} - {{m}}
</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
movies: ["aaa", "bbb", "ccc", "ddd"],
isActive: [true, false, false, false],
preIdx: 0,
curIdx: 0
},
methods: {
btnClick: function (index) {
console.log("点击了" + index);
this.isActive.splice(this.preIdx, 1, false)
this.preIdx = index;
Vue.set(this.isActive, index, true)
},
liClick(index){
this.curIdx = index
}
},
});
</script>
</body>
</html>
- v-bind动态绑定style对象:使用驼峰命名
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>v-bind动态绑定style(对象语法)</title>
</head>
<body>
<div id="app">
<!-- 使用驼峰命名:fontSize -- font-size -->
<h2 :style="{fontSize: finalSize+'px', color: finalColor}">
Hello Vue (对象)
</h2>
<!-- 补充: 绑定函数 -->
<h2 class="title" :style="getStyles()">Hello Vue (对象)</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
finalSize: 100,
finalColor: "red",
},
methods: {
getStyles: function () {
return {
fontSize: this.finalSize + "px",
color: this.finalColor,
};
},
},
});
</script>
</body>
</html>
- v-bind动态绑定style数组
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>v-bind动态绑定style(数组语法)</title>
</head>
<body>
<div id="app">
<!-- 使用驼峰命名:fontSize -- font-size -->
<h2 :style="[baseStyle, bgcStyle]">Hello Vue (对象)</h2>
<!-- 补充: 绑定函数 -->
<h2 class="title" :style="getStyles()">Hello Vue (对象)</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
baseStyle: { fontSize: "100px", color: "red" },
bgcStyle: { backgroundColor: "blue" },
},
methods: {
getStyles: function () {
return [this.baseStyle, this.bgcStyle];
},
},
});
</script>
</body>
</html>
2.8 computed属性
- computed基本语法
<div id="app">
<!-- 直接拼接,过于繁琐 -->
<h2>{{firstName +' '+ lastName}}</h2>
<h2>{{firstName}} {{lastName}}</h2>
<!-- Mustache语法中支持解析方法 -->
<h2>{{getFullName()}}</h2>
<!-- computed计算属性 -->
<h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
firstName: "zfcer",
lastName: "best",
},
// 计算属性:在变量没有修改情况下,第一次执行后有缓存
computed: {
fullName: function () {
return this.firstName + " " + this.lastName;
},
},
// 方法options:每次执行都要调用方法
methods: {
getFullName: function () {
return this.firstName + " " + this.lastName;
},
},
});
</script>
- computed复杂操作
<div id="app">总价格为:{{totalPrice}}</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
books: [
{ id: 100, name: "aaa", price: 119 },
{ id: 101, name: "bbb", price: 120 },
{ id: 102, name: "ccc", price: 101 },
{ id: 103, name: "ddd", price: 99 },
],
},
computed: {
totalPrice: function () {
let finalPrices = 0;
// for(let i=0; i<this.books.length; i++){
// finalPrices += this.books[i].price;
// }
for (let book of this.books) {
finalPrices += book.price;
}
return finalPrices;
},
},
});
</script>
- computed的setter和getter
<div id="app">
<!-- computed计算属性 -->
<h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
firstName: "zfcer",
lastName: "best",
},
// 计算属性:在变量没有修改情况下,第一次执行后有缓存
computed: {
// 写法1: 简写
// fullName: function () {
// return this.firstName + " " + this.lastName;
// },
// 写法2
// 第一种写法是第二种写法的简写
fullName: {
get: function () {
// 获取返回值:由vue自身来调用get方法
return this.firstName + " " + this.lastName;
},
set: function (newValue) {
// 一般computed只读,所以setter方法可以省略
let names = newValue.split(" ");
this.firstName = names[0];
this.lastName = names[1];
},
},
},
});
</script>~~~
- computed与methods属性对比:computed有缓存,methods没有缓存
~~~html
<div id="app">
<!-- Mustache语法中支持解析方法 -->
<!-- 会执行6次,无缓存。 -->
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<!-- computed计算属性 -->
<!-- 会执行1次,有缓存。只有当firstName或lastName发生更改时,才会重新执行 -->
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
firstName: "zfcer",
lastName: "best",
},
// 计算属性:在变量没有修改情况下,第一次执行后有缓存
computed: {
fullName: function () {
console.log("fullName");
return this.firstName + " " + this.lastName;
},
},
// 方法options:每次执行都要调用方法
methods: {
getFullName: function () {
console.log("getFullName");
return this.firstName + " " + this.lastName;
},
},
});
</script>
2.9 v-on语法
- v-on时间监听基本语法:在事件监听时,如果方法没有参数,可以省略(); 但是在Mustache语法中是不能省略的。v-on语法糖
@
<div id="app">
<h1>当前计数:{{counter}}</h1>
<button v-on:click="counter++">+</button>
<button v-on:click="counter--">-</button>
<br />
<br />
<!-- v-on的语法糖@ -->
<!-- 注意:在事件监听时,如果方法没有参数,可以省略(); 但是在Mustache语法中是不能省略的 -->
<button @click="add">+</button>
<button @click="sub()">-</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
counter: 0,
},
methods: {
add() {
console.log("add执行了");
// 要用this.
this.counter++;
},
sub() {
this.counter--;
},
},
});
</script>
- v-on时间监听参数问题:浏览器参数可以通过$event获得;当函数只需要event一个参数时,可以通过省略()的方式获得浏览器产生的event
<div id="app">
<!-- 当监听事件没有参数时,可以省略() -->
<button @click="btn1Click">按钮1</button>
<!-- 当监听事件需要一个参数,但是省略小括号时,Vue会默认将浏览器产生的event时间对象作为参数传入到方法中 -->
<!-- 不省略()会出现undefined -->
<button @click="btn2Click()">按钮2-不省略()</button>
<!-- 省略()会获取浏览器event对象 -->
<button @click="btn2Click">按钮2-省略()</button>
<!-- 当监听事件需要包含event对象在内的多个参数时,需要借助$event传入event对象 -->
<button @click="btn3Click('zfcer', $event)">按钮3-直接传参</button>
<button @click="btn3Click(name, $event)">按钮3-vue data 传参</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
counter: 0,
name: "best",
},
methods: {
btn1Click() {
console.log("btn1Click ...");
},
btn2Click(event) {
console.log("btn2Click ...", event);
},
btn3Click(name, event) {
console.log("btn2Click ...", name, event);
},
},
});
</script>
- v-on事件监听修饰符
- .stop - 调用 event.stopPropagation()。
- .prevent - 调用 event.preventDefault()。
- .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
- .native - 监听组件根元素的原生事件。
- .once - 只触发一次回调。
<div id="app">
<!-- 1. .stop修饰符阻止冒泡 -->
<div @click="divClick">
aaaaa <br />
<button @click.stop="btn1Click">按钮1</button>
</div>
<br />
<br />
<!-- 2. .prevent修饰符阻止默认提交 -->
<form action="www.baidu.com">
<input type="submit" value="提交" @click.prevent="submitClick" />
</form>
<br />
<br />
<!-- 3. 监听键盘的键帽:当回车键还原时触发 -->
<input type="text" @keyup.enter="keyUp" />
<br />
<br />
<!-- 4. 按钮只触发一次: 了解 -->
<button @click.once="btn2Click">按钮2</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
counter: 0,
name: "best",
},
methods: {
divClick() {
console.log("divClick");
},
btn1Click() {
console.log("btn1Click");
},
submitClick() {
console.log("submitClick");
},
keyUp() {
console.log("keyUp");
},
btn2Click() {
console.log("btn2Click");
},
},
});
</script>
2.10 v-if/v-else-if/v-else语法
- v-if与v-else使用:v-if与v-else作用在标签上
<div id="app">
<h2 v-if="isShow">
<span>aaa</span>
<span>bbb</span>
<span>ccc</span>
{{message}}
</h2>
<h2 v-else>isShow为false,不显示...</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "zfcer",
isShow: true,
},
});
</script>
- v-else-if
<div id="app">
<!-- 一般复杂的逻辑是通过computed计算属性来进行处理的 -->
<h2 v-if="score>=90">优秀</h2>
<h2 v-else-if="score>=80">良好</h2>
<h2 v-else-if="score>=60">及格</h2>
<h2 v-else>不及格</h2>
<h2>成绩表现:{{result}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "zfcer",
score: 92,
},
computed: {
result() {
let resultMessage = "";
if (this.score >= 90) resultMessage = "优秀";
else if (this.score >= 80) resultMessage = "良好";
else if (this.score >= 60) resultMessage = "及格";
else resultMessage = "不及格";
return resultMessage;
},
},
});
</script>
- 练习:用户登录方式切换。
- 注意:==由于虚拟DOM的存在,导致两个input控件会复用。==这里需要特别使用key来做区分,保证登录方式切换后,input不被复用(密码清空)
<div id="app">
<!-- 注意:由于虚拟DOM的存在,导致两个input控件会复用。
这里需要特别使用key来做区分,保证登录方式切换后,input不被复用(密码清空) -->
<span v-if="isUser">
<label for="username">用户登录:</label>
<input
type="text"
id="username"
key="username-input"
placeholder="输入用户密码"
/>
</span>
<span v-else>
<label for="email">邮箱登录:</label>
<input
type="text"
id="email"
key="email-input"
placeholder="输入邮箱密码"
/>
</span>
<button @click="btnClick">切换登录方式</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
isUser: true,
},
methods: {
btnClick() {
this.isUser = !this.isUser;
},
},
});
</script>
-
v-if与v-show的区别
- v-if是真正把标签元素移除了、
- v-show只是加了个display: none
注意:当元素在显示与隐藏之间频繁切换时,推荐使用v-show当元素就是一次切换,推荐使用v-if
<div id="app">
<!-- 注意:当元素在显示与隐藏之间频繁切换时,推荐使用v-show
当元素就是一次切换,推荐使用v-if -->
<!-- v-if当为false时,会使v-if修饰的元素不在DOM中 -->
<h2 v-if="isShow">{{message}}</h2>
<!-- v-show当为false时, 只是简单的加了display:none 样式 -->
<h2 v-show="isShow">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "zfcer",
isShow: true,
}
});
</script>
2.11 v-for语法
- v-for遍历数组&对象
<div id="app">
<!-- 数组遍历 -->
<ul>
<li v-for="item in movies">{{item}}</li>
</ul>
<ul>
<li v-for="(item, index) in movies">{{index}} -> {{item}}</li>
</ul>
<br>
<!-- 对象遍历 -->
<ul>
<li v-for="value in info">{{value}}</li>
</ul>
<ul>
<li v-for="(value, key) in info">{{key}} -> {{value}}</li>
</ul>
<ul>
<li v-for="(value, key, index) in info">{{index}} -> {{key}} -> {{value}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
movies: ['aaa', 'bbb', 'ccc', 'ddd'],
info: {
name: 'zfcer',
age: 23,
height: 17.8
}
}
})
</script>
- v-for中绑定key:绑定唯一的key后让v-for操作更加高效—DIff算法
<div id="app">
<!-- 加入key的主要作用是为了高效更新DOM。但是key一定要能唯一代表一个item(index显然不行,假如在BC之间插入E,index就会发生改变)
使用key前:在BC之间插入E,先把C更新为E,D更新为C,最后插入D --- 默认Diff算法
使用key后:直接在BC之间插入E,高效很多 --- 有唯一标识的Diff算法 -->
<!-- 浏览器输入:app.letters.splice(2, 0, 'E')表示在第二个位置后,删除0个元素,插入元素E -->
<ul>
<li v-for="item in letters" :key="item">{{item}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
letters: ['A', 'B', 'C', 'D'],
}
})
</script>
2.12 数组的响应式操作
- 数组响应式函数:push、pop、shift、unshift、splice、sort、reverse
- 注意:直接根据数组下标修改数组元素不是响应式的
- 非响应式操作改进手段:用splice/set代替
<div id="app">
<ul>
<li v-for="item in letters" :key="item">{{item}}</li>
</ul>
<button @click="btnClick">按钮</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
letters: ['A', 'B', 'C', 'D'],
},
methods: {
btnClick(){
// 响应式操作:push、pop、shift、unshift、splice、sort、reverse
// 1. push
// this.letters.push('aaa', 'bbb', 'ccc')
// 2. pop
// 3. shift 删除第一个元素
// this.letters.shift()
// 4. unshift 从头插入元素
// this.letters.unshift('aaa', 'bbb', 'ccc')
// 5. splice 删除元素/插入元素/替换元素
// this.letters.splice(1, 1, 'aa', 'bb') //可以同时插入多个元素
// 6. sort
// 7. reverse
// 非响应式操作
// this.letters[0] = 'bbbb'
// 解决方法:用splice/set代替
// this.letters.splice(0, 1, 'bbbb')
Vue.set(this.letters, 0, 'bbbb')
}
}
})
</script>
2.13 书籍购物车案例
- style.css
table {
border: 1px solid #e9e9e9;
border-collapse: collapse;
border-spacing: 0;
}
th,
td {
padding: 8px, 16px;
border: 1px solid #e9e9e9;
text-align: left;
}
th {
background-color: #f7f7f7;
color: #5c6b77;
font-weight: 600;
}
- main.js:过滤器属性filters
const app = new Vue({
el: '#app',
data: {
books: [
{
id: 1,
name: 'java study',
date: '2021-7-15',
count: 1,
price: 127.00
},
{
id: 2,
name: 'java study',
date: '2021-7-15',
count: 1,
price: 100.00
},
{
id: 3,
name: 'java study',
date: '2021-7-15',
count: 2,
price: 152.00
},
{
id: 4,
name: 'java study',
date: '2021-7-15',
count: 3,
price: 59.00
}
]
},
computed: {
totalPrice(){
let allPrices = 0;
// for循环的三种写法
// for(let book of this.books)
// allPrices += book.price * book.count;
// for(let i = 0; i<this.books.length; i++){
// allPrices += this.books[i].price * this.books[i].count
// }
// for(let i in this.books){
// // i为索引
// allPrices += this.books[i].price * this.books[i].count
// }
// 使用reduce高阶函数求price总和: 箭头函数
allPrices = this.books.reduce((preValue, book) => preValue + book.price * book.count, 0)
return allPrices;
}
},
methods: {
showFixedPrice(price){
return '¥'+price.toFixed(2); //toFixed(2)保留两位小数
},
decrement(index){
if(this.books[index].count == 1)
return;
this.books[index].count--;
},
increment(index){
this.books[index].count++;
},
removeBook(index){
this.books.splice(index, 1);
}
},
// 过滤器:注意它的使用 {{value | showPrice}}
filters: {
showPrice(price){
return '¥'+price.toFixed(2); //toFixed(2)保留两位小数
}
}
})
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>书籍购物车案例</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="app">
<table>
<thead v-if="books.length">
<tr>
<th>编号</th>
<th>书籍名</th>
<th>出版日期</th>
<th>价格</th>
<th>数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(book, index) in books" :key="book.id">
<td>{{book.id}}</td>
<td>{{book.name}}</td>
<td>{{book.date}}</td>
<!-- 函数方式处理价格保留两位小数 -->
<!-- <td>{{showFixedPrice(book.price)}}</td> -->
<!-- 过滤器方式处理价格保留两位小数 -->
<td>{{book.price | showPrice}}</td>
<td>
<!-- 绑定disabled属性 -->
<button @click="decrement(index)" :disabled="book.count <= 1"> - </button>
{{book.count}}
<button @click="increment(index)">+</button>
</td>
<td><button @click="removeBook(index)">移除</button></td>
</tr>
</tbody>
</table>
<div v-if="books.length">
<!-- 复用过滤器 -->
<h2>总价格为:{{totalPrice | showPrice}}</h2>
</div>
<div v-else><h2>购物车为空</h2></div>
</div>
<script src="../../js/vue.js"></script>
<script src="main.js"></script>
</body>
</html>
2.14 v-model语法
- 表单双向绑定
<div id="app">
<!-- 在输入框上使用v-model就是实现双向绑定 -->
<input type="text" v-model="message" />
<textarea name="" id="" cols="30" rows="10" v-model="message"></textarea>
{{message}}
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "zfcer",
},
});
</script>
- v-model原理:v-bind 与 v-on
<div id="app">
<!-- v-model原理:v-bind 与 v-on -->
<input type="text" v-model="message" />
<input type="text" :value="message" @input="valueChange($event)" />
<!-- 当valueChange不传入参数时,默认会把$event传入 -->
<input type="text" :value="message" @input="valueChange" />
<input
type="text"
:value="message"
@input="message = $event.target.value"
/>
<h2>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "zfcer",
},
methods: {
valueChange(event) {
console.log(event);
this.message = event.target.value;
},
},
});
</script>
- v-model与radio结合使用
<div id="app">
<!-- 在不使用v-model的情况下,一定要加name属性来保证radio互斥 -->
<label for="male">
<input type="radio" id="male" name="sex" value="男" v-model="sex" />男
</label>
<label for="female">
<input type="radio" id="female" name="sex" value="女" v-model="sex" />女
</label>
<h2>{{sex}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
sex: "女",
},
});
</script>
- v-model与checkbox结合使用
<div id="app">
<!-- 单选框:boolean类型 -->
<label for="agree">
<input
type="checkbox"
name="agree"
value="isAgree"
v-model="isAgree"
/>同意协议
</label>
<button :disabled="!isAgree">下一步</button>
<h2>{{isAgree}}</h2>
<br />
<br />
<!-- 复选框:数组类型 -->
<input type="checkbox" value="篮球" v-model="hobbits" />篮球
<input type="checkbox" value="足球" v-model="hobbits" />足球
<input type="checkbox" value="乒乓球" v-model="hobbits" />乒乓球
<input type="checkbox" value="羽毛球" v-model="hobbits" />羽毛球
<h2>{{hobbits}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
isAgree: false,
hobbits: [],
},
});
</script>
- v-model与select结合使用
<div id="app">
<!-- 单选框:boolean类型 -->
<select name="aaa" v-model="fruit">
<option value="苹果">苹果</option>
<option value="橘子">橘子</option>
<option value="香蕉">香蕉</option>
</select>
<h2>{{fruit}}</h2>
<br />
<br />
<br />
<!-- 复选框:数组类型 -->
<!-- 使用multiple变为多选框 -->
<select name="bbb" v-model="fruits" multiple>
<option value="苹果">苹果</option>
<option value="橘子">橘子</option>
<option value="香蕉">香蕉</option>
</select>
<h2>{{fruits}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
fruit: "香蕉",
fruits: [],
},
});
</script>
- v-model与v-bind值绑定:就是动态的给value赋值,如果不使用v-bind:value就会导致value=“ f ”值就是f字符串,而不是fruit
<div id="app">
<!-- 复选框:数组类型 -->
<!-- 通过绑定label for 和 input id完成label与input关联 -->
<label :for="f" v-for="f in orginalFruits">
<input type="checkbox" :id="f" :value="f" v-model="fruits" />{{f}}
</label>
<br />
<br />
<br />
<!-- 复选框:数组类型 -->
<!-- 使用multiple变为多选框 -->
<select v-model="fruits" multiple>
<!-- value属性一定要使用v-bind:value -->
<option v-for="f in orginalFruits" :value="f">{{f}}</option>
</select>
<h2>{{fruits}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
fruit: "香蕉",
fruits: [],
orginalFruits: ["苹果", "香蕉", "橘子", "葡萄"],
},
});
</script>
- v-model修饰符的使用
- lazy懒加载
- number修饰符
- trim修饰符
<div id="app">
<!-- 1. lazy懒加载:只在input回车/移出焦点下才触发双向绑定 -->
<input type="text" v-model.lazy="message" /> {{message}}
<br />
<br />
<br />
<!-- 2. number,限制input中类型为number类型 -->
<!-- 默认情况下,输入框会被转化为string处理,即使我们在vue中age: 23,更改age后还是会转为string -->
<!-- 修饰符可以叠加使用 -->
<!-- typeof(value)用来展示value的类型 -->
<input type="text" v-model.number.lazy="age" /> {{age +' age的类型为:'+
typeof(age)}}
<br />
<br />
<br />
<!-- 3. trim,去除多余首尾空格 -->
<input type="text" v-model.trim="message" /> {{message}}
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "zfcer",
age: 23,
},
});
</script>
3 Vue组件化开发
组件化是Vue.js中的重要思想:组件树
3.1 组件化基本使用
- 组件化使用步骤:1 创建组件构造器对象; 2 注册组件; 3 使用组件
<div id="app">
<!-- 3. 使用组件 -->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 组件化的三步操作
// 1. 创建组件构造器对象
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
`,
});
// 2. 注册组件(全局组件)
Vue.component("my-cpn", cpnC);
const app = new Vue({
el: "#app",
data: {
message: "zfcer",
},
});
</script>
3.2 全局组件&局部组件
<script>
// 组件化的三步操作
// 1. 创建组件构造器对象
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
`,
});
// 2. 注册组件(这里注册的是全局组件,可以在多个Vue实例中使用)
// Vue.component("cpn", cpnC);
const app = new Vue({
el: "#app",
data: {
message: "zfcer",
},
components: {
// 这里注册的是局部组件:作用范围在div#app标签下
cpn: cpnC,
},
});
</script>
3.3 父组件与子组件
<div id="app">
<cpn1></cpn1>
<!-- 子组件因为既没有在全局组件中注册,也没有在Vue实例中注册,故无效! -->
<cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>
// 1. 两个组件构造器
// 注意:父组件一定要写在子组件后面,不然就会报找不到子组件异常(父组件需要注册子组件)
// 子组件
const cpnC2 = Vue.extend({
template: `
<div>
<h2>标题2</h2>
<p>这是内容部分2</p>
</div>
`,
});
// 父组件
const cpnC1 = Vue.extend({
template: `
<div>
<h2>标题1</h2>
<p>这是内容部分1</p>
<!-- 使用子组件 -->
<cpn2></cpn2>
</div>
`,
components: {
// 注册子组件
cpn2: cpnC2,
},
});
// 2. 注册局部组件
const app = new Vue({
el: "#app",
components: {
// 注册局部组件
cpn1: cpnC1,
},
});
</script>
3.4 组件注册语法糖
- 将Vue.extend({})省略掉,直接在注册组件时传入template
<div id="app">
<!-- 3. 使用组件 -->
<!-- 这种语法糖的注册方式,本质还是进行了Vue.extend({})操作 -->
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>
// 组件化的三步操作
// 1. 创建组件构造器对象:省略构建器对象
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
`,
});
// 2. 注册组件(这里注册的是全局组件,可以在多个Vue实例中使用)
Vue.component("cpn1", {
template: `
<div>
<h2>我是全局组件</h2>
<p>我是内容</p>
</div>
`,
});
const app = new Vue({
el: "#app",
data: {
message: "zfcer",
},
components: {
// 这里注册的是局部组件
cpn2: {
template: `
<div>
<h2>我是局部组件</h2>
<p>我是内容</p>
</div>
`,
},
},
});
</script>
3.5 组件模板的分离写法
- 推荐使用模板分离template的方式进行模板分离
<div id="app">
<!-- 3. 使用组件 -->
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<!-- 1. 模板分离script方法 -->
<script type="text/x-template" id="cpn1">
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
</script>
<!-- 2. 模板分离template -- 推荐 -->
<template id="cpn2">
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 2. 注册组件(这里注册的是全局组件,可以在多个Vue实例中使用)
Vue.component("cpn1", {
template: "#cpn1",
});
const app = new Vue({
el: "#app",
data: {
message: "zfcer",
},
components: {
// 这里注册的是局部组件
cpn2: {
template: "#cpn2",
},
},
});
</script>
3.6 组件中数据存放问题
- 组件中数据使用函数方式存放:避免多个组件实例之间数据共享导致混乱
<div id="app">
<!-- 3. 使用组件 -->
<!-- data使用函数的作用:划分作用域,当使用多个组件实例时,如果不用data函数,非常容易混淆counter变量,导致全局counter出现 -->
<cpn1></cpn1>
<cpn1></cpn1>
<cpn1></cpn1>
<!-- <cpn2></cpn2> -->
</div>
<template id="cpn1">
<div>
<!-- 在模板中使用的数据,必须是在组件中data函数中定义的,如果只是在Vue实例data中注册是读取不到的 -->
<h2>当前计数为:{{counter}}</h2>
<button @click="decrement" :disabled="counter <= 0">-</button>
<button @click="increment">+</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 定义全局对象
let obj = {
counter: 0,
};
// 2. 注册组件(这里注册的是全局组件,可以在多个Vue实例中使用)
Vue.component("cpn1", {
template: "#cpn1",
data() {
return {
// 在组件中定义counter变量
counter: 0,
};
// return obj; //此时使用的是同一个obj对象,显然不符合要求
},
methods: {
decrement() {
if (this.counter <= 0) return;
this.counter--;
},
increment() {
this.counter++;
},
},
});
const app = new Vue({
el: "#app",
data: {
message: "zfcer123",
},
components: {
// 这里注册的是局部组件
cpn2: {
template: "#cpn1",
data() {
return {
message: "zfcer best",
};
},
},
},
});
</script>
3.7 父子组件通信
- 父传子(props)
<div id="app">
<!-- 完成父传子:v-bind绑定子属性chobbits、cmessage
如果不使用v-bind绑定子属性就会把hobbits、message识别为字符串 -->
<cpn :chobbits="hobbits" :cmessage="message"></cpn>
</div>
<template id="cpn">
<!-- template模板里面的内容要用div包裹一下 -->
<div>
<ul>
<li v-for="hobbit in chobbits">{{hobbit}}</li>
</ul>
{{cmessage}}
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 自定义构造函数PersonName
function PersonName(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// 子组件: prop -- 父传子
const cpn = {
template: "#cpn",
// 1. 直接传值
// props: {
// cmessage: "",
// chobbits: [],
// },
// 2. 加入类型检测, 默认值,required
props: {
cmessage: {
// 验证type: String、Number、Boolean、Array、object、Date、Function、Symbol
// 当我们有自定义构造函数时,验证也支持自定义类型
type: [String, Number, PersonName],
default: "aaaa",
required: true, //表示必须加上这个属性
},
chobbits: {
type: Array,
// default: [], //vue2.5以上,当type是Array/Object时不能直接给数组赋默认值 [] {}
default() {
return [];
},
},
},
data() {
// 如果写data,必须要加上return,否则就会报错
return {};
},
};
// 把Vue实例看成Root组件
const app = new Vue({
el: "#app",
data: {
message: "zfcer",
hobbits: ["篮球", "足球", "羽毛球"],
},
// 子组件
components: {
// cpn: cpn,
// 增强写法
cpn,
},
});
</script>
- 父传子的驼峰标识问题:v-bind:props属性时,必须把props属性改为驼峰命名
<div id="app">
<!-- 注意:在父传子的时候,因为v-bind不支持驼峰命名,这里的cInfo必须转为c-info -->
<!-- <cpn :cInfo="info"></cpn> -->
<cpn :c-info="info"></cpn>
</div>
<template id="cpn">
<!-- template中尽量用div包裹 -->
<div>
<h2>{{cInfo}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: "#cpn",
props: {
// 在标签上使用
cInfo: {
type: Object,
defalut() {
return {};
},
required: true,
},
},
};
const app = new Vue({
el: "#app",
data: {
info: {
name: "zfcer",
age: 23,
height: 1.87,
},
},
components: {
cpn,
},
});
</script>
- 子传父
- 子组件emit发射出数据
- 子组件标签中通过属性将数据传递给父组件
<div id="app">
<!-- 利用v-on根据子组件emit出的名字来接受category-click,避免使用驼峰命名
没有传入参数是因为可以默认接受发射出来的category对象: 与接受浏览器默认event类似 -->
<cpn @category-click="recive"></cpn>
<div :style="{fontSize:'20px'}">{{result}}</div>
</div>
<template id="cpn">
<div>
<button v-for="category in categories" @click="btnClick(category)">
{{category.name}}
</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 子组件
const cpn = {
template: "#cpn",
data() {
return {
categories: [
{ id: "aaa", name: "热门推荐" },
{ id: "bbb", name: "手机数码" },
{ id: "ccc", name: "家电家用" },
{ id: "ddd", name: "电脑办公" },
],
};
},
methods: {
btnClick(category) {
// 1. 子组件发射出event,带名字category-click, 这里不要用驼峰命名
// 可以发送多个信息,接收时按顺序接受即可
this.$emit("category-click", category, "abc");
},
},
};
const app = new Vue({
el: "#app",
data: {
result: null,
},
components: {
cpn,
},
methods: {
recive(category, name) {
// 父组件接收子组件发射来的category
// 也可以接收多个参数(按顺序接收)
console.log(category);
console.log(name);
this.result = category;
},
},
});
</script>
- 父子通信案例:通过props和emit实现版
<!--------props 和 emit 实现版--------->
<div id="app">
<cpn
:cnum1="num1"
:cnum2="num2"
@num1change="num1Recive"
@num2change="num2Recive"
></cpn>
</div>
<template id="cpn">
<div>
cnum1: {{cnum1}}
<br />
dnum1: {{dnum1}}
<br />
<input type="text" :value="dnum1" @input="dnum1Input" />
<br />
<br />
cnum2: {{cnum2}}
<br />
dnum2: {{dnum2}}
<input type="text" :value="dnum2" @input="dnum2Input" />
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: "#cpn",
data() {
return {
dnum1: this.cnum1,
dnum2: this.cnum2,
};
},
props: {
cnum1: {
type: Number,
default: 0,
},
cnum2: {
type: Number,
default: 0,
},
},
methods: {
dnum1Input(event) {
this.dnum1 = parseFloat(event.target.value);
if (event.target.value == null || event.target.value == "")
this.dnum1 = 0;
this.$emit("num1change", this.dnum1);
this.$emit("num2change", this.dnum1 * 100);
},
dnum2Input(event) {
this.dnum2 = parseFloat(event.target.value);
if (event.target.value == null || event.target.value == "")
this.dnum2 = 0;
this.$emit("num2change", this.dnum2);
this.$emit("num1change", this.dnum2 / 100);
},
},
};
const app = new Vue({
el: "#app",
data: {
num1: 1,
num2: 2,
},
components: {
cpn,
},
methods: {
num1Recive(dnum1) {
console.log(typeof dnum1);
this.num1 = parseFloat(dnum1);
},
num2Recive(dnum2) {
console.log(typeof dnum2);
this.num2 = parseFloat(dnum2);
},
},
});
</script>
- 父子通信案例:通过watch实现版
<div id="app">
<cpn
:cnum1="num1"
:cnum2="num2"
@num1change="num1Recive"
@num2change="num2Recive"
></cpn>
</div>
<template id="cpn">
<div>
cnum1: {{cnum1}}
<br />
dnum1: {{dnum1}}
<br />
<input type="text" v-model.lazy="dnum1" />
<br />
<br />
cnum2: {{cnum2}}
<br />
dnum2: {{dnum2}}
<input type="text" v-model.lazy="dnum2" />
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: "#cpn",
data() {
return {
dnum1: this.cnum1,
dnum2: this.cnum2,
};
},
props: {
cnum1: {
type: Number,
default: 0,
},
cnum2: {
type: Number,
default: 0,
},
},
methods: {
// dnum1Input(event) {
// this.dnum1 = parseFloat(event.target.value);
// if (event.target.value == null || event.target.value == "")
// this.dnum1 = 0;
// this.$emit("num1change", this.dnum1);
// this.$emit("num2change", this.dnum1 * 100);
// },
// dnum2Input(event) {
// this.dnum2 = parseFloat(event.target.value);
// if (event.target.value == null || event.target.value == "")
// this.dnum2 = 0;
// this.$emit("num2change", this.dnum2);
// this.$emit("num1change", this.dnum2 / 100);
// },
},
watch: {
dnum1(newValue, oldValue) { //函数名与监听的变量名同名,newValue表示监听的新值,oldValue表示监听的旧值
console.log(oldValue)
this.dnum2 = newValue * 100; //dnum2的改变也会被watch监听到
this.$emit("num1change", newValue);
},
dnum2(newValue) {
this.dnum1 = newValue / 100; //dnum1的改变也会被watch监听到
this.$emit("num2change", newValue);
},
},
};
const app = new Vue({
el: "#app",
data: {
num1: 1,
num2: 2,
},
components: {
cpn,
},
methods: {
num1Recive(dnum1) {
console.log(typeof dnum1);
this.num1 = parseFloat(dnum1);
},
num2Recive(dnum2) {
console.log(typeof dnum2);
this.num2 = parseFloat(dnum2);
},
},
});
</script>
3.8 组件访问
- 父访问子($childern、$refs)
- 在组件标签上设置ref属性名后,就可以通过$childern、$refs完成对子组件数据的访问。 推荐使用$refs。
<div id="app">
<cpn></cpn>
<cpn></cpn>
<!-- 用ref属性标识,这样$refs.aaa就可以直接取出这个组件实例 -->
<cpn ref="aaa"></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>
<h2>这是组件</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
cpn: {
template: '#cpn',
data(){
return {
name: '我是cpn组件'
}
}
}
},
methods: {
btnClick(){
// 1. $childern利用索引下标从数组中取vueComponent元素 -- vueComponent元素表示组件实例
// this.$children不推荐使用,因为当插入新元素后,数组索引下标后改变
// console.log(this.$children[0].name)
// 2. this.$refs:推荐使用
console.log(this.$refs.aaa.name)
this.$refs.aaa.name = "hahahaahaha"; //修改
console.log('按钮执行了')
}
}
})
</script>
- 子访问父($parent、$root)
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>这是cpn组件</h2>
<ccpn></ccpn>
</div>
</template>
<template id="ccpn">
<div>
<h2>这是ccpn组件</h2>
<button @click="btnClick">按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'zfcer root'
},
components: {
cpn: {
template: '#cpn',
data(){
return {
name: '我是cpn组件'
}
},
// 在cpn下
components: {
ccpn: {
template: '#ccpn',
data(){
return {
name: '我是ccpn组件'
}
},
methods: {
btnClick(){
// 1. this.$parent: 获取当前vueComponent元素的父组件
// 不推荐使用,一般组件要尽量减少与其他组件的联系
// console.log(this.$parent)
// console.log(this.$parent.name)
// 2. this.$root: 直接获取root组件 -- vue实例
console.log(this.$root)
console.log(this.$root.message)
console.log('按钮执行了')
}
}
}
},
}
},
})
</script>
3.9 slot插槽
- slot基本使用
<div id="app">
<!-- 使用默认标签 -->
<cpn></cpn>
<cpn><span>haha</span></cpn>
<cpn><span>hehe</span></cpn>
<cpn>
<h2>插槽标签1</h2>
<span>插槽标签2</span>
<button>插槽按钮</button>
</cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>组件标题</h2>
<span>组件内容</span>
<!-- 定义插槽 -->
<!--
1. 使用空插槽
2. 插槽中可以设置默认标签 button
3. 插槽中可以插入多个标签
-->
<slot><button>组件按钮</button></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
}
const app = new Vue({
el: '#app',
data: {
message: 'zfer'
},
components: {
cpn
}
})
</script>
- slot具名插槽
<div id="app">
<!-- 使用默认标签 -->
<cpn></cpn>
<!-- 只能替换没有设置name的插槽 -->
<cpn><span>haha</span></cpn>
<!-- 替换指定插槽 -->
<cpn>
<span slot="left">left</span>
<span slot="center">center</span>
<span slot="right">right</span>
</cpn>
</div>
<template id="cpn">
<div>
<!-- 当有多个插槽时,可以给slot设置name属性,来指定slot插入
一旦slot设置了name属性,在使用的时候标签中必须使用slot属性指定插槽的name -->
<!-- 默认插槽 -->
<slot><button>组件按钮</button></slot>
<slot name="left"><button>返回</button></slot>
<slot name="center"><input type="text" value="请输入搜索内容"></slot>
<slot name="right"><button>搜索</button></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
}
const app = new Vue({
el: '#app',
data: {
message: 'zfer'
},
components: {
cpn
}
})
</script>
- 作用域插槽
- 编译的作用域:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译
- 作用域插槽的使用:1 template标签上绑定数据languages; 2 组件标签上使用slot-scope获取slotProps
<!-- 父组件替换插槽的标签,但是内容由子组件来提供 -->
<div id="app">
<cpn></cpn>
<cpn>
<!-- 2. 在使用组件时,最好在template标签下使用slot-scope属性,来获取组件传来的值 -->
<!-- vue2.5.x版本以前必须使用template标签包裹 -->
<template slot-scope="slot">
<!-- join函数 -->
<span>{{slot.languages.join(' * ')}}</span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<!-- 1. 在模板slot上绑定data --- 命名是随意的但是必须使用v-bind -->
<slot :languages="languages">
<ul>
<li v-for="language in languages">{{language}}</li>
</ul>
</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
components: {
cpn: {
template: "#cpn",
data() {
return {
languages: ["java", "vue", "javascript", "c++"],
};
},
},
},
});
</script>
4 Vue CLI详解
4.1 ES6模块化实现
- 导入方式
// 1.导入的{}中定义的变量
import {flag, sum} from "./aaa.js";
if (flag) {
console.log('小明是天才, 哈哈哈');
console.log(sum(20, 30));
}
// 2.直接导入export定义的变量
import {num1, height} from "./aaa.js";
console.log(num1);
console.log(height);
// 3.导入 export的function/class
import {mul, Person} from "./aaa.js";
console.log(mul(30, 50));
const p = new Person();
p.run()
// 4.导入 export default中的内容, 可以省略{}
import defaultname from "./aaa.js";
defaultname('你好啊');
// 5.统一全部导入
// import {flag, num, num1, height, Person, mul, sum} from "./aaa.js";
import * as aaa from './aaa.js'
console.log(aaa.flag);
console.log(aaa.height);
- 导出方式
var name = '小明'
var age = 18
var flag = true
function sum(num1, num2) {
return num1 + num2
}
if (flag) {
console.log(sum(20, 30));
}
// 1.导出方式一:
export {
flag, sum
}
// 2.导出方式二:
export var num1 = 1000;
export var height = 1.88
// 3.导出函数/类
export function mul(num1, num2) {
return num1 * num2
}
export class Person {
run() {
console.log('在奔跑');
}
}
// 4.export default
// 一个模块内只能有一个export default,不然导出的时候就乱套了
const address = '北京市'
export default address
export default function (argument) {
console.log(argument);
}
4.2 webpack使用
- webpack起步
- 安装webpack,需要node.js的环境
- node -v #查看node版本
- npm install webpack@3.6.0 -g #全局安装webpack3.6.0
- npm install webpack@3.6.0 --save-dev #局部安装webpack3.6.0
- 注意:在终端直接执行webpack命令,使用的全局安装的webpack;当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack。
- webpack打包指令:
webpack src/main.js dist/bundle.js
- 安装webpack,需要node.js的环境
//1. webpack导入方式
const {add, mul} = require('./mathUtils.js')
//2. webpack导出方式
function add(num1, num2){
return num1+num2
}
function mul(num1, num2){
return num1*num2
}
// webpack方式导出
module.exports = {
add,
mul
}
-
webpack配置
-
通过
npm init
生成package.json
文件, 并配置脚本
{
“name”: “meetwebpack”,
“version”: “1.0.0”,
“main”: “index.js”,
“scripts”: {
“test”: “echo “Error: no test specified” && exit 1”,
“build”: “webpack”
},
“author”: “”,
“license”: “ISC”,
“devDependencies”: {
“babel-core”: “^6.26.3”,
“babel-loader”: “^7.1.5”,
“babel-preset-es2015”: “^6.24.1”,
“css-loader”: “^2.0.2”,
“file-loader”: “^3.0.1”,
“less”: “^3.9.0”,
“less-loader”: “^4.1.0”,
“style-loader”: “^0.23.1”,
“url-loader”: “^1.1.2”,
“webpack”: “^3.6.0”
},
“description”: “”
}- 创建`webpack.config.js`文件, 配置入口、出口 ~~~js // 这里导入的path是node.js中的path: 通过npm init获取node中的path环境
-
const path = require(‘path’)
module.exports = {
entry: ‘./src/main.js’,
output: {
// __dirname的下划线是两个
path: path.resolve(__dirname, ‘dist’),
filename: ‘bundle.js’
}
}
// 通过npm install webpack@3.6.0 --save-dev 安装局部开发webpack,
// 最后会生成package.json、package-lock.json文件
~~~
-
webpack的loader:在开发中我们不仅仅有基本的js代码处理,我们也需要加载css、图片,也包括一些高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等。给webpack扩展对应的loader就可以实现上述的功能。
- loader使用过程:1 npm安装对应的loader(loader可以在webpack官网上找到);2在webpack.config.js中的modules关键字下进行配置;
- 安装的loader:css-loader、style-loader、less-loader、url-loader、file-loader、ES6语法处理loader(babel-loader、babel-core、babel-preset-es2015)
/package.json中的devDependencies
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-preset-es2015": "^6.24.1",
"css-loader": "^2.0.2",
"file-loader": "^3.0.1",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"webpack": "^3.6.0"
},
// webpack.config.js中的moudle
module: {
rules: [
{
test: /\.css$/i,
// css-loader只负责将css文件进行加载
// style-loader负责将样式添加到DOM中
// 使用多个loader时,是从右往左加载 -- 注意先后顺序
use: ["style-loader", "css-loader"],
},
{
test: /\.less$/i,
loader: [
// compiles Less to CSS
"style-loader",
"css-loader",
"less-loader",
],
},
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
// 当加载的图片,小于limit时,会将图片编译为base64字符串形式 -- 不需要打包到dist中
// 当加载的图片,大于limit时,需要使用file-loader模块进行加载 -- 需要打包到dist中
limit: 5632,
// 人为设置打包到dist文件夹下的图片路径, hash:8标识截取hash的前8位做图片名;ext是图片后缀
name: 'img/[name].[hash:8].[ext]'
},
},
],
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
// 利用babel将es6转为es5,
// 安装命令如下:npm install --save-dev babel-loader@7.1.5 babel-core@6.26.3 babel-preset-es2015@6.24.1
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
}
],
},
//main.js中导入css、less
// 导入base.css文件
require('./css/base.css')
// 导入.less文件
require('./css/special.less')
-
webpack配置vue
-
安装vue:
npm install vue --save
-
runtime-only问题:在webpack.config.js中配置
-
// 添加vue:runtime-compiler环境
resolve: {
alias: {
‘vue$’: ‘vue/dist/vue.esm.js’ // 用 webpack 时需用’vue/dist/vue.common.js’
}
}
3. .vue文件封装处理::安装vue-loader、vue-template-compiler
- webpack配置plugin:loader主要用于转换某些类型的模块,它是一个转换器;plugin是插件,它是对webpack本身的扩展,是一个扩展器。
- plugin的使用过程:1通过npm安装需要使用的plugins;2在webpack.config.js中的plugins中配置插件
~~~js
//webpack.config.js
// 引用插件
plugins: [
new HtmlWebpackPlugin({
template: 'index.html' //为了在dist index.html中配置index.html中的div#app
}),
new UglifyJsPlugin(), //压缩dist中的bundle.js
new webpack.BannerPlugin('最终版权归zfcer所有'),
]
-
webpack-server配置
-
安装webpack-server:
npm install --save-dev webpack-dev-server@2.9.1
-
在webpack.config.js中配置
devServer: {
contentBase: ‘./dist’,
inline: true //inline表示页面实时刷新
} -
-
配置分离
- 配置base.config.js,在dev.config.js和pro.config.js中导入基本配置
-
在package.json中使用配置
“scripts”: {
“test”: “echo “Error: no test specified” && exit 1”,
“build”: “webpack --config ./build/pro.config.js”,
“dev”: “webpack-dev-server --open --config ./build/dev.config.js”
},
~~~
4.3 Vue CLI2
-
安装Vue CLI:
npm install -g @vue/cli@3.0.4
、npm install -g @vue/cli-init
按照Vue CLI2的方式初始化项目 -
初始化项目:
vue init webpack my-project
- 目录结构
- Runtime-Compiler和Runtime-only的区别
- Vue程序运行过程: template --> ast --> render --> VDOM --> UI
当我们在使用.vue文件时,.vue中的template是由vue-template-compiler来处理的,所以可以直接使用render来渲染。runtime only ,比runtime compiler少6k空间 (效率更高,空间更小)。
- npm run build
- npm run dev
- webpack.base.conf.js起别名
4.4 Vue CLI3
-
Vue CLI3 与 Vue CLI2区别
- vue-cli 3 是基于 webpack 4 打造,vue-cli 2 还是 webapck 3
- vue-cli 3 的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目录
- vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化
- 移除了static文件夹,新增了public文件夹,并且index.html移动到public中
-
创建Vue CLI3项目:
npm create my-project
- 目录结构
- 在
vue ui
上查看配置:一大堆配置文件被放在node_modules/@vue/cli-service/lib/Service.js文件下
- 起别名,在项目目录下创建
vue.config.js
文件
5 vue-router
5.1 vue-router的使用
-
后端路由&前端路由
- 后端路由:早期的网站开发整个HTML页面是由服务器来渲染的(JSP)。当我们页面中需要请求不同的路径内容时, 交给服务器来进行处理, 服务器渲染好整个页面, 并且将页面返回给客户端.p这种情况下渲染好的页面, 不需要单独加载任何的js和css, 可以直接交给浏览器展示, 这样也有利于SEO的优化.
- 前端路由:
- 前后端分离阶段:后端只提供API来返回数据, 前端通过Ajax获取数据, 并且可以通过JavaScript将数据渲染到页面中。(根据Ajax请求从静态服务器中获取对应的静态资源)
- 单页面富应用SPA阶段:SPA最主要的特点就是在前后端分离的基础上加 了一层前端路由。(根据axios路由请求一次性把所有静态资源获取(路由lazy加载))
-
URL变更
- location.href来修改url
- history.pushState({}, ‘’, ‘/foo’)相当于压栈,在浏览器中可以点击返回按钮
- history.replaceState({}, ‘’, ‘/foo’), 在浏览器中不能点击返回按钮
- history.go(-1)弹栈一个元素,等价于history.back() (并不是真正弹栈)
- history.forward() 则等价于 history.go(1)
-
安装vue-router:
npm install vue-router --save
-
使用vue-router:1创建路由组件;2配置路由映射:组件与路径映射关系;3使用路由:通过和
- 路由嵌套:children: [ ]
- 参数传递:动态url,’/user/:userId’传递参数userId。在组件中通过
this.$route.params.userId
来获取 - 导航守卫:通过在beforeEach中利用to来获取meta来传递导航栏标题, 通过
to.matched[0].meta.title
来获取meta参数 - kepp-alive:keep-alive包裹router-view设置访问缓存, 这样可以让组件created只执行一次(缓存)。组件内的 activated 和 deactived 生命周期函数必须在设置keep-alive标签的包裹下来生效
- : 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个标签.
- : 该标签会根据当前的路径, 动态渲染出不同的组件.
//index.js import Vue from 'vue' import Router from 'vue-router' // 路由懒加载: 不要直接注册组件 const Home = () => import('@/components/Home') const HomeMessages = () => import('@/components/HomeMessages') const HomeNews = () => import('@/components/HomeNews') const About = () => import('@/components/About') const User = () => import('@/components/User') const Profile = () => import('@/components/Profile') // 1. 通过Vue.use(插件),安装插件 Vue.use(Router) // 源码分析:Vue.use(Router)的内部操作是install(Vue类), 在install内部全局注册了RouterView 和 RouterLink const routes = [ { path: '/', redirect: '/home', meta: { title: '首页' }, }, { path: '/home', component: Home, meta: { title: '首页' }, children: [ { path: '', component: HomeNews }, { path: 'news', //children中的path一定不能加'/', routes中的path可以加'/' component: HomeNews }, { path: 'messages', component: HomeMessages } ] }, { path: '/about', component: About, meta: { title: '关于' }, }, { path: '/user/:userId', //注册动态url component: User, meta: { title: '用户' }, }, { path: '/profile', component: Profile, meta: { title: '档案' }, } ] // 2. 创建Router对象, 并导出Router const router = new Router({ routes, mode: 'history', //默认使用的是hash, 这里替换为history方式没有# linkActiveClass: 'active', //linkActiveClass在路由上设置默认激活样式; active是App.vue中定义的style }) router.beforeEach((to, from, next) => { document.title = to.matched[0].meta.title //获取meta.title next() //释放router,类似filter效果 console.log(to) // 当存在children时,直接取meta是没有值的。通过打印发现,在matched数组中有要的meta.title }) // 3. 将router对象传入的Vue实例中 export default router
~~~js
//main.js
import Vue from 'vue'
import App from './App'
import router from './router' //自动找router/index.js
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router, //将router传给Vue类中的$router, 所以router 与 $router是同一个对象
render: h => h(App)
})
<template>
<div id="app">
<!-- router-link请求path -->
<!-- router-link属性:
to表示映射的path;
tag表示渲染成其他标签;
replace表示使用replaceStatus,让浏览器无法记录栈,而不能操作返回按钮;
active-class当<router-link>对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active的class
router-link-active是router-link标签中被选中的元素默认加上的class -->
<!-- <router-link to="/home" tag="button" replace active-class="active">Home</router-link>
<router-link to="/about" replace active-class="active">About</router-link> -->
<!-- <router-link to="/home" tag="button" replace>Home</router-link>
<router-link to="/about" replace>About</router-link> -->
<br>
<br>
<br>
<button @click="homeClick">Home</button>
<button @click="aboutClick">About</button>
<!-- <router-link :to="'/user/'+userId">User</router-link>
<router-link :to="{path: '/profile', query: {name: 'zfcer', age: 23, height: 1.87}}">Profile</router-link> -->
<button @click="userClick">User</button>
<button @click="profileClick">Profile</button>
<!-- router-view占位展示 -->
<!-- keep-alive包裹router-view设置访问缓存, 这样可以让组件created只执行一次(缓存) -->
<!-- exclude可以用来取消组件缓存,exclude本身是正则表达式,所以不能有空格 -->
<keep-alive exclude="User">
<router-view/>
</keep-alive>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return {
userId: '1206682'
}
},
methods: {
homeClick(){
this.$router.push('/home')
console.log('homeClick run')
},
aboutClick(){
this.$router.push('/about')
console.log('aboutClick run')
},
userClick(){
this.$router.push('/user/' + this.userId)
},
profileClick(){
this.$router.push({
path: '/profile',
query: {
name: 'best',
age: 18,
height: 1.87
}
})
}
}
}
// 1. $router 和 $route 是Vue类的原型中定义的:
// 源码中的install.js中通过object.defineProperty(Vue.prototype, '$router', ...)完成$router 和 $route的注册
// 2. 所有自己创建的组件都继承自Vue类,所以都有$router 和 $route
// 3. 将router (main.js中定义的)传给Vue类中的$router, 所以router 与 $router是同一个对象
// 4. $route 表示当前被激活的route
// $router 与 $route 都来自Vue类中的this._routerRoot对象
</script>
<style>
/* router-link-active是vue默认加上的class */
.active{
color: #f00;
}
</style>
-
$router 和 $route 说明
-
$router 和 r o u t e 是 V u e 类 的 原 型 中 定 义 的 : 源 码 中 的 i n s t a l l . j s 中 通 过 ‘ o b j e c t . d e f i n e P r o p e r t y ( V u e . p r o t o t y p e , ′ route 是Vue类的原型中定义的:源码中的install.js中通过`object.defineProperty(Vue.prototype, ' route是Vue类的原型中定义的:源码中的install.js中通过‘object.defineProperty(Vue.prototype,′router’, …)`完成$router 和 $route的注册
-
所有自己创建的组件都继承自Vue类,所以都有$router 和 $route
-
将router (main.js中定义的)传给Vue类中的$router, 所以router 与 $router是同一个对象
-
$route 表示当前被激活的route
-
$router 与 $route 都来自Vue类中的this._routerRoot对象
-
5.2 案例tabbar
学会封装组件
6 Vuex详解
6.1 Promise语法
// Promise基本使用
new Promise((resolve, reject) => {
setTimeout(() => {
// 成功的时候调用resolve
resolve("Hello World");
// 失败的时候调用reject
// reject("error message");
}, 1000);
})
.then((data) => {
// data是resolve传过来的数据
// 1.100行的处理代码
console.log(data);
console.log(data);
console.log(data);
console.log(data);
console.log(data);
})
.catch((err) => {
// err是reject传过来的数据
console.log(err);
});
// Promise all 实现两个请求同时完成后再进行下一步操作
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name: "why", age: 18 });
}, 2000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name: "kobe", age: 19 });
}, 1000);
}),
]).then((results) => {
console.log(results);
});
6.2 Vuex使用
Vuex就是为了提供这样一个在多个组件间共享状态的插件,Vuex扮演着大管家的角色(全局单例模式)
-
vuex安装:npm install vuex --save
-
vuex使用步骤:1注册插件; 2创建对象并导出store; 3使用store
//index.js import Vue from 'vue' import Vuex from 'vuex' import {INCREMENT} from '@/store/mutations-types.js' const moduleA = { state: { // 模块中的state调用:this.$store.state.a.name name: 'zfcer' }, mutations: { // 模块中的mutations调用: this.$store.commit('mutations名字'), 调用方式不变 updateName(state, payload){ state.name = payload } }, actions: { aUpdateName(context) { setTimeout(() => { console.log('模块内actions执行了') // 模块内的actions只能调用模块内的mutations context.commit('updateName', '模块内的actions调用了模块内的mutations') }) } }, getters: { // 模块中的getters调用: this.$store.getters.fullName 调用方式不变。所以方法名不要与模块外的方法名重复 fullName(state){ return state.name + '111' }, fullName2(state, getters){ return getters.name + '222' }, fullName3(state, getters, rootState){ // rootState获取Root的state return getters.fullName2 + rootState.counter } }, modules: { } } // 1. 注册插件 Vue.use(Vuex) // 2. 创建对象, 并导出 export default new Vuex.Store({ state: { // 共享变量 counter: 1000, students: [ {id: 110, name: 'zfcer', age: 23}, {id: 111, name: 'best', age: 26}, {id: 112, name: 'one', age: 18}, ], info: { name: 'zfcer', age: 23, height: 1.87 } }, mutations: { // Vuex的store状态的更新唯一方式:提交Mutations // 方法中是 默认传入state参数的 // mutations中提交参数可以通过payload提交对象 [INCREMENT](state){ state.counter++ }, decrement(state){ state.counter-- }, incrementCountOne(state, payload){ console.log(payload) //payload是一个对象 }, incrementCount(state, payload){ console.log(payload.cnt1 + payload.cnt2) state.counter += payload.cnt1 }, addStudent(state, stu){ state.students.push(stu) }, updateInfo(state){ // 直接修改info中已有的属性是响应式的,这是因为Vue对属性做了监听可以做到响应式 state.info.name = 'zfcer best' // 直接在info中添加新属性不是响应式的 // state.info['address'] = '苏州' // Vue.set(state.info, 'address', '苏州') //利用Vue.set实现响应式 // 直接删除info中属性不是响应式的 // delete state.info.age // Vue.delete(state.info, 'age') //利用Vue.delete实现响应式 } }, actions: { // actions用来处理异步操作 // 方法一 // context上下文,可以理解为就是store对象 // payload传递参数 // aUpdateInfo(context, payload){ // console.log("---------------") // setTimeout(() => { // context.commit('updateInfo') // console.log(payload.message) // payload.success() //执行回调函数 // }, 1000) // } // 方法二 aUpdateInfo(context, payload){ return new Promise((resolve, reject) => { setTimeout(() => { context.commit('updateInfo') console.log(payload) resolve('内部执行成功') //传递信息可以被外部then获取 }, 1000) }) } }, getters: { // 与计算属性类似 powerCounter(state){ return state.counter * state.counter }, more20Stus(state){ return state.students.filter(s => s.age >= 20) }, more20StusLength(state, getters){ // 在getters中可以定义getters参数来获得getters中其他函数 return getters.more20Stus.length }, moreAgeStus(state){ // 通过内部创建函数来获得moreAgeStus传过来的参数 // return function(age){ // return state.students.filter(s => s.age >= age) // } return age => state.students.filter(s => s.age >= age) } }, modules: { // 模块最终会被加载到state中,还是一个实体 a: moduleA } }) // 对象的解构 const obj = { name: 'zfcer', age: 18, height: 1.87 } const {name, height, age} = obj // 数组解构 const names = ['zfcer', 'best', 'one'] const [name1, name2, name3] = names
// main.js import Vue from 'vue' import App from './App' import router from './router' import store from './store' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, store, //3. 使用store render: h => h(App) })
-
vuex项目结构
7 axios
- 全局Axios
// 一、直接用axios是用的全局Axios
// axios全局配置
axios.defaults.baseURL = 'http://152.136.185.210:7878/api/m5'
axios.defaults,headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.defaults.timeout = 5000
// 1. axios基本使用
// 指定method的请求,默认是get请求
axios({
// 局部配置
baseURL: 'http://152.136.185.210:7878/api/m5',
url: '/home/multidata',
method: 'get'
}).then(res => console.log(res))
// get请求
axios.get('/home/multidata')
// 带参数的get请求
axios({
url: '/home/data',
// 针对get请求的参数拼接
params: {
type: 'pop',
page: 1
}
}).then(res => console.log(res))
// 2. axios并发使用
// then中获取结果集
axios.all([axios({
url: '/home/multidata',
}), axios({
url: '/home/data',
// 针对get请求的参数拼接
params: {
type: 'sell',
page: 5
}
})])
.then(res => {
//合并后的结果
console.log(res)
})
// then中获取分别的结果
axios.all([axios({
url: 'http://123.207.32.32:8000/home/multidata',
}), axios({
url: 'http://152.136.185.210:7878/api/m5/home/data',
// 针对get请求的参数拼接
params: {
type: 'sell',
page: 5
}
})])
.then(axios.spread((res1, res2) => {
//两个异步请求分别的结果
console.log(res1)
console.log(res2)
}))
- 局部Axios
// 二、axios实例创建与使用
const instance = axios.create({
baseURL: 'http://152.136.185.210:7878/api/m5',
timeout: 5000,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
instance({
url: '/home/data',
method: 'get'
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
- 封装axios:创建network文件夹–>request.js工具类,在main.js中调用封装的request
//--------------------request.js---------------------------
import axios from 'axios'
export function request1(config, success, failure){
// 1.创建axios实例
const instance = axios.create({
baseURL: 'http://152.136.185.210:7878/api/m5',
timeout: 5000
})
// 2.1.axios拦截器
instance.interceptors.request.use(config => {
console.log(config)
// (1)config中一些信息不符合服务器要求需要拦截
// (2)每次发送网络请求时,都希望在界面中显示一个请求的图标
// (3)某些网络请求(比如登录token),必须携带一些特殊信息
return config //释放拦截,如果不释放拦截就无法继续执行
}, err => {
console.log(err)
})
// 2.2.axios响应拦截
instance.interceptors.response.use(res => {
console.log(res)
// 做一些响应处理拦截
return res.data //只需要把data信息返回即可
}, err => {
console.log(err)
})
// 3.发送真正的网络请求
instance(config)
.then(res => {
// console.log(res);
success(res)
})
.catch(err => {
// console.log(err)
failure(err)
})
}
export function request2(config){
// 创建axios实例
const instance = axios.create({
baseURL: 'http://152.136.185.210:7878/api/m5',
timeout: 5000
})
// 发送真正的网络请求
instance(config.baseConfig)
.then(res => {
// console.log(res);
config.success(res)
})
.catch(err => {
// console.log(err)
config.failure(err)
})
}
export function request3(config) {
return new Promise((resolve, reject) => {
// 创建axios实例
const isntance = axios.create({
baseURL: 'http://152.136.185.210:7878/api/m5',
timeout: 5000
})
// 发送真正的网络请求
instance(config)
.then(res => {
resolve(res)
})
.catch(err => {
reject(err)
})
})
}
export function request4(config) {
// 创建axios实例
const instance = axios.create({
baseURL: 'http://152.136.185.210:7878/api/m5',
timeout: 5000
})
// 发送真正的网络请求
return instance(config) //因为axios实例本身就是Promise
}
//-------------------main.js----------------------------
// 三、axios封装
import {request1, request2, request3, request4} from '@/network/request.js'
request1({
url: '/home/multidata'
}, res => {
console.log(res)
}, err => {
console.log(err)
})
request2({
baseConfig: '/home/multidata',
success: function(res){
console.log(res)
},
failure: function(err){
console.log(err)
}
})
request3({
url: '/home/multidata'
}).then(res => console.log(res))
.catch(err => console.log(err))
request4({
url: '/home/multidata'
}).then(res => console.log(res))
.catch(err => console.log(err))
8 项目实战
项目还在学习ing