1.认识Vue
官网地址:https://v2.cn.vuejs.org/v2/guide/
Vue.js 是一套构建用户界面的渐进式框架。
Vue 2 是在2016年发布使用,2020是 vue3 才刚发布,时隔一年左右就已经将 vue3 作为了默认版本
尤雨溪,Vue.js和Vite的作者,HTML5版Clear的打造人,独立开源开发者。
曾就职于Google Creative Labs和Meteor Development Group。
由于工作中大量接触开源的JavaScript项目,最后自己也走上了开源之路,现全职开发和维护Vue.js。
尤雨溪毕业于上海复旦附中,在美国完成大学学业
尤雨溪大学专业并非是计算机专业,在大学期间他学习专业是室内艺术和艺术史
引入VUE:<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>,
,最好下载下来以文件路径引入,会受到网络因素的影响。
为什么要使用vue?
DOM操作比较耗费性能,是前端的性能瓶颈,而vue几乎没有DOM操作,
- 虚拟DOM :性能高
- 数据驱动视图,在html中操作变量。找真实DOM和虚拟DOM之间的差别,只更新有差别的地方,使用diff算法比较很快,不怎么耗费性能,而不是全部做修改
- 组件化:作用域彼此独立,组件化便于复用
- 数据与视图分离
2.基本语法
html
<div id="app">
{{ message }} {{info}}
</div>
<script>
let app = new Vue({
//配置项
el: '#app',//挂载点
data: {
message: 'Hello',
info:"vue"
}
})
</script>
el:挂载点,使用div作为vue挂载点,将来的vue项目要渲染到哪里。
data:以对象的形式存储数据,
在html中写的是变量,相应的数据存在data中,之后如果需要修改数据,不需要做DOM操作,只需要修改变量即可。
双大括号插值表达式:双大括号其实提供了一个js执行环境,可以写任意js表达式,不能写语句,不识别html结构。
一个元素中可以有多个插值表达式
数据统一存放在data中
3.指令
注意:所有指令后面的引号是提供一个js执行环境的,不是字符串的引号。
- 不带标签的只有内容的用双大括号插值表达式{{}};(不用v-text)
- 内容带标签的用v-html;
- 绑定属性,属性是变化的,注意class和style的特殊性
- 绑定事件
- v-if v-else 条件渲染 频繁创建与删除元素
- v-show 显示与隐藏 一开始就会创建出来元素,再做显示隐藏的修改
- v-for 循环创建数目不定的元素 注意要用 :key="index"
- v-model 表单的双向绑定
javascript
v-bind:属性名="数据"
简写 :属性名="属性值"
如 :class="{'act':true}"
javascript
v-on:事件名="函数名"
简写: @事件名="函数名"
如 @click="change"
在JQuery中的思想是先获取元素做DOM操作,而在Vue中是直接操作数据。
v-html
把一段html结构渲染到它所绑定的元素里面
如果元素本身内部有内容,会被指令中的内容覆盖掉
v-text
把一段文本内容渲染到它所绑定的元素中
v-bind
动态绑定属性,所有写在开始标签里的都是属性。
javascript
v-bind:属性名="数据"
常用简写 :属性名="数据"
html
<div id="app">
<div :id='a' :class='b'>
{{msg}}
</div>
</div>
<script>
let app=new Vue({
el:"#app",
data:{
a:"box",
b:"title",
msg:"Hello",
info:"<h1>world</h1>"
}
})
</script>
v-bind的特殊情况
- class
html
<!-- 方式一:绑定字符串 一个class名 -->
<div id="app">
<h1 :class="myClass">
{{msg}}
</h1>
</div>
<script>
let app = new Vue({
el: "#app",
data: {
myClass: "title",
msg: "hello",
}
})
</script>
<!-- 方式二同方式三:直接绑定对象内容 多个class名 -->
<div id="app">
<h1 :class="{aa:flag,bb:false,cc:true,dd:false}">
{{msg}}
</h1>
</div>
<script>
let app = new Vue({
el: "#app",
data: {
myClass: "title",
flag: true,
msg: "hello",
}
})
</script>
<!-- 方式三:绑定对象 多个class名 -->
<div id="app">
<h1 :class="obj">
{{msg}}
</h1>
</div>
<script>
let app = new Vue({
el: "#app",
data: {
myClass: "title",
flag: true,
msg: "hello",
obj: { aa: true, bb: false, cc: true, dd: false }
}
})
</script>
<!-- 方式四:绑定数组 多个class名 -->
<div id="app">
<h1 :class="['aa','bb',myClass]">
<!-- 或者
<h1 :class="[obj,obj2]">
<h1 :class="arr">
<h1 :class="arr2">
-->
{{msg}}
</h1>
</div>
<script>
let app = new Vue({
el: "#app",
data: {
myClass: "title",
flag: true,
msg: "hello",
obj: { aa: true, bb: false, cc: true, dd: false },
obj2: { ee: true },
arr: ['aa', 'bb'],
arr2:[ { aa: true, bb: false, cc: true, dd: false },{ ee: true },]
}
})
</script>
- 可以绑定一个字符串,字符串名就是class名
- 可以绑定一个对象,对象的属性名就是class名,对象的属性值是布尔,代表是否有这个class
- 可以绑定一个数组,数组里是变量名,变量值就是class名字
- style:在DOM操作中得到的样式就是一个对象,注意:写样式的时候样式的属性值要加引号,并且属性名是有多个单词组成的要写成驼峰式。
html
<!-- 方式一同方式二:直接绑定对象内容 一个样式 -->
<div id="app">
<h1 :style="{width:'200px',height:'200px',backgroundColor:bgColor}">
{{msg}}
</h1>
</div>
<script>
let app=new Vue({
el:"#app",
data:{
msg:"hello",
bgColor:"red"
}
})
</script>
<!-- 方式二:绑定对象,一个样式 -->
<div id="app">
<h1 :style="cssStyle">
{{msg}}
</h1>
</div>
<script>
let app=new Vue({
el:"#app",
data:{
msg:"hello",
cssStyle:{width:'200px',height:'200px',backgroundColor:'red'}
}
})
</script>
<!-- 方式三:绑定数组,多个样式 -->
<div id="app">
<h1 :style="[cssStyle,cssStyle2]">
<!-- 或者 <h1 :style="cssStyle3"> -->
{{msg}}
</h1>
</div>
<script>
let app=new Vue({
el:"#app",
data:{
msg:"hello",
cssStyle:{width:'200px',height:'200px',backgroundColor:'red'},
cssStyle2:{color:'blue',fontSize:'35px'},
cssStyle3:[
{width:'200px',height:'200px',backgroundColor:'red'},
{color:'blue',fontSize:'35px'}
]
}
})
</script>
- 绑定到一个对象,对象就是样式对象
- 绑定到一个数组,就是绑定到多个样式对象
v-on
javascript
v-on:事件名="函数名"
缩写为 @事件名="函数"
函数不放在data中,放在另一个配置项methods中。
函数如果有参数,在指令里面直接加括号写参数就行了,没有参数就不需要加括号。
关于事件对象:想要既拿到事件对象又要自己的参数,(只有事件触发的时候才会有事件对象),用匿名函数再包一层,在匿名函数里面执行。
javascript
@事件名="(e)=>{fn(e,实参)}"
html
<!-- 使用例子 -->
<div id="app">
<!-- <button @click="fn">按钮</button> 输出:2-->
<!-- <button @click="fn2('你好')">按钮</button> 输出:你好 -->
<button @click="(e)=>{fn3(e,'hello')}">按钮</button>
<!-- 输出:事件对象 hello -->
</div>
<script>
new Vue({
el: "#app",
data: {
msg: "这是我的第一个vue程序!"
},
methods: {
fn() {
console.log(2)
},
fn2(m) {
//输出传的参数
console.log(m)
},
fn3(e, m) {
//输出带有事件对象的参数
console.log(e, m)
}
}
})
</script>
案例1:点击按钮更改h1的内容。
html
<!-- 点击按钮,h1的内容发生更换 -->
<div id="app">
<h1>{{msg}}</h1>
<button @click="fn">按钮</button>
</div>
<script>
new Vue({
el: "#app",
data: {
msg: "这是我的第一个vue程序",
},
methods: {
fn() {
this.msg = "我是新的数据。"
}
}
})
</script>
案例2:点击按钮控制数据的显示与隐藏。
html
<style>
#title {
width: 200px;
height: 200px;
background-color: red;
}
</style>
<div id="app">
<div id="title" :style="{display:show}"></div>
<button @click="toggle">切换</button>
</div>
<script>
new Vue({
el: "#app",
data: {
show: "block"
},
methods: {
toggle() {
//使用三目运算符 判断show的值
this.show = (this.show === "block" ? "none" : "block")
}
}
})
</script>
按键修饰符:常用在表单input中。
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键
html
<input @keyup.enter="submit">
<input @keyup.down="onPageDown">
为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:
.enter
.tab
.delete
(捕获"删除"和"退格"键).esc
.space
.up
.down
.left
.right
v-if v-else v-else-if
类似于JQuery中的show()和hide()
控制整个元素是否渲染,不是基于样式的, 是真正的条件渲染,满足条件就创建出来,不满足条件就删掉。
html
<h1 v-if=" grades=='A' ">优秀</h1>
<h1 v-else-if="grades=='B'">良好</h1>
<h1 v-else-if="grades=='C'">及格</h1>
<h1 v-else>不及格</h1>
注意,使用v-if的时候
- v-if的元素是和v-else必须是同级
- v-if的元素和v-else的元素必须紧挨着,中间不允许夹杂其他元素
- 适合用于多选一的情况
v-show显示与隐藏
基于样式的切换,而并不是把元素删掉了。
案例2:点击按钮控制数据的显示与隐藏。
html
<div id="app">
<div id="title" v-show="show"></div>
<button @click="toggle">切换</button>
</div>
<script>
new Vue({
el: "#app",
data: {
show: true
},
methods: {
toggle() {
this.show = !this.show
}
}
})
</script>
v-if 与 v-show的性能对比
v-if 是"真正"的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做------直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多------不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,需要频繁的创建与删除元素;而 v-show 有更高的初始渲染开销,不管是否需要显示都先创建出来再改样式,这样在大量数据需要处理的时候就回产生巨大的开销。因此,如果需要非常频繁地切换,则使用 v-show 较好,不需要反复的删除和创建元素;如果在运行时条件很少改变,则使用 v-if 较好。
v-for 元素个数是变量
1.基本用法
将v-for放在元素的开始标签中,代表的是循环创建多个该元素,删除和创建元素时就变为了修改数组和对象的内容。
- 可以循环数组 (value,index)
- 可以循环对象 (value,key,index)
- 可以循环整数 item
html
<!-- v-for使用方法 -->
<div id="app">
<!-- 1 循环数组 -->
<select>
<option v-for="(item,index) in provinces">
{{index}} {{item}}
<!-- item值 index角标 -->
</option>
</select>
<!-- 2 循环对象 -->
<select>
<option v-for="(item,key,index) in obj">
{{index}} {{key}}:{{item}}
<!-- item值 key属性名 index循环次数 -->
</option>
</select>
<!-- 循环整数 -->
<div v-for="(item,index) in 10" :key="index">
{{item}}
<!-- 输出1-10的整数 -->
</div>
</div>
<script>
new Vue({
el: "#app",
data: {
provinces: ["山东省", "河北省", "河南省", "山西省"],
obj: { name: "张三", age: 18, gender: "boy" }
},
})
</script>
建议尽可能在使用
v-for
时提供key
attribute除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
注意:v-if和v-for尽量避免同时使用,可以将v-if套在v-for的外层使用:因为v-for的优先级比v-if更高,当v-if为false时会先执行v-for创建元素,再判断v-if为false删除元素。
<template></template>
标签也是一个容器 ,在需要的时候可以发挥作用,但在编译时不存在了,可以在需要一个容器但是与页面结构无关的时候使用。
html
<div id="app">
<template v-show="show">
<div v-for="i in 9">
{{i}}
</div>
</template>
<!-- 这里使用<template></template>标签是因为如果使用div标签会产生一个与页面结构无关的标签 -->
</div>
<script>
new Vue({
el:"#app",
data:{
show:true
}
})
</script>
key的作用
为什么需要key?
html
<div id="app">
<input type="text" v-if="see" key="a"/>
<input type="password" v-else key="b"/>
<button @click="change">更改</button>
<!-- <button @click="see=!see">更改</button> -->
</div>
<script>
new Vue({
el: '#app',
data: {
see:true
},
methods:{
change(){
this.see=false
}
}
})
</script>
<!-- 实现效果:先显示type="text"的输入框,点击更改后显示为type="password"的输入框,但是原来text中的内容还会在password中,因为vue是只更改不一样的部分,二者都是输入框,只是类型不一样,vue只做了更改类型的操作
这时就可以给两个input不一样的key属性,当发现key不一样时会做删除添加元素,而不进行复用。
-->
尽量不要使用索引值index作key值,一定要用唯一标识的值,如id等。因为若用数组索引index为key,当向数组中指定位置插入一个新元素后,因为这时候会重新更新index索引,对应着后面的虚拟DOM的key值全部更新了,这个时候还是会做不必要的更新,就像没有加key一样,因此index虽然能够解决key不冲突的问题,但是并不能解决复用的情况。如果是静态数据,用索引号index做key值是没有问题的。
响应式原理
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化 。
在实际工作中,使用的Vue.set()只更新一个的情况很少,一般都是直接将数组和对象整体进行替换。
对于对象来说:
property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的,只能做修改,不能添加。
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。
javascript
//想要响应式的添加属性,可以使用如下方法嵌套对象添加响应式 property
Vue.set(object, propertyName, value)
//哪个对象 想要增加哪个属性名 属性值
this.obj.age = 58//age属性已经存在 ,修改属性值
Vue.set(this.obj, "salary", 2500)//salary属性不存在,添加属性
对于数组来说:
直接通过角标赋值是无法设置为响应式的。
直接修改长度也是无法设置为响应式的。
方法一:需要修改数组的某一项时,不是仅替换该项,而是将整个数组替换掉,需要使用深复制的方法(扩展运算符简单)。(该方法需要解决深复制的问题,不如方法二简单)
javascript
//search是数组变量
let arr = [...this.search]
arr[2] = 8
this.search = arr
方法二: 只修改数组的某一项,比方法一更简单,不用深复制。
javascript
//只想修改数组的某一项,可以使用如下方法为数组添加响应式 property
Vue.set(vm.items, indexOfItem, newValue)
//哪个数组 数组的第几项 新值
Vue.set(this.search,1,8);
总结:
数组:法一:完整替换;
法二:深复制.
javascript
let narr=[...arr]
narr[0]="w"
state.arr=narr
法三:set().
对象:法一:完整替换;
法二:深复制,相同属性名后面的属性值覆盖前面的属性值。
javascript
state.person={...person,salary:8000}
法三:set()
数组更新检测
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push()
pop()
shift()
unshift()
splice()
删除或添加数组的某一项sort()
reverse()
v-model 表单双向绑定
基本使用
其他的vue都是数据驱动视图,双向绑定既可以数据驱动视图,又可以视图驱动数据,这样在vue中想要拿到表单的value值只需要取变量即可,不像之前的js中还得等到用户有输入onblur了才能拿到值,在vue中直接随时就可以拿到。变量的初始值可以设置为""。
表单的双向绑定,v-model默认绑定到表单的value属性。
- 输入框input:直接绑定到值value,变量初始值可以设置为"".
- 多选框checkbox: 单个多选框,绑定到布尔值,若变量初始值设置为true,则默认勾选了;多个复选框,绑定的是value的数组,变量的初始值可以设置为[],若变量中的初始值有一个默认的值["打篮球"],那么该value值对应的多选框会被默认选中。单个多选框常用于注册时的确认协议,直接获取绑定的变量值是否为true/flase来判断是否选中.多个多选框判断绑定的变量值中是否有某个值来判断是否选中了该项。
- 单选框radio:直接绑定到值value。若将变量的初始值设置为某个值,则显示出来的是默认选中该项。
- 下拉选择框select:直接绑定到值value.
- 双向绑定也可以绑定到数组的某一项或者对象的某一项,让该项的内容给到表单的value.
案例1:todolist待办事项。
html
<!--
1.在工作中只要遇见了表单一定会加双向绑定的。
2.给每个li绑定点击事件,点谁谁删除.
splice删除哪一项时index刚好与之对应
想要在方法中使用index,可以将index作为参数在函数中传递
-->
<div id="app">
<input type="text" placeholder="请输入您的待办事项" v-model="todo" @keyup.enter="add">
<button @click="add">添加</button>
<div>
<ul>
<li v-for="(item,index) in list" :key="index" @click="remove(index)">
{{item}}
</li>
</ul>
</div>
</div>
<script>
new Vue({
el: "#app",
data: {
todo: "",
list: [],
},
methods: {
add() {
this.list.push(this.todo)
this.todo = ""
},
remove(index) {
this.list.splice(index, 1)
}
}
})
</script>
案例2:使用vue实现导航条高亮效果
html
<style>
* {
padding: 0;
margin: 0;
}
.clear {
clear: both;
}
.tab {
float: left;
width: 60px;
height: 30px;
line-height: 30px;
text-align: center;
margin-right: 15px;
background-color: gray;
color: white;
}
.active {
background-color: pink;
color: blue;
}
</style>
<body>
<!--
作业:导航条
导航条的内容是通过列表动态渲染的 list:["首页","特惠","资讯","游记","地区"]
点谁谁高亮 背景色和文字颜色 其余的的恢复默认
思路:我自己不知道怎么只选中一个div
是否应该存在.act 使用一个变量flag,其初始值为0,默认选中第一个导航条,当点击时就将index赋值给flag,判断flag与index是否相等来决定是否要有.act
-->
<div id="app">
<div class="tab" v-for="(item,index) in list" :key="index" @click="change(index)" :class="{'active':index===flag}">
<!-- 注意:这里的类名要加上引号 change()传递index参数-->
{{item}}
</div>
<div class="clear"></div>
</div>
<script>
new Vue({
el: "#app",
data: {
list: ["首页", "特惠", "资讯", "游记", "地区"],
flag: 0, //当前点的项的序号
},
methods: {
change(index) {
this.flag = index
}
}
})
</script>
</body>
修饰符
html
<!-- 在"change"时而非"input"时更新 在失去焦点时更新,而不是每次都更新-->
<input v-model.lazy="msg">
<!--表单的数据默认是字符串类型的,如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:-->
<input v-model.number="num">
<!--如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:-->
<input v-model.trim="msg">
4.methods 配置项
方法的集合,鼓励使用es6的形式。
5.computed 计算属性 配置项
在后端一般不存储字符串,而是存储数字,占内存小,使用数字代表其中字符串的含义,如1代表男,2代表女,这样从后端拿回到的数据就是1 2,而我们要渲染到页面中的是字符串,这时我们就需要将数数字转换成对应的字符串。
<h1>性别:{``{gender==1?"男":"女"}}</h1>
这样可以解决问题。但是不这么用,因为这样会将大量的逻辑写在html中,结构很乱,影响页面的整洁性,而且对于多步骤的运算实现不了。
<h1>>性别:{``{sex()}}</h1>
sex(){return gender==1?"男":"女"}
,这样将判断写在函数中,调用函数也是可以实现的。但是也不这么用,因为这样如果修改另一个与之不相关的数据内容,而调用函数的数据并没有要改变,但是方法还会再执行一次,影响性能。(vue是数据驱动视图的,只要改了数据,视图就需要重新渲染,方法就需要重新调用)
html
<!--计算属性的用法:-->
<div id="app">
<h1>{{sex}}</h1>
<!-- 这里调用计算属性不加括号,是属性不是函数 -->
</div>
<script>
new Vue({
el: "#app",
data: {
gender: 2
},
computed: {
sex() {
return this.gender === 1 ? "男" : "女"
}
}
})
</script>
- 计算属性是个函数,要求一定有返回值。
- 不能和data中的数据重名。
- 计算属性是基于缓存的,只有在他计算依赖的项有变化的时候他才会重新运算,否则不运算。
- 依赖:此函数中用到的数据有变化,那么计算属性都会重新运行。
- 计算属性最终的结果是个属性,调用的时候不能加括号,在vue内部自动帮忙调用了。
- 计算属性也是可以传参的,只需要让返回值是个函数即可,最终用的是里层的函数 。
- 计算属性中除了可以使用data中的数据,也可以使用其他的计算属性和props的内容。
html
<!-- 计算属性传参数,return一个函数,将操作写在return的函数中即可 -->
<h1>{{order(2)}}</h1>
<script>
new Vue({
el:
data:
methods:
computed:{
order(){
return (status)=>{
console.log(status)
}
}
}
})
</script>
计算属性跟函数的区别?(面试题)
1.计算属性一定要返回值,函数不一定。
2.函数需要主动调用才会执行,计算属性依赖的项变化的时候才执行。
3.计算属性比函数性能高,因为是基于缓存的,不会像函数一样每次都调用,只有依赖的项有变化的时候才执行。
6.watch监听器 配置项
监听某个数据,只要该数据变了,就会执行函数。
优先考虑用计算属性computed能否解决问题,在实际工作中的watch用的还是比较少的,滥用watch可能会导致性能问题
- 函数名必须是一个已存在的在data中的数据名,函数名即是你所监听的数据名。
- 首次加载不执行,只有在后续数据变化的时候才会运行。
- 检测的数据可以是data中的,也可以是computed,还可以是props属性 和 $route。
- 接受两个参数,第一个是最新的值,第二个是变化之前的值。
computed和watch的异同点?(面试题)
1.计算属性是返回一个值,watch是监听一个值做某件事情的。
2.计算属性是值变化的时候重新运算,返回一个新值;watch值变化的时候执行函数,值变化时做事。
3.都不需要主动调用,都是基于值变化的。
7.组件
使用组件化的原因:便于开发 ,便于复用,多次使用时不用多次复制。
组件:一段封装了html css js的代码。
组件的名字可以自定义,一般看到的不认识的标签就是组件。
组件的两种注册方式
- 全局注册:可以在任何vue实例中生效,但基本用不到,因为基本只用一个Vue实例。
- 局部注册:在组件内部注册,在哪用就在注册,用的很多。
注意:1. 对象参数中template必须要有,其他的可以没有,html代码使用es6的字符串扩展``来写。
2.组件标签只能有一个根节点,最外层要由一个大的div包裹。
3.组件名在js中是使用驼峰式,在html中改为以横线 `-`连接的小写字母。
4.组件中的data必须是函数,并且必须要return一个对象,就是我们的数据对象.这是为了每次调用的时候都是自己独立的数据而不是共享的。【根组件(vue实例)中的data是对象,而data如果是对象的话,组件复用时使用的data数据是共用的一份,更改其中一个其他的也会受到影响。】
javascript
//全局注册 没有挂载点 多了一个template属性
Vue.component("组件名",{
template:"html代码",
data(){return {数据 }},//data必须是函数,返回一个对象,保证每个组件使用的都是自己独立的值。
methods:{},
computed:{},
watch:{}
})
//局部注册 将配置项单独声明,在哪用就在哪注册 在vue实例/其他组件中使用components:{组件名:组件配置项}注册
//也可以组件套组件 在配置项的template中使用另一个组件,然后在里面使用components注册,前提是要使用的另一个组件必须已经在前面定义好了
//组件配置项之后大部分不会改了,用const声明
<head></head>
const 组件配置项名/组件名 = {
template: `html代码`,
data(){return {数据}},
methods:{},
computed{},
watch:{}
}
new Vue({
el: "#app",
data: {},
components: {
//组件名:组件配置项 一般将组件名和组件配置项写成一样
head
}
})
html
<!-- 全局注册的使用:在同一个html文件中 -->
<div id="app">
<my-com></my-com>
<my-com></my-com>
</div>
<script>
//全局注册
Vue.component("myCom", {
template: `
<div>
<h1 @click="change" :style="obj">{{msg}}</h1>
<a :href="url">{{info}}</a>
</div>
`,
data() {
return {
msg: "这是vue组件全局注册方式",
url: "http://www.baidu.com",
info: "百度一下,你就知道",
obj: { color: "red" }
}
},
methods: {
change() {
this.url = "http://jd.com"
this.info = "京东"
}
<!-- 两个组件之间的内容互不干预,点谁改的就是谁的,另一个组件内的内容不受影响 -->
}
})
</script>
<!-- 局部注册的使用 -->
<div id="app">
<!-- <mycom></mycom> -->
<mycom2></mycom2>
</div>
<script>
//局部组件的配置项
const mycom = {
template: `
<div>
<h1>{{msg}}</h1>
<h2>{{gender}}</h2>
</div>
`,
data() {
return {
sex: 1,
msg: "这是局部注册的第一个组件"
}
},
computed: {
gender() {
return this.sex === 1 ? "男" : "女"
}
}
}
const mycom2 = {
template: `
<div>
<h1>{{msg}}</h1>
<h2>{{gender}}</h2>
<mycom></mycom>
</div>
`,
data() {
return {
sex: 1,
msg: "这是局部注册的第二个组件"
}
},
computed: {
gender() {
return this.sex === 1 ? "男" : "女"
}
},
components: {
mycom
}
}
new Vue({
el: "#app",
data: {},
components: {
mycom2
}
})
</script>
组件之间的传值--简单场景下
1.父 -> 子 props 组件之间产生嵌套关系 如点击父组件,让子组件的显示属性发生变化 把父组件的数据传给子组件,让在子组件中显示
2.子 -> 父 自定义事件 更改子组件的数据,父组件的内容也发生了变化
3.同级:先子给父,再父给子
父->子、子->父传值这种方法只适用于简单情况,复杂的子组件之间传值使用vuex来实现。
判断是不是父组件与子组件之间的关系:父组件中要用到子组件的标签<>。
组件之间传值的三种情况:
**1.父->子:props(属性) **
注意:子组件不允许直接修改父组件传入的属性,如果需要更新(更改)这个属性,只能让父组件重新传。
孙子级的组件想要使用根组件的数据,要通过中间组件的传递,中间组件既要接收数据,又要传出数据。
**用法:**类似带形参的函数定义及调用的过程(共有3步:函数定义时的形参,形参在函数中的使用,调用函数时传递实参)。
1.子组件内部要接收属性并规定属性的使用场景.
2.父组件中使用子组件要给属性传实际的数据.
父->子传值案例
html
<div id="app">
<my-com :a="msg" :b="change" :c="arr"></my-com>
</div>
<script>
const myCom={
props:["a","b","c"],//在子组件中加props配置项,其值为数组,类似函数定义时的形参可以有多个
template:`<div>
<h1 @click="b">我是组件:{{a}}</h1>
<ul>
<li v-for="item in c">{{item}}</li>
</ul>
</div>`,//在子组件中使用定义的属性
}
new Vue({
el:"#app",
data:{
box:"welcome",
msg:"我是父组件的数据",
info:"哈哈",
arr:[1,2,3,4]
},
methods:{
change(){
console.log(this)//this指向还是父组件
}
},
components:{
//组件名:组件配置项
myCom
}
})
html
<!-- 父->子案例:孙子级组件使用根组件的数据 通过中间组件传递数据-->
<body>
<div id="app">
<my-com :child="msg"></my-com>
</div>
<script>
const myCom1 = {
props: ["child"],
template: `<h2>孙子级组件:{{child}}</h2>`,
}
const myCom = {
props: ["child"],
template: `<div>
<h1>子组件:{{child}}</h1>
<myCom1 :child="child"></myCom1>
</div>`,
components: { myCom1 }
}
new Vue({
el: "#app",
data: {
msg: "我是根组件的数据"
},
components: { myCom }
})
</script>
</body>
2.子->父:自定义事件
vue是单向数据流,只能从父->子,但由于实际需要子->父,因此解决方法时需要自定义事件。
在html中使用子组件时不能给子组件加类、id、样式和指令,这些都是用于实际存在的标签的,而我们自定义的组件并不是实际存在的,编译后就换成了组件中的 template
中的内容,因此不能给子组件加之前规定好的属性和指令。但是可以加自定义的属性和事件,就像上面的在子组件中使用 props
接收自定义属性,像下面的使用 $emit
主动触发自定义事件。
自定义事件需要使用$emit("自定义事件名",参与方法的参数)
主动触发。this.$emit()
用于主动触发绑定在本组件标签上的自定义事件。
子->父传值过程:在子组件上标签绑定一个自定义事件,自定义事件的函数值是定义在父组件的methods中的,在函数里面是要有参数的,将接受的参数赋给父组件的数据;在子组件中需要$emit
主动触发自定义事件,给子组件中的内容绑定一个click事件,在子组件的click事件函数中主动触发,并把自己要传给父组件的数据作为参数传到父组件的自定义事件函数中。(子组件的自定义事件的函数是写在父组件的methods中的,类比于<h1 @click="change"></h1>
的click事件。)
子父传值案例
html
<div id="app">
<h1 >我是父组件{{msg}}</h1>
<child @abc="fn"></child>
</div>
<script>
const child={
data(){
return{
info:"我是子组件的数据"
}
},
template:`<h1 @click="change">我是子组件</h1>`,
methods:{
change(){
//$emit用于主动触发绑定在本组件标签上的自定义事件,其他组件标签上的自定义事件是无法触发的
//$emit(自定义事件名,参与方法的参数)
this.$emit("abc",this.info)
}
}
}
new Vue({
el:"#app",
data:{
msg:""
},
components:{
child
},
methods:{
fn(m){
this.msg=m
}
}
})
</script>
组件间传值小案例--对话框显示与关闭
点击【显示】按钮,显示出来对话框;再点击对话框中的【关闭】按钮,关闭对话框。使用v-show
来控制对话框的显示与隐藏。
由于是点击父组件中的【显示】按钮让子组件显示,所以将控制对话框是否显示的变量定义在父组件中,父-》子传值 props
。
当子组件中的【关闭】按钮点击时,需要让父组件重新把控制对话框是否显示的变量传过来,子-》父传值 自定义事件
。
html
<div id="app">
<div class="dialog">
<button @click="show=true">显示</button>
<modal :visible="show" @close="close"></modal>
</div>
</div>
<script>
const modal = {
props: ['visible'],
template: `<div class="modal" v-show="visible">
<button @click="close">关闭</button>
</div>`,
methods: {
close(){
this.$emit("close")
}
}
}
new Vue({
el: "#app",
data: {
show: false
},
components: {
modal
},
methods:{
close(){
this.show=false
}
}
})
</script>
关于props
注意,属性名在js中定义的时候如果由多个单词组成,要采用驼峰式命名
在html 里使用的时候要改为短横线连接
例如:
javascript
props:["myAttr"]
<xu-button my-atrr="属性">我是按钮</xu-button>
1.数组的形式
javascript
props:["type","round"],
2.对象的形式 限制属性值的类型
javascript
props: {
//属性名:属性的类型
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
time:Date,
s:Symbol,
contactsPromise: Promise // or any other constructor
}
3.传入的是布尔值的情况
vue
<!--只传入属性名,没有属性值,说明传入的是布尔值true-->
<blog-post is-published></blog-post>
<!--此时要求属性必须规定是布尔值类型-->
props:{
isPublished:Boolean
},
4.复杂写法
javascript
props:{
//该属性有可能是多种类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required:true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
}
8.插槽
插槽<slot></slot>
:其实就是给组件标签中的内容(可以是多个标签)预留的位置
普通插槽
对于普通插槽,如果组件模板中有多个插槽,那么组件中标签的内容将会复制,插入到多个插槽中,多个插槽中的内容都是一样的。
html
<div id="app" >
<my-head>
<h1>hello</h1>
</my-head>
</div>
<script>
var head = {
template: `<div>
<slot></slot>
<h1 >{{msg}}</h1>
</div>`,
data: function () { return { msg: "我是头部" } },
}
new Vue({
el:"#app",//挂载点
components:{
myHead:head
}
})
</script>
具名插槽 v-slot
想要使用多个插槽,让组件标签中的指定的内容对应到不同的插槽中去。在组件使用时的插槽模板中使用 v-slot:插槽名
来区分不同的插槽,然后在组件内部的模板中使用slot
标签的name
属性 <slot name="插槽名"></slot>
。
v-slot
** 只能用于**组件
或 template
上,因此要将组件标签的内容使用 <template></template>
标签包起来。
v-slot
简写为:#
.
在普通插槽中也有name属性,只是name属性默认为deafult.
html
<body>
<div id="app" >
<my-head>
<!-- v-slot只能用于组件或template上 -->
<template v-slot:before> <!-- v-slot:可以缩写为# -->
<h1>hello</h1>
</template>
<template #after>
<h1>world</h1>
</template>
</my-head>
</div>
<script>
var head = {
template: `<div>
<slot name="before"></slot>
<h1>{{msg}}</h1>
<slot name="after"></slot>
</div>`,
data: function () { return { msg: "我是头部" } },
}
new Vue({
el:"#app",//挂载点
components:{
myHead:head
}
})
</script>
</body>
编译作用域插槽
如果插槽模板(组件内标签的插槽模板)中想使用该子组件内部的数据,在正常情况下使用的都是根组件中的数据,如果想要使用子组件中的数据,首先需要在组件模板的插槽中定义
:属性名="属性值"
或v-bind:属性名="属性值"
然后在组件调用的地方插槽通过
#插槽名="props"
接收属性 props代表所有属性的集合,是一个对象。在插槽模板中使用的时候直接按照对象的格式调用即可props.属性名
。通过
#插槽名="props"
接收属性的时候也可以直接解构赋值#插槽名="{属性名}"
html
<body>
<div id="app">
<mycom>
<template #before="props">
<h1>{{props.abc}}</h1>
<h2>{{props.def}}</h2>
<!-- 如果要使用组件中的数据不多的话,也可以用解构赋值来做 -->
<!-- <template #before="{abc,def}">
<h1>{{abc}}</h1>
<h2>{{def}}</h2> -->
</template>
<template #after>
<p>123456</p>
</template>
<template #middle>
<h3>就是开心</h3>
</template>
</mycom>
</div>
<script>
const mycom = {
data(){
return{
msg:"我是子组件的数据1",
info:"我是子组件的数据2"
}
},
//属性名是abc。属性值是变量msg的内容
template: `
<div>
<h1>我是组件</h1>
<slot name="before" :abc="msg" :def="info"></slot>
<ul>
<slot name="middle"></slot>
<li>1</li>
<li>2</li>
</ul>
<slot name="after"></slot>
</div>
`
}
new Vue({
el: "#app",
components: {
mycom
},
data:{
msg:"我是父组件"
}
})
</script>
</body>
9.组件的生命周期
生命周期:组件从创建到销毁的完整过程。
生命周期(钩子)函数:在特定的时间节点会自动触发的函数。
javascript
//只要是组件就有生命周期,这些生命周期函数所在的地方与配置项同级
beforeCreate(){
console.log("组件创建之前")
},
created(){
console.log("组件创建完毕")
},
beforeMount(){
console.log("组件渲染前")
},
mounted(){
//我们就能看到组件渲染在页面上了,可以在页面上看到DOM结构了
console.log("组件挂载完毕")
},
//1.页面重新渲染的时候触发,页面视图中没有用到的数据发生更新时并不会触发组件更新的钩子函数,因为vue是数据驱动视图的。
//2.子组件用到的属性渲染到了视图中
//就以视图是否发生更新为依据判断是否触发了更新
beforeUpdate(){
//首次不执行,数据更新可以算是组件更新了
console.log("组件更新前")
},
updated(){
//首次不执行
console.log("组件更新后")
},
beforeDestroy(){
//首次不执行
console.log("组件销毁前")
},
destroyed(){
//首次不执行
console.log("组件销毁后")
},
//下面两个是被缓存的组件特有的生命周期
activated(){
//组件被kepp-alive缓存后被重新激活时使用,即从详情页返回列表页时
}
deactivated(){
//缓存组件失活时使用,即从列表页转向详情页的时候
}
10.动态组件
基础知识
<componet is="组件名"></component>
标签是vue自带的标签,代表的是一个组件,至于代表的是哪一个组件由它的属性is
的值来决定的,而is
的值是可以以变量来变化的,从而实现组件的切换。
html
<div id="app">
<component :is="title"></component>
<button @click="change(2)">按钮</button>
</div>
<script>
var coma = {
template: `<div><h1>我是第一个组件</h1></div>`,
}
var coma2 = {
template: `<div><h1>我是第二个组件</h1></div>`,
}
var coma3 = {
template: `<div><h1>我是第三个组件</h1></div>`,
}
var app = new Vue({
el: "#app",
data: {
tab: "mycom",
title:"mycom"
},
components: {
mycom: coma,
mycom2: coma2,
mycom3: coma3,
},
methods:{
change(n){
this.title="mycom"+n
}
}
})
</script>
案例:使用动态组件实现选项卡的切换
html
<!-- 动态组件实现选项卡的切换 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<style>
* {
margin: 0;
padding: 0;
}
.tab {
float: left;
width: 100px;
height: 50px;
background-color: gray;
margin-right: 8px;
line-height: 50px;
text-align: center;
color: #fff;
}
li {
list-style: none;
}
.act {
background-color: red;
}
.bgc {
width: 424px;
height: 200px;
background-color: pink;
}
</style>
</head>
<body>
<!-- 作业:选项卡的内容用组件来表示 动态组件实现选项卡作业 -->
<div id="app">
<div v-for="(item,index) in list" :id="index" class="tab" @click="change(index)" :class="{'act':num===index}">
{{item}}
</div>
<div style="clear: both;"></div>
<component :is="mycom"></component>
</div>
<script>
const tem = `<div>
<ul class="bgc">
<li v-for="(item,index) in cont" :id="index">{{item}}</li>
</ul>
</div>`
const myCom1 = {
template: tem,
data() {
return {
cont: ["我是体育新闻1", "我是体育新闻2", "我是体育新闻3", "我是体育新闻4"]
}
}
}
const myCom2 = {
template: tem,
data() {
return {
cont: ["我是科技新闻1", "我是科技新闻2"]
}
}
}
const myCom3 = {
template: tem,
data() {
return {
cont: ["我是电影新闻1", "我是电影新闻2", "我是电影新闻3"]
}
}
}
const myCom4 = {
template: tem,
data() {
return {
cont: ["我是娱乐新闻1", "我是娱乐新闻2", "我是娱乐新闻3", "我是娱乐新闻4", "我是娱乐新闻5"]
}
}
}
new Vue({
el: "#app",
data: {
list: ["体育新闻", "科技新闻", "电影新闻", "娱乐新闻"],
num: 0,
mycom: "myCom1",
},
methods: {
change(index) {
this.num = index
index++
this.mycom = "myCom" + index
}
},
components: {
myCom1, myCom2, myCom3, myCom4
}
})
</script>
</body>
</html>
11.综合案例:使用vue实现购物车
使用计算属性较多,基本都是依赖于数组中的是否选中的状态来操作的,写成计算属性这样只要依赖的数据发生了变化就会自动重新调用,不用像写成函数那样每次都需要主动去调用了。
注意点:
- 我们所需要的数据都是从后端返回的,然后赋值给list的,而不是我们直接在list中写的,这些数据只要是我们需要的都可以从后端传过来。
- 给数据list的每一项加上一个id,符合实际项目。
- 在给data中的变量定义时,除了后端传过来的数据,先尽量不要写其他的变量定义,可以通过计算属性实现的就使用计算属性来实现。方法中需要的参数是依赖于data中的数据的,而可以写成计算属性来实现。
- 从后端传过来的数据中是否选中是布尔值的形式,这样的情况时可以和后端交涉将其定为我们的圆圈图片的路径。如果还是布尔值的形式的话,就采用计算属性判断是否选中的情况来定路径。计算属性中想要用传过来的参数,则要return 函数,在return的函数中写逻辑。直接在img的src中用三目运算符判断。
- 需要知道操作的是哪个商品,需要在函数中传参数,尽量传index不要传item。
- 已选数量取决于数组中selecetd的个数,直接用计算属性来做即可,不用单独再在data中加一个变量表示已选数量。使用过滤函数filter(),返回的结果是一个满足条件的数组。
"已选(1)" 和 "全选" 使用es6的模板字符串 `` 拼接字符,变量用 ${变量名}表示。
- 计算总价写成计算属性,不写成函数。因为总价是依赖于数组中的单价和数量的,只要它们有所变化,就会重新执行计算总价的函数。这样只需要在总价的地方执行计算属性,如果写成函数,其他像加减和选中圆圈会改变总价的情况时每次都得再去调用计算总价的函数。
- 先写每个需要点击的基本功能就行,其他的使用计算属性逐步基本能实现。
- 全选按钮取决于数组中selected的数量,还是使用计算属性来实现。使用every(),每一项的selected都是true时是选中状态,否则就是未选中状态。
- 结算按钮的灰色状态,使用some()函数,只要数组中有selected是true的就变为正常。
11.该案例中使用计算属性比较多是因为数据之间的关联性较强,基本都是取决于是否选中的状态。这些计算属性写成函数也能实现,但是性能肯定是没有计算属性好的 。
- 如果我们要做的操作时想要改变data中的数据的值,使用的时methods方法;如果我们要做的操作所需要的值是依赖于data中的数据,使用的是computed计算属性。
javascript
arr.filter((item)=>{
return 条件
})
//返回的是一个满足条件的新数组
javascript
arr.every((item)=>{
return 条件
})
//返回的是布尔值,数组中的每一项都满足条件时返回true,否则返回false
javascript
arr.some((item)=>{
return 条件
})
//返回的是布尔值,只要数组中有满足条件的就返回true,否则返回flase
老师写的:计算属性依赖
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<style>
table {
width: 500px;
margin: 0 auto;
border-collapse: collapse;
text-align: center;
height: 50px;
}
td {
border: 1px solid;
}
</style>
</head>
<body>
<div id="app">
<table>
<!-- 表头行 -->
<tr>
<td>选择</td>
<td>名称</td>
<td>价格</td>
<td>数量</td>
<td>操作</td>
<td>删除</td>
</tr>
<!-- 商品行 -->
<tr v-for="(item,index) in list" :key="item.id">
<td>
<img :src="check(item)" @click="select(index)">
</td>
<td>{{item.name}}</td>
<td>{{item.price}}</td>
<td>{{item.count}}</td>
<td>
<button @click="add(index)">+</button>
<button @click="sub(index)">-</button>
</td>
<td>
<img src="images/delete.png" @click="remove(index)">
</td>
</tr>
</table>
<table>
<tr>
<td>
<img :src="totalSelect" @click="sel">
<span>{{total}}</span>
</td>
<td>总计{{price}}元</td>
<td>
<button :disabled="deal">结算</button>
</td>
</tr>
</table>
</div>
<script>
// 要修改data中的数据用方法,依赖data中的数据做之后的操作用计算属性
new Vue({
el: " #app", data: {
list: [
{ id: '2001', name: "iPhone14", price: 10000, count: 1, selected: false },
{ id: '2002', name: "粉底液", price: 300, count: 1, selected: false },
{ id: '2003', name: "ps5", price: 5000, count: 1, selected: false },
{ id: '2004', name: "键盘", price: 400, count: 1, selected: false }
],
},
computed: {
check() {
return (item) => {
return item.selected ? "images/selected.png" : "images/unselected.png"
}
},
//4 已选数量 依赖于数组中selected的数量 使用计算属性
//使用filtet筛选函数 筛选出selected为true的项组成新数组
total() {
let len = this.list.filter((item) => {
return item.selected == true
}).length
return len === this.list.length ? "全选" : `已选(${len})个`
},
//5 全选圆圈按钮 是否切换成勾取决于selected的数量
totalSelect() {
return this.list.every((item => {
return item.selected == true
})) ? "images/selected.png" : "images/unselected.png"
},
//6 计算总价
//使用filter函数过滤出elected为true的项组成新数组,再对新数组的价格和数量做操作
price() {
let arr = this.list.filter((item) => {
return item.selected == true
})
let total = 0;//总价
for (let i = 0; i < arr.length; i++) {
total += arr[i].price * arr[i].count
}
return total
},
//7 结算按钮 取决于selected的数量,只要有selected为true的就改变属性,否则不变
deal() {
return this.list.some((item => {
return item.selected == true
})) ? false : true
}
},
methods: {
//1 选择圆圈的切换
select(index) {
this.list[index].selected = !this.list[index].selected
},
//2 加减按钮
add(index) {
this.list[index].count++
},
sub(index) {
if (this.list[index].count > 1) {
this.list[index].count--
}
},
//3 删除按钮
remove(index) {
this.list.splice(index, 1)
},
//8 点击全选按钮 要改变数组中的selected值
//先通过判断原来是否全选中,如果是则改为全部未选中
//否则改为全部选中
//不能简单的将selected取反
sel() {
let len = this.list.filter((item) => {
return item.selected
}).length
if (len == this.list.length) {
for (let i = 0; i < this.list.length; i++) {
this.list[i].selected = false
}
} else {
for (let i = 0; i < this.list.length; i++) {
this.list[i].selected = true
}
}
}
}
})
</script>
</body>
</html>
自己写的:方法主动调用
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<style>
#app {
width: 800px;
margin: 100px auto;
}
.product {
border: 1px solid;
padding: 20px;
}
.pro_name {
font-weight: bold;
}
.pro_price {
color: red;
font-size: 20px;
}
.total {
margin-top: 100px;
}
.deal {
width: 80px;
height: 30px;
}
</style>
</head>
<body>
<div id="app">
<div v-for="(item,index) in list" :id="index" class="product">
<img :src="proSelect(item)" @click="select(item)">
<span class="pro_name">{{item.name}}</span>
<span class="pro_price">¥{{item.price}}</span>
<button @click="decCount(item)">-</button>
<span>{{item.count}}</span>
<button @click="addCount(item)">+</button>
<img src="images/delete.png" @click="deletePro(index)">
</div>
<div class="total">
<img :src="totalSrc" @click="totalSelect">
<span>{{allSelect}}</span>
<span>¥{{totalPrice}}</span>
<button class="deal" :disabled="dis">结算</button>
</div>
</div>
<script>
new Vue({
el: "#app",
data: {
list: [
{ name: "iPhone14", price: 10000, count: 1, selected: false },
{ name: "粉底液", price: 300, count: 1, selected: false },
{ name: "ps5", price: 5000, count: 1, selected: false },
{ name: "键盘", price: 400, count: 1, selected: false }
],
totalSrc: "images/unselected.png",//全选圆圈
totalCount: 0,//已选商品数量
totalPrice: 0,//商品总价
dis: true,//结算按钮不可用
},
methods: {
//1 商品数量加减:
//如果该商品已选中,点击加减按钮后,商品数量、总价发生变化
//如果该商品未选中,点击加减按钮后,商品数量发生变化。
decCount(item) {
if (item.count > 1) {
item.count--
}
if (item.selected) {
this.price()
}
},
addCount(item) {
item.count++
if (item.selected) {
this.price()
}
},
//2 商品是否选中的圆圈
//如果原来圆圈未选中,点击之后:选中按钮、已选数量、总价发生变化
//如果原来的圆圈选中,点击之后:选中按钮、已选数量、总价发生变化
//如果点击之后选中的圆圈数量等于商品数量:全选按钮选中,修改完善之后可以使用计算属性来实现
//只要有商品选中,结算按钮恢复状态
select(item) {
let proNum = this.list.length;//商品数量
item.selected ? this.totalCount-- : this.totalCount++
item.selected = !item.selected
this.price()
this.totalSrc = this.totalCount === proNum ? "images/selected.png" : "images/unselected.png"
if (this.totalCount > 0) {
this.dis = false
}
},
//3 计算总价::找到选中的商品,总价=单价*数量
//计算总价的功能最好使用计算属性来实现。
price() {
let total = 0;//总价
for (let i = 0; i < this.list.length; i++) {
if (this.list[i].selected) {
total += this.list[i].count * this.list[i].price
}
}
this.totalPrice = total
},
//4 删除按钮
//都要做的操作是:删除该条商品记录
//如果删除的是选中的商品:这一条商品记录消失、已选商品数量、总价发生变化
//如果删除的是未选中的商品:这一条商品记录消失、判断是否是否全选了商品
//注意:删除操作和其他操作的执行顺序
deletePro(index) {
if (this.list[index].selected) {
this.totalCount--
}
this.list.splice(index, 1)
this.price()
//判断删除后是否全选
let proNum = this.list.length;//商品数量
this.totalSrc = this.totalCount === proNum ? "images/selected.png" : "images/unselected.png"
},
//5 全选按钮
//先判断原来的选中状态的数量
//如果原来就是全部选中:全选按钮、商品选中按钮,总价变为0,已选数量,结算按钮不可点击
//如果原来不是全部选中:全选按钮、商品选中按钮,总价发生变化,已选数量,结算按钮可点击
totalSelect() {
let proNum = this.list.length;//商品数量
//原来已经全部选中
if (this.totalCount === proNum) {
for (let i = 0; i < this.list.length; i++) {
this.list[i].selected = false
}
this.totalSrc = "images/unselected.png"
this.totalPrice = 0
this.dis = true
this.totalCount = 0
} else {
for (let i = 0; i < this.list.length; i++) {
this.list[i].selected = true
}
this.totalSrc = "images/selected.png"
this.price()
this.dis = false
this.totalCount = proNum
}
}
},
computed: {
//计算属性:后台传过来的是是否选中,而我们需要渲染的是选中和未选中的图片,
proSelect() {
return function (item) {
return item.selected ? "images/selected.png" : "images/unselected.png"
}
},
//全部选中时,文本由"已选几个"变为"全选"
allSelect() {
let num = this.list.length;//商品总数量
return this.totalCount === num ? "全选" : "已选(" + this.totalCount + ")"
}
}
})
</script>
</body>
</html>
12.Vue脚手架
下载node.js:vue/react是依赖于node环境的,也能用于写后端。在官网上下载长期支持版本,一直点击下一步安装即可。
检验node是否安装完成:搜索cmd打开命令提示符,输入node -v
,能显示出版本号则表示安装成功。输入npm -v
能显示出版本号则表示可以使用npm。
npm:包管理工具,想要下载什么包直接用npm下载不用再自己找了。包:文件夹。
下载包的方法:npm install 包名
,
不用再像以前一样引入vue文件了,新建一个"脚手架"的文件夹,在脚手架的文件夹里,在上面的路径中输入cmd就直接跳转到了脚手架文件夹所在的路径的命令提示符,输入npm install vue
就会自动下载相应的包到脚手架的文件夹下。
什么是脚手架?
Vue脚手架是Vue官方提供的标准化开发工具(开发平台),它提供命令行和UI界面,方便创建vue工程、配置第三方依赖、编译vue工程。
如果没有脚手架,我们要如何搭建项目?超级麻烦,幸好我们现在可以使用脚手架直接安装搭建项目,简单又方便
- 通过npm init初始化项目,生成项目配置文件package.json,包名:cli,其他的内容直接回车即可。
- 安装webpack和脚手架
- npm install webpack webpack-cli -D
- 配置ES6/7/8转ES5代码
- npm install babel-loader @babel/core @babel/preset-env -D
- 创建webpack.config.js文件,配置webpack
- 安装html-webpack-plugin依赖
- npm install html-webpack-plugin -D
- 安装vue-loader
- npm install vue-loader
- 安装 vue-template-compiler
- npm install vue-template-compiler
- 安装 cache-loader 用于缓存loader编译的结果
- npm install cache-loader
- 安装less loader 安装sass-loader 安装style-loader file-loader url-loader postcss-loader autoprefixer
- 安装webpack-dev-server
- 安装 vue-router vuex axios
- 配置webpack...
脚手架的安装:在脚手架文件夹下的命令提示符中输入:
plain
npm install -g @vue/cli
-g 表示全局安装,可以在任意文件夹下运行,否则只能在当前文件夹下运行
验证脚手架是否成功安装:在脚手架文件夹下的cmd中输入
vue/cli --version
查看脚手架的版本号
利用脚手架创建项目并进行配置方法:在脚手架的文件夹下的cmd中输入以下
plain
vue create 项目名字(用英文不要中文)
手动选择风格Manually select features:*代表选中,上下键切换项,空格切换*选中,我们需要选中:Babel(es6转码成es5)、Router、CSS Pre-processors、Linter/Formatter代码审查。(现在没装的之后还可以装)。
像TS(vue3用)、vuex(之后用)可以再用的时候再装,方法之后再讲
之后安装的方法:npm install vuex.
忘记之前是否已经安装过:在package.json中查看依赖是否有。
选择vue版本:2.x
User history mode for router?Y 默认使用的是Hash mode,但我们现在要使用history模式比较好看。
Pick a CSS pre-processor预处理器:Less
Pick a linter/formatter config代码审查工具:ESLint with error prevention only
Pick additional lint features什么时候进行代码风格检查:Lint on save
Where do you prefer placing config for Babel在哪个地方去存一下配置文件:in package.json
Save this as a preset for future project?N
等待安装...直到 Successfully created project mypro
-------------从下面是打开项目
Get started with the following commands:如果是紧跟着上面的步骤则使用下面的(1)进入mypro文件夹下,否则直接打开mypro文件夹里在里面的地址栏输入cmd转到mypro文件夹下。
(1)cd mypro 转到mypro目录下或直接到mypro的目录中cmd
(2)等待安装...直到 Successfully created project mypro 运行服务器必须是在项目mypro的文件夹里
生成地址:
直接在浏览器中输入地址进入到项目中
不要关闭命令行,否则就会关闭服务器
或者直接将我们的mypro文件夹拖动用vscode打开,在终端中打开直接输入 npm run serve
项目文件解读
**node_modules:**项目依赖的核心模块,该文件夹一定不要动,且该文件夹很大,一般传输项目时不压缩发送该文件夹。----------一定不要修改
**public:**存放静态文件。-----------一般情况下也不需要修改
- favicon.ico:里面存放的是地址栏上面的图标。
- index.html:项目中唯一的html文件,里面没有什么内容,只是提供一个挂载点。vue是一个单页面应用SPA。
src:最重要的文件,我们要编写的代码基本都位于src目录下。
- **main.js:**是项目的入口文件。之后的项目中就只有js文件了,在项目中只需要引入这一个main.js文件就行了,其他的js文件都会通过不同的渠道导入到main.js中来,否则js文件就会不生效。该main.js文件在唯一的html文件中挂载,没有在html文件中引入,是底层自动实现的。----------一般是不需要更改的在该文件中有创建vue实例:
javascript
import Vue from 'vue' //引入我们的vue文件(包),只要是没有./路径的,引入的就是node_modules中的核心模块
import App from './App.vue' //引入App.vue组件 ./代表当前目录
import router from './router' //如果直接写的是文件夹的名字,默认引入的是该文件夹下面的index.js文件 等同于import router from './router/index.js'
Vue.config.productionTip = false //关闭生产环境的错误提示,不然在用户使用的时候发生Bug会出现提示
new Vue({
router,
render: h => h(App)
}).$mount('#app')
//该main.js文件在唯一的html文件中挂载,没有在html文件中引入,是底层自动实现的。
与以往学习的vue实例的不同点:1、没有使用el:"#app"挂载点
:$mount('#app')
也是挂载点的写法,二者的实现效果相同。区别:原来是配置项的形式,现在是主动方法可以主动调用。2、router:vue路由,路由也是vue的配置项,之前也有只是没讲而已。router:router
3、没有data、methods:根实例只是用于创建实例,不用于处理逻辑,渲染数据写逻辑不在这写,用组件。4、需要用到组件 App.vue,但没有注册组件:render函数接收App组件。关于render函数:接收vue组件,创建元素,渲染到页面中, 把App组件渲染出来,App组件是项目中最大的组件。render: h => h(App)的原理等同于 :h=>render{return h(App)}等同于 render:function(h){return h(App)}等同于render:function(createElement){return createElement(App)}
- **App.vue:**单文件组件,在里面可以写组件所需的html页面、样式和js逻辑。要记得默认导出,否则main.js引入就失败了。安装了Vue VSCode Snippets插件之后快捷创建模块:vbase-css
vue
<!-- 生成的模板 -->
<template>
<div>
</div>
</template>
<script>
export default{
//这里是组件中的data、methods、computed等
}
</script>
<style scoped>
</style>
- assets文件夹:一般用来放静态图片资源、css3字体等。
- components文件夹:里面默认的.vue文件组件可以删除。一般用来存放公共布局组件(像侧边栏、顶部导航、尾部等页面都有的)。
- router文件夹:一般用来存放路由相关的文件,如果之前没有安装路由则该文件夹就不会存在了。
- views文件夹:里面默认的.vue文件组件可以删除。创建的新页面一般都放在里面(像经常切换的页面的功能组件)
**.gitignore:**git上传需要忽略的文件格式。编辑器带的配置不要上传到仓库。
**babel.config.js:**主要用于在当前和较旧的浏览器或环境中将ES6代码转化为向后兼容的版本。---------不需要更改
jsconfig.json: -jsconfig.json文件指定根目录和JavaScript服务提供的功能选项。
**package-lock.json:**锁定安装模块的版本号。在安装依赖的时候避免安装的是不同的版本导致的问题。
**package.json:**模块基本信息,依赖的模块,名称,版本号。-----不用自己改
生产环境(上线时的环境)、开发环境(程序员开发过程中的使用的)、 测试环境(测试用的)。
生产依赖(项目上线时还需要的东西)、开发依赖(开发过程中使用,但在上线的时候就不用了)
调试代码时不发node_modules文件夹,直接在终端执行npm install
就会依据package.json安装相应的依赖,安装的速度是很快的,在网上或公司中拿到的项目基本都是没有node_modules文件夹需要自己安装的。
package.json文件中的 "scripts"中定义了一系列的命令,像下面中的serve如果改为了"dev",则启动项目的命令就变为了 npm run dev,实际上是让npm 帮助我们去运行vue-cli-service serve,这个是可以根据公司做修改的。
**README.md:**对项目的主要信息进行描述,使用说明。
**vue.config.js:**vue的其他配置,与webpack相关的。关于请求代理的部分就写在这里。
javascript
//vue.config.js文件
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
//跨域的解决方法:请求代理的配置
devServer: {
proxy: {
"/api": {
target: "https://api.binstd.com/recipe/search", //每次需要更改的地方
changeOrigin: true,//允许跨域
pathRewrite: {
'^/api': '' //请求的时候使用这个api就可以
}
}
}
}
// devServer:请求本地/开发服务器
// proxy:/ˈprɒksi/ 代理
// target:存放用来想请求的路径地址
// pathRewrite:路径重写
// '^/api' :^代表以什么开头,以/api开头,重写为空,因为在真正发送请求的时候路径中是不存在/api的
// 用 /api来代替想要请求的地址,
})
13.单文件组件App.vue
单文件组件:一个文件就是一个组件,都是以.vue结尾的,允许正常的编写html,支持模块化的样式。自动刷新
如果没有单文件组件的话:
1.多个组件写在一个页面中,乱
2.没有html提示,没有语法高亮
3.样式的模块化很明显被遗漏了
views/pages视图文件夹里面一般用来存放创建的.vue新页面一--像经常切换的页面的功能组件);components文件夹一般用来存放公共布局组件(像侧边栏、顶部导航、尾部等页面都有的);router文件夹一般是存放路由相关的文件。
单文件组件使用的注意点:
1、要记得默认导出。
2、在要使用的页面中要导入,相同路径下要加"./"
3、在导入的文件中要注册要使用的组件。
4、导入时的组件名和文件名都要求是多个单词组成的,并且是大驼峰式的写法,但是在html中使用组件时改为"-"。默认导出的文件在导入时的文件名可以是任意的。
5、样式组件化生效:在style标签中加scoped
<style scoped></style>
表示样式只在当前组件中生效。scoped是因为在不同的页面生成不同的随机值,
使用过程步骤:
导出/其他子组件:1、多个单词的大驼峰式的命名;2、默认导出(快捷键创建的模板中带有自动导出);
APP.vue引入/在需要该子组件的组件中:1、导入(由于是默认导出,文件名可以是任意的,多个单词组成的大驼峰式命名)(注意:①引入文件的路径的格式:②当前路径下要加'./';③文件名不加引号;④导入多个组件时,每次导入的语句后要加';'标点符号);2、注册(组件名就是引入的文件名);3、在html中使用子组件(字母之间用'-'代替);
vue
<!-- 子组件导出 -->
<template>
<div>
<h1>{{msg}}</h1>
</div>
</template>
<script>
export default {
data(){
return {
msg:'我是Info子组件'
}
}
}
</script>
<style scoped>
h1{
color: yellow;
}
</style>
vue
<!-- App.vue文件导入 -->
<template>
<div>
<h1>{{msg}}</h1>
<!-- 3 使用 -->
<my-info></my-info>
</div>
</template>
<script>
//1 导入
import MyInfo from './views/MyInfo.vue'
export default {
data(){
return {
msg:"我是App根组件"
}
},
//2 注册
components:{MyInfo}
}
</script>
<style scoped>
h1{
color: blue;
}
</style>
重新启动项目的方法:在终端控制台中输入ctrl+c,Y,再重新启动服务器npm run serve.