AngelLover0 2019-06-26
Vue是一款前端开发框架,现在的框架本身是帮我们把一些重复的经过验证的模式,抽象到一个设计好的封装的API中,帮我们去应对这些复杂的问题。
但是,框架自身也会有复杂度,这里就抽象出一个问题,就是要做的应用的复杂度与所使用的框架的复杂度的对比。 进一步说,是所要解决的问题的内在复杂度,与所使用的工具的复杂度进行对比。
怎么看待前端框架的复杂度呢,现在的前端开发已经越来越工程化,可以根据下面对的图片分析
我们在任何情况下都需要响应式的渲染功能,并尽可能希望进行可变的命令式操作,尽可能让DOM的更新操作是自动的,状态变化的时候它就应该自动更新到正确的状态;我们需要组件系统,将一个大型的界面切分成一个一个更小得可控单元客户端路由--这是针对单页应用而言,不做就不需要,如果需要做单页应用,那么就需要有一个URL对应到一个应用的状态,那就需要有路由解决方案,大规模的状态管理————当应用简单的时候,可能一个很基础的状态和界面映射可以解决问题,但是当应用变得很大,恩,very large 。设计多人协作的时候
,就会涉及多个组件之间的共享,多个组件需要去改动同一份状态,以及如何使得这样大规模应用依然能够高效运行,这就涉及大规模状态管理的问题,当然也涉及到可维护性,还有构建工具,现在,如果放眼前端的未来,当HTTP2普及后,可能会带来构建工具的一次革命,但就目前而言,尤其是在中国的网络环境下,打包和工程构建依然是非常重要且不可避免的一个环节。
Vue非常专注的只做状态到界面映射,以及组件。
Vue的特点是有自己的配套工具,核心虽然只解决一个很小的问题。但它有生态圈及配套的可选工具,当把它们一个一个加进来,就可以组成非常强大的栈。就可以涵盖其他的这些更完整的框架所涵盖的问题。
(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命令创建一个新的项目,
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基本的模板和语法
<div id="app"> {{ message }} </div>
var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })
这样在页面中显示
Hello Vue!
然后,vue中基本的指令属性v-bind
,指令前都带有前缀v-
,以表示Vue提供的特殊属性,渲染在DOM上的特殊响应式行为。这里v-bind
的作用是,将这个元素节点的title
属性和Vue实例的message
属性保持一致
示例:
<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 等实例属性还无法使用。
background-color: blue;background-color: yellow;<input type="button" value="变蓝" @click="changeColorT