VUE 2.0
社区资料:https://www.vue-js.com/
vue官方手册:https://cn.vuejs.org/v2/api/
1.入门
1.1 简介
- vue.js是一套构建用户界面的渐进式框架
- vue只关注视图层,采用自底向上增量开发的设计。
- Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。
- vue是基于MVVM设计模式,支持数据的双向绑定。
MVVM:model view viewmodel
MVC:model模型,数据层、view视图、controller控制器
1.2 vue安装
1 下载vue.js直接引入
<script src="vue.js"></script>
2 CDN加载
https://cdn.staticfile.org/vue/2.2.2/vue.min.js
3 npm 安装
npm install vue -g
npm install vue-cli -g
通过
npm run dev 启动项目(2.0) npm run serve(3.0)
npm run build 打包项目
1.3 基本使用
<div id="app">
<h2>{{msg}}</h2>
</div>
<script>
const vm = new Vue({
el:'#app',//el为vue的作用范围
//data为vue的数据
data:{
msg:'hello vue.js'
},
//定义函数方法
methods:{
}
})
</script>
2.指令(重点)
2.1 vue插入值
v-html
v-text
v-html和v-text区别?
- v-html相当于
innerHTML
,可以识别标签和文本,表单提交的时候不能使用,容易导致XSS攻击 - v-text相当于
txtContent
,只可以识别文本
<div id="app">
<!-- 方式一:采用{{}} -->
<p>{{msg}}</p>
<!-- vue的指令 v-... -->
<!-- 方式二:v-text -->
<p v-text='msg'></p>
<!-- 方式三:v-html -->
<p v-html='msg'></p>
</div>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script>
new Vue({
//选项
//el代表的是element 元素
el:'#app',
data:{
msg:'学习VUE'
}
})
</script>
2.2 vue条件语句
v-if
v-else
v-else-if
v-if //给出布尔值,true就显示,false就隐藏
v-else
v-else-if
<div id="app">
<p v-if="active">明天下雨吗</p>
<p>明天是大晴天</p>
<button v-on:click="change">切换</button>
<p v-if="active">珊瑚宫</p>
<p v-else>心海</p>
<p v-if="star>=6">五郎</p>
<p v-else-if="star>=4">托马</p>
<p v-else>凌人</p>
</div>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script>
new Vue({
//选项
//el代表的是element 元素
el:'#app',
data:{
active:false,
star:5
},
//方法
methods:{
change:function(){
this.active=!this.active
}
}
})
</script>
效果:
2.3 vue显示隐藏
v-if
v-show
v-if 和 v-show的区别?
- v-if直接操作DOM,隐藏之后直接把标签移除
- v-show通过display:none来隐藏元素
<p v-if="true">珊瑚宫心海</p>
<p v-show='false'>反抗军五郎</p>
2.4 vue循环遍历
v-for
<div id="app">
<div v-for="items in arr">
<p>{{items}}</p>
</div>
<hr>
<div v-for="items in obj">
<p>{{items}}</p>
</div>
</div>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script>
new Vue({
//选项
//el代表的是element 元素
el:'#app',
data:{
arr:['胡桃','甘雨','行秋'],
obj:{
name:'七七',
stars:5,
attr:'冰属性'
}
}
})
</script>
效果:
游览器中查看代码源码,是没有我们写的vue代码存在的
2.5 vue样式绑定
v-bind
在vue中,我们采用v-bind来绑定属性和class样式
class属性的绑定,可简写为‘ :’
.bgColor{
background-color: rgb(110, 209, 240);
width: 200px;
height: 50px;
line-height: 50px;
text-align: center;
}
.txt{
color: white;
}
<div id="app">
<!-- 方式一:传入对象,多个样式之间采用逗号连接 -->
<p v-bind:class="{bgColor:isActive,txt:isActive}">珊瑚宫心海</p>
<!-- 方式二:传入数组,多个样式之间采用逗号连接 -->
<p v-bind:class=[bg,txt]>反抗军五郎</p>
<!-- 方式三(不推荐):内联样式 -->
<p v-bind:style="{color:'white',background:'lightblue',fontSize:'20px'}">雷电将军</p>
</div>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script>
new Vue({
//选项
//el代表的是element 元素
el:'#app',
data:{
isActive:true,
bg:'bgColor',
txt:'txt'
}
})
</script>
效果:
注意:在v-bind:绑定属性和class样式时,可以简写为’:'
2.6 vue事件处理
v-on
数量加减案例:
<p><button v-on:click="num--">-</button>{{num}}<button v-on:click="num++">+</button></p>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script>
new Vue({
//选项
//el代表的是element 元素
el:'#app',
data:{
num:1
}
})
</script>
效果:
点击切换样式案例:
ul li{
width: 200px;
height: 50px;
line-height: 50px;
background: rgb(126, 196, 243);
text-align: center;
color: white;
}
.bgColor{
background: rgb(245, 146, 17);
}
<div id="app">
<!-- 事件绑定 -->
<ul>
<li :class="{bgColor:isActive}" @click="change()">
{{num}}<button @click.stop="num++">+</button>
</li>
</ul>
</div>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script>
new Vue({
//选项
//el代表的是element 元素
el:'#app',
data:{
num:1,
isActive:false,
},
methods:{
change(){
this.isActive=!this.isActive
}
}
})
</script>
效果:
注意:v-on:可以简写为’@'
事件修饰符
.stop 阻止事件冒泡行为
.prevent 阻止事件默认行为
.capture 使用事件捕获模式
.self 阻止事件委派(只能当前元素触发事件而不是子元素)
.once 事件只触发一次
<!-- 阻止事件冒泡 -->
<div class="father" v-on:click="fatherSay">
<div class="son" v-on:click.stop="sonSay"></div>
</div>
<!-- 阻止事件默认行为 -->
<a href="http://www.mi.com" v-on:click.prevent="sonSay">跳转小米</a>
<!-- once只触发一次 -->
<button v-on:click.once="num+=1">点击一次</button>
按键修饰符
.enter 回车键
.tab tab键
.delete (捕获 "删除" 和 "退格" 键)
.esc ESC键
.space 空格键
.up 上键
.down 下键
.left 左键
.right 右键
.ctrl Ctrl键
.alt alt键
.shift shift键
.meta meta键
2.7 vue双向绑定
v-model
vue中采用v-model进行数据双向绑定
<!-- 输入框 -->
<h2>输入框</h2>
输入框:<input type="text" v-model.lazy="msg">
<p>输入的内容为:{{msg}}</p>
<!-- 文本域 -->
<h2>文本域</h2>
<textarea name="" id="" cols="30" rows="10" v-model="msg2"></textarea>
<p>文本域输入内容为:{{msg2}}</p>
<!-- 单选框 -->
<h2>单选框</h2>
<input type="radio" name="sex" v-model="sex" value="男">男
<input type="radio" name="sex" v-model="sex" value="女">女
<input type="radio" name="sex" v-model="sex" value="保密">保密
<p>单选框选中的为:{{sex}}</p>
<!-- 复选框 -->
<h2>复选框</h2>
<p>你最想吃啥:</p>
<input type="checkbox" name="foods" value="重庆火锅" v-model="foods">重庆火锅
<input type="checkbox" name="foods" value="冰淇淋" v-model="foods">冰淇淋
<input type="checkbox" name="foods" value="麻辣小龙虾" v-model="foods">麻辣小龙虾
<input type="checkbox" name="foods" value="辣条" v-model="foods">辣条
<p>复选框选中的为:{{foods}}</p>
<!-- 下拉框 -->
<h2>下拉框</h2>
<select name="city" id="" v-model="city">
<option value="武汉">武汉</option>
<option value="成都">成都</option>
<option value="美国">美国</option>
<option value="意大利">意大利</option>
</select>
<p>下拉框选中的内容为:{{city}}</p>
const vm = new Vue({
el:'#app',
data:{
msg:'',
msg2:'',
sex:'男',
foods:['麻辣小龙虾','辣条'],//复选框需要传入一个数组
city:'',
age:'',
msg2:''
}
})
表单修饰符
.lazy 输入框再change事件中同步
.number 将用户输入的内容自动转换为number类型
.trim 将去除用户输入的内容的首尾空格
关于getter/setter方法
1.当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项 (图中就是 a 对象中的 b 属性,即 a.b),Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter 方法。
2. 用户看不到 getter/setter 方法,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
3. 同时 Vue 会对模板进行编译,解析之后会生成指令对象,也例如 v-text, v-hide 等。当指令中 v-text='a.b’时,其实就是触发 getter 方法,获取对应的数据。
2.8 自义定组件
Vue.directive(指令的名字,{指令执行的方法})
Vue.directive('focus', {
// 当绑定元素插入到 DOM 中。
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
3.安装node环境和vue环境
node环境:
https://note.youdao.com/s/dXarCr8e
vue环境:
https://note.youdao.com/s/bR6pMdUO
4.计算属性
在vue内计算属性的关键词为:computed
<div id="app">
<!-- 调用computed的方法时,不需要加小括号 -->
<p>{{splitStr}}</p>
</div>
const vm = new Vue({
el:'#app',
data:{
str:'你们现在看过招聘信息吗'
},
computed:{
// 定义一个拆分字符串的方法 所有的计算操作都放到了vue的实例中
splitStr(){
return this.str.split('')
}
}
})
methods和computed的区别
我们可以使用 methods 来替代 computed,效果上两个都是一样的。
但是 computed 是基于它的依赖缓存,只有相关依赖发生改变时才会重新取值。
而使用 methods ,在重新渲染的时候,函数总会重新调用执行。
5.实例方法/数据
vm.$watch 监听数据的变化
vue.$set 为数组和对象添加指定的值
vue.$delete 为数组和对象删除指定的值
5.1 vm.$watch
vue中watch属性可以去响应数据的变化
方式一:全局监听
监听的内容发生改变则触发函数
vm.$watch(监听的内容,function(){
})
方式二:局部监听 推荐使用
watch:{
//写法一:不能深度监听
items(){
Storage.save('todoList',this.items)
}
//写法二(重点)
// 当items发生改变则自动调用handler函数
items:{
// 必须为handler函数
handler(){
Storage.save('todoList',this.items)
},
deep:true//深度监听,可检测到数组中某个对象属性的变化
}
}
5.2 vue.$set
语法:vue.$set( target, propertyName/index, value )
this.$set(obj,'isFinished',true);
5.3 vm.$delete
vue.$delete( target, propertyName/index )
this.$delete(this.items,i)
6.组件
组件(component)是vue中最强大的功能之一。可以拓展html标签,可以封装可重用性的代码。组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树。
组件的划分:根据结构划分、根据功能划分
6.1组件的使用
1、定义组件,并将组件导出
(在src/components文件夹内新建一个.vue后缀的文件作为组件)
每一个.vue文件由templete、script、style构成
注意:子组件内style可以写上scoped属性,限制当前的css样式只对当前的组件生效
组件的名称一般首字母大写
2、引入组件
语法:import 自定义组件名 from '组件路径'
注意:
1、若导入的文件名为vue后缀则可以省略(vue中可以自动识别.vue文件后缀)
2、导入的路径可以写绝对路径(@默认代表项目中的src目录)
3、在局部components内注册组件(多个组件之间采用逗号连接)
components: {
Head:Head,
Content:Content
}
简写为:
components: {
Head,Content
}
4、使用组件(相当于使用标签)
方式一:
<自定义的组件名 />(建议使用)
方式二
<自定义的组件名><自定义的组件名 />
注意:写成双标签的形式,一般标签内不能写任何html代码
6.2.组件传值(重点)
1 父组件向子组件传值
父组件向子组件使用props传值
步骤:
1)、在父组件App.vue内使用v-bind:绑定一个属性
<First :send="msg"/>
2)、在子组件内定义props接收自定义属性(和父组件定义的属性同名)
export default {
name:"First",
data(){
return{
}
},
//方法一:用数组获取值
// , props:['send']
//方法二:用对象获取值
props:{
send:String,
}
}
2 子组件向父组件传值
子组件向父组件传值需要使用自定义事件
使用 $on(eventName) 监听事件
使用 $emit(eventName) 触发事件
步骤:
1)、在子组件内通过emit触发一个自定义事件
export default {
name:"First",
data(){
return{
info:'123456'
}
},
created(){
this.$emit('asinfo',this.info);// (事件名,参数)
}
}
2)、在父组件内监听自定义的事件
<First @asinfo="info"/>
methods:{
info(data){
console.log(data)
}
}
6.3.组件中插槽slot的使用
在子组件内使用特殊的元素<slot></slot>就可以为子组件开启一个slot(插槽)。在父组件的模板内,插入在子组件标签内的所有内容将代替子组件的<slot>标签及其内部的内容
1 单个插槽的使用
父组件:
<template>
<div id="app">
<First :send="msg" @asinfo="info">
<p>插槽的使用</p>
</First>
</div>
</template>
子组件:
<template>
<div id="First">
<h1>这是一个组件</h1>
<p>{{send}}</p>
<slot></slot>
</div>
</template>
2 命名插槽的使用
父组件:
<template>
<div id="app">
<First :send="msg" @asinfo="info">
<p slot="last">插槽的使用222222</p>
<p slot="first">插槽的使用11111</p>
</First>
</div>
</template>
子组件:
<template>
<div id="First">
<slot name="last"></slot>
<h1>这是一个组件</h1>
<slot name="first"></slot>
<p>{{send}}</p>
</div>
</template>
7.全局配置
全局配置需要写在main.js中的new Vue之前,引入Vue之后
所有的全局配置的属性都在Vue.config
// 自定义按键修饰符
//main.js的写法
Vue.config.keyCodes.f2 = 113;
//在app.vue中使用
<input type="text" @keyup.f2 = "getMsg">
8.全局API
Vue.extend
Vue.nextTick
Vue.set
Vue.delete
Vue.directive 自定义指令
Vue.filter 过滤器
Vue.component 注册组件
Vue.use 注册插件
Vue.mixin
Vue.compile
Vue.observable
Vue.version
8.1过滤器
作用:将数据格式化成所需要的格式:比如时间、价格
1 局部过滤器
filters
只作用于当前组件,
<template>
<div id="app">
单价:<input type="text" v-model="price"><br>
数量:<input type="number" v-model="num"><br>
<h2>总计:{{price*num | formatMoney}}</h2>
<h2>总计:{{price*num | money}}</h2>
</div>
</template>
export default {
name: 'App',
data(){
return{
price:15,
num:1
}
},
filters:{
// 局部过滤器
money(value){
return '$'+value.toFixed(2)+'美元';
}
}
}
注意:'|'叫做管道符
管道符前面的为需要格式化的数据
管道符后为格式化数据的过滤器方法,该方法会自动传入需要格式化的数据
可以出现多个管道符,比如{{a | guan1 | guan2 | guan3}},依次把前面值传给|后面的管道函数
2 全局过滤
Vue.filter('全局过滤函数名',过滤方法)
任意组件都可以使用
main.js中定义:
Vue.filter('formatMoney',function(value){
// value自动获取管道符之前的内容
return '¥'+value.toFixed(2)+'元';
})
//组件中的使用
<template>
<div id="app">
单价:<input type="text" v-model="price"><br>
数量:<input type="number" v-model="num"><br>
<h2>总计:{{price*num | formatMoney}}</h2>
</div>
</template>
8.2 格式化时间插件的使用
安装插件
npm install moment --save-dev
引入插件
import moment from 'moment';
定义过滤器
filters:{
// dateStr不需要手动传入 由管道符自动传入
formatDate(dateStr,pattern='YYYY/MM/DD HH:mm:ss'){
return moment(dateStr).format(pattern)
}
}
在组件内使用过滤器
<template>
<div id="app">
<h2>{{time | formatDate('YYYY年MM月DD日 HH时mm分ss秒')}}</h2>
<h2>{{time | formatDate}}</h2>
</div>
</template>
9.生命周期
生命周期函数也叫做钩子函数,可以自动绑定this到上下文,可以访问数据(data)、方法(methods)、计算(computed)等
注意:不能使用箭头函数定义生命周期的方法
常见的生命周期函数:
分为四大类:
1、创建前后 beforeCreate created
2、挂载前后 beforeMount mounted
3、更新前后 beforeUpdate updated
4、销毁前后 beforeDestroy destroyed
beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
created:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前尚不可用。
beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。
mounted:实例被挂载后调用,这时 el 被新创建的 vm. e l 替 换 了 。 如 果 根 实 例 挂 载 到 了 一 个 文 档 内 的 元 素 上 , 当 m o u n t e d 被 调 用 时 v m . el 替换了。 如果根实例挂载到了一个文档内的元素上,当mounted被调用时vm. el替换了。如果根实例挂载到了一个文档内的元素上,当mounted被调用时vm.el也在文档内。
注意: mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick
beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。
beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
destroyed:实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
10.路由的使用
官方手册:https://router.vuejs.org/zh/
10.1 静态路由
手动配置一个静态路由:
安装路由
命令:vue create 项目名
注意:初始化时 Install vue-router需要选择yes
先创建一个空的没有配置路由的vue项目
vue create router3
安装路由
npm i vue-router --save
在src目录下创建两个空的文件夹
router:里面放路由的配置文件index.js
views:里面放需要显示的视图界面
在index.js中引入路由文件并配置
import Vue from 'vue';
//1.下载并导入路由文件
import Router from 'vue-router';
//2.使用路由插件
Vue.use(Router);
//4.配置路由
const routes=[
{
},
]
// 3.实例化并导出路由
export default new Router({
routes//这里写法为ES6的简写,原写法为‘routes:routes’
})
在views文件夹下写上两个视图文件
<template>
<div class="home">
<p>这是首页</p>
</div>
</template>
<template>
<div class="about">
<p>这是关于页</p>
</div>
</template>
再回到index.js配置路径和component的映射关系
import Vue from 'vue';
//1.下载并导入路由文件
import Router from 'vue-router';
//2.使用路由插件
Vue.use(Router);
//导入首页要显示的内容
import Home from '@/views/Home.vue'
//4.配置路由
const routes=[
{
path:'/',
name:'Home',
component:Home
},
{
path:'/about',
name:'About',
component:()=> import ('@/views/About.vue')
},
]
// 3.实例化并导出路由
export default new Router({
routes//这里写法为ES6的简写,原写法为‘routes:routes’
})
在main.js里面导入和挂载路由
import Vue from 'vue'
import App from './App.vue'
//引入路由的配置文件index.js,这里是简写的,vue会自动帮我们寻找index.js文件
import router from './router'
Vue.config.productionTip = false
new Vue({
//挂载路由
router,
render: h => h(App),
}).$mount('#app')
最后在App.vue中使用配置好的路由文件
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<!--路由视图 -->
<router-view/>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>
运行结果:
对上述功能的总结:
除了上述的手动创建之外,项目开发的时候应该在创建vue项目的时候配置自动安装好默认的路由文件。
<router-link to=""></router-link>路由的跳转(相当于超链接)
属性to代表跳转的地址(写上路由配置表内配置的路径)
<router-view></router-view>路由出口
将匹配的路由路径对应的组件替换掉router-view的内容
在router内index.js配置如下:
import Login from '@/components/login'
import Register from '@/components/register'
路由文件的配置
routes: [
{
path: '/',//定义路由的路径 /为默认路径
component: HelloWorld//定义当前路径匹配到的组件
},
{
path:'/login',
component:Login
},
{
path:'/register',
component:Register
}
]
10.2 动态路由
1 小案例起步
先在首页编写好界面
<template>
<div class="home">
<p>这是首页</p>
<ul>
<li v-for="item in goods" :key="item.id">
<p><img :src="item.path" ></p>
<p>{{item.name}}</p>
<p>{{item.price}}</p>
</li>
</ul>
</div>
</template>
<script>
export default{
name:'Home',
data(){
return {
goods:[
{
path:require('@/assets/images/1.jpg'),
name:'鹌鹑蛋',
price:16
},
{
path:require('@/assets/images/2.jpg'),
name:'哇哦零食',
price:16
},
{
path:require('@/assets/images/3.jpg'),
name:'奶糖',
price:11.8
},
{
path:require('@/assets/images/4.jpg'),
name:'原切牛肉',
price:53
},
{
path:require('@/assets/images/4.jpg'),
name:'原切牛肉',
price:53
},
]
}
}
}
</script>
<style lang="less">
ul{width: 1200px;margin: 0 auto; display: flex;flex-wrap: wrap;}
li{width: 300px;height: 350px; border: 1px solid red; box-sizing: border-box;}
</style>
效果:
编写简单的路由跳转
router目录下的index.js
Home.vue
效果:
把当前点击的商品参数传递到详细页
方法一:使用path和query传递
详细页接收参数:
效果:
方式二:通过name和param传递参数(推荐)
在路由配置里面配置动态参数,name也是路由配置里面标记的名字
接收参数
效果:
当前点击的商品的信息传递到详细页之后,应该在data数据里面定义一个变量接收,然后再在界面上显示出来,所以代码做了如下的更改:
<template>
<div class="detail">
<p>这是详细页</p>
<!-- <p>{{this.$route.query}}</p> -->
<p><img :src="good.path" ></p>
<p>{{good.name}}</p>
<p>{{good.price}}</p>
</div>
</template>
<script>
export default{
name:'Detail',
data(){
return{
good:this.$route.params.item
}
}
}
</script>
所以最终效果为:
2 设置全屏显示
在原有views视图页的基础上增加一个视图来显示全屏
<template>
<div class="whole">
<p>这是全屏显示的界面</p>
</div>
</template>
<script>
export default{
}
</script>
<style lang="less">
</style>
把App.vue里面只留一个视图出口:
<template>
<div id="app">
<!--路由视图 -->
<router-view/>
</div>
</template>
把控制路由跳转的代码复制到Home.vue中
<template>
<div class="home">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/whole">全屏</router-link>
</div>
<p>这是首页</p>
<ul>
<li v-for="item in goods" :key="item.id">
<!-- <router-link :to="{path:'/detail',query:{item}}"> -->
<router-link :to="{name:'Detail',params:{item}}">
<p><img :src="item.path" ></p>
<p>{{item.name}}</p>
<p>{{item.price}}</p>
</router-link>
</li>
</ul>
</div>
</template>
这时候看效果已经可以实现全屏显示了
但是这是我们可以发现路由跳转这段代码比较多,而且在开发小程序时这段代码会在多个view视图中使用到,所以我们可以把这段代码写成一个组件。
然后在首页和关于页导入组件即可
<template>
<div class="about">
<TabBar></TabBar>
<p>这是关于页</p>
</div>
</template>
<script type="text/javascript">
import TabBar from '@/components/TabBar.vue'
export default{
name:'About',
components:{
TabBar
}
}
</script>
3 捕获所有404路由
如果需要设置一个默认的404界面,只需要在路由配置的最后一个位置加上如下代码即可:
{
// 匹配所有的路径 一般作为404页面 放在所有的路径之后
path:'*',
name:'Error',
component: () => import('../views/Error.vue')//需要手写一个404界面
}
4 嵌套路由
接下来给全屏显示的界面写一个可点击切换的路由选项
首先写好界面:
现在配置子路由:
{
path:'/whole',
name:'Whole',
component:()=> import ('../views/Whole.vue'),
children:[
{
path:'/beijing',
name:'Beijing',
component:()=>import ('../views/Beijing.vue')
},
{
path:'/shanghai',
name:'Shanghai',
component:()=>import ('../views/Shanghai.vue')
},
]
}
效果:
子路由的格式跟父路由类似,但是写成上述方式在界面上就会出现上面的效果途中显示的问题:直接进入主界面时没有加载默认需要显示的内容。
现在只需要修改要默认显示加载的界面的path的值为空。
但是建议最好写成如下方式:
效果:
以上操作出现了bug:点击’北京’选项没反应
还需用v-bind来绑定需要跳转的界面,在对象里面写上响应的name属性,不然点击’北京’选项不会有反应。
<template>
<div class="whole">
<p>这是全屏显示的界面</p>
<router-link :to="{name:'Beijing'}">北京</router-link>|
<!-- <router-link to="/shanghai">上海</router-link> -->
<router-link :to="{name:'Shanghai'}">上海</router-link>
<router-view></router-view>
</div>
</template>
最终效果:
写在最后:
子路由是可以层层嵌套的,比如:
{
path:'',
name:'',
component:'',
chlidren:[
path:'',
name:'',
component:'',
chlidren:[
path:'',
name:'',
component:'',
]
]
}
5 编程式导航
回顾所有可以实现跳转的方法:
HTML:
<a href="#"></a>
JavaScript:
//location对象
window.location.href='url'//属性 有历史记录
window.location.assign('url')//方法 有历史记录
window.location.replace('url')//方法 无历史记录
//history对象
history.forword()//前进 不带参数
history.back()//后退 不带参数
history.go()//参数 1,2,3.. 前进1,2,3...条记录
//参数 -1,-2,-3... 后退1,2,3...条记录
Vue:(相当于HTML中用a标签来实现跳转)
<router-link to="/login"></router-link>
<router-link :to="{path:'',query:{}}"></router-link>
//接收数据
this.$route.query.item
<router-link :to="{name:'',params:{}}"></router-link>
//接收数据
this.$route.params.item
Vue编程式导航:(相当于JavaScript来实现跳转)
router.push()
// 字符串
this.$router.push('home')
// 对象
this.$router.push({ path: 'home' })
// 命名的路由
this.$router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
this.$router.push({ path: 'register', query: { plan: 'private' }})
router.replace()
router.replace()和router.push()的使用方式相同,但是router.replace不会向history内添加历史记录
router.go()
router.go()方法相当于window.history.go方法,可以访问到指定的某条历史记录信息
router.go(-1)回到上一条历史记录
登录注册跳转问题
6 命名路由
通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router 实例的时候,在routes配置中给某个路由设置名称
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'User',
component: User
}
]
})
router-link使用命名路由跳转
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
router.push()使用命名路由跳转
router.push({ name: 'user', params: { userId: 123 }})
7 命名视图
官方对命名视图的解释:
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view
没有设置名字,那么默认为 default
。
测试案例:
先写一个新的视图
<template>
<div class="side">
<p>side</p>
</div>
</template>
<script>
</script>
<style lang="less">
.side{
position: relative;
p{
position: absolute;
right: 0;
top: 100px;
width: 50px;
height: 200px;
background: pink;
}
}
</style>
配置命名视图:
使用命名视图:
效果:
8 重定向和别名
重定向
重定向也是通过 routes
配置来完成,下面例子是从 /a
重定向到 /b
:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
重定向的目标也可以是一个命名的路由:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
甚至是一个方法,动态返回重定向目标:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})
别名
/a
的别名是 /b
,意味着,当用户访问 /b
时,URL 会保持为 /b
,但是路由匹配则为 /a
,就像用户访问 /a
一样。
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
9 路由组件传参
在组件中使用 $route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。
使用 props
将组件和路由解耦:
布尔模式
商品展示界面传递id的动态参数
<template>
<div class="home">
<TabBar></TabBar>
<p>这是首页</p>
<ul>
<li v-for="(item ,index) in goods" :key="item.id">
<!-- <router-link :to="{path:'/detail',query:{item}}"> -->
<router-link :to="{name:'Detail',params:{id:index}}">
<p><img :src="item.path" ></p>
<p>{{item.name}}</p>
<p>{{item.price}}</p>
</router-link>
</li>
</ul>
</div>
</template>
路由配置动态参数和开启props解耦
{
path:'/detail/:id',
name:'Detail',
component:()=> import ('../views/Detail.vue'),
props:true
}
<template>
<div class="detail">
<p>这是详细页</p>
<p>{{id}}</p>
</div>
</template>
<script>
export default{
name:'Detail',
props:['id'],//注意props要和data(){}是同一级的
data(){
return{
}
}
}
</script>
效果:现在点击了每个商品之后,都能够将相应的商品id传递到详细页面。
注意:对于包含命名视图的路由,你必须分别为每个命名视图添加 props
选项
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
对象模式
{
path:'/detail/:id',
name:'Detail',
component:()=> import ('../views/Detail.vue'),
// props:route=>({params:route.params.id})
props:{id:true}
}
<template>
<div class="detail">
<p>这是详细页</p>
<p>{{id}}</p>
</div>
</template>
<script>
export default{
name:'Detail',
props:['id'],
data(){
return{
}
}
}
</script>
通过这个结果可以看出,对象模式只能传布尔值
如果 props
是一个对象,它会被按原样设置为组件属性。当 props
是静态的时候有用。
const router = new VueRouter({
routes: [
{
path: '/promotion/from-newsletter',
component: Promotion,
props: { newsletterPopup: false }
}
]
})
函数模式
{
path:'/detail/:id',
name:'Detail',
component:()=> import ('../views/Detail.vue'),
props:route=>({params:route.params.id})//如果使用的是query传参则改为query
}
<template>
<div class="detail">
<p>这是详细页</p>
<p>{{params}}</p>
</div>
</template>
<script>
export default{
name:'Detail',
props:['params'],
data(){
return{
}
}
}
</script>
效果:
你可以创建一个函数返回 props
。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。
const router = new VueRouter({
routes: [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
})
URL /search?q=vue
会将 {query: 'vue'}
作为属性传递给 SearchUser
组件。
请尽可能保持 props
函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义 props
,请使用包装组件,这样 Vue 才可以对状态变化做出反应。
10.历史记录模式
vue-router默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载
如果不想要很丑的 hash,我们可以用路由的 history 模式
const router = new VueRouter({
mode: 'history',//历史记录模式
routes: [...]
})
11.导航守卫
全局前置守卫
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
to: Route
: 即将要进入的目标 路由对象from: Route
: 当前导航正要离开的路由next: Function
: 一定要调用该方法来 resolve 这个钩子。执行效果依赖next
方法的调用参数。
全局后置钩子
router.afterEach((to, from) => {
// ...
})
路由独享的守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内的守卫
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
导航守卫例子:
https://blog.csdn.net/qq_26769677/article/details/101003337
12.更多进阶功能
路由元信息、过渡效果、数据获取、滚动行为等见官网:
https://router.vuejs.org/zh/guide/advanced/navigation-guards.html
11.Vuex
1 入门
vuex是为vue.js专门提供的状态管理模式
简单解释:将所有组件公用的数据、方法提取到指定的地方,进行统一管理
2 安装
下载vuex
npm i vuex --save
在src根目录中新建一个store文件夹并新建一个index.js文件,并在index.js中引入vue和vuex
import Vue from 'vue'
//导入vuex插件
import Vuex from 'vuex'
//使用vuex插件
Vue.use(Vuex)
export default new Vuex.Store({
state:{//state相当于普通组件中的data数据域
},
getters:{//getter相当于computed对象
},
mutations:{//mutations相当于methods对象
},
actions:{
},
modules:{//分割模块
}
})
在main.js中导入index.js文件并挂载
3 核心概念的使用
3.1 state
state为vuex中的公共状态,我们可以看成state为所有组件的data,用于保存所有组件的公共数据·。
export default new Vuex.Store({
state:{//state相当于普通组件中的data数据域
names:['胡桃','甘雨']
}
})
在任意组件内可以使用this.$store.state.names
来获取到state里面的数据
<p>{{this.$store.state.names}}</p>
3.2 getters
vuex的getters属性可以理解为所有组件的computed属性,也就是计算属性.getters的返回值会根据其依赖缓存起来的,只有当依赖的值发生改变,getters才会重新计算
export default new Vuex.Store({
state:{//state相当于普通组件中的data数据域
names:['胡桃','甘雨'],
num:5
},
getters:{//getter相当于computed对象
add(state){//state的数据会自动传入add的方法
return state.num+5
}
}
})
在任意的组件内使用this.$store.getters.add
调用计算的方法
<p>{{this.$store.state.names}}</p>
<p>{{this.$store.getters.add}}</p>
3.3 mutations
vuex中的mutations可以理解为所有组件的methods方法。mutations对象中保存了更改数据的回调函数。第一个参数为state,第二个参数为payload,相当于自定义参数
export default new Vuex.Store({
state:{//state相当于普通组件中的data数据域
names:['胡桃','甘雨'],
num:5
},
getters:{//getter相当于computed对象
add(state){
return state.num+5
}
},
mutations:{//mutations相当于methods对象
add(state,payload){//注意:必须传入state参数,payload为自定义参数
state.num+=payload;
}
}
})
在任意组件内通过this.$store.commit()
触发mutations内的方法
<template>
<div id='Home'>
<p>{{this.$store.state.names}}</p>
<p>{{this.$store.getters.add}}</p>
<p><input type="text" v-model="this.$store.state.num"/> <span @click="add()">+</span></p>
</div>
</template>
<script>
export default{
name:'Home',
methods:{
add(){
// this.$store.commit('evnetName',自定义的参数)
this.$store.commit('add',5)
}
}
}
</script>
每一次点击都会给state里面的num数据加5
3.4 actions
actions和mutations类似,不同点在于:
1、actions提交的是mutations,而不是直接变更状态
2、actions中包含异步,mutations中绝对不允许出现异步
3、actions中回调函数的第一个参数为context,得到一个与store具有相同属性和方法的对象
export default new Vuex.Store({
state:{//state相当于普通组件中的data数据域
names:['胡桃','甘雨'],
num:5
},
getters:{//getter相当于computed对象
add(state){
return state.num+5
}
},
mutations:{//mutations相当于methods对象
add(state,payload){//注意:必须传入state参数,payload为自定义参数
state.num+=payload;
}
},
actions:{
addSync(context,payload){
setTimeout(()=>{
//add为mutations内定义的函数
//通过commit调用mutations内的函数
context.commit('add',payload)
},1000)
}
},
})
组件内绑定事件
<p><input type="text" v-model="this.$store.state.num"/> <span @click="addSync()">+</span></p>
通过 this.$store.dispatch
调用actions内的异步方法
methods:{
addSync(){
this.$store.dispatch('addSync',2)
}
}
测试的效果就是每次点击之后,要过1s才会改变state里面的数值num
3.5 modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
在组件内分模块进行访问
<h2>{{this.$store.state.a.count}}</h2>
<h2>{{this.$store.state.b.msg}}</h2>
4 辅助函数的使用
mapState、mapGetters、mapMutations、mapActions
引入
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
在computed中使用扩展运算符进行定义
export default {
name: 'list',
data () {
return {
}
},
computed:{
...mapState({
//Index则代表state的num属性
index:'num'
}),
...mapGetters({
// 属性值add则代表getters内的add方法
add:'add'
})
},
methods:{
...mapMutation({
addNum:'addNum'
}),
...mapActions({
})
}
}
5 vuex好文档推荐
参考资料:https://vuex.vuejs.org/zh/
https://www.jianshu.com/p/a804606ad8e9
组件间通讯八种方式:https://blog.csdn.net/zhoulu001/article/details/79548350