文章目录
Vue 学习
目标
- 基本用法
- 模板语法
- 常用特性
- 实现案例效果
概述
官网:https://cn.vuejs.org/
Vue:渐进式 JavaScript 框架
易用、灵活、高效
MVVM
基本使用
插值表达式({{}})
模板语法
前端渲染:把数据填充到 HTML 标签中
渲染方式:原生 js 拼接字符串(将数据以字符串的方式拼接到 HTML 标签中)、使用前端模板引擎(基于模板引擎 art-template,拥有自己的一套模板语法规则)、使用 vue 特有的模板语法
指令
指令的本质就是自定义属性
指令的格式:以 v- 开始(比如:v-cloak)(style 里加)
插值表达式存在的问题:”闪动“
原理:先隐藏,替换好值之后再显示最终的值
必须通过变量添加
v-text 填充纯文本
v-html 填充 HTML 片段(本网站内部数据可以使用,来自第三方的数据不可以用)
v-pre 填充原始信息(显示原始信息,跳过编译过程)
数据响应式
响应式: HTML5 中的响应式(屏幕尺寸的变化导致样式的变化),数据的响应式(数据的变化导致页面内容的变化)
数据绑定:将数据填充到标签中
v-once:只编译一次,显示内容之后不再具有响应式功能,如果显示的信息后续不需要再修改,可以用
双向数据绑定:交互 v-model
事件绑定
v-on
<div>
<div>{{num}}</div>
<button v-on:click='num++'>点击</button>
<button @click='num++'>++</button><!-- 简写 -->
</div>
在 js 中设置 num 值
<div>
<div>{{num}}</div>
<button v-on:click='num++'>加</button>
<button @click='num++'>++</button><!-- 简写 -->
<button @click='handle'>+</button>
<button @click='handle()'>自加</button>
</div>
js
let vm = new Vue({
data: {
num: 0
},// 数据
methods: {
handle: function () {
// this 是 vue 的实例对象
this.num++
}
}
})
如果事件直接绑定函数名称,那么会传递事件对象作为事件函数的第一个参数
如果事件绑定函数调用,那么事件对象必须作为最后一个参数显示传递,并且事件对象的名称必须是 $event
.stop
阻止冒泡
.prevent
阻止默认行为(如 a 标签的跳转)
顺序很重要
.enter
回车键
.delete
删除键
自定义按键修饰符:自定义名字,对应的值必须是按键对应的 event.keyCode
值
v-bind
<a v-bind:hrf='url'>百度</a>
<a :href='url'>百度一下</a><!-- 简写 -->
在 js 中定义 url 的值
Class 与 Style 绑定
样式绑定
<div v-bind:class='{active:isActive, error:isError}'></div>
<div v-bind:class='[activeClass,errorClass]'></div>
对象绑定和数组绑定可以结合使用、class 绑定的值可以简化操作、默认的 class 会保留
<div v-bind:style='{border:borderStyle.width:widthStyle,height:heightStyle}'></div>
<div v-bind:style='[overrideStyle,objStyles]'></div>
条件渲染
分支结构
v-if
、v-else
、v-else-if
、v-show
v-if 控制元素是否渲染到页面
v-show 控制元素是否显示(已经渲染到了页面)
循环结构
v-for
<ul>
<li :key='index' v-for='(item,index) in list'>{{item}}</li>
</ul>
在 js 中定义 list 数组
常用特性
表单操作
input 单行文本
textarea 多行文本
select 下拉多选
radio 单选框
checkox 多选框
修饰符
.lazy
,.number
,.trim
自定义指令
定义是不用加 v-,使用时需要加
钩子函数
钩子函数参数
侦听器
应用场景:数据变化时执行异步或开销较大的操作
过滤器
作用:格式化数据,比如将字符串格式化为首字母大写,将日期格式化为指定的格式等
格式化日期
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>过滤器</title>
</head>
<body>
<div id="app">
<div>{{date|format('yyyy-MM-dd hh:mm:ss')}}</div>
</div>
<script src="vue.js"></script>
<script>
// 格式化日期
Vue.filter('format', function (value, arg) {
function dateFormat(date, format) {
if (typeof date === 'string') {
let mts = date.match(/(\/Date\((\d+)\)\/)/)
if (mts && mts.length >= 3) {
date = parseInt(mts[2])
}
}
date = new Date(date)
if (!date || date.toUTCString() == 'Invalid Date') {
return ''
}
let map = {
'M': date.getMonth() + 1,// 月份
'd': date.getDate(),// 日
'h': date.getHours(),// 小时
'm': date.getMinutes(),// 分
's': date.getSeconds(),// 秒
'q': Math.floor((date.getMonth() + 3) / 3),// 季度
'S': date.getMilliseconds()// 毫秒
}
format = format.replace(/([yMdhmsqS])+/g, function (all, t) {
let v = map[t]
if (v !== undefined) {
if (all.length > 1) {
v = '0' + v
v = v.substr(v.length - 2)
}
return v
} else if (t === 'y') {
return (date.getFullYear() + '').substr(4 - all.length)
}
return all
})
return format
}
return dateFormat(value, arg)
})
let vm = new Vue({
el: '#app',
data: {
date: new Date()
}
})
</script>
</body>
</html>
生命周期
挂载(初始化相关属性)
- beforeCreate(说明在 beforeCreate 事件触发时,Vue 刚刚实例化,而此时数据 data 和事件方法 methods还未绑定到 app 对象上)
- created(说明在create事件触发时,数据data和方法methods绑定到应用对象app上)
- beforeMount(在渲染页面之前,数据是没有挂载的,根据数据生成的DOM对象是获取不到的)
- mounted(在渲染页面之后,数据是已经挂载的,可以获取数据生成的DOM对象)
更新(元素或组件的变更操作)
- beforeUpdata(在修改数据之前能获取到数据)
- updated(修改数据之后也能获取到数据)
销毁(摧毁相关属性)
- beforeDestroy
- destroyed
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<div>{{msg}}</div>
<button @click='update'>更新</button>
<button @click='destroy'>销毁</button>
</div>
<script src="vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: '生命周期'
},
methods: {
update: function () {
this.msg = 'hh'
},
destroy: function () {
this.$destroy()
}
},
beforeCreate: function () {
console.log('beforeCreate')
},
created: function () {
console.log('created')
},
beforeMount: function () {
console.log('beforeMount')
},
mounted: function () {
console.log('mounted')
},
beforeUpdate: function () {
console.log('beforeUpdate')
},
updated: function () {
console.log('updates')
},
beforeDestroy: function () {
console.log('beforeDestroy')
},
destroyed: function () {
console.log('destroyed')
},
})
</script>
</body>
</html>
数组相关
变异方法(修改原有数据):push()
,pop()
,shift()
,unshift()
,splice()
,sort()
,reverse()
替换数组(生成新的数组):filter()
,concat()
,slice()
// 响应式
Vue.set(vm.list, 0, 'a')// 修改数组中元素 数组名,索引,修改的值
vm.$set(vm.list, 1, 'b')// 修改数组中元素
组件化开发
组件化开发思想
标准、分治、重用、组合
Web Components 通过创建封装好功能的定制元素规范组件化
组件注册
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 使用组件 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script src="vue.js"></script>
<script>
// 组件注册
Vue.component('button-counter', {
data: function () {
return { count: 0 }
},
template: '<button @click="handle">点击了{{count}}次</button>',
methods: {
handle: function () {
this.count += 1
}
}
})
let vm = new Vue({
el: '#app',
data: {
},
})
</script>
</body>
</html>
- data 必须是一个函数
- 组件模板内容必须是单个根元素
- 组件模板内容可以是模板字符串(``)
- 如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中用驼峰的方式使用组件,但是在普通的标签模板中,必须使用短横线的方式使用组件,如命名为
MyComponent
,使用时my-component
Vue 调试工具
https://github.com/vuejs/vue-devtools
组件间数据交互
父组件向子组件传值
- 组件内部通过 props 接收传递过来的值
- 父组件通过属性将值传递给子组件
props 属性命名规则:在 props 中使用驼峰形式,模板中需要使用短横线的形式;字符串形式的模板中没有这个限制
子组件向父组件传值
- 子组件通过自定义事件向父组件传递信息(
$emit()
) - 父组件监听子组件的事件
非父子组件间传值
- 单独的事件中心管理组件间的通道
- 监听事件与销毁事件
- 触发事件
let eventHub=new Vue()
eventHub.$on('add-tode', addTode)
eventHub.$off('add-todo')
eventHub.$emit('add-todo',id)
组件插槽
父组件向子组件传递内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<my-alert>bug</my-alert>
<my-alert>undefined</my-alert>
<my-alert></my-alert>
</div>
<script src="vue.js"></script>
<script>
// 组件插槽
Vue.component('my-alert', {
template: `
<div>
<strong>ERROR:</strong>
<slot>默认的</slot>
</div>
`
})
let vm = new Vue({
el: '#app',
data: {
},
})
</script>
</body>
</html>
具名插槽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<base-layout>
<p slot='header'>title</p>
<p>1</p>
<p>2</p>
<p>3</p>
<p slot='footer'>bottom</p>
</base-layout>
</div>
<script src="vue.js"></script>
<script>
// 具名插槽
Vue.component('base-layout', {
template: `
<div>
<header>
<slot name='header'></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name='footer'></slot>
</footer>
</div>
`
})
let vm = new Vue({
el: '#app',
data: {
pstr: 'hh',
},
})
</script>
</body>
</html>
作用域插槽
应用场景:父组件对子组件的内容进行加工处理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<fruit-list :list='list'>
<template slot-scope='slotProps'>
<strong v-if='slotProps.info.id==2' class="current">{{slotProps.info.name}}</strong>
<span v-else>{{slotProps.info.name}}</span>
</template>
</fruit-list>
</div>
<script src="vue.js"></script>
<script>
// 作用域插槽
Vue.component('fruit-list', {
props: ['list'],
template: `
<div>
<li :key='item.id' v-for='item in list'>
<slot :info='item'>{{item.name }}</slot>
</li>
</div>
`
})
let vm = new Vue({
el: '#app',
data: {
pstr: 'hh',
list: [{ id: 1, name: 'apple' }, { id: 2, name: 'orange' }, { id: 3, name: 'banana' }]
},
})
</script>
</body>
</html>
前后端交互
接口调用方式:原生 ajax,基于 jQuery 的 ajax,fetch,axios
URL 地址格式:传统形式的 URL,Restful 形式的 URL
Promise
Promise 是异步编程的一种解决方案,从语法上讲,Promise 是一个对象,从它可以获取异步操作的消息
then 参数中的函数返回值:
返回 Promise 实例对象;(返回的该实例对象会调用下一个 then)
返回普通值(返回的普通值会直接传递给下一个 then,通过 then 参数中函数的参数接收该值)
实例方法
p.then()
:得到异步任务的正确结果
p.catch()
:获取异常信息
p.finally()
:成功与否都会执行(尚且不是正式标准)
对象方法
Promise.all()
:并发处理多个异步任务,所有任务都执行完成才能得到结果
Promise.race()
:并发处理多个异步任务,只要有一个任务完成就能得到结果
fetch
更加简单的数据获取方式,功能更强大、更灵活,可以看做是 xhr 的升级版
基于 Promise 实现
常用配置选项
methods(String)HTTP 请求方法,默认为 GET(GET/POST/PUT/DELDET)
body(String)HTTP 的请求参数
headers(Object)HTTP的请求头,默认为{}
响应数据格式
text() 将返回体处理成字符串类型
json() 返回结果和 JSON.parse(responseText) 一样
axios
axios 是一个基于 Promise 用于浏览器和 node.js 的 HTTP 端
- 支持浏览器和 node.js
- 支持 Promise
- 能拦截请求和响应
- 自动转换 JSON 数据
常用 API :get(查询数据),post(添加数据),put(修改数据),delete(删除数据)
响应结果的主要属性
- data 实际响应回来的数据
- headers 响应头数据
- status 响应状态码
- statusText 响应状态信息
axios 拦截器
请求拦截器:在请求发出之前设置一些信息
响应拦截器:在获取数据之前对数据做一些加工处理
async/await
async/await 是 ES7 引入的新语法,可以更加方便的进行异步操作
async 关键字用于函数上(async 函数的返回值是 Promise 实例对象)
await 关键字用于 async 函数当中(await 可以得到异步的结果)
可以处理多个异步请求
前端路由
路由
路由的本质就是对应关系
在开发中,路由分为后端路由和前端路由
后端路由
根据不同的用户 URL 请求,返回不同的内容
本质:URL 请求地址 与服务器资源之间的对应关系
后端路由根据不同的 URL 地址分发不同的资源
SPA
- 后端渲染(存在性能问题)
- Ajax 前端渲染(前端渲染提高性能,但是不支持浏览器的前进后退操作)
- SPA 单页面应用程序:整个网站只有一个页面,内容的变化通过 Ajax 局部更新实现、同时支持浏览器地址栏的前进和后退操作
- SPA 实现原理之一:基于 URL 地址的 hash(hash 的变化会导致浏览器记录访问历史的变化、但是 hash 的变化不会触发新的 URL 请求)
- 在实现 SPA 过程中,最核心的技术点就是前端路由
前端路由
根据不同的用户事件,显示不同的页面内容
本质:用户事件与事件处理函数之间的对应关系
前端路由负责事件监听,触发事件后,通过事件函数渲染不同内容
Vue Router
它和 Vue.js 的核心深度集成,可以非常方便的用于 SPA 应用程序的开发
功能:支持 HTML5 历史模式或 hash 模式;支持嵌套路由;支持路由参数;支持编程式路由;支持命名路由
路由重定向:用户在访问地址 A 的时候,强制用户跳转到地址 C,从而展示特定的组件页面
通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向
$route
与对应路由形成高度耦合,不够灵活,所以可以使用 props 将组件和路由解构
props 的值为布尔类型
props 的值为对象类型
props 的值为函数类型
命名路由
为了更加方便的表示路由的路径,可以给路由规则起一个别名,即为“命名路由”
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<router-link to='/user/1'>User1</router-link>
<router-link to='/user/2'>User2</router-link>
<router-link :to='{name:"user",params:{id:3}}'>User3</router-link>
<router-link to='/register'>Register</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>
<script src="vue.js"></script>
<script src="vue-router.js"></script>
<script>
// 路由组件
const User = {
props: ['id', 'uname', 'age'],
template: '<h1>User 组件 --- id:{{$route.params.id}} --- {{id}} --- {{uname}} --- {{age}}</h1>'
}
const Register = {
template: `
<div>
<h1>Register 组件</h1>
<hr>
<router-link to='/register/tab1'>tab1</router-link>
<router-link to='/register/tab2'>tab2</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>`
}
const Tab1 = { template: '<h3>Tab1 子组件</h3>' }
const Tab2 = { template: '<h3>Tab2 子组件</h3>' }
// 路由实例对象
const router = new VueRouter({
// 路由规则
routes: [
// { path: '/', redirect: '/user/:id' },// 路由重定向
{
name: 'user',// 命名路由
path: '/user/:id',// 动态路由匹配
component: User,
// 路由组件传递参数--布尔类型,对象类型,函数类型
props: /*true*/ /*{ uname: 'hh', age: 20 }*/ route => ({ uname: 'gg', age: 2, id: route.params.id })
},
{
// 嵌套路由
path: '/register', component: Register, children: [
{ path: '/register/tab1', component: Tab1 },
{ path: '/register/tab2', component: Tab2 },
]
},
]
})
// vue 实例对象
const vm = new Vue({
el: '#app',
data: {
},
// 挂载路由实例对象
router
})
</script>
</body>
</html>
编程式导航
声明式导航:通过点击链接实现导航的方式
编程式导航:通过调用 JavaScript 形式的 API 实现导航的方式
this.$router.push('hash地址')
,this.$router.go(n)