Vue是一款前端开发框架,如今的框架自己是帮咱们把一些重复的通过验证的模式,抽象到一个设计好的封装的API中,帮咱们去应对这些复杂的问题。
可是,框架自身也会有复杂度,这里就抽象出一个问题,就是要作的应用的复杂度与所使用的框架的复杂度的对比。 进一步说,是所要解决的问题的内在复杂度,与所使用的工具的复杂度进行对比。
怎么看待前端框架的复杂度呢,如今的前端开发已经愈来愈工程化,能够根据下面对的图片分析javascript
咱们在任何状况下都须要响应式的渲染功能,并尽量但愿进行可变的命令式操做,尽量让DOM的更新操做是自动的,状态变化的时候它就应该自动更新到正确的状态;咱们须要组件系统,将一个大型的界面切分红一个一个更小得可控单元客户端路由--这是针对单页应用而言,不作就不须要,若是须要作单页应用,那么就须要有一个URL对应到一个应用的状态,那就须要有路由解决方案,大规模的状态管理————当应用简单的时候,可能一个很基础的状态和界面映射能够解决问题,可是当应用变得很大,恩,very large 。设计多人协做的时候
,就会涉及多个组件之间的共享,多个组件须要去改动同一份状态,以及如何使得这样大规模应用依然可以高效运行,这就涉及大规模状态管理的问题,固然也涉及到可维护性,还有构建工具,如今,若是放眼前端的将来,当HTTP2普及后,可能会带来构建工具的一次革命,但就目前而言,尤为是在中国的网络环境下,打包和工程构建依然是很是重要且不可避免的一个环节。
Vue很是专一的只作状态到界面映射,以及组件。
Vue的特色是有本身的配套工具,核心虽然只解决一个很小的问题。但它有生态圈及配套的可选工具,当把它们一个一个加进来,就能够组成很是强大的栈。就能够涵盖其余的这些更完整的框架所涵盖的问题。html
(1)声明式渲染
如今基本全部的框架都已经认同这个见解————DOM应尽量是一个函数式到状态的映射,状态便是惟一的真相,而DOM状态只是数据状态的一个映射。全部的逻辑尽量在状态的层面去进行。当状态改变的时候。View应该是在框架帮助下自动更新到合理的状态。而不是说当你观测到数据变化以后手动选择一个元素,再命令式去改动它的属性。在Vue2.0中,渲染层的实现作了根本性的改动,就是引入了虚拟DOM。Vue的编译器在编模板以后。会把这些模板编译成一个渲染函数,而函数被调用的时候就会渲染而且返回一个虚拟DOM的树,这个树很是轻量,它负责描述当前界面所应处的状态,当咱们有了这个虚拟的树以后,再交给一个patch函数,负责把这些虚拟DOM真正施加到真实的DOM上。在这个过程当中,Vue有自身的响应式系统来侦测在渲染过程当中所依赖到的数据来源 。在渲染过程当中,侦测到数据来源时候,以后就能够精确感知数据源的变更,到时候就能够根据须要从新进行渲染,当从新进行渲染后,会生成一个新的树,将新树与旧树进行对比,就能够最终得出应施加到 真实DOM上的改动,最后再经过patch函数施加改动。
(2)组件系统
在Vue中,父子组件之间的通讯是经过Props传递。从父向子单向传递;而若是子组件想要在父组件做用里面产生反作用,就须要去派发事件。这样就造成一个基本的父子通讯模式,在涉及大规模状态管理的时候会有额外的方案。Vue的组件引入构建工具以后有一个单文件组件概念。
(3)客户端路由
在作一个界面复杂度很是的高应用时,会有不少的状态,这样的应用显然不可能在每作一次操做后都刷新一个页面做为用户反馈,这就要这个用用有多个复杂的状态,一样这些状态还要对应到URL。有一个重要的功能叫作deep-linking,也就是当用户浏览到一个URL ,把它穿给另外的人或者复制从新打开,应用须要直接渲染出这个URL对应的状态。这就意味着应用的url和组件树的状态之间有一个映射关系,客户端路由的职责就是让这个映射关系声明式地对应起来。
(4)状态管理前端
(5)构建工具
全局安装的vue-cli ,全局安装以后,就能够用Vue命令建立一个新的项目,vue
npm install -g vue-cli vue init webpack-simple my-app cd my-app npm my-app npm install #or yarn npm run dev
下面简单介绍一下vue基本的模板和语法java
<div id="app"> {{ message }} </div>
var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })
这样在页面中显示webpack
Hello Vue!web
而后,vue中基本的指令属性v-bind
,指令前都带有前缀v-
,以表示Vue提供的特殊属性,渲染在DOM上的特殊响应式行为。这里v-bind
的做用是,将这个元素节点的title
属性和Vue实例的message
属性保持一致vue-cli
示例:npm
<div id="app-2"> <span v-bind:title="message"> 鼠标悬停几秒钟查看此处动态绑定的提示信息! </span> </div>
var app2 = new Vue({ el: '#app-2', data: { message: '页面加载于 ' + new Date().toLocaleString() } })
再次打开浏览器的javascript控制台的输入app2.message='新消息',就会再一次看到这个绑定的title
属性的HTML已经发生了更新。数组
控制切换一个元素的显示也至关简单:
<div id="app-3"> <p v-if="seen">如今你看到我了</p> </div>
var app3 = new Vue({ el: '#app-3', data: { seen: true } })
如今你看到我了
继续在控制台设置app3.seen=false
,会发现消息消失了
说明既能够绑定DOM文本到数据,也能够绑定DOM结构到数据,并且Vue也提供一个强大的过渡效果系统。也能够在Vue插入/更新/删除元素时自动应用过分效果。
还有其余不少指令,每一个都有特殊的功能。例如,v-for
指令能够绑定数组的数据来渲染一个项目列表:
<div id="app-4"> <ol> <li v-for="todo in todos"> {{ todo.text }} </li> </ol> </div>
var app4 = new Vue({ el: '#app-4', data: { todos: [ { text: '学习 JavaScript' }, { text: '学习 Vue' }, { text: '整个牛项目' } ] } })
在控制台里,输入app4.todos.push({text:'新项目'})
,列表中添加一个新项
为了让用户和应用进行互动,能够用v-on
指令绑定一个事件监听器,经过它调用咱们Vue实例中定义方法:
<div id="app-5"> <p>{{ message }}</p> <button v-on:click="reverseMessage">逆转消息</button> </div>
var app5 = new Vue({ el: '#app-5', data: { message: 'Hello Vue.js!' }, methods: { reverseMessage: function () { this.message = this.message.split('').reverse().join('') } } })
注意在 reverseMessage
方法中,咱们更新了应用的状态,但没有触碰 DOM——全部的 DOM 操做都由 Vue 来处理,你编写的代码不须要关注底层逻辑。
Vue 还提供了 v-model
指令,它能轻松实现表单输入和应用状态之间的双向绑定。
<div id="app-6"> <p>{{ message }}</p> <input v-model="message"> </div>
var app6 = new Vue({ el: '#app-6', data: { message: 'Hello Vue!' } })
Vue组件能够扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些状况下,组件也能够表现为用 is 特性进行了扩展的原生 HTML 元素。
组件的使用能够为全局注册和局部注册。
要注册一个全局组件,可使用 Vue.component(tagName, options)
。例如:
Vue.component('my-component', { // 选项 })
组件在注册以后,即可以做为自定义元素 <my-component></my-component>
在一个实例的 模板中使用。注意确保在初始化根实例以前注册组件:
<div id="example"> <my-component></my-component> </div>
// 注册 Vue.component('my-component', { template: '<div>A custom component!</div>' }) // 建立根实例 new Vue({ el: '#example' })
渲染为:
<div id="example"> <div>A custom component!</div> </div>
局部注册,能够经过某个Vue实例或组件实例将其注册仅在其做用域内可用的组件
var Child = { template: '<div>A custom component!</div>' } new Vue({ // ... components: { // <my-component> 将只在父组件模板中可用 'my-component': Child } })
在vue实例(咱们也叫做根组件)下能够新建子组件,子组件下还能够新建子组件,这样层层嵌套,能够将其内部的组件构成一个系统能够成为组件树,咱们能够看一个实例:
首先,咱们new 一个 vue的实例,在里面自定义三个子组件:
var vm = new Vue({ el: '#app',//挂载DOM元素 components: { AppHead, AppMain, AppSide } });
接着,咱们在html页面内的挂载元素内添加子组件
<div id="app"> <app-head></app-head> <app-main></app-main> <app-side></app-side> </div>
注意,在这里咱们根据ES6的规范,驼峰式命名要转成 ‘-’ 加小写字母 ,而后在script里面注册一下咱们刚刚添加的组件
var AppHead = { template: `<div class='app-head'></div>` } var AppMain = { template: `<div class='app-main'></div>`, components:{ AppMainUnit } } var AppSide = { template: `<div class='app-side'></div>`, components:{ AppSideUnit } }
同时的,咱们在AppMain和AppSide 里面也自定义了下面的子组件,AppMainUnit和AppSideUnit。
一样要分别在他们的父组件的前面注册他们:
var AppMainUnit={ template:`<div class='app-main-unit'></div>` } var AppMain = { template: `<div class='app-main'><app-main-unit></app-main-unit><app-main-unit></app-main-unit></div>`, components:{ AppMainUnit } } var AppSideUnit={ template: `<div class='app-side-unit'></div>`, } var AppSide = { template: `<div class='app-side'><app-side-unit></app-side-unit><app-side-unit></app-side-unit></div>`, components:{ AppSideUnit } }
这样,最后的结果:
<Root> <AppHead> <AppMain> <AppMainUnit> <AppMainUnit> <AppSide> <AppSideUnit> <AppSideUnit>
建立Vue实例的时候大多数选项均可以在组件中使用,可是data除外,让咱们来看一下实例:
<div id="example-2"> <simple-counter></simple-counter> <simple-counter></simple-counter> <simple-counter></simple-counter> </div>
var data = { counter: 0 } Vue.component('simple-counter', { template: '<button v-on:click="counter += 1">{{ counter }}</button>', // 技术上 data 的确是一个函数了,所以 Vue 不会警告, // 可是咱们却给每一个组件实例返回了同一个对象的引用 data: function () { return data } }) new Vue({ el: '#example-2' })
因为这三个组件实例共享了同一个data对象,所以递增一个counter会影响全部组件
因此,咱们应该返回一个对象里面存放数据:
data: function () { return { counter: 0 } }
如今每一个counter都会有本身内部的状态了
组件需配合使用,最多见的就是父子组件,一个组价A在它的模板中使用了组件B,那么A和B之间必然要发生通信,父组件要把数据下发子组件吗,子组件要告知父组件监听其内部发生的事件,经过一个良好定义的接口来尽量将父子组件解耦也是很重要的。这保证了每一个组件的代码能够在相对隔离的环境中书写和理解,从而提升了其可维护性和复用性。父子组件的关系能够总结为 prop 向下传递,事件向上传递。父组件经过 prop 给子组件下发数据,子组件经过事件给父组件发送消息。
使用prop传送数据
在Vue组件实例做用域是孤立的,全部子组件不能直接访问父组件内的数据,这样咱们就应该经过prop从父组件向下传递数据。
Vue.component('child', { // 声明 props props: ['message'], // 就像 data 同样,prop 也能够在模板中使用 // 一样也能够在 vm 实例中经过 this.message 来使用 template: '<span>{{ message }}</span>' })
传入一个普通的字符串
<child message="hello!"></child>
或者说,在父组件中的data中加数据
data:{ mymessage:[1,2,2,3,4,5,6] },
在子组件prop中添加以下:
props: ['message','myMessage'],
而后
<child message='hello' :my-message='mymessage'></child>
template: '<span>{{ message }}{{myMessage}}</span>'
恩,这样就能够经过V-bind动态绑定的my-message属性访问到父组件中的mymessage
属性了。
若是props传回的数据是对象,咱们还能够经过遍历将里面的每一项列出。
template:"<div><span v-for='(item,index) in myMessage'>{{item}}</span></div>"
注意:遍历的时候不要将v-for
绑到根元素上,由于模板根元素默认只能有一个。
prop是单向绑定的,当父组件的属性发生变化时,会传递给子组件,而子组件的改变却不会影响到父组件。这是为了防止子组件无心间修改了父组件的状态,来避免应用的数据流变得难以理解。
props的改变无非就是两种状况
Prop 做为初始值传入后,子组件想把它看成局部数据来用;
Prop 做为原始数据传入,由子组件处理成其它数据输出。
先看第一种,这个时候,定义一个局部变量,用prop的值去初始化它
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }
这样就能够对counter
进行处理
第二种使用计算属性对prop进行处理输出
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,若是 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。
能够为组件的prop提供验证规则,要指定验证规则,须要用对象的形式来定义 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
当验证失败的时候,Vue就会发出警告,注意 prop 会在组件实例建立以前进行校验,因此在 default 或 validator 函数里,诸如 data、computed 或 methods 等实例属性还没法使用。