vue.js
目录
简介
- Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的渐进式框架。
- Vue 只关注视图层, 采用自底向上增量开发的设计。
- Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。
- Vue适用于做单页面应用。
官方文档:https://cn.vuejs.org/
菜鸟教程:https://www.runoob.com/vue2/vue-tutorial.html
使用:官网下载vue.js,然后再html中通过<script></script>
标签引入即可。
基本用法
创建vue对象
说明:js中使用箭头函数可以使this指向外部对象,所以在创建vue对象时,若需要在回调函数中使用vue对象的属性,可以使用箭头函数代替回调函数。
vue对象的生命周期:
new Vue()
beforeCreate
created
beforeMount
mounted
beforeUpdated
updated
beforeDestroy
destroyed
创建vue对象示例代码:
var vm = new Vue({
el: '#root', // 要控制的元素
data: { // el要用到的数据
msg: '<h1>vue.js</h1>',
date: new Date(),
arr: ['刘备', '关羽', '张飞']
},
methods: {
// el要用到的事件处理函数,函数中对数据的重新赋值会直接渲染
show: function(e){
this.msg = '<h2>vue.js</h2>'
alert(this.msg)
}
},
filters:{ // 定义私有过滤器, 当与全局过滤器同名时,这里优先级更高
myfilter2: function(data, params){
return params + data
}
},
directives:{ // 自定义私有指令
'focus': {
inserted: function(el, binding, vnode, oldVnode){
el.focus()
}
}
},
// 使用生命周期函数
created: function () {
// 异步请求
axios.get('./data/1.json').then(res => {
this.arr = res.data
})
}
})
模板替换
模板替换是将vue对象中data属性中的内容替换到html中,并且在任何时候对这些属性的修改都会使得html重新渲染。
{{ msg}}
:灵活替换标签内的文本内容,但是网速较慢时会展示出vue表达式内容;v-text="msg"
:文本替换,完全替换标签内的文本内容;v-cloak
:解决{{ msg}}
的问题;v-html="msg"
:html替换;v-bind:属性名="msg"
或者:属性名="msg"
:给html标签的属性绑定值;v-model:属性名=msg
:表单数据双向绑定,即在浏览器修改数据会直接体现到js对象中;
computed
:该属性可以定义一些计算属性,本质是一个方法,但可以把这个方法名称当作一个普通的data
属性使用。定义方式如下:
var vm = new Vue({
el: '#root',
data: {
msg: 'vue.js'
},
computed: {
htmlMsg: function(){
return '<h2>' + this.msg + '</h2>'
}
},
}
使用示例:<div v-html="htmlMsg"></div>
事件绑定
v-on:事件名="show"
或者@事件名
:绑定事件;
事件修饰符:
.stop
:阻止冒泡,即阻止子元素的事件触发父元素的事件;.prevent
:阻止默认行为;.capture
:使用捕获机制,即先触发父元素事件,再触发子元素事件;.self
:只捕获自身直接触发的事件;.once
:只触发一次;
事件修饰符使用格式示例:v-on:click.stop
或@click.stop
按键修饰符:
.enter
.tab
.delete
.esc
.space
.up
.down
.left
.right
按键修饰符用于监听指定按键事件,如:@keyup.enter
可以监听enter键的按键事件。另外除了上面的按键修饰符,也可以使用Vue.config.keyCodes.xxx=键盘码
自定义全局键盘修饰符,也可以直接通过@keyup.键盘码
方式使用。
watch
循环
数组循环:<li v-for="val in arr">{{val}}</li>
或<li v-for="(val, i) in arr">{{val}}</li>
;
对象循环:<li v-for="(val, key, i) in obj" :key="key">{{key}} --- {{val}}</li>
次数循环:<li v-for="i in 10">{{i}}</li>
(注意:i从1开始)
注意:同react一样,循环出现的标签最好指定一个唯一的key
判断
v-if
:会根据表达式的值创建或删除元素,一般性能较低;v-show
:会根据表达式的值切换display的值,一般性能较高;v-else
:当表达式为false时显示
过滤器
vue.js可以自定义过滤器,用作一些常见的文本格式化,且过滤器只在双括号、v-bind表达式两个地方使用,它还应该添加在表达式的末尾,由管道符表示。
定义全局过滤器:
// 过滤器需要定义在new Vue({})创建vue对象方法之前
// data是管道符的前一个数据的值,可以是双括号、v-bind表达式的对象,也可以是上一个过滤器的返回值
// params是使用过滤器时传入的值
Vue.filter('myfilter1', function(data){
var y = data.getFullYear()
var m = (data.getMonth() + 1).toString().padStart(2, '0')
var d = data.getDate().toString().padStart(2, '0')
var H = data.getHours().toString().padStart(2, '0')
var M = data.getMinutes().toString().padStart(2, '0')
var s = data.getSeconds().toString().padStart(2, '0')
return `${y}-${m}-${d} ${H}:${M}:${s}`
})
定义私有过滤器:见创建vue对象。
使用过滤器(多个过滤器会按顺序执行):
{{date | myfilter1 | myfilter2('当前时间:')}}
自定义指令
自定义全局指令:
// 需要定义在创建vue对象方法之前
// focus: 只定义的指令名称
// {}: 包含指令相关函数的对象
Vue.directive('focus', {
inserted: function(el, binding, vnode, oldVnode){
// el是绑定元素的原生js对象
// binding包含使用指令时的相关信息,如传入的参数
// vnode虚拟节点
// oldVnode之前的虚拟节点
el.focus()
}
})
指令相关的函数包含:
- bind:指令绑定到元素上时执行,只执行一次;
- inserted:元素插入到dom中时执行,只执行一次;
- updated:元素更新时执行
只定义指令的使用:同原生v-text
等指令相同,直接使用v-focus="xxx"
使用。
自定义私有指令:见创建vue对象。
异步请求
在vue中,可以使用vue-resource
、axios
等第三方库发送请求。
vue-resource使用说明:https://github.com/pagekit/vue-resource
axios github文档:https://github.com/axios/axios
axios中文文档:http://www.axios-js.com/
说明:对于跨域请求,axios
默认使用了cors
,该方式只需要后端做好相关配置即可跨域,前端请求不需要做任何改变。axios
默认不支持jsonp
方式的跨域,如要使用,可以单独引入jsonp实现跨域。
axios
发送请求示例:
get
:
axios.get('./data/1.json').then(res => {
this.arr = res.data
})
post
:
// 方式一:Content-Type: application/x-www-form-urlencoded
axios.post('./data/1.json', 'name=张三').then(res => {
this.arr = res.data
})
// 方式二:Content-Type: application/json;charset=UTF-8
axios.post('./data/1.json', {"name": "张三", "age": 20}).then(res => {
this.arr = res.data
})
通用:
// 下载文件
axios({
url: 'http://localhost/img/1.jpeg',
method: 'GET',
responseType: 'blob'
}).then((response) => {
const url = URL.createObjectURL(new Blob([response.data], {"type": response.headers['content-type']}))
const a = document.createElement('a')
a.download = '1.jpeg'
a.href = url
a.target = '_blank'
a.click()
})
示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>vue</title>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="root">
{{date | myfilter1 | myfilter2('当前时间:')}}
<div v-html="htmlMsg"></div>
<div v-cloak>==> {{ htmlMsg }}</div>
<div v-text="htmlMsg">==</div>
<input type="text" v-bind:value="msg" readonly /><br />
<input type="text" :value="msg" readonly /><br />
<input type="text" v-model:value="msg" v-focus /><br />
<input type="button" value="按钮" v-on:click="show" />
<li v-for="val in arr">{{val}}</li>
</div>
<script src="./js/vue.js"></script>
<script src="./js/axios.js"></script>
<script>
// 定义全局过滤器
Vue.filter('myfilter1', function (data) {
var y = data.getFullYear()
var m = (data.getMonth() + 1).toString().padStart(2, '0')
var d = data.getDate().toString().padStart(2, '0')
var H = data.getHours().toString().padStart(2, '0')
var M = data.getMinutes().toString().padStart(2, '0')
var s = data.getSeconds().toString().padStart(2, '0')
return `${y}-${m}-${d} ${H}:${M}:${s}`
})
var vm = new Vue({
el: '#root', // 要控制的元素
data: { // el要用到的数据
msg: 'vue.js',
date: new Date(),
arr: ['刘备', '关羽', '张飞']
},
methods: {
// el要用到的事件处理函数,函数中对数据的重新赋值会直接渲染
show: function (e) {
alert(this.msg)
}
},
filters: { // 定义私有过滤器, 当与全局过滤器同名时,这里优先级更高
myfilter2: function (data, params) {
return params + data
}
},
directives: { // 自定义私有指令
'focus': {
inserted: function (el, binding, vnode, oldVnode) {
el.focus()
}
}
},
computed: {
htmlMsg: function(){
return '<h2>' + this.msg + '</h2>'
}
},
// 使用生命周期函数
created: function () {
axios.get('./data/1.json').then(res => {
this.arr = res.data
})
/* axios({
url: 'http://localhost/img/1.jpeg',
method: 'GET',
responseType: 'blob'
}).then((response) => {
console.log(response)
const url = URL.createObjectURL(new Blob([response.data], {"type": response.headers['content-type']}))
const a = document.createElement('a')
a.download = '1.jpeg'
a.href = url
a.target = '_blank'
a.click()
}) */
}
})
setInterval(() => {
vm.date = new Date()
}, 1000)
</script>
</body>
</html>
组件和模块化
组件:从UI界面角度对代码进行的划分,方便UI组件的重用;
模块化:从代码逻辑角度对代码进行的划分,保证每个功能模块职能单一;
组件
创建
方式一:
Vue.component('my-com1', Vue.extend({
template: '<h1>组件一要展示的内容</h1>'
}))
// 也可以
Vue.component('my-com2', {
template: '<h1>组件二要展示的内容</h1>'
})
方式二:
<!-- 需要在vue对象控制的元素外边,且标签的名字固定为template -->
<template id='tpl'>
<h1>这是方式二创建的组件,更好用</h1>
</template>
<script>
Vue.component('my-com2', {
template: '#tpl'
})
</script>
上面定义的都是全局组件,定义私有组件的方法如下:
var vm = new Vue({
el: '#root',
components: {
myCom3: {
template: '<h1>这是一个私有组件</h1>'
}
}
})
使用
// vue会自动把驼峰命名的组件,改为如下形式的名字
<my-com1></my-com1>
定义属性
Vue.component('my-com1', Vue.extend({
template: '<h1 @click="show">组件一要展示的内容,数据:{{msg}}</h1>',
data: function () {
return { msg: '组件的数据' }
},
methods: {
show: function() {
alert('组件的方法')
}
}
}))
组件切换
<!-- :is的值为组件的id,会根据id展示组件 -->
<component :is="'my-com2'"></component>
组件间的通讯
向子组件传值:父组件可以在引用子组件的时候,通过属性绑定(v-bind)的方式,把数据传递给子组件,且该部分数据在子组件中是只读的。
子组件调用父组件的方法:同样,父组件的方法是通过事件绑定(v-on)的方式传递给子组件的。
子组件向父组件传值:需要借助子组件调用父组件方法的方式,在子组件中调用父组件的方法,将数据作为参数传递给方法,然后父组件就可以在方法中获取到子组件的数据了。
另外:vue中推荐通过$refs
获取元素和组件,当用该方式获取组件时,也可以获取到子组件的数据和方法。
示例代码如下:
<div id="root">
<my-com1 :msg="msg" @func="show" ref="myh"></my-com1>
</div>
var vm = new Vue({
el: '#root',
data: {
msg: 'Hello'
},
methods: {
show(data){
alert(this.msg + data + "\n子组件数据:" + this.$refs.myh.msg1)
}
}
// 子组件接收
components: {
myCom1: {
template: '<h1 @click=show>这是一个私有组件,父组件传入的值:{{msg}}</h1>',
// props用于接收父组件通过属性绑定(v-bind)传入的值
props: ['msg'],
data(){
return {msg1: '子组件'}
},
methods: {
//调用父组件的方法
show(){
this.$emit('func', ' world')
}
}
}
}
})
样式
官方文档: https://vue-loader.vuejs.org/zh/guide/css-modules.html
在vue文件中导入的外部css样式和style节点下定义的样式默认都是全局样式,要使用局部样式有一下两种方式;
- 设置
<style>
为``
{
test: /\.css$/, oneOf: [
// 匹配 `<style module>`
{ resourceQuery: /module/, use: ['style-loader', { loader: 'css-loader', options: { modules: true } }] },
// 这里匹配普通的 `<style>` 或 `<style scoped>`
{ use: ['style-loader', 'css-loader'] }
]
}
示例代码
组件相关功能完整示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>vue</title>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="root">
<my-com1></my-com1>
<my-com2></my-com2>
<my-com3 :msg="msg" @func="show" ref="myh"></my-com3>
<component :is="'my-com1'"></component>
</div>
<template id='tpl'>
<h1>这是方式二创建的组件,更好用</h1>
</template>
<script src="./js/vue.js"></script>
<script src="./js/axios.js"></script>
<script>
Vue.component('my-com1', Vue.extend({
template: '<h1 @click="show">组件一要展示的内容,数据:{{msg}}</h1>',
data: function () {
return { msg: '组件的数据' }
},
methods: {
show: function() {
alert('组件的方法')
}
}
}))
Vue.component('my-com2', {
template: '#tpl'
})
var vm = new Vue({
el: '#root',
data: {
msg: 'Hello'
},
methods: {
show(data){
alert(this.msg + data + "\n子组件数据:" + this.$refs.myh.msg1)
}
},
components: {
myCom3: {
template: '<h1 @click="show">这是一个私有组件,父组件传入的值:{{msg}}</h1>',
props: ['msg'],
data(){
return {msg1: '子组件'}
},
methods: {
show(){
this.$emit('func', ' world')
}
}
}
}
})
</script>
</body>
</html>
前端路由
概念:对于单页面应用程序,不同页面之间的切换主要是通过URL中的hash(#号)来实现。该方式的特点是:http请求不会包含hash的相关内容,而是页面内部跳转。
vue中推荐使用vue-router
实现路由功能:
中文文档:https://router.vuejs.org/zh/
官方文档:https://reacttraining.com/react-router/web/guides/quick-start
创建
在vue对象中创建并配置路由:
// 创建路由
var routerObj = new VueRouter({
routes: [
// 重定向
{path: '/', redirect: '/index'},
{path: '/index', component: {template: '<h1>首页组件</h1>'}}
]
})
// 使用路由
new Vue({
el: '#root',
router: routerObj
})
在html页面中使用路由:
<div id="root">
<router-view></router-view>
</div>
跳转与传参
方式一:直接使用a
标签的#实现跳转
<a href="#/">首页</a>
方式二:使用vue-router
提供的router-link
标签实现跳转
<router-link to="/login?id=1&name=张三" tag='button'>登录</router-link>
<router-link to="/register/2/李四">注册</router-link>
另外:两种方式都可以使用?
或者url
两种方式传参,针对两种传参方式的接收方式如下:
{path: '/login', component: {
template: '<h1>登录组件==>{{$route.query.id}}:{{$route.query.name}}</h1>',
created() {
console.log(this.$route)
},
}},
{path: '/register/:id/:name', component: {
template: '<h1>注册组件==>{{$route.params.id}}:{{$route.params.name}}</h1>',
created() {
console.log(this.$route)
},
}}
另外:若页面跳转使用的router-link
,那么被选中的标签默认会加上值为router-link-active
的class属性,可以据此调整被选中路由的样式。如:
.router-link-active{
font-weight: bold;
color: red
}
路由嵌套
概念:即一个路由内套用其他路由,可以使用children来配置,如下:
var routerObj = new VueRouter({
routes: [
{path: '/', redirect: '/index'},
{path: '/index', component: {template: '#index'},
children: [
// 注意不要有/
{path: '', redirect: 'tab1'},
{path:'tab1', component: {template: '<h2>首页-页签一</h2>'}},
{path:'tab2', component: {template: '<h2>首页-页签二</h2>'}}
]
}
]
})
<div id="root">
<a href="#/">首页</a>
<router-view></router-view>
</div>
<template id="index">
<div>
<h1>首页</h1>
<router-link to="/index/tab1">页签一</router-link>
<router-link to="/index/tab2">页签二</router-link>
<router-view></router-view>
</div>
</template>
命名路由
当一个页面有多个需要路由的页面时,可以使用命名路由,命名路由可以将指定url匹配多个路由并填充到指定html位置。但若需要做到url改变也能匹配多个路由,可以使用嵌套路由来实现。具体如下:
{path: '/index',
components: {
default: {template: '#index'},
foot: {template: '<h2>底部</h2>'}
},
children: [
{path: '', redirect: 'tab1'},
{path:'tab1', component: {template: '<h2>首页-页签一</h2>'}},
{path:'tab2', component: {template: '<h2>首页-页签二</h2>'}}
]
},
<div id="root">
<a href="#/index">首页</a>
<router-view></router-view>
<router-view name="foot"></router-view>
</div>
<template id="index">
<div>
<h1>首页</h1>
<router-link to="/index/tab1">页签一</router-link>
<router-link to="/index/tab2">页签二</router-link>
<router-view></router-view>
</div>
</template>
watch
watch在vue中用于监听vue对象属性的变化,它可以监听data、路由等属性的变化,用法如下:
new Vue({
el: '#root',
router: routerObj,
watch: {
'$route.path': function(newVal, oldVal){
console.log(oldVal + '===>' + newVal)
}
}
})
示例代码
路由相关功能完整实例代码:
<!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">
<style>
.router-link-active{
font-weight: bold;
color: red
}
</style>
<title>vue路由</title>
<script src="./js/vue.js"></script>
<script src="./js/vue-router.js"></script>
</head>
<body>
<div id="root">
<a href="#/">首页</a>
<router-link to="/login?id=1&name=张三" tag='button'>登录</router-link>
<router-link to="/register/2/李四">注册</router-link>
<router-view></router-view>
<router-view name="foot"></router-view>
</div>
<template id="index">
<div>
<h1>首页</h1>
<router-link to="/index/tab1">页签一</router-link>
<router-link to="/index/tab2">页签二</router-link>
<router-view></router-view>
</div>
</template>
</body>
<script>
var routerObj = new VueRouter({
routes: [
{path: '/', redirect: '/index'},
{path: '/index',
components: {
default: {template: '#index'},
foot: {template: '<h2>底部</h2>'}
},
children: [
{path: '', redirect: 'tab1'},
{path:'tab1', component: {template: '<h2>首页-页签一</h2>'}},
{path:'tab2', component: {template: '<h2>首页-页签二</h2>'}}
]
},
{path: '/login', component: {
template: '<h1>登录组件==>{{$route.query.id}}:{{$route.query.name}}</h1>',
created() {
console.log(this.$route)
},
}},
{path: '/register/:id/:name', component: {
template: '<h1>注册组件==>{{$route.params.id}}:{{$route.params.name}}</h1>',
created() {
console.log(this.$route)
},
}}
]
})
new Vue({
el: '#root',
router: routerObj
})
</script>
</html>
mint-ui
mint-ui是基于 Vue.js 的移动端组件库,包含丰富的 CSS 和 JS 组件,能够满足日常的移动端开发需要。通过它,可以快速构建出风格统一的页面,提升开发效率。
官方文档:
http://mint-ui.github.io/docs/#/zh-cn2
vue-cli
简介
vue cli是基于vue进行快速开发的完整系统,可以通过命令行快速搭建vue开发环境,也可以通过vue ui
命令使用图形化界面快速搭建开发环境;
参考文档:https://cli.vuejs.org/zh/guide/
搭建:vue-cli的搭建方式非常简单,执行vue ui
命令,然后按照页面提示选择需要的选项就可以快速创建项目了。
静态资源
public文件夹下的静态资源文件只会被简单的复制,而不会经过webpack;
引用静态资源
<!-- 相对路径 -->
<img src="./image.png">
<!-- 相对路径,~后的内容可以是node模块中的资源 -->
<img src="~/img/foo.png">
<!-- @是别名,可以认为是使用绝对路径 -->
<img src="@/img/foo.png">
引入css
// 局部引入,文件名必须以.module.css结尾
import css from '@/assets/css/a.module.css'
// 全局引入
import '@/assets/css/a.css'
环境变量
要创建环境变量,需要在项目根目录下创建.env
文件,文件名规则如下:
.env # 在所有的环境中被载入
.env.local # 在所有的环境中被载入,但会被 git 忽略
.env.[mode] # 只在指定的模式中被载入
.env.[mode].local # 只在指定的模式中被载入,但会被 git 忽略
其中的mode,默认只有development
、test
和production
三种,但可以通过vue-cli-service
命令的--mode
选项修改。文件内容只包含键值对,如:a=test1
,一个键值对一行,同时,只有以VUE_APP_
开头的变量才会被以webpack.DefinePlugin
的方式嵌入到应用中,即只有这些变量可以在应用中访问,如:console.log(process.env.VUE_APP_TEST1)
,另外,还有两个特殊的变量始终可用:
BASE_URL
:与publicPath
配置项值相同,为应用部署路劲;NODE_ENV
:即上面的mode;
参考配置
配置文档:https://cli.vuejs.org/zh/config/
const path = require('path')
module.exports = {
publicPath: './',
outputDir: path.join(__dirname, './dist'),
assetsDir: 'static',
devServer: {
port: 8080,
proxy: {
'/api': {
target: 'http://localhost/',
ws: false,
changeOrigin: true,
pathRewrite: {'^/api': '/api'}
}
}
}
}
vuex
简介
vuex是vue中用于状态管理的一种模式,一般在大型web单页面应用中用于状态的管理,小型应用中使用vuex的意义不大,vuex更多的是用于解决跨组件通讯和作为数据中心集中式存储数据;
几个核心概念:
- state:状态,是状态管理的数据源;
- getter:与filters类似,可以将state过滤后输出;
- mutation:在严格模式下,是改变state的唯一途径,并且是同步操作;
- action:对state的异步操作,是提交到mutation改变状态;
- module:根据业务需求将store分为多个模块;
对于父子组件的通讯,一般有两种方式:
- 通过prop属性实现父→子传递数据;
- 通过子组件中触发事件向父组件传递数据;
而非父子组件之间的通讯,可以通过一个空的vue实例作为中转站(可以称为事件中心),然后通过在A组件触发事件,B组件接收事件的方式传递数据;但是该方式使用复杂!此时vuex就能更好的应对了。
在实际开发中,vuex一般有两种用途:
- 管理组件之间全局共享的数据;
- 管理后端的异步请求数据,在action中封装数据操作:该方式可以在一定程度上对逻辑代码和视图代码进行分层;
使用示例
新建文件,定义store;(实际开发应该根据情况将state、mutations、actions、getters定义在不同的文件,如有必要,还可以考虑分modules)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userInfo: {id: 1, name: '张三'},
},
mutations: {
setNameM(state, name) {
state.userInfo.name = name
}
},
actions: {
setNameA({commit, state}, name) {
setTimeout(() => {
commit('setNameM', name)
}, 5000)
}
},
getters: {
getName: (state, getters, rootState, rootGetters) => {
return "登录名:" + state.userInfo.name
}
},
modules: {}
})
引入:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
new Vue({router, store, render: h => h(App)}).$mount('#app')
在组件中使用:
<template>
<div class="about">
<h1>{{$store.state.userInfo.name}}</h1>
<h1>{{getName()}}</h1>
<h1>{{n}}</h1>
</div>
</template>
<script>
import {mapState, mapMutations, mapGetters, mapActions} from 'vuex'
export default {
methods: {
...mapMutations(['setNameM']),
...mapActions(['setNameA']),
...mapGetters(['getName'])
},
computed: {
...mapState({n: state => state.userInfo.name})
},
mounted() {
// this.$store.state.userInfo.name = '麻子'
// this.$store.commit('setNameM', '李四')
// this.$store.dispatch('setNameA', '王二')
this.setNameM('李四')
this.setNameA('王二')
console.log(this.getName())
}
}
</script>