前言
前些天刚完成了全栈的实践,基本上一个完成了主要的页面内容.
如果熟悉Vue对自己的技术栈有一定的提升,也提升了开发效率.
Vue是基于MVVM的思想进行设计.
其实Vue也没啥难得,MVVM的理论早就开始盛行了,只是Vue的设计比较巧妙,赢得了开发者的喜爱.
通过一篇文章想对Vue的理解提升到一定的层次是不实际的,看是看不会的,计算机本来就是实践性很强的课程,希望大家最好去实战一下完成一个小项目,去提升自己的能力.
基础环境
构建工具是用的webpack,js语法支持es2015,样式处理是采用的less.
实例
如何创建一个Vue实例
let vm = new Vue();
数据绑定
我们有了这个实例可以去进行哪些操作,如何进行数据传递.
let obj = {test:'data'};
let vm = new Vue({
data:obj
});
vm.data == obj //true
上述的例子中vm的data属性引用了obj,obj.test的值改变会影响到vm.test的值.
vm.a = 'test'
动态新增属性,虽然vm有了新的属性但是这样不是响应式的,也就不会对视图进行影响,因此如果需要响应式的数据,我们可以在data提前声明一个默认值
实例属性与方法都与$
符号开头.
模板
指令
Vue当中以v-
开头的都是指令,一般为 JavaScript 表达式
<select v-bind:selected="true"></select>
v-bind指令用于将元素的属性与表达式进行绑定,可以简写为:
.
v-on指令用于将元素
v-bind
可以简写为:
v-on
可以简写为@
<div @click='clickDiv'> </div>
输出文本
我们如何输出data中的一个JS变量,
<div>{{message}}</div>
我们可以用v-once
指令 ,限制渲染次数,渲染一次后不在渲染,与数据解绑.
<div v-once >{{test}}</div>
test数据发生改变后不会影响div里的内容展示
绑定html属性
如何将select 下拉框的disabled属性和我们的数据进行关联呢
<select :disabled="isDisabled"></select>
isDisabled 的值是 null、undefined 或 false,则 disabled 特性甚至不会被包含在渲染出来的 select
元素中。
JavaScript表达式
模板不仅可以输出变量,也可以使用JavaScript**表达式**
<div id=" 'list' + id ">
{{test.reverse()}}
</div>
上述例子使用了两次js表达式
id属性的字符串的拼接,模板输出
计算属性和侦听器
计算属性
当我们要对属性进行复杂的逻辑处理时,就不能简单的在模板当中使用JS表达式.要将属性写成计算属性.
定义
new Vue({
computed:{
reverseError(){
return this.error.reverse();
}
}
})
上述例子我们定义了一个error
的计算属性.这个属性实质是一个getter函数,声明在了computed当中.
缓存机制
计算属性是基于他们的依赖(响应式的依赖)进行缓存.
一个computed可以依赖于另一个computed
Setter
默认的定义方式我们只可以定义getter函数,除此之外我们也可以定义setter函数
computed:{
myProcess:{
get: function () {
//todo
},
set: function (newValue) {
//todo
}
}
}
侦听属性
当我们需要监控一个对象的变化时,我们需要使用到这个方法.
例如:
watch:{
$route(to, from){
console.log(to);
console.log(from)
}
}
当route的path发生变化的时候,这样我们可以route输出新旧值.
样式处理
样式处理的方式比较灵活也很强大,与jQuery
操作完全不一致,所以这个理解起来有些复杂.
类与内联样式用法一致,只不过在绑定时一个绑定style
一个绑定class
.
对象语法
<div :class="{active:isActive,'error':isError}"></div>
上述例子将active 展示类与属性isActive属性进行了绑定.当isActive变为false时,那么active class将不会进行显示.
样式对象绑定
非内联可以减少耦合.
<div :class="classObj"></div>
data:{
classObj:{
isActive:true,
'text-error':false
}
}
计算属性
computed:{
classObj(){
return {
isActive:true,
'text-danger':this.error && this.error.type === 'fatal'
}
}
}
三元与数组
<div class="[isActive?active: 'abnormal', normal]"></div>
条件处理
分组渲染
<div v-if="isShow == A"></div>
<div v-else-if="isShow == B"></div>
<div v-else></div>
上述例子当中,如果符合对应的条件就渲染对应的div.
会触发子组件的销毁以及重建.
v-show
v-show 只是简单地切换元素的 CSS 属性 display,v-if是控制这个属性是否渲染在html中。
<div v-show="myShow"></div>
列表渲染
如何遍历数组
v-for
可以将数组渲染成多个元素.
<ul>
<li v-for="item in listItem">{{item.info}}</li>
</ul>
<ul>
<li v-for="(item, index) in listItem">{{item.info}}</li>
</ul>
如何遍历对象
<ul>
<li v-for="value in obj">{{value}}</li>
</ul>
<ul>
<li v-for="(value, key,) in obj">{{value, key}}</li>
</ul>
<ul>
<li v-for="(value, key, index) in obj">{{value, key, index}}</li>
</ul>
当需要数组的索引时,可以指定index.
遍历时顺序
默认情况下,顺序发生改变时,Vue不会将当前渲染改变顺序.
如果需要顺序变化时,需要key
进行绑定:
<div v-for="item in items" :key="item.id">
</div>
何时触发重新渲染
1.Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
2.替换数组
当我们进行数组替换时,也会触发视图的重新渲染.
注意:
以下时机不会进行重新
1.当你利用索引直接设置一个项时,不会进行改变 TODO:Why
2.当你修改数组的长度时
3.动态添加属性需要使用Vue.set()方式进行添加,否则对象将不会是响应式的.
v-for与v-if
v-for比v-if拥有更高的优先级,每次执行v-for会调用v-if指令进行判断.
事件处理
v-on(@)
可以监听DOM事件.
<div id="example-1">
<button @:click="counter += 1">Add 1</button>
</div>
接收原生event对象
<div @click="clickButton"></div>
我们可以默认接收event对象
methods:{
clickButton(event){
//TODO
}
}
这样我们就可以在点击事件当中接收对应的事件了.
传入自定义的参数
当我们触发事件时,可能我们不仅需要event对象还需要额外的自定义的参数那么我们就可以 这样做:
<div @click="clickButton(info)"></div>
methods:{
clickButton(info,event){
//TODO
}
}
这样我们既可以接收自定义的参数,也可以接收event对象.
事件修饰符
当我们需要对事件进行处理时,需要对事件触发增加条件限制,我们就可以用到修饰符.
比如一个创建订单按钮 防止用户重复点击.我们可以将点击事件设置为触发一次.
<div @click.once=></div>
修饰符如下:
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
表单输入绑定
v-model
我们可以使用v-model指令在表单input
及textarea
进行数据的双向绑定.
注意:v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源.
下拉框示例:
<div>
<select v-model="selected">
<option disabled value="">请选择</option>
<option v-for="(item, index) in items" :value="idex">{{item}}</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
data:{
items:['A', 'B', 'C']
}
v-model的修饰符
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步.
<input v-model.lazy="msg" >
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:
<input v-model.number="age" type="number">
trim修饰符,将用户输入的空格过滤.
<input v-model.trim="msg">
组件
组件就是封装可用的代码可以在多处进行调用.
使用组件
全局注册
全局注册顾名思义,全局注册执行后,所有的vue实例都可以用这个组件.
Vue.component('my-component', {
// 选项
})
局部注册
局部注册后只有指定实例可以使用该组件.
var Child = {
template: '<div>A custom component!</div>'
}
new Vue({
// ...
components: {
// <my-component> 将只在父组件模板中可用
'my-component': Child
}
})
is
的使用
is
可以在一些元素当中防止解析产生问题.如table
,ul
,ol
等当中允许包含限制元素.
<table>
<my-table-thead></my-table-thead>
</table>
上述例子当中元素的解析可能会产生问题.这时候我们可以使用is
进行指定组件.
<table>
<thead is="my-table-thead"></thead>
</table>
将组件内data设置为函数
这样做的目的是为了防止一个模板当中渲染多个组件,data引用了一个相同的对象,这样往往会造成不想要的结果.
Vue.component('my-component', {
template: '<div>{{source}}</div>',
data: function () {
return {
source:11
}
}
})
组件通信
Vue当中的父子组件通信的逻辑稍微复杂.
数据流是单向的,一般情况下,子组件不能直接修改父组件传递的数据
父组件向子组件传递数据
prop传递数据
组件实例的作用域是孤立的。这意味着不能 在子组件的模板内直接引用父组件的数据。父组件的数据需要通过 prop 才能下发到子组件中。
子组件:
Vue.component('child', {
props: ['message'],
template: '<span>{{ message }}</span>'
})
父组件:
<child :message="parentMsg!"></child>
new Vue({
el: '#prop-example-2',
data: {
parentMsg: 'Message from parent'
}
})
这样我们就可以将父组件的值传递于子组件,而且是动态更新的.
prop验证
Vue.component('example', {
props: {
// 基础类型检测 (`null` 指允许任何类型)
propA: Number,
// 可能是多种类型
propB: [String, Number],
// 必传且是字符串
propC: {
type: String,
required: true
},
// 数值且有默认值
propD: {
type: Number,
default: 100
},
// 数组/对象的默认值应当由一个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
}
})
type检测的类型:
String
Number
Boolean
Function
Object
Array
Symbol
子组件向父组件传递数据
再次强调,子组件不能直接修改父组件的数据.那么如何向父组件传递数据呢,需要采用事件的处理方式进行通信.
事件机制
$emit(eventName, optionalPayload);
$on(eventName)
optionalPayload 可以在触发时间时提供额外的参数.
<child @click='handleSendMessage'></child>
<div id="message-event-example" @message='hanleMessage'></div>
Vue.component('child', {
props: ['message'],
methods: {
handleSendMessage: function () {
this.$emit('message', { message: this.message })
}
}
})
new Vue({
el: '#message-event-example',
data: {
messages: []
},
methods: {
handleMessage: function (payload) {
this.messages.push(payload.message)
}
}
})
双向数据绑定
sync修饰符
.sync
修饰符可以使数据进行数据的双向绑定,只要子组件修改也会变更父组件的数据.
本质是对.sync
进行了转换,将数据转换成事件机制进行同步.
因此在父组件当中
<child :message.sync="message"></child>
这样就可以在父子组件当中进行数据传递.
表单输入事件
v-model
进行双向数据绑定 本质上是进行了转换
<my-input v-model="price"></my-input>
转换后:
<my-inpu v-bind:value="price" v-on:input="price = arguments[0]"></my-input>
组件内部需要显示的声明value在props
Vue.component('my-input', {
props: ['value'],
})
自定义组件
下拉选框与复选框的value被用做了别的用途,因此我们可能不需要改变value的值.
Vue.component('my-checkbox', {
model: {
prop: 'checked',//组件内接受的属性
event: 'change' //组件内改事件触发后,将父组件元素绑定的model进行转换
},
props: {
checked: Boolean,
value: String
}
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>
转换后
<my-checkbox
:checked="foo"
@change="val => { foo = val }"
value="some value">
</my-checkbox>
插槽
单个插槽
子组件通过slot可以获取在父组件创建的子组件内部的内容.实质是进行内容的替换.
父组件
<child> <p>插入内容</p></child>
子组件
<template><div class="child"><slot></slot></div></template>
编译后:
<div class="child"><p>插入内容</p></div>
具名插槽
具名插槽就是告诉子组件插入到指定的插槽当中.
父组件
<child><h1 slot="header">插入标题</h1> <p slot="content">插入内容</p></child>
子组件
<template><div class="child"><slot name="header"></slot><slot name="content"></slot></div></template>
编译后:
<div class="child"><h1 slot="header">插入标题</h1><p>插入内容</p></div>
插件
插件内部比较复杂,这里先只做简单的介绍.
1.添加全局方法或者属性,如: vue-custom-element
2.添加全局资源:指令/过滤器/过渡等,如 vue-touch
3.通过全局 mixin 方法添加一些组件选项,如: vue-router
4.添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
5.一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router
混入
混入的使用:将插件的可插拔的机制发挥的淋漓尽致
选项合并
注意:
1.混入对象与自身组件components,mehtods,directives
会有冲突,自身组件的属性优先级更高.
2.混入对象的钩子函数会在组件钩子函数之前调用,不会替换.
var mixin = {
methods: {
foo: function () {
console.log('foo')
},
conflicting: function () {
console.log('from mixin')
}
}
}
var vm = new Vue({
mixins: [mixin],
methods: {
bar: function () {
console.log('bar')
},
conflicting: function () {
console.log('from self')
}
}
})
vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"
全局混入
一旦使用全局混入对象,将会影响到 所有 之后创建的 Vue 实例.
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"
创建
当我们创建一个MyPlugin的插件,我会添加一些操作
MyPlugin.install = function (Vue, options) {
Vue.myGlobalMethod = function () {
}
Vue.prototype.$myMethod = function (methodOptions) {
}
Vue.mixin({
created: function () {
}
})
}
使用
使用之前请调用use
方法
import MyPlugin from 'XXX';
Vue.use(MyPlugin, { someOption: true })
vue-router
vuerouter使用了hashurl进行路由,这种方式浏览器不会重新请求新的url.
配置
routes 中可以设置对应路由展现对应的组件
import Router from 'vue-router'
import Foo from 'XXX'
import Bar from 'XXX'
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
let router = new Router({routes})
new Vue ({
router
})
路由参数
当一个视图跳转到另一个视图可能需要一些参数,我们可以获取路由当中的参数.
设置当前的参数
const routes = [
{
path:'/foo/id/:id/name/:name',
component:Foo
}
]
获取参数
const Foo = {
template:'<div>{{$route.params.id}}</div>'
}
路由的watcher
watch
通过watch我们可以观察,route的变化
const Foo = {
template: '...',
watch: {
'$route' (to, from) {
//to目的路由 from 原路由
}
}
}
钩子函数
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// 别忘记调用next()函数
next()
}
}
嵌套路由
meta
我们可能需要一些route的额外信息,那么就可以将其存入到meta字段里
new Router(
routes:[{
path: "/routea",
name: "routea",
component: routeA,
meta:{
keepAlive:false
}
}]
);
我们便可以这样访问其数据信息.
$route.meta.keepAlive
嵌套路由
有一种情况我们可能需要在当前的路由当中再进行一次路由,注意这个和跳转是不一样的,路由分为父子路由.
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
{
//路由地址 /user/:id/posts
path: 'posts',
component: UserPosts
},
//实现默认路由
{ path: '', component: UserHome },
]
}
]
})
场景:左侧导航栏与右侧展示页面,我们可以使用这种路由配置进行页面展示.
路由跳转
声明式跳转:
<router-link :to="/user">
编程式跳转:
router.push({ name: 'user', params: { userId: 123 }})
后退与前进:
router.go(-1);
router.go(1);
页面刷新,但是不存入浏览历史
router.replace(location, onComplete?, onAbort?)
<router-link :to="..." replace>
场景:声明式跳转相当于声明一个a标签,点击后进行路由的跳转,当然也可以自己实现相同的功能.
命名路由以及命名视图
声明route规则时,可以指定名字字段.
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
跳转执行代码
router.push({ name: 'user', params: { userId: 123 }});
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
当一个页面有多个视图时,我们需要命名视图来告知渲染正确的组件.
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
keep-alive
keep-alive时我们可以将组件进行缓存.
<keep-alive><component></component></keep-alive>
部分页面的缓存
我们可以结合vue-router进行缓存相应的跳转页面.这样会将所有的跳转页面不需要这样做,如何对部分页面进行缓存.
new Router({
routes:[
{
path: "/routea",
name: "routea",
component: routeA,
meta:{
keepAlive:false
}
},
{
path: "/routeb",
name: "routeb",
component: routeB,
meta:{
keepAlive:true
}
}
})
<keep-alive>
<router-view v-if="$route.meta.keepAlive">
</router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive">
</router-view>
这样在读取了meta配置的原信息后,会决定是否渲染带有缓存的组件.