U19和U50谁的谁有小米6性价比高高

1489人阅读
vuejs(16)
什么是组件?
组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。
之前说过,我们可以用 Vue.extend() 创建一个组件构造器:
var MyComponent = Vue.extend({
要把这个构造器用作组件,需要用 `ponent(tag, constructor)` **注册** :
ponent('my-component', MyComponent)
&p class="tip"&对于自定义标签名字,Vue.js 不强制要求遵循 (小写,并且包含一个短杠),尽管遵循这个规则比较好。
组件在注册之后,便可以在父实例的模块中以自定义元素 &my-component& 的形式使用。要确保在初始化根实例之前注册了组件:
id="example"&
var MyComponent = Vue.extend({
template: '&div&A custom component!&/div&'
ponent('my-component', MyComponent)
el: '#example'
id="example"&
&A custom component!&
注意组件的模板替换了自定义元素,自定义元素的作用只是作为一个挂载点。可以用实例选项 replace 决定是否替换。
不需要全局注册每个组件。可以让组件只能用在其它组件内,用实例选项 components 注册:
var Child = Vue.extend({
var Parent = Vue.extend({
template: '...',
components: {
'my-component': Child
这种封装也适用于其它资源,如指令、过滤器和过渡。
注册语法糖
为了让事件更简单,可以直接传入选项对象而不是构造器给 <ponent() 和 component 选项。Vue.js 在背后自动调用 Vue.extend():
ponent('my-component', {
template: '&div&A custom component!&/div&'
var Parent = Vue.extend({
components: {
'my-component': {
template: '&div&A custom component!&/div&'
组件选项问题
传入 Vue 构造器的多数选项也可以用在 Vue.extend() 中,不过有两个特例: data 和 el。试想如果我们简单地把一个对象作为 data 选项传给 Vue.extend():
var data = { a: 1 }
var MyComponent = Vue.extend({
data: data
这么做的问题是 `MyComponent` 所有的实例将共享同一个 `data` 对象!这基本不是我们想要的,因此我们应当使用一个函数作为 `data` 选项,让这个函数返回一个新对象:
var MyComponent = Vue.extend({
data: function () {
return { a: 1 }
同理,`el` 选项用在 `Vue.extend()` 中时也须是一个函数。
Vue 的模板是 DOM 模板,使用浏览器原生的解析器而不是自己实现一个。相比字符串模板,DOM 模板有一些好处,但是也有问题,它必须是有效的 HTML 片段。一些 HTML 元素对什么元素可以放在它里面有限制。常见的限制:
a 不能包含其它的交互元素(如按钮,链接)
ul 和 ol 只能直接包含 li
select 只能包含 option 和 optgroup
table 只能直接包含 thead, tbody, tfoot, tr, caption, col, colgroup
tr 只能直接包含 th 和 td
在实际中,这些限制会导致意外的结果。尽管在简单的情况下它可能可以工作,但是你不能依赖自定义组件在浏览器验证之前的展开结果。例如 &my-select&&option&...&/option&&/my-select& 不是有效的模板,即使 my-select 组件最终展开为 &select&...&/select&。
另一个结果是,自定义标签(包括自定义元素和特殊标签,如 &component&、&template&、 &partial& )不能用在 ul, select, table 等对内部元素有限制的标签内。放在这些元素内部的自定义标签将被提到元素的外面,因而渲染不正确。
对于自定义元素,应当使用 is 特性:
is="my-component"&&
`` 不能用在 `` 内,这时应使用 ``,`` 可以有多个 ``:
v-for="item in items"&
&Even row&
使用 Props 传递数据
组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用 props 把数据传给子组件。
“prop” 是组件数据的一个字段,期望从父组件传下来。子组件需要显式地用
声明 props:
ponent('child', {
props: ['msg'],
template: '&span&{{ msg }}&/span&'
然后向它传入一个普通字符串:
msg="hello!"&&
驼峰式vs.横杠式
HTML 特性不区分大小写。名字形式为 camelCase 的 prop 用作特性时,需要转为 kebab-case(短横线隔开):
ponent('child', {
props: ['myMessage'],
template: '&span&{{ myMessage }}&/span&'
my-message="hello!"&&
动态 Props
类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 v-bind 绑定动态 Props 到父组件的数据。每当父组件的数据变化时,也会传导给子组件:
v-model="parentMsg"&
v-bind:my-message="parentMsg"&&
使用 `v-bind` 的缩写语法通常更简单:
:my-message="parentMsg"&&
字面量语法 vs. 动态语法
初学者常犯的一个错误是使用字面量语法传递数值:
some-prop="1"&&
因为它是一个字面 prop,它的值以字符串 `”1”` 而不是以实际的数字传下去。如果想传递一个实际的 JavaScript 数字,需要使用动态语法,从而让它的值被当作 JavaScript 表达式计算:
:some-prop="1"&&
Prop 绑定类型
prop 默认是单向绑定:当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。不过,也可以使用 .sync 或 .once 绑定修饰符显式地强制双向或单次绑定:
比较语法:
:msg="parentMsg"&&
:msg.sync="parentMsg"&&
:msg.once="parentMsg"&&
双向绑定会把子组件的 msg 属性同步回父组件的 parentMsg 属性。单次绑定在建立之后不会同步之后的变化。
注意如果 prop 是一个对象或数组,是按引用传递。在子组件内修改它会影响父组件的状态,不管是使用哪种绑定类型。
组件可以为 props 指定验证要求。当组件给其他人使用时这很有用,因为这些验证要求构成了组件的 API,确保其他人正确地使用组件。此时 props 的值是一个对象,包含验证要求:
ponent('example', {
propA: Number,
propM: [String, Number],
type: String,
required: true
type: Number,
default: 100
type: Object,
default: function () {
return { msg: 'hello' }
twoWay: true
validator: function (value) {
return value & 10
coerce: function (val) {
return val + ''
coerce: function (val) {
return JSON.parse(val)
type 可以是下面原生构造器:
type 也可以是一个自定义构造器,使用 instanceof 检测。
当 prop 验证失败了,Vue 将拒绝在子组件上设置此值,如果使用的是开发版本会抛出一条警告。
父子组件通信
子组件可以用 this.$parent 访问它的父组件。根实例的后代可以用 this.$root 访问它。父组件有一个数组 this.$children,包含它所有的子元素。
尽管可以访问父链上任意的实例,不过子组件应当避免直接依赖父组件的数据,尽量显式地使用 props 传递数据。另外,在子组件中修改父组件的状态是非常糟糕的做法,因为:
这让父组件与子组件紧密地耦合;
只看父组件,很难理解父组件的状态。因为它可能被任意子组件修改!理想情况下,只有组件自己能修改它的状态。
自定义事件
Vue 实例实现了一个自定义事件接口,用于在组件树中通信。这个事件系统独立于原生 DOM 事件,用法也不同。
每个 Vue 实例都是一个事件触发器:
使用 $on() 监听事件;
使用 $emit() 在它上面触发事件;
使用 $dispatch() 派发事件,事件沿着父链冒泡;
使用 $broadcast() 广播事件,事件向下传导给所有的后代。
不同于 DOM 事件,Vue 事件在冒泡过程中第一次触发回调之后自动停止冒泡,除非回调明确返回 true。
简单例子:
id="child-template"&
v-model="msg"&
v-on:click="notify"&Dispatch Event&
id="events-example"&
&Messages: {{ messages | json }}&
ponent('child', {
template: '#child-template',
data: function () {
return { msg: 'hello' }
methods: {
notify: function () {
if (this.msg.trim()) {
this.$dispatch('child-msg', this.msg)
this.msg = ''
var parent = new Vue({
el: '#events-example',
messages: []
'child-msg': function (msg) {
this.messages.push(msg)
使用 v-on 绑定自定义事件
上例非常好,不过从父组件的代码中不能直观的看到 "child-msg" 事件来自哪里。如果我们在模板中子组件用到的地方声明事件处理器会更好。为此子组件可以用 v-on 监听自定义事件:
v-on:child-msg="handleIt"&&
这样就很清楚了:当子组件触发了 `”child-msg”` 事件,父组件的 `handleIt` 方法将被调用。所有影响父组件状态的代码放到父组件的 `handleIt` 方法中;子组件只关注触发事件。
子组件索引
尽管有 props 和 events,但是有时仍然需要在 JavaScript 中直接访问子组件。为此可以使用 v-ref 为子组件指定一个索引 ID。例如:
id="parent"&
v-ref:profile&&
var parent = new Vue({ el: '#parent' })
var child = parent.$refs.profile
v-ref 和 v-for 一起用时,ref 是一个数组或对象,包含相应的子组件。
使用 Slot 分发内容
在使用组件时,常常要像这样组合它们:
注意两点:
&app& 组件不知道它的挂载点会有什么内容,挂载点的内容是由 &app& 的父组件决定的。
&app& 组件很可能有它自己的模板。
为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个处理称为内容分发(或 “transclusion”,如果你熟悉 Angular)。Vue.js 实现了一个内容分发 API,参照了当前 ,使用特殊的 &slot& 元素作为原始内容的插槽。
编译作用域
在深入内容分发 API 之前,我们先明确内容的编译作用域。假定模板为:
msg 应该绑定到父组件的数据,还是绑定到子组件的数据?答案是父组件。组件作用域简单地说是:
父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译
一个常见错误是试图在父组件模板内将一个指令绑定到子组件的属性/方法:
v-show="someChildProperty"&&
假定 someChildProperty 是子组件的属性,上例不会如预期那样工作。父组件模板不应该知道子组件的状态。
如果要绑定子组件内的指令到一个组件的根节点,应当在它的模板内这么做:
ponent('child-component', {
template: '&div v-show="someChildProperty"&Child&/div&',
data: function () {
someChildProperty: true
类似地,分发内容是在父组件作用域内编译。
父组件的内容将被抛弃,除非子组件模板包含 &slot&。如果子组件模板只有一个没有特性的 slot,父组件的整个内容将插到 slot 所在的地方并替换它。
&slot& 标签的内容视为回退内容。回退内容在子组件的作用域内编译,当宿主元素为空并且没有内容供插入时显示这个回退内容。
假定 my-component 组件有下面模板:
&This is my component!&
如果没有分发内容则显示我。
父组件模板:
&This is some original content&
&This is some more original content&
渲染结果:
&This is my component!&
&This is some original content&
&This is some more original content&
&slot& 元素可以用一个特殊特性 name 配置如何分发内容。多个 slot 可以有不同的名字。具名 slot 将匹配内容片段中有对应 slot 特性的元素。
仍然可以有一个匿名 slot,它是默认 slot,作为找不到匹配的内容片段的回退插槽。如果没有默认的 slot,这些找不到匹配的内容片段将被抛弃。
例如,假定我们有一个 multi-insertion 组件,它的模板为:
name="one"&&
name="two"&&
父组件模板:
slot="one"&One&
slot="two"&Two&
&Default A&
渲染结果为:
slot="one"&One&
&Default A&
slot="two"&Two&
在组合组件时,内容分发 API 是非常有用的机制。
多个组件可以使用同一个挂载点,然后动态地在它们之间切换。使用保留的 &component& 元素,动态地绑定到它的 is 特性:
el: 'body',
currentView: 'home'
components: {
archive: {
:is="currentView"&
keep-alive
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:
:is="currentView" keep-alive&
activate 钩子
在切换组件时,切入组件在切入前可能需要进行一些异步操作。为了控制组件切换时长,给切入组件添加 activate 钩子:
ponent('activate-example', {
activate: function (done) {
var self = this
loadDataAsync(function (data) {
self.someData = data
注意 `activate` 钩子只作用于动态组件切换或静态组件初始化渲染的过程中,不作用于使用实例方法手工插入的过程中。
transition-mode
transition-mode 特性用于指定两个动态组件之间如何过渡。
在默认情况下,进入与离开平滑地过渡。这个特性可以指定另外两种模式:
in-out:新组件先过渡进入,等它的过渡完成之后当前组件过渡出去。
out-in:当前组件先过渡出去,等它的过渡完成之后新组件过渡进入。
:is="view"
transition="fade"
transition-mode="out-in"&
.fade-transition {
transition: opacity .3s ease;
.fade-enter, .fade-leave {
opacity: 0;
组件和 v-for
自定义组件可以像普通元素一样直接使用
v-for="item in items"&&
但是,不能传递数据给组件,因为组件的作用域是孤立的。为了传递数据给组件,应当使用 props:
v-for="item in items"
:item="item"
:index="$index"&
不自动把 item 注入组件的原因是这会导致组件跟当前 v-for 紧密耦合。显式声明数据来自哪里可以让组件复用在其它地方。
编写可复用组件
在编写组件时,记住是否要复用组件有好处。一次性组件跟其它组件紧密耦合没关系,但是可复用组件应当定义一个清晰的公开接口。
Vue.js 组件 API 来自三部分——prop,事件和 slot:
prop 允许外部环境传递数据给组件;
事件 允许组件触发外部环境的 action;
slot 允许外部环境插入内容到组件的视图结构内。
使用 v-bind 和 v-on 的简写语法,模板的缩进清楚且简洁:
:foo="baz"
:bar="qux"
@event-a="doThis"
@event-b="doThat"&
slot="icon" src="..."&
slot="main-text"&Hello!&
在大型应用中,我们可能需要将应用拆分为小块,只在需要时才从服务器下载。为了让事情更简单,Vue.js 允许将组件定义为一个工厂函数,动态地解析组件的定义。Vue.js 只在组件需要渲染时触发工厂函数,并且把结果缓存起来,用于后面的再次渲染。例如:
ponent('async-example', function (resolve, reject) {
setTimeout(function () {
template: '&div&I am async!&/div&'
工厂函数接收一个 resolve 回调,在收到从服务器下载的组件定义时调用。也可以调用 reject(reason) 指示加载失败。这里 setTimeout 只是为了演示。怎么获取组件完全由你决定。推荐配合使用 :
ponent('async-webpack-example', function (resolve) {
require(['./my-async-component'], resolve)
资源命名约定
一些资源,如组件和指令,是以 HTML 特性或 HTML 自定义元素的形式出现在模板中。因为 HTML 特性的名字和标签的名字不区分大小写,所以资源的名字通常需使用 kebab-case 而不是 camelCase 的形式,这不大方便。
Vue.js 支持资源的名字使用 camelCase 或 PascalCase 的形式,并且在模板中自动将它们转为 kebab-case(类似于 prop 的命名约定):
components: {
myComponent: {
也没问题:
import TextBox from './components/text-box';
import DropdownMenu from './components/dropdown-menu';
export default {
components: {
DropdownMenu
组件在它的模板内可以递归地调用自己,不过,只有当它有 name 选项时才可以:
var StackOverflow = Vue.extend({
name: 'stack-overflow',
'&stack-overflow&&/stack-overflow&' +
上面组件会导致一个错误 “max stack size exceeded”,所以要确保递归调用有终止条件。当使用 <ponent() 全局注册一个组件时,组件 ID 自动设置为组件的 name 选项。
在使用 template 选项时,模板的内容将替换实例的挂载元素。因而推荐模板的顶级元素始终是单个元素。
不这么写模板:
&root node 1&
&root node 2&
推荐这么写:
I have a single root node!
下面几种情况会让实例变成一个片断实例:
模板包含多个顶级元素。
模板只包含普通文本。
模板只包含其它组件(其它组件可能是一个片段实例)。
模板只包含一个元素指令,如 &partial& 或 vue-router 的 &router-view&。
模板根节点有一个流程控制指令,如 v-if 或 v-for。
这些情况让实例有未知数量的顶级元素,它将把它的 DOM 内容当作片断。片断实例仍然会正确地渲染内容。不过,它没有一个根节点,它的 $el 指向一个锚节点,即一个空的文本节点(在开发模式下是一个注释节点)。
但是更重要的是,组件元素上的非流程控制指令,非 prop 特性和过渡将被忽略,因为没有根元素供绑定:
v-show="ok" transition="fade"&&
:prop="someData"&&
v-if="ok"&&
当然片断实例有它的用处,不过通常给组件一个根节点比较好。它会保证组件元素上的指令和特性能正确地转换,同时性能也稍微好些。
如果子组件有 inline-template 特性,组件将把它的内容当作它的模板,而不是把它当作分发内容。这让模板更灵活。
inline-template&
&These are compiled as the component's own template&
&Not parent's transclusion content.&
但是 inline-template 让模板的作用域难以理解,并且不能缓存模板编译结果。最佳实践是使用 template 选项在组件内定义模板。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:600749次
积分:9492
积分:9492
排名:第1472名
原创:295篇
转载:53篇
评论:183条
文章:54篇
阅读:207289
文章:18篇
阅读:25317
阅读:7924
阅读:11461
文章:11篇
阅读:13419
文章:37篇
阅读:80730
(4)(4)(5)(10)(29)(16)(9)(2)(15)(8)(23)(44)(38)(56)(8)(22)(16)(8)(6)(11)(8)(1)(5)(7)公司目前制作一个H5活动,特别是有一定统一结构的活动,都要码一个重复的轮子。后来接到一个基于模板的活动设计系统的需求,便有了下面的内容。借油开车。
需求一到,接就是怎么实现,技术选型自然成为了第一个问题。鉴于目前web前端mvvm框架以及组件化开发方式的流行,决定技术栈采用:vue + es6 + 组件化。
这里首先简单说下web前端组件化开发方式的历程:
最早的组件化结构,代码结构可能如下:
- lib/components/calendar
|- calendar.css
|- calendar.js
|- calendar.html
将同功能的组件文件放到同一目录下,结构清晰、职责明确,视图、样式、脚本的关系显著,也易于单元测试,是独立展示和交互的最小单元。
在之前基础上对组件进行了生命周期的加工(初始化、获取资源、渲染、更新、销毁等),理顺了组件的各个阶段,有助于对组件实现(从初始化到销毁)的理解。并且借助于组件各个阶段的钩子可以对组件有更好的利用和扩展。对外暴露接口,数据绑定或者说数据仓库的加入,各种xMD模块加载器的出现,也让这种这种开发方式上升了一个层级。ExtJs、YUI等都是这方面的专家。
有了之前发展,进步是很大的,但依然不够。组件的可复用性(基础样式,基础逻辑,基础属性、可复用的稳定业务逻辑等)、组件间通信、全局状态管理、甚至是能否有更好的代码组织方式等依然是问题。Angular、React、Polymer、Vue等mvvm框架和webpack、browserify等构建、预编译工具的出现正试图解决这些问题。
在正式开始vue之前,因为本项目用到了es6,那么就谈谈大家都关注的EcmaScript6。多余的就不说了,es6经历了多年的苦,终于在2015年下半年定稿,正式名称:EcmaScript2015。每个刚开始接触es6的人应该都有这么一个问题,es6的出现到底是为了什么,或者说它解决了什么。老版本es4/5虽然坑多,就像Brendan Eich评价js一样:"优秀之处并非原创,原创之处并不优秀"。但我们不也是去其槽粕,留其精髓,一路填坑走过了吗?
来直接一点,es6常用的特性有:class类的支持、箭头函数、对象和数组的解构、默认参数、不定参数、对象合并、let与const关键字、for of迭代、字符串模板、对象字面量增强、同名对象字面量缩写、模块化import/export、map、promise、* yeild生成器等。
这里挑出几个常用的简单说下:
首先class:
在没有class的时候,创建类的一种比较标准的方式是将非函数的属性放到构造函数里,函数属性在原型链里添加。类的继承的实现就更为多样:对象冒充、call/apply方式、原型链方式等。es6的class和extends关键字的出现给出了一个统一的规范
class People {
constructor (name, age, gender){
this.name = name
sayName (){
return this.name
class Student extends People {
constructor (name, age, gender, skill){
super(name, age, gender)
this.skill = skill
saySkill (){
return this.skill
let tom = new Student('tom', 16, 'male', 'computer')
tom.sayName()
// =& 'tom'
tom.saySkill()
// =& 'computer'
tom.__proto__ == Student.prototype // =& true
Student.__proto__ == People // =& true
可以看出虽然是新的规范,但是还是遵守js的原则:对象的__proto__指向它的构造函数(类)的prototype。es6对象字面量的__proto__注入也能快速的实现继承。
接下来是let:
es6之前js只有函数作用域,let的出现有了块级作用域,也就算是if、else、for这类也有了作用域,块内用let声明的变量外面是访问不到的,在js预解析的时候,是不会被提升到当前函数作用域的前面的。基于该特性,在for迭代的时候,每次迭代都会产生一个块级作用域的独立的迭代变量,让最后的结果就是我们期待的结果。
var arr = [];
for (let i = 0; i & 10; i ++){
arr[i] = function (){
//如果用var声明i,无论多少次迭代,外层的i始终被每次迭代的函数内部引用着(闭包),不会被当做垃圾回收,最后的结果都指向同一个i,值为10。
//以往为了避免这个问题,通常会这么做:
for (var i = 0; i & 10; i ++){
arr[i] = (function (i){
return function (){
最后讲讲箭头函数:
es6之前的function有一个特点:函数内部的上下文并不是由该函数写在那里决定的,而是由谁调用决定的,谁调用函数内部的this就指向谁。然后我们有些时候并不想让他这样,但又没办法,只能通过先保存this,或者call/apply,或者bind来调整上下文。箭头函数的出现解决了这个宁人苦恼的问题,因为箭头函数内的上线文(this)是由函数写在哪决定的,无论被哪个对象调用,上下文都不会改变。
var obj = {
test1 : function (){
window.setTimeout(function (){
test2 : function (){
window.setTimeout(() =& {
obj.test1() // =& Window
obj.test2() // =& obj
用普通函数还是箭头函数并非绝对,箭头函数也不能完全替代普通函数,要用哪个由具体逻辑决定,前提是要先了解他们的区别。
箭头函数还有一个特点就是能够简化return的书写。
var a = function (n){
var b = (n) =& n //可以省略return和花括号
var c = n =& n //如果只有一个参数,中括号也可以省略
&从这几个简单的例子可以看出,es6不仅仅是新增了几颗糖,对之前js的一些不友好的地方的改善才是重点。
进入正题,
Vue.js(读音 /vju:/, 类似于&view)是一个构建数据驱动的 web 界面的库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。
Vue.js 自身不是一个全能框架&&它只聚焦于视图层。因此它非常容易学习,非常容易与其它库或已有项目整合。另一方面,在与相关工具和支持库一起使用时,Vue.js 也能完美地驱动复杂的单页应用。
& 文中关于vue的大部分内容引用自vue的,感谢作者的工作!
响应的数据绑定:
&div id="app"&
&p&{{ message }}&/p&
&input v-model="message"&
el : '#app',
message : 'Hello Vue.js!'
结果:改变输入框的值,&p&标签的文本也会对应改变。
基本工作原理:&input&输入框的值与vue实例的message属性进行了绑定,&p&标签的文本也与message属性进行了绑定。输入框值的变化会改变message的值,message值的变化会反应到&p&标签的文本上。
Vue.js 的核心是一个响应的数据绑定系统,它让数据与 DOM 保持同步非常简单。在使用 jQuery 手工操作 DOM 时,我们的代码常常是命令式的、重复的与易错的。Vue.js 拥抱数据驱动的视图概念。通俗地讲,它意味着我们在普通 HTML 模板中使用特殊的语法将 DOM &绑定&到底层数据。一旦创建了绑定,DOM 将与数据保持同步。每当修改了数据,DOM 便相应地更新。这样我们应用中的逻辑就几乎都是直接修改数据了,不必与 DOM 更新搅在一起。这让我们的代码更容易撰写、理解与维护。
组件系统:
组件系统是 Vue.js 另一个重要概念,因为它提供了一种抽象,让我们可以用独立可复用的小组件来构建大型应用。如果我们考虑到这点,几乎任意类型的应用的界面都可以抽象为一个组件树:
实际上,一个典型的用 Vue.js 构建的大型应用将形成一个组件树。
你可能已经注意到 Vue.js 组件非常类似于自定义元素&&它是 Web 组件规范的一部分。实际上 Vue.js 的组件语法参考了该规范。例如 Vue 组件实现了 Slot API 与 is 特性。但是,有几个关键的不同:
Web 组件规范仍然远未完成,并且没有浏览器实现。相比之下,Vue.js 组件不需要任何补丁,并且在所有支持的浏览器(IE9 及更高版本)之下表现一致。必要时,Vue.js 组件也可以放在原生自定义元素之内。
Vue.js 组件提供了原生自定义元素所不具备的一些重要功能,比如组件间的数据流,自定义事件系统,以及动态的、带特效的组件替换。
组件系统是用 Vue.js 构建大型应用的基础。另外,Vue.js 生态系统也提供了高级工具与多种支持库,它们和 Vue.js 一起构成了一个更加&框架&性的系统。
这里简单介绍下vue最常用也较重要的两块:响应式原理和组件系统。
响应式原理:
Vue.js的数据观测实现原理和Angular有着本质的不同。了解Angular的读者可能知道,Angular的数据观测采用的是脏检查(dirty checking)机制。每一个指令都会有一个对应的用来观测数据的对象,叫做watcher;一个作用域中会有很多个watcher。每当界面需要更新时,Angular会遍历当前作用域里的所有watcher,对它们一一求值,然后和之前保存的旧值进行比较。如果求值的结果变化了,就触发对应的更新,这个过程叫做digest cycle。
脏检查有两个问题:
1.任何数据变动都意味着当前作用域的每一个watcher需要被重新求值,因此当watcher的数量庞大时,应用的性能就不可避免地受到影响,并且很难优化。2.当数据变动时,框架并不能主动侦测到变化的发生,需要手动触发digest cycle才能触发相应的DOM 更新。Angular通过在DOM事件处理函数中自动触发digest cycle部分规避了这个问题,但还是有很多情况需要用户手动进行触发。
Vue.js采用的则是基于依赖收集的观测机制。从原理上来说,和老牌MVVM框架Knockout是一样的。依赖收集的基本原理是:
1.将原生的数据改造成 &可观察对象&。一个可观察对象可以被取值,也可以被赋值。2.在watcher的求值过程中,每一个被取值的可观察对象都会将当前的watcher注册为自己的一个订阅者,并成为当前watcher的一个依赖。3.当一个被依赖的可观察对象被赋值时,它会通知所有订阅自己的watcher重新求值,并触发相应的更新。4.依赖收集的优点在于可以精确、主动地追踪数据的变化,不存在上述提到的脏检查的两个问题。但传统的依赖收集实现,比如Knockout,通常需要包裹原生数据来制造可观察对象,在取值和赋值时需要采用函数调用的形式,在进行数据操作时写法繁琐,不够直观;同时,对复杂嵌套结构的对象支持也不理想。
Vue.js利用了ES5的Object.defineProperty方法,直接将原生数据对象的属性改造为getter和setter(这是ES5的特性,需要js解释引擎的支持,无法通过各种打shim补丁来实现。这也是为什么Vue不支持IE8及以下版本的原因),在这两个函数内部实现依赖的收集和触发,而且完美支持嵌套的对象结构。对于数组,则通过包裹数组的可变方法(比如push)来监听数组的变化。这使得操作Vue.js的数据和操作原生对象几乎没有差别[注:在添加/删除属性,或是修改数组特定位置元素时,需要调用特定的函数,如obj.$add(key, value)才能触发更新。这是受ES5的语言特性所限。在操作对象类型数据的时候一定要注意这点,否则无法实现响应。
变化检测:
受 ES5 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。例如:
var data = {a : 1}
var vm = new Vue({
data : data
//vm.a 和 data.a 现在是响应的
//vm.b 不是响应的
data.b = 2
//data.b 不是响应的
不过,有办法在实例创建之后添加属性并且让它是响应的。
对于 Vue 实例,可以使用 $set(key, value) 实例方法:
vm.$set('b', 2)
//vm.b 和 data.b 现在是响应的
对于普通数据对象,可以使用全局方法 Vue.set(object, key, value):
Vue.set(data, 'c', 3)
//vm.c 和 data.c 现在是响应的
有时你想向已有对象上添加一些属性,例如使用 Object.assign() 或 _.extend() 添加属性。但是,添加到对象上的新属性不会触发更新。这时可以创建一个新的对象,包含原对象的属性和新的属性:
// 不使用 Object.assign(this.someObject, {a : 1,b : 2})
this.someObject = Object.assign({}, this.someObject, {a : 1, b : 2})
计算属性的奥秘:
你应该注意到 Vue.js 的计算属性不是简单的 getter。计算属性持续追踪它的响应依赖。在计算一个计算属性时,Vue.js 更新它的依赖列表并缓存结果,只有当其中一个依赖发生了变化,缓存的结果才无效。因此,只要依赖不发生变化,访问计算属性会直接返回缓存的结果,而不是调用 getter。
为什么要缓存呢?假设我们有一个高耗计算属性 A,它要遍历一个巨型数组并做大量的计算。然后,可能有其它的计算属性依赖 A。如果没有缓存,我们将调用 A 的 getter 许多次,超过必要次数。
由于计算属性被缓存了,在访问它时 getter 不总是被调用。考虑下例:
var vm = new Vue({
msg : 'hi'
computed : {
example : function (){
return Date.now() + this.msg
计算属性 example 只有一个依赖:&vm.msg&。&Date.now()& 不是 响应依赖,因为它跟 Vue 的数据观察系统无关。因而,在访问 &vm.example&&时将发现时间戳不变,除非 &vm.msg&&变了。
有时希望 getter 不改变原有的行为,每次访问 &vm.example&&时都调用 getter。这时可以为指定的计算属性关闭缓存:
computed : {
example : {
cache : false,
get : function (){
return Date.now() + this.msg
现在每次访问 &vm.example&&时,时间戳都是新的。但是,只是在 JavaScript 中访问是这样的;数据绑定仍是依赖驱动的。如果在模块中这样绑定计算属性 &{{example}}&,只有响应依赖发生变化时才更新DOM。
组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。
1.创建和注册组件:
可以用 &Vue.extend()&&创建一个组件构造器:
var MyComponent = Vue.extend({
template : '&div&A custom component!&/div&'
要把这个构造器用作组件,需要用 &ponent(tag, constructor)&&注册(这个注册是全局的):
//全局注册组件,tag 为 my-component
ponent('my-component',
MyComponent)
组件在注册之后,便可以在父实例的模块中以自定义元素 &&my-component& &的形式使用。要确保在初始化根实例之前注册了组件:
&div id="example"&
&my-component&&/my-component&
最后渲染为:
&div id="example"&
&div&A custom component!&/div&
当然,可以让组件只能用在其它组件内,用实例选项 components 注册,比如:
var Child = Vue.extend({ /* ... */ })
var Parent = Vue.extend({
template : '...',
components : {
// &my-component& 只能用在父组件模板内
'my-component': Child
这种局部注册的方式也适用于其它资源,比如指令、过滤器和过渡。他们都支持全局和局部组件注册。
前面提到组件是可以被复用的,多个实例可能会共享一个组件构造器,那么请注意一个组件选项的问题:
传入 Vue 构造器的多数选项也可以用在 &Vue.extend()&中,不过有两个特例: data 和 el。试想如果我们简单地把一个对象作为 data 选项传给 &Vue.extend()&:
var data = {a : 1}
var MyComponent = Vue.extend({
data : data
这么做的问题是 MyComponent 所有的实例将共享同一个 data 对象!因为对象是引用传递的,这基本不是我们想要的,因此我们应当使用一个函数作为 data 选项,让这个函数返回一个新对象:
var MyComponent = Vue.extend({
data : function (){
return {a : 1}
同理,el 选项用在 &Vue.extend()&& 中时也须是一个函数。
&2.使用props传递数据
当一个组件内部还有一个子组件的时候,由于组件实例的作用域是孤立的,这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。这时,父组件可以使用props把数据传给子组件:
&prop& 是组件数据的一个字段,期望从父组件传下来。子组件需要显式地用 props 选项 声明 props:
<ponent('child', {
//camelCase in JavaScript
props : ['myMessage'],
template : '&span&{{ myMessage }}&/span&'
然后向它传入一个普通字符串:
&child my-message="hello!"&&/child&
子组件的渲染结果:
由于命名的习惯,请注意camelCase和kebab-case:HTML 特性不区分大小写。名字形式为 camelCase 的 prop 用作特性时,需要转为 kebab-case(短横线隔开)。
根据vue响应的特性,props也可以是动态的:
类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 &v-bind&&绑定动态 Props 到父组件的数据。每当父组件的数据变化时,也会传导给子组件:
&input v-model="parentMsg"&
&child v-bind:my-message="parentMsg"&&/child&
也可以使用v-bind的缩写语法来简化绑定:
&child :my-message="parentMsg"&&/child&
渲染结果:
改变输入框的值,子组件的文本会跟着改变
关于props的其他介绍,请参考 :
3.父子组件的通信
子组件可以用 &this.$parent&&访问它的父组件。根实例的后代可以用 this.$root 访问它。父组件有一个数组 &this.$children&,包含它所有的子元素。
尽管可以访问父链上任意的实例,不过子组件应当避免直接依赖父组件的数据,尽量显式地使用 props 传递数据。另外,在子组件中修改父组件的状态是非常糟糕的做法,因为:
这让父组件与子组件紧密地耦合;
只看父组件,很难理解父组件的状态。因为它可能被任意子组件修改!理想情况下,只有组件自己能修改它的状态。
Vue 实例实现了一个自定义事件接口,用于在组件树中通信。这个事件系统独立于原生 DOM 事件,用法也不同。
每个 Vue 实例都是一个事件触发器:
使用&&$on()&&监听事件;
使用&&$emit()&&在它上面触发事件;
使用&&$dispatch()&&派发事件,事件沿着父链冒泡;
使用&&$broadcast()&&广播事件,事件向下传导给所有的后代。
不同于 DOM 事件,Vue 事件在冒泡过程中第一次触发回调之后自动停止冒泡,除非回调明确返回&&true&。
一个简单的例子:
&!-- 子组件模板 --&
&template id="child-template"&
&input v-model="msg"&
&button v-on:click="notify"&Dispatch Event&/button&
&/template&
&!-- 父组件模板 --&
&div id="events-example"&
&p&Messages: {{ messages | json }}&/p&
&child&&/child&
在子组件的输入框输入值以后,点击按钮,父组件的Messages:[]文本会对应变化
4.再来说说动态组件
多个组件可以使用同一个挂载点,然后动态地在它们之间切换。使用保留的&&&component&&元素,动态地绑定到它的&is&特性:
el : 'body',
currentView : 'home'
components : {
home : { /* ... */ },
posts : { /* ... */ },
archive : { /* ... */ }
&component :is="currentView"&
&!-- 组件在 vm.currentview 变化时改变 --&
&/component&
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个&&keep-alive&&指令参数:
&component :is="currentView" keep-alive&
&!-- 非活动组件将被缓存 --&
&/component&
其他动态组件的详细介绍,请参考:
在创建复杂应用的时候,动态组件或许就显得不那么灵活了,这时可以使用路由,vue-router路由扩展可以看做是动态组件的升级版,可参考:
5.最后,组件实例的生命周期:
Vue 实例在创建时有一系列初始化步骤&&例如,它需要建立数据观察,编译模板,创建必要的数据绑定。在此过程中,它也将调用一些生命周期钩子,给自定义逻辑提供运行机会。例如 created 钩子在实例创建后调用:
var vm = new Vue({
created : function (){
// this 指向 vm 实例
console.log('a is: ' + this.a)
// =& "a is: 1"
也有一些其它的钩子,在实例生命周期的不同阶段调用,如 compiled、 ready 、destroyed。钩子的 this 指向调用它的 Vue 实例。一些用户可能会问 Vue.js 是否有&控制器&的概念?答案是,没有。组件的自定义逻辑可以分割在这些钩子中。
声明周期的图示:
组件的简单介绍就到这里。
在大型应用中,状态管理常常变得复杂,因为状态分散在许多组件内,在不同的作用域内。以vue来说,当使用vue-router以及组件化开发(.vue)来构建大型单页应用的时候,组件之间状态的数据的传递会很困难,虽然props、dispatch、broadcast等能够进行跨组件的数据传递,但是大量使用它们会使组件之间的耦合程度很高,组件越多,层级越多,维护起来就越复杂。怎么办呢?能否在全局提供一个状态管理构架?
这里得提出一个概念:Flux
Flux是Facebook用来构建用户端的web应用的应用程序体系架构。它通过利用数据的单向流动为React的可复用的视图组件提供了补充。相比于形式化的框架它更像是一个架构思想,不需要太多新的代码你就可以马上使用Flux构建你的应用。
Flux应用主要包括三部分:dispatcher、store和views(React components),千万不要和MVC(model-View-Controller)搞混。Controller在Flux应用中也确实存在,但是是以controller-view的形式。view通常处于应用的顶层,它从stores中获取数据,同时将这些数据传递给它的后代节点。另外,action creators - dispatcher辅助方法 - 一个被用来提供描述应用所有可能存在的改变的语义化的API。把它理解为Flux更新闭环的第四个组成部分可以帮助你更好的理解它。
一句话:Flux就是手动将Action从数据流底层视图中的事件手动绑定到数据顶层的数据流架构。
单向数据流的设计目的:任何UI不能直接对数据有写操作,就是防止同一份数据有多个地方同时在写。相对于直接进行双向绑定,编码稍微会复杂一点,但换来了排错和维护的便捷。
Flux 架构常用于 React 应用中,但它的核心理念也可以适用于 Vue.js 应用。比如 Vuex 就是一个借鉴于 Flux,但是专门为 Vue.js 所设计的状态管理方案。React 生态圈中最流行的 Flux 实现 Redux 也可以通过简单的绑定和 Vue 一起使用。
什么是Vuex
Vuex 是一个专门为 Vue.js 应用所设计的集中式状态管理架构。它借鉴了 Flux 和 Redux 的设计思想,但简化了概念,并且采用了一种为能更好发挥 Vue.js 数据响应机制而专门设计的实现。
为什么需要它?
当你的应用还很简单的时候,你多半并不需要 Vuex。也不建议过早地使用 Vuex。但如果你正在构建一个中型以上规模的 SPA,你很有可能已经需要思考应该如何更好地归纳 Vue 之外,应用的其他组成部分。这就是 Vuex 要大显身手的时刻。
我们在单独使用 Vue.js 的时候,通常会把状态储存在组件的内部。也就是说,每一个组件都拥有当前应用状态的一部分,整个应用的状态是分散在各个角落的。然而我们经常会需要把状态的一部分共享给多个组件。一个常见的解决策略为:使用定制的事件系统,让一个组件把一些状态&发送&到其他组件中。这种模式的问题在于,大型组件树中的事件流会很快变得非常繁杂,并且调试时很难去找出究竟哪错了。
为了更好的解决在大型应用中状态的共用问题,我们需要对组件的 组件本地状态(component local state) 和 应用层级状态(application level state) 进行区分。应用级的状态不属于任何特定的组件,但每一个组件仍然可以监视(Observe)其变化从而响应式地更新 DOM。通过汇总应用的状态管理于一处,我们就不必到处传递事件。因为任何牵扯到一个以上组件的逻辑,都应该写在这里。此外,这样做也能让我们更容易地记录并观察状态的变更(Mutation,原意为突变),甚至可以实现出华丽如时光旅行一般的调试效果。(译注:是时候安利一波 vue-devtools 了)
Vuex 也对如何管理分撒各地的状态增加了一些约束,但仍保留有足够面对真实使用场景的灵活性。
最简单的store
创建 Vuex store 的过程相当直截了当 - 只要提供一个初始化的 state 对象,以及一些 mutations:
import Vuex from 'vuex'
const state = {
const mutations = {
INCREMENT (state){
state.count ++
export default new Vuex.Store({
现在,你可以通过&&store.state &&来读取 state 对象,还可以通过 dispatch 某 mutation 的名字来触发这些状态变更:
store.dispatch('INCREMENT')
console.log(store.state.count) // -& 1
如果你倾向于对象风格的分发方式,你可以用这种语法:
// 效果同上
store.dispatch({
type : 'INCREMENT'
再次强调,我们通过分发 mutation 的方式,而非直接改变&&store.state&,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,我们甚至可以实现如时间穿梭般的调试体验。
Vuex 使用 单一状态树 && 是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个『唯一数据源(SSOT)』而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
以上只是一个用来展示 store 究竟是什么的一个极简例子。再谈谈三哥核心概念:State(状态),Mutations(变更) 和 Actions(动作)。
State和Getters
1.安装 Vuex 并且将您的根组件引入 store 实例:
import Vue from 'vue'
import Vuex from 'vuex'
import store from './store'
import MyComponent from './MyComponent'
// 关键点,教 Vue 组件如何处理与 Vuex 相关的选项
Vue.use(Vuex)
var app = new Vue({
el : '#app',
store, // 把 store 对象提供给 &store& 选项,这可以把 store 的实例注入所有的子组件
components : {
MyComponent
通过在根实例中注册&store&选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过&&this.$store&&访问到。不过事实上,我们几乎不会需要直接引用它。
2.在子组件中,通过在 &vuex.getters&&选项里定义的&getter&方法来读取状态:
// MyComponent.js
export default {
template : '...',
data (){ ... },
// 此处为我们从 store 实例中取回状态的位置
getters : {
// 该 getter 函数将会把仓库的 `store.state.count` 绑定为组件的 `this.count`
count : (state) =& state.count
请留意 vuex 的这个特殊选项(译注:getters 子对象)。它是我们指定当前组件能从 store 里获取哪些状态信息的地方。它的每个属性名将对应一个 getter 函数。该函数仅接收 store 的整个状态树作为其唯一参数,之后既可以返回状态树的一部分,也可以返回从状态树中求取的计算值。而返回结果,则会依据这个 getter 的属性名添加到组件上,用法与组件自身的计算属性一毛一样。
组件不能直接修改store实例的状态:
请始终记得非常重要的这点,就是:组件永远都不应该直接改变 Vuex store 的状态。因为我们想要让状态的每次改变都很明确且可追踪,Vuex 状态的所有改变都必须在 store 的 mutation handler (变更句柄) 中管理。为了强化该规则,在开启(严格模式(Strict Mode))时,若有 store 的状态在 mutation 句柄外被修改,Vuex 就会报错。现在有了这一规则,我们 Vue 组件的职能就少了很多:他们通过只读的 getter 与 Vuex store 的状态相绑定,组件唯一能影响全局状态的方法就是想办法触发 mutations(我们接下来会谈到)。若有必要,组件仍然能够处理和操作本地状态,但是我们不再在单独的组件中放置任何数据请求或全局状态变更的逻辑。这些操作全部都集中于 Vuex 相关的文件中,这样能让大型应用变得更容易理解和维护。
Mutations 本质上是一个事件系统:每个 mutation 都有一个&事件名 (name)&和 一个&回调函数 (handler). 任何一个 Mutation handler 的第一个参数永远为所属 store 的整个 state 对象:
import Vuex from 'vuex'
const store = new Vuex.Store({
mutations : {
INCREMENT (state){
// 改变 state
state.count ++
用全部大写命名 mutation 是一个惯例,方便将它和 actions 区分开。
你不能直接调用 mutation handler. 这里传入 Store 构造函数的选项更像是在注册事件回调:当INCREMENT&事件被触发时,调用这个 handler。触发 mutation handler 的方法是 dispatch 一个 mutation 的事件名:
store.dispatch('INCREMENT')
Mutation必须是同步函数:
  因为当 mutation 触发的时候,回掉函数还没有被调用,我们不知道什么时候回调函数实际上被调用。任何在回调函数中进行的的状态的改变都是不可追踪的。
Mutation必须遵守Vue的响应系统规则:
  1.尽可能在创建 store 时就初始化 state 所需要的所有属性。
  2.当添加一个原本不存在的属性时,需要使用&&Vue.set(obj, 'newProp', 123)&&或者拷贝并替换原本的对象。利用 stage 2 的语言特性 object spread syntax,我们可以使用这样的语法: &&state.obj = {...state.obj, newProp : 123}&
Actions 是用于分发 mutations 的函数。按照惯例,Vuex actions 的第一个参数是 store 实例,附加上可选的自定义参数。
// 最简单的 action
function increment (store){
store.dispatch('INCREMENT')
// 带附加参数的 action
// 使用 ES2015 参数解构
function incrementBy ({dispatch}, amount){
dispatch('INCREMENT', amount)
乍一眼看上去感觉多此一举,我们直接分发 mutations 岂不更方便?实际上并非如此,还记得&mutations 必须同步执行这个限制么?Actions 就不受约束!我们可以在 action 内部执行异步操作,比如执行一个ajax请求数据的操作:
function getData ({dispatch}){
url : "...",
data : {...},
success : (data) =& {
dispatch("SET_DATA", data)
我们可以这样在组件中调用actions:
// 某组件内部
// 导入actions
import {incrementBy} from './actions'
const vm = new Vue({
getters : { ... }, // state getters
actions : {
incrementBy
上述代码所做的就是把原生的 incrementBy action 绑定到组件的 store 实例中,暴露给组件一个 &vm.increamentBy &实例方法。所有传递给&&vm.increamentBy&的参数变量都会排列在 store 变量后面然后一起传递给原生的 action 函数,所以调用&&vm.incrementBy(1)&等价于&&incrementBy(vm.$store, 1)&。虽然多写了一些代码,但是组件的模板中调用 action 更加省力了:
&button v-on:click="incrementBy(1)"&increment by one&/button&
通常在大型 App 中,action 应该按不同目的进行 分组 / 模块化 的管理,具体请参考:
下面再谈谈一个重要的东西,数据流:
为了更好地理解 Vuex app 中的数据流,我们来开发一个简单的计数器 app。注意:这个例子仅仅是为了更好地解释概念,在实际情况中并不需要在这种简单的场合使用 Vuex.
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 应用初始状态
const state = {
// 定义所需的 mutations
const mutations = {
INCREMENT (state){
state.count ++
DECREMENT (state){
state.count --
// 创建 store 实例
export default new Vuex.Store({
// actions.js
export const increment = ({ dispatch }) =& dispatch('INCREMENT')
export const decrement = ({ dispatch }) =& dispatch('DECREMENT')
&!- temmplate --&
Clicked: {{ count }} times
&button v-on:click="increment"&+&/button&
&button v-on:click="decrement"&-&/button&
// 仅需要在根组件中注入 store 实例一次即可
import store from './store'
import {increment, decrement} from './actions'
const app = new Vue({
el : '#app',
getters: {
count: state =& state.count
actions: {
increment,
你会注意到组件本身非常简单:它所做的仅仅是绑定到 state、然后在用户输入时调用 actions。
你也会发现整个应用的数据流是单向的,正如 Flux 最初所定义的那样:
1.用户在组件中的输入操作触发 action 调用。2.Actions 通过分发 mutations 来修改 store 实例的状态。3.Store 实例的状态变化反过来又通过 getters 被组件获知。
最后:Vuex 并不强制要求所有的状态都必须放在 Vuex store 中 ,如果有些状态你觉得并没有需要对其变化进行追踪,那么你完全可以把它放在 Vuex 外面(比如作为组件的本地状态)。比如公共组件对外的接口,通过props传递数据更为有效。
Vuex的完整介绍请参考:
vue-devtools
vue-devtools是chrome的一个vue开发插件,可以在chrome商店下载crx扩展包进行安装。提供Components和Vuex预览(state变化跟踪等)功能,有助于开发和调试。
可以看到组件的prop属性、计算属性、vue getter属性等,以及Vuex中的触发的mutation、state 当前的值等我们可能关注的内容都直观地展示了出来。
Vue模块化&
对于大型项目,为了更好地管理代码使用模块构建系统非常必要。推荐代码使用 CommonJS 或 ES6 模块,然后使用 Webpack 或 Browserify 打包。
Webpack 和 Browserify 不只是模块打包器。两者都提供了源码转换 API,通过它可以用其它预处理器转换源码。例如,借助 babel-loader 或 babelify 代码可以使用 ES 语法。
你可以使用 Webpack + vue-loader 或 Browserify + vueify 构建这些单文件 Vue 组件。
选择哪种构建工具取决于你的经验和需求。Webpack 的功能更强大,如代码分割,将静态资源当作模块,提取组件的 CSS 到单独的一个文件等,不过它的配置相对复杂一点。如果你不需要 Webpack 的那些功能,使用 Browserify 更简单,最快的构建方式是使用官方出品的脚手架工具 vue-cli。参考:
活动模板设计系统
这个设计系统只是对活动模板要展示的内容进行设计,具体的样式和交互由活动h5页面根据视觉和交互设计来定夺。活动里面的每一个子项都可以抽象为一个组件,h5展示端拿到每个组件的内容再套上对应组件的样式和交互逻辑,最终就形成了一个h5活动页面。
每一个活动组件对应三个模式组件:
  1.标签组件,通过拖动来创建对应类型的组件
& & & 2.预览组件,展示当前组件各项的内容
& & & 3.编辑组件,用来编辑当前选中的组件的各项内容
完成后大概是这样的,以一个最简单的节标题组件为例:
如上图所示:左侧容器排列着这些常用组件的标签。将活动需要的组件标签拖入预览区域后,会生成对应的预览组件和编辑组件;点击这个预览组件,组件编辑区域会显示对应的编辑组件;在编辑组件中可以对组件各项进行编辑。编辑完成后,通过事先的数据绑定,预览区域对应的组件就会更新视图,显示组件当前的最新内容。
以上就是这个系统的一个大概方案,下面谈谈具体的实现。
首先,从标签区域开始:
标签组件是每个活动组件的开端,也就说每一个标签组件必须有一个属性来标识它代表的是哪一个活动组件。那就先给它们指定类型 type:
  节标题&&type :'sectionTitle'&
  投票 &type :'vote'
  正文 &type :'content'&
  用户 &type :'user'&
  图片 &type :'image'&
  视频 &type :'video'&
  音频 &type :'audio'&
  跳转链接 &type :'link'
然后每当我们拖动一个标签组件到预览区域,再根据该标签组件的type生成对应的预览和编辑组件。预览和编辑组件需要确定的无非就是有哪些编辑项,这些编辑项是什么内容。以节标题组件为例,它就只有一个编辑项:节标题的文本。也就是说节标题的预览组件用来显示节标题的文本,编辑组件需要有一个文本域来对节标题文本进行编辑,在模板事先绑定好对应的数据,文本域的数据变化会反应到预览组件的DOM上。
我们需要有一个保存所有组件数据(对象)的容器,可以使用一个数组。
我更喜欢操作一个数组而不是对象的原因:vue对数组的基本方法(push、splice、shift等)都进行了封装,通过这些方法来改变数组的数据,结果都是响应的。而在保持响应的情况下,改变对象的数据要麻烦些,特别是复杂的嵌套对象。如果使用对象可以通过id直接匹配到对应数据,通过数组需要遍历一下。但是有了es6的for of,代码还是很简单,而且也不是在操作DOM,性能影响不大。
//widgetData.js
{id : "100",type : "vote", ...}, //投票
{id : "101",type : "image", ...}, //图片
{id : "102",type : "video", ...}, //视频
每个组件数据对象的id属性是唯一的,是拖入标签组件时生成的,这个id属性是关联预览组件与对应编辑组件的关键,通过它可以找到每个预览组件对应的编辑组件。为什么不通过type来判断呢?因为每个活动可能有多个相同的组件,比如节标题。通过type没法确定对应关系。
这里我们通过Vuex创建一个store来存储及修改这个数组(官方点的说法就是管理state状态)。按照上面提到的Vuex的数据流规则:UI不允许直接修改数据。在编辑项里面改变某项输入框的值,并不是直接改变了对应组件数据中那一项的值,而是通过DOM事件触发对应的action,action再派发对应的mutaion处理函数来修改state。这种方式可以确保所有对某项组件数据的修改都是通过触发某一个公共的action来完成的,这个action就是进行某项修改的统一和唯一的入口。
当我们知道需要生成什么预览和编辑组件的时候,并放进组件数据容器的时候,我们就必须知道这个组件到底有哪些编辑项(除了组件类型外,我们放入的这个组件数据对象还需要哪些属性),这时候我们就需要一个map,来管理组件type和组件编辑项的关系,以活动的投票组件为例:
根据需求,投票组件需要有以下编辑项:
1.投票的标题
2.投票项,每项要有一个名称,后续每项可能还会有其他属性(类似正确选项的标记等)
//typeDataMap.js
export default {
type : "vote",
title : "投票标题文本",
{name : "投票项1"},
//每个投票项
{name : "投票项2"},
{name : "投票项3"}
只要知道是什么类型,通过&&typeData[type]&&就能获取到组件数据并存入组件数据容器了。由于我们在预览组件和编辑组件的模板视图已事先对DOM进行了数据绑定,当我们改变组件容器中某个组件的数据项时,更新就会反应到DOM上。当我们保存整个模板的时候,只需要取出组件数据容器中的值就行了,其实也就是那个数组本身。H5展示端通过这个组件数据数组,可以拿到组件的数据以及排序,按照定好的模板渲染出来即可。当然,像投票组件这类有交互数据的组件,该系统设计的模板只是确定了要展示的固定的内容。具体的投票总数、每项投票数等属性需要后端处理后插入到对应组件数据里面,供展示端显示。
整个系统大概的设计思想就是这样的,下面挑些具体的来讲讲:
因为标签组件的表现和交互逻辑等都是一致的,这里做了一个公共可复用的标签组件,对外接收两个参数:title(标签文本)和type(标签类型)。在标签容器组件创建一个包含所有标签组件数据对象的数组,在模板视图中遍历这个数组,就创建了所有的标签组件。
公共标签组件的统一的属性和方法等存入了一个对象字面量里面,导入以后通过mixin方式混合,组件就会拥有这些属性和方法。目前这样做的意义不大,因为已经有一个公共的标签组件了,mixin里面的东西完全可以直接写到这个公共组件内。但如果每个类型的标签组件都是一个单独的.vue组件文件,mixin的好处就体现出来了:可复用、易维护。
具体实现的代码,省略掉样式
//labelWrapper.vue 标签组件容器(组件标签区域)
&template&
&div class="label-wrapper"&
&div class="label-title"&组件标签区域&/div&
&div class="label-box"&
&common-label v-for="label in labelArr" :title="label.title" :type="label.type"&&/common-label&
&/template&
import commonLabel from './widget/commonLabel.vue' //导入公共标签组件
export default {
name : "label_wrapper",
components : {
commonLabel //注册为子组件(es6同名对象字面量缩写)
labelArr : [
{title : "节标题", type : "sectionTitle"},
{title : "投票", type : "vote"},
{title : "正文", type : "content"},
{title : "用户", type : "user"},
{title : "图片", type : "image"},
{title : "视频", type : "video"},
{title : "音频", type : "audio"},
{title : "跳转链接", type : "link"}
&style lang="stylus"&
//commonLabel.vue 公共标签组件
&template&
&div class="label-item-wrapper" title="拖入模板设计区域" draggable="true" @dragstart="dragStart"&
&img class="label-icon" alt="{{title}}" :src="iconUrl"&
&span class="label-text"&{{title}}&/span&
&/template&
//导入mixin
import labelMixin from './mixin/labelMixin'
export default {
name : "label",
title : String,
type : String
mixins : [labelMixin],
computed : {
iconUrl (){
this.type + '.png'
&style lang="stylus"&
//labelMixin.js
import typeDataMap from './typeDataMap'
export default {
methods : {
dragStart (e){
var id = parseInt(Date.now() + "" + parseInt(Math.random() * 90))
var widgetData = typeDataMap[this.type]
       var dt = e.dataTransfer
widgetData['id'] = id
dt.setData("id", id)
dt.setData("type", this.type)
dt.setData("widgetData", JSON.stringify(widgetData))
预览组件相对较简单,除了数据的绑定,就是拖动排序。拖动排序的实现是通过html5原生的drag事件,基于vue数据驱动的原理,拖动的时候并不需要去手动改变预览区域内各组件的DOM顺序,只需要改变组件数据数组里面各数据对象的index即可,数据的变化会反应到DOM上。简单的节标题预览组件:
&template&
&div class="preview-item-wrapper" draggable="true" :class="{'active': isActive}"
@click="showEdit"
@dragover="allowDrop"
@dragstart="dragStart"
@drop="dropIn"
&span class="preview-item-del" :class="{'active': isActive}" title="删除该组件"&
&div v-on:click="delMe"&x&/div&
&label class="preview-item-label"&- 节标题 -&/label&
&div class="preview-item-input-wrapper"&
&div class="title-text"&{{text}}&/div&
&/template&
//导入action
import {addPreviewAndData, deleteWidgetPreview, changeWidgetEdit, changPreviewAndDataIndex} from '../../../store/actions'
//导入mixin
import previewMixin from './mixin/previewMixin'
export default {
name : "sectionTitle_preview",
mixins : [previewMixin],
id : Number,
index : Number
computed : {
//mixin外的私有属性
for (let value of this.widgetDataArr)
if (value.id == this.id) return value.text
//绑定mixin需要的属性和方法
getters : {
widgetDataArr : (state) =& state.widgetDataArr,
currentEditWidgetId : (state) =& state.currentEditWidgetId
actions : {
addPreviewAndData,
deleteWidgetPreview,
changeWidgetEdit,
changPreviewAndDataIndex
&style lang="stylus"&
* previewMixin.js
* 预览组件的mixin
* @提取同类组件之间可复用的计算属性与方法
export default {
computed : {
//该预览组件是否为当前点击的
isActive (){
return this.id == this.currentEditWidgetId
methods : {
//删除该预览组件
this.deleteWidgetPreview(this.id)
//显示该预览组件对应的编辑组件
showEdit (){this.changeWidgetEdit(this.id)
//允许向该预览组件拖放其他组件
allowDrop (e){
e.preventDefault();
//开始拖放该预览组件
dragStart (e){
var dt = e.dataTransfer
dt.setData("index", this.index)
//向该预览组件拖放其他组件(预览组件或者标签组件)
dropIn (e){
e.preventDefault()
e.stopPropagation()
var dt = e.dataTransfer
var id = parseInt(dt.getData("id"))
if (id){ //有id表明拖入的是标签组件
var type = dt.getData("type")
var widgetData = JSON.parse(dt.getData("widgetData"))this.changeWidgetEdit(id)
this.addValidation(id) //添加组件验证项
var index = parseInt(dt.getData("index"))
this.changPreviewAndDataIndex(index, this.index)
//清空dataTransfer
dt.clearData()
还是以节标题组件为例:
&template&
&div class="edit-item-wrapper"&
&label class="edit-item-label"&节标题文本&/label&
&validator name="titleValidator"&
&div class="edit-item-input-wrapper"&
&textarea class="title-edit-input" placeholder="必填项,16字以内"
v-model="text"
v-validate:text="{
required: {rule: true,message: '请填写节标题文本'},
maxlength: {rule: 16,message: '节标题文本限制在16字以内'}
@input="inputValue"
@valid="onValid"
@invalid="onInvalid"
&&/textarea&
&div class="edit-input-err" v-if="$titleValidator.text.required"&{{$titleValidator.text.required}}&/div&
&div class="edit-input-err" v-if="$titleValidator.text.maxlength"&{{$titleValidator.text.maxlength}}&/div&
&/validator&
&/template&
//导入action
import {changeWidgetData, changeValidation} from '../../../store/actions'
//导入mixin
import editMixin from './mixin/editMixin'
export default {
name : "title_edit",
mixins : [editMixin],
id : Number
computed : {
//mixin外的私有属性
for (let value of this.widgetDataArr)
if (value.id == this.id) return value.text
methods : {
//mixin外的私有方法
inputValue (e){
this.changeWidgetData(this.id, 'text', e.target.value)
getters : {
widgetDataArr : (state) =& state.widgetDataArr
actions : {
changeWidgetData,
changeValidation
&style lang="stylus"&
* editMixin.js
* 编辑组件的mixin
export default {
//isValid : false
methods : {
onValid (){ //验证通过
this.isValid = true
this.changeValidation(this.id, true)
onInvalid (){ //验证失败
this.isValid = false
this.changeValidation(this.id, false)
还有一些公共组件以及store等就不再介绍了,前面的讲解已基本包含,差不多就到这里了。最后完成后是这样的:
本文有些内容引用自各位大神的资料,对他们表示感谢,也谢谢各位的支持。
阅读(...) 评论()

我要回帖

更多关于 6s和7哪个性价比高 的文章

 

随机推荐