Vue 是一套用于构建用户界面的渐进式框架
也意味着,既能够把VUE做为该应用的一部分嵌入到一个现成的服务端应用,或者在先后端分离的应用中,利用Vue 的核心库及其生态系统,把更多的逻辑放在前端来实现。html
渐进式框架前端
与Vue相比,React学习曲线陡峭,在学习React以前,须要了解JSX和ES2015,固然入门后,发现还要学习React全家桶。而Vue就能够在简单阅读了文档后,开始构建应用程序。vue
这就要得益于Vue主张的 渐进式。
能够简单看下官方给出这张图:react
能够看出来,主要是介绍了Vue设计思想,就是框架作分层设计,每层均可选,能够单独引入,为不一样的业务需求制定灵活的方案。主张最少,不会多作职责之外的事。webpack
Vue做者尤雨溪的观点,Vue设计上包括的解决方案不少,可是使用者彻底不须要一上手,就把全部东西全都用上,由于彻底没有必要,通常都是根据项目的复杂度,在核心的基础上任意选用其余的部件,不必定要所有整合在一块儿。git
这样渐进式的解决方案,使得学习成本大大减小了。github
也就是说,DOM状态只是数据状态的一个映射,基本全部的框架都已经认同了这个见解,Vue也是主张 数据驱动状态。web
说到这里,基本都会提到如今主流的MVVM
的模式。ajax
采用了双向数据绑定的思想,基本能够分为三层:vue-router
基于这个思想,Vue从一开始就利用ViewModel与view,model进行交互
ViewModel是Vue.js的核心,它是一个Vue实例,做用在某个HTML元素上,通常都是指定 id= app
的元素,图中 的DOM listeners
和Data Bindings
能够看作两个工具,它们是实现双向数据绑定的关键。
从用户(View)角度看,DOM Liisteners
利用在相应的元素上添加事件绑定,捕获用户的点击,滑动等手势动做,在事件流中改变对应的Model
。好比 经常使用的 v-model
指令,就是捕获表单元素的input
,change
等事件,改变相应的绑定值。
从Model方向看,Data Bindings
则将操做的数据变化,反应到view上。好比经过ajax 从后台获取的数据,能够刷新数据列表,反应到用户界面。这也是实现双向数据绑定的关键。
Vue2中是经过Object.definedProperty
方法中定义的getters和 setters构造器来实现数据响应的。能够简化下源码中的实现:
Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { return value }, set: function reactiveSetter (newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if ("development" !== 'production' && customSetter) { customSetter(); } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); } }); }
经过这种方法定义对象obj
上的某个属性,每次获取属性值的时候就,会主动触发get
对应的回调函数,而后给该属性赋值时,就会触发里面的set
对应的回调函数,在set
回调函数里面,加入了dep.notify()
方法,而后能够看下这个方法
notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }
里面的定义的常量subs
每次深拷贝this.subs
数组,数组里面保存的就是全部的subscriber
订阅者,对应的发布者就是obj
里面对应的属性,或者说是Vue中的data
值。通知全部的订阅者,数据更新了。原生js实现发布订阅模式(publish/Subscribe),能够参考这里
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>hello world</title> <script src="https://gw.alipayobjects.com/as/g/h5-lib/vue/2.4.4/vue.min.js"></script> </head> <body> <div id="app"> {{message}} </div> <script> var app = new Vue({ el:"#app", data:{ message:'hello vue' } }) </script> </body> </html>
这样就简单建立了一个Vue 应用,数据message
和DOM页面产生了关联,相似html模板引擎,把相应的数据渲染到页面中。
指令 (Directives) 是带有 v- 前缀的特殊属性,这些特殊属性能够响应式的做用域DOM,
<p v-if = "seen">如今你看到我了</p>
,经过seen
的真假来插入/移除< p>元素。 这里判断的时候使用 === 全等,seen = “false” 的时候,也会插入 <a v-bind:href="url">...</a>
。 缩写形式<a :href="url">...</a>
。 经常使用于改变dom的style, class ,href ,src 等属性。 动态绑定的属性能够写成 :属性名="属性值" <a v-on:click="doSomething">...</a>
,简写形式 <a @click="doSomething">...</a>
, doSomething
对应的指向methods
里面定义的函数。 注意,除非在须要传递参数的时候,写成 @click = "doSomething($event,args1,args2)",$event
表明事件对象,args
表明自定义参数将v-bind
用于class
和style
时,Vue.js作了专门的加强,表达式结果的类型除了字符串以外,还能够是对象或数组。
绑定HTML Class
直接赋值。
< div :class="className"> </div> data:{ className:"div-class" }
结果:
<div class="div-class"></div>
对象语法。
< div class="static" :class="{active:isActive,'text-danger':hasError}" /></div> data: { isActive:true, hasError:false, }
结果:
<div class="static active" ></div>
数组语法,
<div :class="['one',bTwo?'two':'three']" </div> data:{ bTwo:true } <style> .one{} .two{}
结果:
<div class='one two'></div>
绑定内联样式
对象语法
<div :style = "{color:activeColor,fontSize:fontSize+'px'}"></div> data: { activeColor: 'red', fontSize: 30 }
结果:
<div style="color:red:font-size:30px;"></div>
数组语法
<div v-bind:style="[baseStyles, overridingStyles]"></div> data:{ baseStyles:{ color:'red' }, overridingStyles:{ fontSize:'30px' } }
结果:
<div style="color:red:font-size:30px;"></div>
条件渲染
v-if
一样也是一个指令,添加到一个元素上,对应利用 ===
全等判断绑定的值true
或false
来决定是否渲染里面的节点。
能够与它一块儿使用的指令有 v-else
,v-else-if
,v-else
元素必须紧跟在都有v-if
或者v-else-if
的元素后面
<div v-if= "num===0"> 0 </div> <div v-else-if ="num ===1"> 1 </div> <div v-else> not 0/1 </div> data:{ num:3 }
结果:
<div> not 0/1 </div>
v-show
根据条件展现元素的选项,简单的切换元素的内联样式display
适用场景:
列表渲染
v-for 把一个数组对应为一组元素,推荐给每一个列表,添加惟一标识的key
值
<div v-for="(item,idx) in items" :key="idx"> {{idx}} --- {{item.product}} </div> data:{ items:[ {product:"foo"}, {product:"bar"} ] }
结果:
<div> 0 --- foo </div> <div> 1 --- bar </div>
数组更新检测
包含一组观察数组变异方法,用来触发试图更新:push()
,pop()
,shift()
,unshift()
,splice()
,sort()
,reverse()
利用索引给数组赋值或者手动修改数组的长度,都不会被检测到更新
替代方案:
Vue.set(example1.items,indexOfItem,newValue) // 或者 example1.items.splice(indexOfItem,1,newValue) // 改变数组的长度: example1.items.splice(newLength)
对于对象中的属性添加或删除,也可使用Vue.set
方法,或者在定义的Vue实例内部,使用this.$set
,this.$set
只是全局Vue.set
的别名
Vue.set(object,key,value)
使用v-model
在表单input
或 <textarea>
元素上建立双向数据绑定。
<input v-model = "message" placeholer= "edit me"> <p>Message is {{message}}</p>
这样input输入框中的值就与P标签中的内容绑定了,一样也适用textarea
,checkbox
,radio
,select
等表单。
实质上,v-model
只是语法糖。
<input v-model = "something"
对应的完整形式:
<input v-bind:value="something" v-on:input="something = $event.target.value">
表单数组校验。
利用修饰符 .number
进行数字校验,是最实用的方法,在v-model
上添加number
修饰符。
<input v-model.number="age" type= "number" >
组件能够用来扩展HTML,封装可重用的代码,全部的组件都是Vue的实例。
命名: 建议遵循W3C规则(小写,而且包含一个短杆)
组件组合:使用中最多见的是造成父子组件的关系,组件A在它的模板中使用了组件B,那么他们之间就须要通讯。组件间通讯的关系能够用下面的图示代表:
归纳为: prop 向下传递,事件向上传递。
利用Prop 传递数据,同时借助.sync
,进行双向数据通讯
父组件的数据要经过Prop才能下发到子组件中。
prop 属性命名:在使用camelCase(驼峰式命名)的prop须要装换成对应的kebab-case(短横线分隔式命名)。同时,也能够绑定动态的Prop传递给子组件。
而后,子组件中prop值改变,是没法反应到父组件中的。在Vue1.x中使用.sync
修饰符能够提供双向绑定,可是违背了单向数据流的思想,在2.0中就移除了,但在2.3.0中做为一种语法糖的形式引入了
Vue.component('child',{ props:['myMessage'], template:'<span @click="handleClick">{{myMessage}}</span>', methods:{ handleClick:function(){ this.$emit("update:myMessage","message from child") } } }) <!-- 在HTML 中使用时 .sync的语法糖 --> <child :my-message.sync="parentMsg"></child>
点击子组件中的span,就能够改变父组件中prop绑定的parentMsg
值。.sync
语法也会被扩展成为
<child :my-message="parentMsg" @update:myMessage = "val => parentMsg = val"
里面 @update:myMessage
就是绑定了自定义事件,回过来看下上面父子通信的规则 prop向下传递,事件向上传递,也很是符合。
非父子组件通讯
官方推荐使用空的Vue实例做事件总线
var bus = new Vue(); // 在A 组件中触发了事件 bus.$emit("change",1); // 在B 组件中监听事件 bus.$on('change',function(id){})
组件通讯变得复杂时,就要考虑使用全局状态管理,Vue也提供了vuex状态管理库。
Vuex 是一个专门为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
固然,使用Vuex并非首选,只有在构建中大型单页面应用时,考虑到全局的状态管理,天然就会想到Vuex。
下面这张图,表示状态管理“单向数据流”的理念
核心概念包括(简单计数器为例):
state: 做为单一状态树,惟一的数据源,而且每一个应用仅仅包含一个store
实例,通常经过计算属性获取某个状态。
state:{ count:0 },
getter: 至关于store的计算属性,数据源发生变化时,返回通过处理后的值,
getters:{ getState:state=>{ return state.count } },
mutation: 相似于事件,对应的回调函数到状态进行处理,必须经过store.commit
的方式手动触发
mutations:{ increment:state => state.count++, decrement:state => state.count-- },
actions: 利用commit
提交mutation,能够执行异步操做,经过 store.dispatch
方式触发
// 模拟异步请求 var delay = (timeout,cb) => new Promise(resolve => setTimeout(()=>{cb(); resolve("test incrementAsync")},timeout)); actions:{ incrementAsync({commit}) { return delay(600,function(){commit("increment")}) }, }
把全部部分组合起来,就构成一个简单的计数器:
var delay = (timeout,cb) => new Promise(resolve => setTimeout(()=>{cb(); resolve("test incrementAsync")},timeout)); var store = new Vuex.Store({ state:{ count:0 }, getters:{ getState:state=>{ return state.count } }, mutations:{ increment:state => state.count++, decrement:state => state.count-- }, actions:{ incrementAsync({commit}) { return delay(600,function(){commit("increment")}) }, decrementAsync({commit}) { return delay(600,function(){commit('decrement')}); } } })
而后组件中触发actions
,就能够
store.dispatch('decrementAsync').then(() => { // ... })
在浏览器中,使用vue-devtool,试下时间旅行功能。
使用Vue.js建立单页面应用,就可使用vue-router,目前版本是3.0.1,把组件映射到对应的路由,经过改变url来渲染不一样的页面。官方中文文档。
vue-router 默认hash模式,每次url只会改变#
后面对应的值,页面就不会从新加载,而且也不须要服务器端做任何配置。
若是使用路由的history模式,url就会正常http://yoursite.com/user/id
,只须要添加配置mode:'history'
,同时须要后端配置,否则页面从新刷新,会匹配不到任何资源。
不一样模式下的服务器配置及生产环境部署,能够参考vue、react等单页面项目应该这样部署到服务器.
基础概念:
< router-link>
<router-link to="/foo">Go to Foo </router-link> <router-link to= "/bar">Go to Bar</router-link>
使用router-link 组件导航,经过传入to
属性指定连接,至关于原生的a
标签。
< router-view>
路由出口,路由匹配到的组件将渲染在这里
<router-view></router-view>
能够在上面添加一些过渡效果
<transiton name="slide"> <router-view></router-view> </transiton>
初始化路由配置
若是使用vue-cli
脚手架构造项目,在init的时候,会出现选项提示用户安装路由
确认后,自动生成src/router.js
文件,相关路由配置文件就能够写在里面。
// 首先引入不一样的vue组件(默认安装了[vue-loader](https://vue-loader.vuejs.org/zh-cn/start/spec.html),每一个.vue文件当作一个完整的组件) import components1 from "./page/xx.vue" import components2 from "./page/xx2.vue" // router 数组用来定义路由配置,非嵌套路由 const routers = [ {path:'/foo',component:components1}, {path:'/bar',component:components2} ] // 最后抛出这个配置数组 export default routes;
注入到router配置参数里面
const app = new Vue({ router }).$mount("#app")
存在嵌套路由的时候,须要使用 children
配置,好比上面的components1
组件内部包含本身的嵌套<router-view>
,就可使用嵌套路由。
const routers = [ { path:'/foo', component:components1, children:[ // 当 /foo/detail 匹配成功后,component3 会被渲染在components1中 <router-view> 的位置 { path:'detail', component:component3 }, // 若是 /foo 匹配成功,没有匹配子路由,默认就会渲染这个空的子路由。 { path:'', component:component4 } ] } ]
router 实例方法
在Vue实例内部,能够经过this.$router
获取实例对象,
router.push({path:'/user',params:{id:'123'}})
跳转router.replace()
,与上面相同,不会添加新的记录。router.go(-1)
,表示在路由记录中前进或者后退多少步。在html <template></template>
中,能够经过 {{$route}},获取路由配置的相关信息,从而渲染DOM。好比: 经过
<template v-for="(items,index) in $router.options.routes"> <title> {{items.name}} </title> </template>
就能够将路由配置信息与页面导航栏对应,列表渲染出导航栏,
在watch
方法中监听$route
,能够动态配置组件,不一样url复用同一组件
watch:{ `$route`:function(to,from) { // 经过to,from 获取url信息 } }
导航守卫(路由钩子)
经过注册一个全局路由钩子函数,在初始化const router = new VueRouter({})
的时候,定义router.beforeEach((to,from,next)=> {...})
,在每次进入目标路由以前触发。配合Vuex
能够很是方便的进行权限管理
router.beforeEach((to, from, next) => { if (store.getters.getisAuthority) { // 检查已经登陆了,就继续跳转。 next() }else if(to.fullPath === "/login"){ // 跳转到登陆页面,则清空登陆相关信息 clearCookie() next() } else { next({ path:"/login" }); }})
Vue 提供一个 官方命令行工具,可用于快速搭建大型单页面应用
目前已经发布到了V3.0.0-alpha.5
npm install -g @vue/cli vue create my-project
基本用法,文档里面也比较清楚,参考这里,
经过下面几步,快速搭建项目基础结构。
# 全局安装 vue-cli $ npm install --global vue-cli # 建立一个基于 webpack 模板的新项目 $ vue init webpack my-project # 安装依赖,走你 $ cd my-project $ npm install/ yarn $ npm run dev
(若是没有使用任何框架的基础上,也想快速搭建一个大型项目的目录结构,能够考虑yeoman快速生成一个新的项目)
基础配置:
/config/index.js
文件中, 手动修改 module.exports = { dev:{ port :8080}}
配置代理
/config/index.js
目录下,(以代理3000端口上数据请求为例)
dev: { proxyTable: { '/rest/*':{ target:'http://127.0.0.1:3000', secure:false, pathRewrite:{ '^/rest':'' } } },
/config/index.js
目录下,build:{} 中的productionSourceMap
改成false
配置路径别名(alias)
一般在项目中会看到诸如这样的 import Cookie from "@/util/cookie.js"
的引入,@
就是vue-cli中默认设置的alias
在 /build/webpack.base.conf.js/
文件中,resolve
对象下添加属性,指向对应的路径
resolve:{ alias:{ 'page':path.resolve(__dirname,'../src/page') } }
区分不一样环境
经过process.env.NODE_ENV
值区分
在 /build/webpack.dev.conf.js
和 /build/webpack.prod.conf.js
中,经过
new webpack.DefinePlugin({ 'process.env': env }),
建立了编译时能够配置的全局常量,用来区分开发/发布/测试环境,env
对应的值,能够在/config/
目录下的,*.dev.js
文件下配置的。
而后,在其它业务代码里面,直接使用这个全局变量,好比在 main.js
里面:
if(process.env.NODE_ENV === "development"){ console.log("开发环境") }
经过命令行区分不一样环境
一样使用上面的方法,添加一个全局变量,不一样的是从命令行中获取参数。
好比,打包时还区分 发布环境 和 预发环境,就能够修改以下,
new webpack.DefinePlugin({ 'process.env': env, 'VERSION':process.argv[2] == "pro"?'"pro"':'"sit"' }),
在命令行中打包时,可使用 npm run build --env sit
,在业务代码中,经过全局变量VERSION
,一样能够区分不一样环境。
参考连接