Vue
vue原理
<div id="box">111</div>
<script>
let box = document.getElementById('box')
var obj = {};
Object.defineProperty(obj, 'name', {
get() {
},
set(data) { //当用obj.name = xxx的时候,就会拿到要修改的数据
console.log('set调用', data)
box.innerHTML = data;
}
})
//用obj.name = '页面变了' ; 用这种方法修改数据,页面就会变更
/*
vue 数据驱动原理
1、数据通过Object.defineProperty 进行get,set拦截
2、通知watcher,(观察者模式,订阅发布模式),触发组件重新渲染,创建行的虚拟dom(js对象模拟dom对比旧的虚拟dom,找到不同的地方,以最小的代价更新节点)
*/
</script>
hello word
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">{{content}}</div> //{{}}模版语法
<script src="../vue.js"></script>
<script>
let app = new Vue({
el: '#app', //指定要渲染的元素
data: {
content: 'hello word'
}
})
</script>
</body>
</html>
指令
v-for:循环
<li v-for="item in list">{{item}}</li>
v-on: 事件绑定,冒号后面是跟要绑定的事件,例如:
点击事件: v-on:click
<button v-on:click="handleBtnClick">提交</button>
v-on可以缩写
用@表示v-on
<button @click="handleBtnClick">提交</button>
v-on修饰符:
事件类:
<button @click.stop="handlClick">阻止事件冒泡</button>
<button @click.prevent="handlEventClick">阻止默认事件</button>
键盘按钮:
//键盘抬起事件
<input @keyup="handlClick">两种都有</input>
v-model:双向数据绑定
v-model绑定的值如果发生了变化,那么data中对应的数据也会发生变化,数据一旦发生变化,视图也会跟着改变
<div id="app">
<input type="text" v-model="inputValue" /> //绑定了inputValue
<button v-on:click="handleBtnClick">提交</button>
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
inputValue: '',
list: ['小红', '小燕']
},
methods: {
handleBtnClick() {
this.list.push(this.inputValue)
this.inputValue = ''
}
}
})
</script>
v-model的原理:
<div id="app" v-cloak>
<input type="text" :value="msg" @input="handlChange"> {{msg}}
//input事件可以监听到input框change的事件
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: "自己实现数据的双向绑定"
},
methods: {
handlChange(e) {
console.log(e.target.value)
this.msg = e.target.value
}
}
})
</script>
v-model控制表单控件:
radio:
<div id="app">
<form>
<label for="male">
<input type="radio" id="male" value="男" v-model="sex">
男
</label>
<label for="famale">
<input type="radio" id="famale" value="女" v-model="sex">
女
</label>
</form>
<h2>{{"选择的性别是:"+sex}}</h2>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
sex: '男'
}
})
</script>
checkbox:
单选:
<div id="app">
<form>
<label for="agree">
<input type="checkbox" id="agree" value="男" v-model="isAgree">
同意协议
</label>
<button :disabled="!isAgree">下一步</button>
</form>
<h2>{{"选择的是:"+isAgree}}</h2>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
isAgree: false
}
})
</script>
多选:
<div id="app">
<input type="checkbox" v-model="hobbies" value="足球">足球
<input type="checkbox" v-model="hobbies" value="蓝球">蓝球
<input type="checkbox" v-model="hobbies" value="乒乓球">乒乓球
<input type="checkbox" v-model="hobbies" value="羽毛球">羽毛球
<h2>{{"选择的是:"+hobbies}}</h2>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
hobbies: []
}
})
</script>
select标签:
<div id="app">
<!-- 单选 -->
<select name="shuiguo" v-model="fruit">
<option value="榴莲">榴莲</option>
<option value="香蕉">香蕉</option>
<option value="苹果">苹果</option>
<option value="葡萄">葡萄</option>
</select>
<h2>{{"选择的是:"+fruit}}</h2>
<!-- 多选 -->
<select name="shuiguo" v-model="fruits" multiple>
<option value="榴莲">榴莲</option>
<option value="香蕉">香蕉</option>
<option value="苹果">苹果</option>
<option value="葡萄">葡萄</option>
</select>
<h2>{{"选择的是:"+fruits}}</h2>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
fruit: '榴莲',
fruits: []
}
})
</script>
v-model的值绑定:
<div id="app">
<label v-for="item in originHobbies" :for="item"> //根据数据动态渲染值
<input type="checkbox" :id="item" :value="item" v-model="hobbies">
{{item}}
</label>
<h2>{{"选择的是:"+hobbies}}</h2>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
hobbies: [],
originHobbies: ["足球", "蓝球", "乒乓球", "羽毛球", "高尔夫球"]
}
})
</script>
v-model的修饰符
lazy修饰符
不会实时加载双向绑定的数据,只会在Input框失去焦点或者回车的时候才会更新
<div id="app">
<input type="text" v-model.lazy="msg">
<h2>{{msg}}</h2>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: '失去焦点的时候才会更新'
}
})
</script>
number修饰符
<div id="app">
<input type="number" v-model.number="msg">
<h2>{{typeof msg}}</h2>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: 0
}
})
</script>
trim修饰符
去除左右两边的空格
<div id="app">
<div>
<input type="text" v-model.tirm="name">
<h2>{{name}}</h2>
</div>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
name: ''
}
})
</script>
v-bind:
父组件向子组件传递值
<todo-item v-for="item in list" v-bind:content="item"></todo-item> //子组件可以用props:['content]来接收这个值
完整用法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="inputValue" />
<button v-on:click="handleBtnClick">提交</button>
<ul>
<todo-item v-for="item in list" v-bind:content="item"></todo-item>
</ul>
</div>
<script>
Vue.component("TodoItem", { //用Vue.component创建一个全局的组件
props: ['content'], //接收传递过来的值
template: '<li>{{content}}</li>' //模版语法
})
let app = new Vue({
el: '#app',
data: {
inputValue: '',
list: ['小红', '小燕']
},
methods: {
handleBtnClick() {
this.list.push(this.inputValue)
this.inputValue = ''
}
}
})
</script>
</body>
</html>
v-bind的简写
可以写成:
<todo-item v-for="item in list" :content="item"></todo-item>
v-text:模版语法
和{{}}插值表达式结果是一样的
<div id="app">
<h1 v-text="message"></h1>
</div>
<script>
let app = new Vue({
el: '#app',
data:{
message: 'hello word'
},
})
</script>
v-html
v-text是会将内容中的标签转义,v-html则不会转义,而是以标签的形式渲染到页面中
{{}}插值表达式和v-text是一模一样的
<div id="app">
<div v-text="message"></div>
<div v-html='name'></div>
</div>
<script>
let app = new Vue({
el: '#app',
data:{
message: '<h1>我是text</h1>',
name: '<h1>我是html</h1>',
},
})
</script>
v-once
只会渲染一次,改变数据之后也不会渲染了
<div id="app">
<div v-once>{{message}}</div>
</div>
<script>
let app = new Vue({
el: '#app',
data:{
message: '我的法克'
},
})
</script>
v-pre
转义{{}}插值表达式,让插值表达式失效,输出原本的内容
<div id="app">
<div v-pre>{{msg}}</div> //页面会原封不动的显示{{msg}}
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: '小红'
},
})
</script>
v-cloak
当加载代码比较慢的时候,页面的插值表达式会被展示出来 ,用户将会看到:{{msg}},如果不希望用户看到这种样子,那么就需要给根组件v-cloak指令
用法:
<div id="app" v-cloak>
<div>{{msg}}</div>
</div>
<script>
setTimeout(() => {
let vm = new Vue({
el: '#app',
data: {
msg: '小红'
},
})
}, 1000)
</script>
v-for
遍历数组:
<div id="app" v-cloak>
<ul>
<li v-for="(item,index) in arr">{{item}}</li> //item=>value index=>下标
</ul>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
arr: ['小红', '小燕', '小蓝']
}
})
</script>
遍历对象:
<div id="app" v-cloak>
<ul>
<li v-for="(value,key) in info">{{value}} {{key}}</li> //第一个是value,第二个是key
</ul>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
info: {
name: '小红',
age: 22
}
}
})
</script>
绑定方法
methods
事件绑定的函数或者其他的函数可以写在methods里面
let app = new Vue({
el: '#app',
data: {
inputValue: '',
list: ['小红', '小燕']
},
methods: { //事件写在里面
handleBtnClick() {
alert('click')
}
}
})
浏览器Event对象
<div id="app" v-cloak>
<div>{{msg}}</div>
<button @click="handlBtnClick('参数')">带参数</button> //调用的时候带了参数
<button @click="handlEventClick">不带参数</button> //调用的时候不带参数
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: '小红'
},
methods: {
handlBtnClick(name) { //可以拿到传递的参数
console.log(name) //参数
},
handlEventClick(e) {//不带参数,默认得到的是浏览器的Event对象
console.log(e) //MouseEvent {}
}
}
})
</script>
即可传递自定义参数,又可以接收浏览器Event对象:
<div id="app" v-cloak>
<div>{{msg}}</div>
<button @click="handl$EventClick('自己的参数',$event)">两种都有</button>
//是用 $event关键字作为浏览器的Event对象实参,这样就可以接收到了
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: '小红'
},
methods: {
handl$EventClick(txt, e) {//接收自定义的参数和event对象
console.log(txt, e)
}
}
})
</script>
局部组件注册
components
用法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="inputValue" />
<button v-on:click="handleBtnClick">提交</button>
<ul>
<!-- <li v-for="item in list" >{{item}}</li> -->
<todo-item v-for="item in list" v-bind:content="item"></todo-item>
</ul>
</div>
<script>
let TodoItem = { //这是一个子组件
props: ['content'],
template: '<li>{{content}}</li>'
}
let app = new Vue({
el: '#app',
components: { //使用componets将子组件注册进来,这样才能正常使用
TodoItem: TodoItem
},
data: {
inputValue: '',
list: ['小红', '小燕']
},
methods: {
handleBtnClick() {
this.list.push(this.inputValue)
this.inputValue = ''
}
}
})
</script>
</body>
</html>
组件data必须是函数
1.组件对象也有一个data属性(也可以有methosd等属性)
因为每个组件都是自己有自己的数据,自己管理自己的数据
2.只是这个对象里的data属性必须是一个函数
每个组件data属性必须是一个函数,因为这是利用函数的闭包性质,保存这块堆内存
3.而且这个函数还要返回一个对象,对象内部保存着数据
这个函数要返回一个对象,是因为要开辟一块新的内存空间,这样才能保证每个组件的内存空间不同,这样就可以实现自己管理自己的数据
父子组件传值
父子组件之间的传值可以用v:bind和$emit共同完成
父组件:
<todo-item
v-for="(item,index) in list"
v-bind:content="item" //传值
@delete="handleItemDelete" //监听子组件的事件,如果子组件触发了这个事件那么就会执行handleItemDelete这个方法
v-bind:index="index"> //传值
</todo-item>
子组件
let TodoItem = {
props: ['content', 'index'], //接收父组件传递过来的值
template: "<li @click='handleItemClick'>{{content}}</li>", //绑定方法
methods: {
handleItemClick() {
this.$emit('delete', this.index) //使用$emit触发delete方法,并带上参数,使用$emit触发的方法,使得父组件可以监听得到
}
}
}
全部代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="inputValue" />
<button v-on:click="handleBtnClick">提交</button>
<ul>
<todo-item
v-for="(item,index) in list"
v-bind:content="item"
@delete="handleItemDelete"
v-bind:index="index">
</todo-item>
</ul>
</div>
<script>
let TodoItem = {
props: ['content', 'index'],
template: "<li @click='handleItemClick'>{{content}}</li>",
methods: {
handleItemClick() {
this.$emit('delete', this.index)
}
}
}
let app = new Vue({
el: '#app',
components: {
TodoItem: TodoItem
},
data: {
inputValue: '',
list: ['小红', '小燕']
},
methods: {
handleBtnClick() {
this.list.push(this.inputValue)
this.inputValue = ''
},
handleItemDelete(index) {
this.list.splice(index, 1)
}
}
})
</script>
</body>
</html>
父组件通过标签的形式给子组件传值
<div id="app">
<counter :count="3"></counter> //给子组件传了个3
<counter :count="2"></counter> //给子组件传了个2
</div>
<script>
Vue.component('counter', {
props:['count'], //接收
template: '<div @click="handleChange">{{count}}</div>', //显示
data() {
return {
}
},
methods:{
handleChange(){
}
}
})
let vm = new Vue({
el: '#app',
data: {
total: 0
},
methods:{
handleTotal(){
this.total = this.$refs.one.number + this.$refs.two.number
}
}
})
</script>
父子组件的访问方式
父组件访问子组件:使用refs
<div id="app">
<com></com>
<button @click='handlClick'>按我</button>
</div>
<template id="com">
<div>子组件</div>
</template>
<script>
let vm = new Vue({
el: '#app',
data: {
},
methods: {
handlClick() {
console.log(this.$children)
this.$children[0].showMessage() //showMessage
console.log(this.$children[0].name) //我是子组件的name
}
},
components: {
com: {
template: "#com",
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMessage() {
console.log('showMessage')
}
},
}
}
})
</script>
子组件访问父组件:使用$parent
<div id="app">
<com></com>
</div>
<template id="com">
<div>
<div>子组件</div>
<button @click='handlClick'>按我</button>
</div>
</template>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: '我是父组件的信息'
},
methods: {
},
components: {
com: {
template: "#com",
methods: {
handlClick() {
console.log(this.$parent)
}
},
}
}
})
</script>
访问根组件:$root
handlClick() {
console.log(this.$parent)
console.log(this.$root.msg)
}
生命周期函数
初始化阶段
beforeCreate: 初始化事件
created:初始化注入&校验
挂载阶段
beforeMount:DOM挂载之前会执行这个方法,就是说这时候DOM还没渲染在页面上
mounted:DOM挂载之后后执行这个方法 ,这时候DOM已经挂载在页面上了
更新阶段
beforeUpdate:数据更新之前会执行
updated:数据更新之后会执行
卸载阶段
beforeDestory:实例即将被销毁的时候执行
destroyed:实例被销毁之后会执行
总结:
let app = new Vue({
el: '#app',
template:"<div>{{message}}</div>",
data:{
message: 'hello word'
},
beforeCreate() {
//初始化事件
console.log('beforeCreate')
},
created(){
//初始化注入&校验
console.log('created')
},
beforeMount(){
//DOM挂载之前会执行这个方法,就是说这时候DOM还没渲染在页面上
console.log(this.$el)
console.log('beforeMount')
},
mounted(){
//DOM挂载之后后执行这个方法 ,这时候DOM已经挂载在页面上了
console.log(this.$el)
console.log('mounted')
},
beforeUpdate(){
//数据更新之前会执行
console.log('beforeUpdate')
},
updated(){
//数据更新之后会执行
console.log('updated')
},
beforeDestory(){
//实例即将被销毁的时候执行
console.log('beforeDestory')
},
destroyed(){
//实例被销毁之后会执行
console.log('destroyed')
}
})
计算属性
computed
computed是一个对象,里面放的是要用于计算的函数,并且每个函数都要有一个返回值
使用:
<div id="app">
{{fullName}} //返回的内容用插值表达式显示出来
</div>
<script>
let app = new Vue({
el: '#app',
data: {
firstName: 'Mike',
lastName: 'jie'
},
computed: { //计算属性
fullName() { //计算属性函数,要求有返回值
return this.firstName + ' ' + this.lastName
}
}
})
</script>
优点:计算属性有一个缓存的机制,就是要计算的属性改变了,它才会执行,如果页面重新渲染了,但是要计算的属性没有改变,那么它就不会执行,继续用上一次执行的结果渲染,只有要计算的属性改变了,才会重新执行。这样能提高性能
计算属性中的set和get
使用:
<div id="app">
{{fullName}}
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
firstName: 'Mike',
lastName: 'jie'
},
computed: {
fullName: {
get() { //获取
return this.firstName + ' ' + this.lastName
},
set(value) { //设置
//可以拿到fullName设置的value
let arr = value.split(' ') //以空格分割字符串,使其变为数组
this.firstName = arr[0] //数组第一项赋值为firstName
this.lastName = arr[1] //数组第二项赋值为lastName
}
}
}
})
vm.fullName = 'xiao hong'
</script>
watch监听器
watch是一个对象,它可以监听data中的数据变化情况,如果数据变化了,可以执行相应的函数,做出相应的操作
watch也是有缓存机制的,和computed一样
使用:
<div id="app">
{{fullName}}
</div>
<script>
let app = new Vue({
el: '#app',
data: {
firstName: 'Mike',
lastName: 'jie',
fullName: 'Mike jie'
},
watch: {
firstName() { //监听函数,函数名对应data中的数据名字,一旦数据变化,就会执行这个函数
this.fullName = this.firstName + ' ' + this.lastName
},
lastName() {
this.fullName = this.firstName + ' ' + this.lastName
}
}
})
</script>
动态样式
第一种:class的对象绑定
<div id="app">
<div @click="handleDivClick" :class="{activated:isActivated}"> //动态绑定class,判断activated是否为true
hello word
</div>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
isActivated:false //控制class中的activated是否显示
},
methods:{
handleDivClick(){
this.isActivated = !this.isActivated //切换取反
}
}
})
第二种:class的数组绑定
<div id="app">
<div @click="handleDivClick" :class="[activated,activatedOne]"> //绑定data中相对于的数据
hello word
</div>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
activated:'',
activatedOne:'activated-one'
},
methods:{
handleDivClick(){
this.activated = this.activated === ''? 'activated' : '' //切换
}
}
})
</script>
第三中:style内联样式
<div id="app">
<div @click="handleDivClick" :style="styleObj"> //双向绑定style,styleObj是一个对象
hello word
</div>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
styleObj: { //这个对象里面有个color属性
color: 'black'
}
},
methods: {
handleDivClick() {
this.styleObj.color = this.styleObj.color === 'black' ? 'red' : 'black' //切换styleObj的color属性
}
}
})
</script>
把:style变成一个数组也是可以的
<div @click="handleDivClick" :style="[styleObj]">
hello word
</div>
条件渲染
v-if
<div v-if="show">
{{msg}}
</div>
v-else
v-if和v-else一定要连在一起写
<div v-if="show">
{{msg}}
</div>
<div v-else>bye bye</div>
v-else-if
<div v-if="show">
{{msg}}
</div>
<div v-else-if="show === b">这是B</div>
<div v-else>bye bye</div>
v-if,v-else-if,v-else,这三个指令是要连载一起写的,中间不能有任何标签分隔开来
v-show
<div v-show="show">我系v-show</div>
总结:
v-if和v-show都可以控制页面元素的出现与隐藏,但是它们有一个不同点,就是v-if隐藏的时候会移除DOM节点,就是把DOM下树,而v-show则是用display:none隐藏,它的DOM节点还是存在于DOM树上的,如果有一个组件频繁的显示与隐藏,那么使用v-show性能就可以提高很多
key
给标签加上key值,可以避免一些不必要的bug,vue的虚拟DOM中,如果一个标签没加上key值,那么在进行隐藏显示的时候,vue会尽量复用已存在的DOM,这样有时候会造成一种bug,例如复用input框的时候,会把input框中用户输入的内容也存留下来
所以,为了解决这个bug,可以使用key来标识这是一个唯一的input
<inpit key="username"/>
操作数组的变异方法
-
pop(删掉数组中最后一项)
-
push(往数组末尾增加一条)
-
shift(删除数组第一项)
-
unshift(往数组第一项增加一条)
-
splice(数组截取)
-
sort(数组排序)
-
reveres(倒转数组)
改变数组的引用地址,也可以改变页面
<div id="app">
<button @click="handleDivClick">按我</button>
<div v-for="item in msg">
{{item}}
</div>
</div>
<script>
[].reveres
let vm = new Vue({
el: '#app',
data: {
msg: ['小红','小燕','小蓝']
},
methods: {
handleDivClick() {
this.msg = [...this.msg,'新来的'] //ES6语法,复制数组,然后加入新内容,同时改变引用地址,页面也会重新渲染
}
}
})
</script>
过滤器
<div id="app" v-cloak>
{{prc | showPrice()}} // | 后面跟着的就是过滤器
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
prc: 2800.89
},
filters: { //过滤器要写在filters这个对象里面
showPrice(prc) {
return '¥' + prc.toFixed(2)
}
}
})
</script>
模版占位符
<template>
如果想循环内容,但是又不想被div包裹,产生无用的标签,那么可以使用template包裹要循环的内容,这个template不会渲染成标签
<template v-for="item in msg">
<div>
{{item}}
</div>
<div>顶你</div>
</template>
循环一个对象
循环对象的第一个属性是 =>键值,第二个属性是 => 键名,第三个属性是下标
<div id="app">
<template v-for="(item,key,index) of msg">
<div>
{{item}} ---- {{key}} --- {{index}}
<!-- item =>键值 -->
<!-- key =>键名 -->
<!-- index =>下标 -->
</div>
</template>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: {
name: '小红',
age: 28
}
}
})
</script>
为对象添加一个属性
和添加数组是一个道理,也是要改变引用地址,才会引起视图的更新
<div id="app">
<template v-for="(item,key,index) of msg">
<div>
{{item}} ---- {{key}} --- {{index}}
<!-- item =>键值 -->
<!-- key =>键名 -->
<!-- index =>下标 -->
</div>
</template>
<button @click='add'>按我</button>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: {
name: '小红',
age: 28
}
},
methods:{
add(){
this.msg = {
...this.msg,
word:'前端开发'
}
}
}
})
</script>
全局的set方法和实例的set方法
如果在外部改变data中的数据,可以使用全局的set方法或者使用实例的set方法
全局:
Vue.set(vm.msg,'word','web')
实例:
vm.$set(msg.msg,'word','php')
is
使用is属性解决模版标签出现的Bug问题
<div id="app">
<table>
<tbody> //tbody里面只能写tr标签,如果写其他标签会有Bug,为了解决这个问题用了is
<tr is="row"></tr> //is表示这个tr标签还是这个row组件
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
</div>
<script>
Vue.component('row', {
template: '<tr><td>this is row </td></tr>'
})
let vm = new Vue({
el: '#app',
})
</script>
ref
需要操作DOM的时候,就要用到ref了
用法:
<div id="app">
<div ref='hello' @click='getRef'>hello world</div>
//关键代码
</div>
<script>
Vue.component('row', {
template: '<tr><td>this is row </td></tr>'
})
let vm = new Vue({
el: '#app',
methods:{
getRef(){
console.log(this.$refs.hello.innerHTML)//关键代码
//用this.$refs.hello就可以获取这个DOM
}
}
})
</script>
例子:
<div id="app">
<counter ref='one' @add='handleTotal'></counter>
<counter ref='two' @add='handleTotal'></counter>
<div >{{total}}</div>
</div>
<script>
Vue.component('counter', {
template: '<div @click="handleChange">{{number}}</div>',
data() {
return {
number: 0
}
},
methods:{
handleChange(){
this.number++;
this.$emit('add') //监听事件
}
}
})
let vm = new Vue({
el: '#app',
data: {
total: 0
},
methods:{
handleTotal(){
this.total = this.$refs.one.number + this.$refs.two.number
}
}
})
</script>
组件参数校验
props:{}
<div id="app">
<child content='hello world'></child> //父组件传值
</div>
<script>
Vue.component('child', {
props: { //接收。props是一个对象,他要求传递过啦的content是一个stirng类型,如果是其他类型的话就会报错
content: String
},
template: '<div>{{content}}</div>',
})
let vm = new Vue({
el: '#app'
})
</script>
有时候需要传递的数据要么是stirng,要么是number类型,那么就要这样写:
props: { //接收。props是一个对象,他要求传递过啦的content是一个stirng类型,如果是其他类型的话就会报错
content: [String,Number]
},
把数据变成必须要传递的数据,不传则会报错
props: {
content: {
type: String, //类型
required: true //是否是必须要传递的,true是要传递,false是可传可不传
}
},
传递的数据可以有默认值
props: {
content: {
type: String,
required: true,
default:'默认值' //如果没传递这个数据,那么就会使用这个默认值
}
},
使用校验器检查传递过来的内容:
props: {
content: {
type: String,
required: true,
default: '默认值',
validator: function (value) { //校验器,value就是传递过来的值
//返回true则不会报错,
//返回false则会报错
//这里可以定义想校验的规则
return (value.length > 5)
}
}
},
如果默认值是对象(Object)或者是数组(Array)的话,那么default就必须是一个函数,里面要返回一个对象或者数组
props: {
content: {
type: Array,
required: true,
default(){ //默认值是数组,default一定要是函数,里面返回的是默认值
return []
}
}
},
props特性和非props特性
props特性是指父组件传递了值,而子组件又在props接收了值,那么就可以直接使用传递过来的值,这叫props特性
非props特性是指父组件传递了值,但子组件并没有在props中接收,这样就使用不了这个值
props特性在DOM标签中不会显示出来
非props特性则会在DOM标签中显示出来
给组件绑定原生事件
给子组件绑定原生事件的时候,而不用通过子组件使用$emit触发
<div id="app">
<child @click.native="handleClick"></child> //在@click后面加上.native就可以绑定原生事件,而不是自定义事件,如果不加native,那么就是自定义事件,需要子组件使用$emit触发
</div>
<script>
Vue.component('child', {
template: "<div>child</div>",
})
let vm = new Vue({
el: '#app',
methods: {
handleClick() {
console.log('11')
}
}
})
</script>
总线机制(发布订阅模式、观察者模式、Bus)
<div id="app">
<child content="小红"></child>
<child content="小燕"></child>
</div>
<script>
Vue.prototype.bus = new Vue() //把实例的地址挂载到Vue原型链的bus属性上
Vue.component('child', {
props: {
content: String
},
data() {
return {
selfContent: this.content
}
},
template: "<div @click='handlClick'>{{selfContent}}</div>",
methods:{
handlClick(){
//实例的bus的地址指向实例,所以bus能使用$emit方法,用$emit触发一个change事件
//并且带上参数
this.bus.$emit('change',this.selfContent)
}
},
mounted(){
let self = this;
//使用bus身上的$on方法监听change事件,一旦触发了change事件,就会执行这个方法
//改变数据
this.bus.$on('change',function(val){
self.selfContent = val
})
}
})
let vm = new Vue({
el: '#app'
})
</script>
插槽
当子组件的内容有一部分是由父组件决定的,那么就可以是用插槽来解决这个问题
<slot>
使用:
<div id="app">
<child>
<h1>小红</h1> //作为插槽的内容传递过去
</child>
</div>
<script>
Vue.component('child', {
template:`
<div>
<p>hello</p>
<slot></slot> //slot接收显示内容
</div>
`
})
let vm = new Vue({
el: '#app'
})
</script>
默认插槽内容:
<div id="app">
<child>
//这里不传内容
</child>
</div>
<script>
Vue.component('child', {
template:`
<div>
<p>hello</p>
<slot>默认内容</slot> //如过不传递内容,将会使用默认的内容
</div>
`
})
let vm = new Vue({
el: '#app'
})
</script>
具名插槽
一个组件中如果想使用两个插槽,那么就要使用具名插槽,如果不是用具名插槽,那么将会渲染两次
使用:
<div id="app">
<body-content>
<div slot="header">header</div> //具名插槽
<div slot="footer">footer</div>
</body-content>
</div>
<script>
Vue.component('body-content', {
template: `
<div>
<slot name="header"></slot> //具名插槽,name对应父组件的slot
<div>content</div>
<slot name="footer">默认值</slot> //具名插槽也会有默认值
</div>
`
})
let vm = new Vue({
el: '#app'
})
</script>
Vue中的作用域插槽
父组件替换插槽的标签,但是内容由子组件来提供
使用:
<div id="app">
<child>
<template slot-scope="props"> //父组件通过template接收子组件传递过来的参数
<h1>{{props.item}}</h1> //给子组件传递插槽并显示子组件传递过来的参数
</template>
</child>
</div>
<script>
Vue.component('child', {
data() {
return {
list: [1, 2, 3, 4]
}
},
template: `
<div> //这里的意思就是子组件帮你遍历数据,然后返回每一个item数据,但是父组件要传递插槽
<slot v-for="item in list" :item="item"></slot>
</div>
`
})
let vm = new Vue({
el: '#app'
})
</script>
动态组件
<component></component>
使用动态组件可以判断是否显示这个组件
使用:
<div id="app">
<child>
<component :is="type"></component> //is绑定了data中的type ,type里面的数据一定要和子组件同名,这样才能成功显示
<button @click='handleBtnClick'>按我切换</button>
</child>
</div>
<script>
Vue.component('child-one', {
template: '<div>child-one</div>'
})
Vue.component('child-two', {
template: '<div>child-two</div>'
})
let vm = new Vue({
el: '#app',
data: {
type: 'child-one' //type,要和子组件同名
},
methods: {
handleBtnClick() {
this.type = this.type === 'child-one' ? 'child-two' : 'child-one'
}
},
})
</script>
这样就可以使用动态组件的功能实现两个组件之间的切换
CSS动画
<transition>做出来的动画,是通过vue识别元素的某一时刻自动帮我们加上类名完成的
使用transition包裹的元素,如果没有写name属性,那么将会使用v-开头的类名作为默认值
<style>
.v-enter { //动画开始第一帧
opacity: 0;
}
.v-enter-active { //监听opacity改变
transition: opacity 2s;
}
.v-leave-to { //动画移除第一帧
opacity: 0;
}
.v-leave-active {
transition: opacity 2s;
}
</style>
</head>
<body>
<div id="app">
<transition>
<div v-if="show">hello world</div>
</transition>
<button @click='hanldClick'>按我</button>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
hanldClick() {
this.show = !this.show
}
}
})
</script>
为了避免冲突,给transition加上name,相应的class类名也要加上一样的类名
<style>
.fate-enter {
opacity: 0;
}
.fate-enter-active {
transition: opacity 2s;
}
.fate-leave-to {
opacity: 0;
}
.fate-leave-active {
transition: opacity 2s;
}
</style>
</head>
<body>
<div id="app">
<transition name="fate">
<div v-if="show">hello world</div>
</transition>
<button @click='hanldClick'>按我</button>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
hanldClick() {
this.show = !this.show
}
}
})
</script>
使用animation执行动画
<style>
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
.fate-enter-active {
transform-origin: left center;
animation: bounce-in 2s;
}
.fate-leave-active {
transform-origin: left center;
animation: bounce-in 2s reverse;
}
</style>
</head>
<body>
<div id="app">
<transition name="fate">
<div v-if="show">hello world</div>
</transition>
<button @click='hanldClick'>按我</button>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
hanldClick() {
this.show = !this.show
}
}
})
</script>
使用Animate.css完成动画
先引入animate.css
使用:
<div id="app">
<transition
name="fate"
enter-active-class='animated swing' //进入的时候加类名
leave-active-class='animated swing' //淡出的时候加类名
>
<div v-if="show">hello world</div>
</transition>
<button @click='hanldClick'>按我</button>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
hanldClick() {
this.show = !this.show
}
}
})
</script>
当页面刷新的时候,也能让动画执行一次
<transition
name="fate"
enter-active-class='animated swing'
leave-active-class='animated shake'
appear //第一步,加上appeart
appear-active-calss='animated swing' //第二步
>
<div v-if="show">hello world</div>
</transition>
使用自己写的动画加上animate.css的动画并解决时长的冲突问题
<style>
.fate-enter,.fate-leave-to {
opacity: 0;
}
.fate-enter-active ,.fate-leave-active{
transition: opacity 2s;
}
</style>
</head>
<body>
<div id="app">
<transition
type="transition" //因为两个动画都有时长,两个时长都不一样,而animate.css是使用animation这个CSS3做的动画效果,自己写的是用transition的这个CSS3做的效果,两个时长可能不一致,为了动画的一致性,所以写了一个type,告诉vue这个时长是以transition为主
name="fate"
enter-active-class='animated swing fate-enter-active'
leave-active-class='animated shake fate-leave-active'
appear
appear-active-calss='animated swing'
>
<div v-if="show">hello world</div>
</transition>
<button @click='hanldClick'>按我</button>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
hanldClick() {
this.show = !this.show
}
}
})
</script>
自定义vue动画时长
<transition
:duration="5000" //使用duration这个属性定义时长
name="fate"
enter-active-class='animated swing fate-enter-active'
leave-active-class='animated shake fate-leave-active'
appear
appear-active-calss='animated swing'
>
<div v-if="show">hello world</div>
</transition>
================================================================================>
也可以传递一个对象,设置入场和出场时间
<transition
:duration="{enter:5000,leave:6000}" //这是一个对象,设置了入场5s,出场6s
name="fate"
enter-active-class='animated swing fate-enter-active'
leave-active-class='animated shake fate-leave-active'
appear
appear-active-calss='animated swing'
>
<div v-if="show">hello world</div>
</transition>
JS动画
js入场动画:
<div id="app">
<transition
name="fate"
@before-enter="handleBeforeEnter"
@enter="handleEnter"
@after-enter="handleAfterEnter">
<div v-if="show">hello world</div>
</transition>
<button @click='hanldClick'>按我</button>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
hanldClick(){
this.show = !this.show
},
handleBeforeEnter(el) {
//动画执行之前,先把颜色变为红色
el.style.color = 'red'
},
handleEnter(el,done) {
//动画执行的时候
setTimeout(()=>{
el.style.color = 'green'
},2000)
// done是一个回调函数,需要调用它才可以执行下面的动画
setTimeout(()=>{
done()
},4000)
},
handleAfterEnter(el) {
el.style.color = 'black'
}
}
})
</script>
js出场动画
<transition
name="fate"
@before-enter="handleBeforeEnter"
@enter="handleEnter"
@after-enter="handleAfterEnter">
<div v-if="show">hello world</div>
</transition>
velocity.js
使用:
<script>
let vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
hanldClick(){
this.show = !this.show
},
handleBeforeEnter(el) {
//动画执行之前,先把颜色变为红色
el.style.color = 'red'
},
handleEnter(el,done) {
Velocity(el,{ //关键代码
opacity:1
},{
duration:1000,
complete:done
})
},
handleAfterEnter(el) {
el.style.color = 'black'
}
}
})
</script>
多个元素或者组件的过渡
多元素
<style>
.fate-enter,
.fate-leave-to {
opacity: 0;
}
.fate-enter-active,
.fate-leave-active {
transition: opacity 1s;
}
</style>
<div id="app">
<transition
name="fate"
mode="in-out" //关键代码
>
<div v-if="show" key="hello">hello world</div> //关键在于key,不能重复
<div v-else key="bye">溜了溜了</div>
</transition>
<button @click='hanldClick'>按我</button>
</div>
多组件
使用component进行切换
<style>
.fate-enter,
.fate-leave-to {
opacity: 0;
}
.fate-enter-active,
.fate-leave-active {
transition: opacity 1s;
}
</style>
</head>
<body>
<div id="app">
<transition
name="fate"
mode="in-out"
>
<component :is="type"></component>
</transition>
<button @click='hanldClick'>按我</button>
</div>
<script>
Vue.component('child',{
template:'<div>child</div>'
})
Vue.component('child-one',{
template:'<div>child-one</div>'
})
let vm = new Vue({
el: '#app',
data: {
type: 'child'
},
methods: {
hanldClick(){
this.type = this.type === 'child'? 'child-one' : 'child'
}
}
})
</script>
Vue中的列表过渡
transition-group
注意点:transition-group里面的元素都是要带上key值的,如果不带上key值,是会报错的
<style>
.fate-enter,
.fate-leave-to {
opacity: 0;
}
.fate-enter-active,
.fate-leave-active {
transition: opacity 1s;
}
</style>
</head>
<body>
<div id="app">
<transition-group name="fate"> //关键代码
<div v-for="item in list" :key="item.id">
{{item.title}}
</div>
</transition-group>
<button @click='addList'>按我</button>
</div>
<script>
let count = 0;
let vm = new Vue({
el: '#app',
data: {
list: []
},
methods: {
addList() {
this.list.push({
id: count++,
title: 'hello world'
})
}
}
})
</script>
动画的封装
使用的时候调用子组件,并传入要做动画的元素和show就可以了
<style>
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-enter-active,
.v-leave-active {
transition: opacity 1s;
}
</style>
</head>
<body>
<div id="app">
<fade :show="show">
<div>hello 小红</div>
</fade>
<button @click='handlClick'>按我</button>
</div>
<script>
Vue.component('fade', {
props: ['show'],
template: `
<transition>
<slot v-if="show"></slot>
</transition>`
})
let vm = new Vue({
el: '#app',
data: {
show: false
},
methods: {
handlClick() {
this.show = !this.show
}
}
})
</script>
把CSS动画和组件抽离
</head>
<body>
<div id="app">
<fade :show="show">
<div>hello 小红</div>
</fade>
<button @click='handlClick'>按我</button>
</div>
<script>
//所有的动画都封装在了子组件中,解耦
Vue.component('fade', {
props: ['show'],
template: `
<transition
@before-enter="handleBeforeEnter"
@enter="handleEnter"
>
<slot v-if="show"></slot>
</transition>`,
methods:{
handleBeforeEnter(el){
el.style.color = 'red'
},
handleEnter(el,done){
setTimeout(()=>{
el.style.color = 'green'
done()
},2000)
}
}
})
let vm = new Vue({
el: '#app',
data: {
show: false
},
methods: {
handlClick() {
this.show = !this.show
}
}
})
</script>
项目工程化
第一步:安装vue-cli脚手架
yarn global add @vue/cli
第二步:vue create创建项目
vue create hello-world
第三步:进入创建好的文件夹,运行命令
yarn serve
这样就运行起项目了
在项目中加入vue-Router
安装:
yarn add vue-router
配置:
在src的Appp.vue文件夹中使用<router-view />标签
<router-view />这个标签可以根据页面的地址返回相应的组件
App.vue:
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'app',
}
</script>
<style>
</style>
在src目录下创建一个pages文件夹,里面存放的是页面的文件
然后分别在pages文件夹下创建两个文件夹,一个home,一个list
home文件夹里面存放Home.vue:
<template>
<div>
我是home
</div>
</template>
<script>
export default {
name:'Home' //创建完记得导出组件
}
</script>
<style lang="">
</style>
list文件夹也一样存放一个List.vue
<template>
<div>
List页面
</div>
</template>
<script>
export default {
name:'List'
}
</script>
<style lang="">
</style>
然后在src下创建一个router文件夹,在文件夹中创建一个Index.js文件
router->index.js:
import Vue from 'vue' //引入vue
import Router from 'vue-router' //引入router
import Home from '@/pages/home/Home' //引入两个组件
import List from '@/pages/list/List'
Vue.use(Router) //调用Vue.use方法,把路由传进去当中间件
export default new Router({ //把路由实例暴露出去
routes: [ //在这里配置相应的路由路径和要显示的组件
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/list',
name: 'List',
component: List
}
]
})
最后一步要配置src目录下的mian.js文件:
import Vue from 'vue'
import App from './App.vue'
import router from './router' //从router引入配置好的路由
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router //挂载到vue实例中,这里key,value一致,省略value
}).$mount('#app')
这样就把路由配置好了,可以在网页中输入地址对应相应的组件
使用router-link进行页面跳转并携带参数
把router-link转化成li标签,使用tag
跳转使用:to,并且在跳转路径后面携带参数
<ul>
<router-link
tag="li" //渲染为li标签
:to="'/detail/'+item.id" //要跳转的页面,并在后面加上携带的参数
class="item border-bottom"
v-for="item of recommendList"
:key="item.id"
>
<img class="item-img" :src="item.imgUrl" alt />
<div class="item-info">
<p class="item-title">{{item.title}}</p>
<p class="item-desc">{{item.desc}}</p>
<button class="item-button">查看详情</button>
</div>
</router-link>
</ul>
动态路由设置
路径后面携带了参数,要在路由里面也配置一下
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/home/Home'
import Detail from '@/pages/detail/Detail'
Vue.use(Router)
export default new Router({
routes: [{
path: '/',
name: 'Home',
component: Home
},{
path: '/detail/:id', //:id 表示接受动态路由
name: 'detail',
component: Detail
}
]
})
接收动态参数:
methods: {
getData() {
console.log(this.$route.params.id); //这里可以拿到动态的id值
}
}
滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/home/Home'
import Detail from '@/pages/detail/Detail'
Vue.use(Router)
export default new Router({
routes: [{
path: '/',
name: 'Home',
component: Home
}, {
path: '/detail/:id',
name: 'detail',
component: Detail
}],
//关键代码=====================>
scrollBehavior(to, from, savedPosition) {
return { x: 0, y: 0 }
}
})
项目初始化
如果要做移动化项目,先在public文件夹下的index.html修改meta标签
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
引入reset.css重置css
在assets文件夹下创建一个styles文件夹,里面创建一个reset.css文件:
@charset "utf-8";
html {
background-color: #fff;
color: #000;
font-size: 12px
}
body,
ul,
ol,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
figure,
form,
fieldset,
legend,
input,
textarea,
button,
p,
blockquote,
th,
td,
pre,
xmp {
margin: 0;
padding: 0
}
body,
input,
textarea,
button,
select,
pre,
xmp,
tt,
code,
kbd,
samp {
line-height: 1.5;
font-family: tahoma, arial, "Hiragino Sans GB", simsun, sans-serif
}
h1,
h2,
h3,
h4,
h5,
h6,
small,
big,
input,
textarea,
button,
select {
font-size: 100%
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: tahoma, arial, "Hiragino Sans GB", "微软雅黑", simsun, sans-serif
}
h1,
h2,
h3,
h4,
h5,
h6,
b,
strong {
font-weight: normal
}
address,
cite,
dfn,
em,
i,
optgroup,
var {
font-style: normal
}
table {
border-collapse: collapse;
border-spacing: 0;
text-align: left
}
caption,
th {
text-align: inherit
}
ul,
ol,
menu {
list-style: none
}
fieldset,
img {
border: 0
}
img,
object,
input,
textarea,
button,
select {
vertical-align: middle
}
article,
aside,
footer,
header,
section,
nav,
figure,
figcaption,
hgroup,
details,
menu {
display: block
}
audio,
canvas,
video {
display: inline-block;
*display: inline;
*zoom: 1
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "\0020"
}
textarea {
overflow: auto;
resize: vertical
}
input,
textarea,
button,
select,
a {
outline: 0 none;
border: none;
}
button::-moz-focus-inner,
input::-moz-focus-inner {
padding: 0;
border: 0
}
mark {
background-color: transparent
}
a,
ins,
s,
u,
del {
text-decoration: none
}
sup,
sub {
vertical-align: baseline
}
html {
overflow-x: hidden;
height: 100%;
font-size: 50px;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: Arial, "Microsoft Yahei", "Helvetica Neue", Helvetica, sans-serif;
color: #333;
font-size: .28em;
line-height: 1;
-webkit-text-size-adjust: none;
}
hr {
height: .02rem;
margin: .1rem 0;
border: medium none;
border-top: .02rem solid #cacaca;
}
a {
color: #25a4bb;
text-decoration: none;
}html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
然后在main.js中引入就可以生效了:
import './assets/styles/reset.css'
border.css文件
有一些手机写border:1px solid #ccc;会有显示问题,可以引入这个文件来解决,同reset.css一起引入就可以了
border.css:
@charset "utf-8";
.border,
.border-top,
.border-right,
.border-bottom,
.border-left,
.border-topbottom,
.border-rightleft,
.border-topleft,
.border-rightbottom,
.border-topright,
.border-bottomleft {
position: relative;
}
.border::before,
.border-top::before,
.border-right::before,
.border-bottom::before,
.border-left::before,
.border-topbottom::before,
.border-topbottom::after,
.border-rightleft::before,
.border-rightleft::after,
.border-topleft::before,
.border-topleft::after,
.border-rightbottom::before,
.border-rightbottom::after,
.border-topright::before,
.border-topright::after,
.border-bottomleft::before,
.border-bottomleft::after {
content: "\0020";
overflow: hidden;
position: absolute;
}
/* border
* 因,边框是由伪元素区域遮盖在父级
* 故,子级若有交互,需要对子级设置
* 定位 及 z轴
*/
.border::before {
box-sizing: border-box;
top: 0;
left: 0;
height: 100%;
width: 100%;
border: 1px solid #eaeaea;
transform-origin: 0 0;
}
.border-top::before,
.border-bottom::before,
.border-topbottom::before,
.border-topbottom::after,
.border-topleft::before,
.border-rightbottom::after,
.border-topright::before,
.border-bottomleft::before {
left: 0;
width: 100%;
height: 1px;
}
.border-right::before,
.border-left::before,
.border-rightleft::before,
.border-rightleft::after,
.border-topleft::after,
.border-rightbottom::before,
.border-topright::after,
.border-bottomleft::after {
top: 0;
width: 1px;
height: 100%;
}
.border-top::before,
.border-topbottom::before,
.border-topleft::before,
.border-topright::before {
border-top: 1px solid #eaeaea;
transform-origin: 0 0;
}
.border-right::before,
.border-rightbottom::before,
.border-rightleft::before,
.border-topright::after {
border-right: 1px solid #eaeaea;
transform-origin: 100% 0;
}
.border-bottom::before,
.border-topbottom::after,
.border-rightbottom::after,
.border-bottomleft::before {
border-bottom: 1px solid #eaeaea;
transform-origin: 0 100%;
}
.border-left::before,
.border-topleft::after,
.border-rightleft::after,
.border-bottomleft::after {
border-left: 1px solid #eaeaea;
transform-origin: 0 0;
}
.border-top::before,
.border-topbottom::before,
.border-topleft::before,
.border-topright::before {
top: 0;
}
.border-right::before,
.border-rightleft::after,
.border-rightbottom::before,
.border-topright::after {
right: 0;
}
.border-bottom::before,
.border-topbottom::after,
.border-rightbottom::after,
.border-bottomleft::after {
bottom: 0;
}
.border-left::before,
.border-rightleft::before,
.border-topleft::after,
.border-bottomleft::before {
left: 0;
}
@media (max--moz-device-pixel-ratio: 1.49), (-webkit-max-device-pixel-ratio: 1.49), (max-device-pixel-ratio: 1.49), (max-resolution: 143dpi), (max-resolution: 1.49dppx) {
/* 默认值,无需重置 */
}
@media (min--moz-device-pixel-ratio: 1.5) and (max--moz-device-pixel-ratio: 2.49), (-webkit-min-device-pixel-ratio: 1.5) and (-webkit-max-device-pixel-ratio: 2.49), (min-device-pixel-ratio: 1.5) and (max-device-pixel-ratio: 2.49), (min-resolution: 144dpi) and (max-resolution: 239dpi), (min-resolution: 1.5dppx) and (max-resolution: 2.49dppx) {
.border::before {
width: 200%;
height: 200%;
transform: scale(.5);
}
.border-top::before,
.border-bottom::before,
.border-topbottom::before,
.border-topbottom::after,
.border-topleft::before,
.border-rightbottom::after,
.border-topright::before,
.border-bottomleft::before {
transform: scaleY(.5);
}
.border-right::before,
.border-left::before,
.border-rightleft::before,
.border-rightleft::after,
.border-topleft::after,
.border-rightbottom::before,
.border-topright::after,
.border-bottomleft::after {
transform: scaleX(.5);
}
}
@media (min--moz-device-pixel-ratio: 2.5), (-webkit-min-device-pixel-ratio: 2.5), (min-device-pixel-ratio: 2.5), (min-resolution: 240dpi), (min-resolution: 2.5dppx) {
.border::before {
width: 300%;
height: 300%;
transform: scale(.33333);
}
.border-top::before,
.border-bottom::before,
.border-topbottom::before,
.border-topbottom::after,
.border-topleft::before,
.border-rightbottom::after,
.border-topright::before,
.border-bottomleft::before {
transform: scaleY(.33333);
}
.border-right::before,
.border-left::before,
.border-rightleft::before,
.border-rightleft::after,
.border-topleft::after,
.border-rightbottom::before,
.border-topright::after,
.border-bottomleft::after {
transform: scaleX(.33333);
}
}
使用这个1px边框很简单:
<div class="border-bottom"></div>
只要在class加上一个border-xxx的类名就可使用了
解决移动端click事件延迟300毫秒的插件
fastclick可以解决这个问题
安装:
yarn add fastclick
使用:
在mian.js中引入并使用
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './assets/styles/reset.css'
import './assets/styles/border.css'
import fastClick from 'fastclick' //引入fastClick
Vue.config.productionTip = false
fastClick.attach(document.body) //绑定到body中,完成
new Vue({
render: h => h(App),
router
}).$mount('#app')
安装stylus方便写CSS
安装:
yarn add stylus
yarn add stylus-loader
使用:
<template>
<div class="header">
<div class="header-left">返回</div>
<div class="header-input">输入城市/景点/游玩主题</div>
<div class="header-right">城市</div>
</div>
</template>
<script>
export default {
name:'HomeHeader'
}
</script>
<style lang="stylus" scoped> //在lang写入stylus,如果不想这里的样式影响到全局,那么就再加上scoped这个属性
.header{
line-height :.86rem;
display :flex;
background #00bcd4;
color: #fff;
.header-left{
width:.64rem;
float:left;
}
.header-input{
flex:1;
height : .64rem;
margin-top:.12rem;
margin-left: .2rem;
background #fff;
border-radius: .1rem;
color :#ccc;
line-height .64rem;
height :.64rem;
}
.header-right{
width :1.24rem;
float:right
text-align :center;
}
}
</style>
这样就可以使用stylus编译css了
使用stylus设置全局的CSS样式变量
第一步:在assets/styles文件夹中创建一个varibles.styl的文件,记得,后缀名是.styl
varibles.styl:
$bgColor = #00bcd4; //定义这个变量为#00bcd4这个颜色
然后在使用的组件中引入:
HeaderHome.vue:
<style lang="stylus" scoped>
@import '../../../assets/styles/varibles.styl' //引入
.header {
line-height: 0.86rem;
display: flex;
background: $bgColor; //使用这个变量
color: #fff;
}
</style>
这样就可以使用变量的形式配置一些全局的样式了
引入的路径优化
使用@符号优化路径,@符号代表了src的根目录,所以,可以通过@符号来查找这些路径,不必通过../来查找
例如:
刚刚引入的CSS变量可以用@符号代替,因为它是CSS里面引入的,所有@符号前面要加上~@
@import '~@/assets/styles/varibles.styl'
vue轮播图插件
安装:
指定版本号安装
yarn add vue-awesome-swiper@2.6.7
安装成功后要在mian.js引入才可以使用:
mian.js:
import VueAwesomeSwiper from 'vue-awesome-swiper' //引入VueAwesomeSwiper
import 'swiper/dist/css/swiper.css' //引入VueAwesomeSwiper的CSS
Vue.use(VueAwesomeSwiper) //通过Vue.use配置一下
配置完成之后就可以开始使用了:
api参考:github.com/surmon-chin…
在component文件夹中创建一个Swiper.vue文件:
<template>
<div class="wrapper"> //在最外层用div包裹住
<swiper :options="swiperOption"> //用swipe包裹,里面是要轮播的每一项
<swiper-slide v-for="item of swiperList" :key="item.id">
<img
class="swiper-img"
:src="item.imgUrl"
alt
/>
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div> //轮播图的小圆圈
</swiper>
</div>
</template>
<script>
export default {
name: "HomeSwiper",
data() {
return {
swiperOption: {
pagination:'.swiper-pagination', //配置使用轮播图的小圆圈
loop:true //配置使用无缝轮播
},
swiperList:[
{id:'0001',imgUrl:'http://mp-piao-admincp.qunarzz.com/mp_piao_admin_mp_piao_admin/admin/20193/b23a39921e8b78f38b61412d691d93ea.jpg_750x200_942ed7bd.jpg'},
{id:'0002',imgUrl:'http://mp-piao-admincp.qunarzz.com/mp_piao_admin_mp_piao_admin/admin/201911/2369a7bacf810913c1ea839d03f9466f.jpg_750x200_b65db1d3.jpg'},
]
};
}
};
</script>
<style lang="stylus" scoped>
.wrapper >>> .swiper-pagination-bullet-active{ //这段代码的意思是穿透了scoped的限制,可以改变
background : #fff; //全局的样式,如果没写 >>> 这个,就不能穿透
}
.wrapper {
width: 100%;
height: 0;
overflow: hidden;
padding-bottom: 31.25%;
// background #eee;
.swiper-img {
width: 100%;
}
}
</style>
vue-awesome-swiper具体配置
vue-awesome-swiper是根据Swiper这个插件封装的Vue插件,所以可以根据Swiper官网里面的api来配置这个插件
这是一个封装好的画廊插件,只要传递一个数组,数组里面是图片的路径就可以使用这个插件了
<template>
<div class="container" @click="handlGallaryClick">
<div class="wrapper">
<swiper :options="swiperOption">
<swiper-slide v-for="(item,index) of imgs" :key="index">
<img
class="gallary-img"
:src="item"
alt
/>
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
</div>
</div>
</template>
<script>
export default {
name: "CommonGallary",
props: {
imgs: {
type: Array,
default() {
return [];
}
}
},
data() {
return {
swiperOption: {
pagination: ".swiper-pagination",
paginationType: "fraction",
observeParents:true,
observer:true
}
};
},
methods:{
handlGallaryClick(){
this.$emit('close')
}
}
};
</script>
<style lang='stylus' scoped>
.container >>> .swiper-container {
overflow: inherit;
}
.container {
display: flex;
flex-direction: column;
justify-content: center;
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: #000;
z-index: 99;
.wrapper {
width: 100%;
height: 0;
padding-bottom: 100%;
.gallary-img {
width: 100%;
}
.swiper-pagination {
color: #fff;
bottom: -1rem;
z-index: 999;
}
}
}
</style>
通过ajax获取数据
使用axios获取请求
yarn add axios
使用:
<template>
<div>
<home-header></home-header>
<HomeSwiper></HomeSwiper>
<HomeIcons></HomeIcons>
<HomeRecommend></HomeRecommend>
<HomeWeekend></HomeWeekend>
</div>
</template>
<script>
import HomeHeader from "./components/Header";
import HomeSwiper from "./components/Swiper";
import HomeIcons from "./components/Icons";
import HomeRecommend from "./components/Recommend";
import HomeWeekend from "./components/Weekend";
import axios from "axios"
export default {
name: "Home",
components: {
HomeHeader,
HomeSwiper,
HomeIcons,
HomeRecommend,
HomeWeekend
},
mounted() {
this.getHomeInfo(); //在生命周期中调用
},
methods: {
getHomeInfo() {
axios.get("/src/pages/home/Home.vue").then(res=>{ //发送请求
console.log(res)
})
},
}
};
</script>
<style lang="">
</style>
better-scroll 页面滚动插件
类似于微信小程序的scroll-view组件,可以在页面滚动
安装
npm install better-scroll -S //第一个版本
或者
npm install better-scroll@next -S //第二个版本
或者
yarn add better-scroll //用yarn安装
使用:
<template>
<div class="list" ref='wrapper'> //用ref获取dom
<div>
<div class="item">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>0</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import BScroll from '@better-scroll/core'; //引入
export default {
name:'Test',
mounted(){
this.scroll = new BScroll(this.$refs.wrapper) //创建实例并把dom传递进去,这样就可以使用了
}
}
</script>
<style>
</style>
scrollToElement:让选中的元素显示在顶部
watch:{
letter(){
if(this.letter){
const element = this.$refs[this.letter][0] //refs获取dom元素
this.scroll.scrollToElement(element) //调用这个方法,并把dom传进去,这样就会把选中的元素显示在顶部
}
}
}
vuex
简单使用:
第一步,在scr路径下创建一个store文件夹,然后在里面创建一个index.js
store->index.js:
import Vue from 'vue' //引入vue
import Vuex from 'vuex' //引入vuex
Vue.use(Vuex) //因为vuex是一个插件,所以要经过vue.use转化
export default new Vuex.Store({ //把vuex.store的实例暴露出去,并且在里面创建一个state对象
state: {
city: '广州'
}
})
第二步,在mian.js中引入store
mian.js:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './assets/styles/reset.css'
import './assets/styles/border.css'
import fastClick from 'fastclick'
import './assets/styles/iconfont.css'
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
import store from './store' //引入store
Vue.config.productionTip = false
fastClick.attach(document.body)
Vue.use(VueAwesomeSwiper)
new Vue({
render: h => h(App),
router,
store, //放在根组件中
}).$mount('#app')
第三步,子组件使用store:
子组件:
<div class="header-right">
{{this.$store.state.city}} //在子组件中,可以使用this.$store获取store中的数据了
<span class="iconfont arrow-icon"></span>
</div>
还可以调用dispatch方法,改变store中的数据
methods:{
handleCityClick(){
let data = this.$store.state.city === '北京'? '广州':'北京'
this.$store.dispatch('changeCity',data)
//派发一个一个名字为changeCity的action
}
}
第四步,store中处理dispatch传递过来的参数,改变store中的数据
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
city: '广州'
},
actions: { //在actions这个对象里面处理dispatch派发的方法
changeCity(ctx, city) { //捕获到这个方法
console.log(city) //第二个参数是传递过来的数据
ctx.commit('changeCity', city) //如果组件调用了changeCity,这个方法,那么这里就调用mutations下面的changeCity方法
}
},
mutations: { //在mutations改变store
changeCity(state, city) { //执行这个方法的时候有两个参数,第一个是state对象,一个是传递过来的数据
state.city = city
}
}
})
这样就可以使用Vuex通信了
如果中间不涉及到异步操作的话,那么也可以在子组件中直接使用commit调用mutations里面的方法来改变store
使用:
子组件
methods:{
handlClick(){
this.$store.commit('changeValue','你已经改变了我')//这里不再是dispatch,而是commit
}
}
store中:
mutations: {
changeValue(state, data) { //同样可以执行这个方法,改变数据
state.value = data
}
}
使用localStorage配合vuex实现刷新数据不丢失
使用try..catch是为了让低版本的浏览器不报错
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
let defaultCity = '上海'
try {
if (localStorage.city) {
defaultCity = localStorage.city
}
} catch (e) {}
export default new Vuex.Store({
state: {
city: defaultCity || '广州',
},
actions: {
changeCity(ctx, city) {
console.log(city)
ctx.commit('changeCity', city)
}
},
mutations: {
changeCity(state, city) {
state.city = city
try {
localStorage.city = city
} catch (e) {}
}
}
})
拆分store
以下三个文件分别在store文件夹下创建:
state.js
let defaultCity = '上海'
try {
if (localStorage.city) {
defaultCity = localStorage.city
}
} catch (e) { throw new Error('fuck') }
export default {
city: defaultCity,
}
actions.js
export default {
changeCity(ctx, city) {
console.log(city)
ctx.commit('changeCity', city)
}
}
mutations.js
export default {
changeCity(state, city) {
state.city = city
try {
localStorage.city = city
} catch (e) {
throw new Error('fuck')
}
}
}
然后在index.js中引入即可:
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import actions from './actions'
import mutations from './mutations'
Vue.use(Vuex)
export default new Vuex.Store({
state,
actions,
mutations
})
使用mapState更方便获取store中的数据
之前要取得store中的数据,一直是用 {{this.$store.state.city}}这种方式来取的,其实vuex已经提供了更方便的取数据方法
在子组件的computed中使用:
<script>
import { mapState } from "vuex"; //引入这个方法
export default {
name: "HomeHeader",
computed: { //在computed中使用
...mapState({ //里面可以是一个对象,这个对象的意思是把store中的city映射给currentCity
currentCity: "city" //这样我们就可以在子组件中使用currentCity渲染内容了
})
}
};
</script>
使用mapMutations调用store中的方法
使用这个方法可以将mutations中的方法映射到子组件中,这样就可以不用调用dispatch或者commit这两个方法都可以改变store中的数据了
import { mapState ,mapMutations} from "vuex"; //引入mapMutations
export default {
name: "HomeHeader",
methods: {
handleCityClick() {
let data = this.$store.state.city === "北京" ? "广州" : "北京";
this.changeCity(data) //这样就可以直接调用这个方法,改变store中的数据了
},
...mapMutations(['changeCity']) //在methods展开这个方法并把changeCity映射到methods中
},
computed: {
...mapState({
currentCity: "city"
})
}
};
</script>
vuex中的Getter
vuex中的Getter类似组件中的计算属性,当我们需要根据state中的数据算出新的数据的时候,我们就可以借助Getter这个工具来提供一些新的数据
定义:
store->index.js:
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import actions from './actions'
import mutations from './mutations'
Vue.use(Vuex)
export default new Vuex.Store({
state,
actions,
mutations,
getters: { //定义getters
doubleCity(state) { //调用的时候,会返回两份state.city
return state.city + ' ' + state.city
}
}
})
使用: 子组件
import { mapState ,mapMutations,mapGetters} from "vuex"; //引入
export default {
name: "HomeHeader",
methods: {
handleCityClick() {
let data = this.$store.state.city === "北京" ? "广州" : "北京";
this.changeCity(data)
},
...mapMutations(['changeCity'])
},
computed: {
...mapState({
currentCity: "city"
}),
...mapGetters(['doubleCity']) //在计算属性中调用这个方法, 映射到组件中
}
};
</script>
template
<div class="header-right">
{{this.doubleCity}}
<span class="iconfont arrow-icon"></span>
</div>
使用Module拆分合并store
可以根据每一个大组件来分配store,这样拆分之后,代码显得没那么臃肿,拆分之后可以用Module来进行合并
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态 取数据的时候要加上前缀
store.state.b // -> moduleB 的状态
使用keep-alive标签优化性能
在App组件中使用,包裹住所有的组件:
<template>
<div id="app">
<keep-alive>
<!-- 使用keep-alive标签,在进入页面的时候会把加载的数据存到内存中,在第二次进入的时候就可以不用重新加载了 -->
<router-view />
</keep-alive>
</div>
</template>
使用keep-alive标签,在进入页面的时候会把加载的数据存到内存中,就是缓存起来,在第二次进入的时候就可以不用重新加载了,而是使用缓存中的数据了
如果有一些页面不想使用缓存的数据,那么就可以使用activated这个生命周期函数来重新获取数据
keep-alive 的 exclude属性
如果你不想页面被缓存,那么,可使用exclude这个属性来实现,exclude=“不想被缓存的组件” 这样就可以实现了
<div id="app">
<keep-alive exclude="Detail">
<router-view />
</keep-alive>
</div>
activated生命周期函数
这个生命周期函数只有在使用了keep-alive这个标签的时候才会出现
使用了keep-alive这个标签之后,你在切换页面的时候会发现,mounted这个生命周期函数只会执行一次,而不会重新执行,这样对于需要刷新数据的页面来说是有缺陷的
activated就是来解决这个问题的,组件来回切换,activated都会重新执行,那么在需要刷新的页面调用这个函数来发送ajax请求就可以了
mounted() {
this.lastCity = this.city //拿到上一次的city
this.getHomeInfo() //发送请求
},
activated(){
if(this.lastCity !== this.city){ //如果上一次的数据和这一次的数据不同,那么发送请求,重新获取数据
this.lastCity = this.city
this.getHomeInfo()
}
},
deactivated生命周期函数
如果在activated这个生命周期函数里绑定了全局的事件,那么就需要在deactivated这里来解绑了
使用:
<script>
export default {
name:'Detail',
methods:{
handleScroll(){
let top = document.documentElement.scrollTop;
console.log(top)
}
},
activated(){
window.addEventListener('scroll',this.handleScroll) //在这里给window绑定了全局的事件
},
deactivated(){
window.removeEventListener('scroll',this.handleScroll)//所以要在这里解绑
}
}
</script>
递归组件的使用
父组件:
<template>
<div>
<detail-banner></detail-banner>
<detail-list :list="list"></detail-list>
</div>
</template>
<script>
import DetailBanner from "./components/Banner";
import DetailList from "./components/List";
export default {
name: "Detail",
components: {
DetailBanner,
DetailList
},
data() {
return {
list: [
{
title: "成人票",
children: [{ title: "三管联票" }, { title: "五管联票" }]
},
{
title: "儿童票", //一级
children: [ //二级
{ title: "三管联票" },
{ title: "五管联票",
children: [ //三级
{title:'儿童六六六联票'}
]
}
]
},
{ title: "特惠票" },
{ title: "活动票" }
]
};
}
};
</script>
<style>
</style>
子组件:
<template>
<div>
<div class="item" v-for="(item,index) of list" :key="index">
{{item.title}}
<div v-if="item.children"> //在每一个item有children的情况下,就调用自己,渲染自己,递归组件完成
<detail-list :list='item.children'></detail-list>
</div>
</div>
</div>
</template>
<script>
export default {
name: "DetailList",
props: {
list: Array
}
};
</script>
<style>
</style>
封装一个动画组件
FadeAnimation.vue:
<template>
<transition>
<slot></slot>
</transition>
</template>
<script>
export default {
name: "FadeAnimation"
};
</script>
<style lang="stylus" scoped>
.v-enter, .v-leave-to {
opacity: 0;
}
.v-enter-active, .v-leave-active {
transition: opacity 2s
}
</style>
其他组件使用:
<fade-animation>
<common-gallary :imgs="imgs" v-show="showGallary" @close="handlGallaryClose"></common-gallary>
</fade-animation>
//用动画组件包裹住就可以实现动画的功能了
解决浏览器不支持Promise的问题
使用一个叫 babel-polyfill的插件就可以解决这个问题
安装:
yarn add babel-polyfill
使用:在mian.js中引入即可
mian.js:
import 'babel-polyfill'
实现异步加载(按需加载)
在router文件夹下的index.js中实现:
没实现异步加载的写法:
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/home/Home'
import Detail from '@/pages/detail/Detail'
Vue.use(Router)
export default new Router({
routes: [{
path: '/',
name: 'Home',
component: Home
}, {
path: '/detail/:id',
name: 'detail',
component: Detail
}],
scrollBehavior() {
return { x: 0, y: 0 }
}
})
异步加载的写法:
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [{
path: '/',
name: 'Home',
component: () =>
import ('@/pages/home/Home')
}, {
path: '/detail/:id',
name: 'detail',
component: () =>
import ('@/pages/detail/Detail')
}],
scrollBehavior() {
return { x: 0, y: 0 }
}
})
不仅仅可以再路由中进行异步加载,还可以在组件中进行异步加载
例如:
export default {
name: "Home",
components: {
HomeHeader:()=>import('./components/Header'), //在注册组件的时候同样可以进行异步加载
HomeSwiper,
HomeIcons,
HomeRecommend,
HomeWeekend
},
mounted() {
console.log('mounted')
},
activated(){
console.log('activated')
},
methods: {
}
}