全部的vuejs组件都是被扩展的vue实例;
var MyComponent = Vue.extend({ //扩展选项对象 }) var myComponentInstance = new MyComponent();
每一个Vue实例都会代理这个实例的data属性对象里的全部属性:
var data = { a: 1 } var vm = new Vue({ data: data }) vm.a === data.a // -> true // 设置属性也会影响到原始数据 vm.a = 2 data.a // -> 2 // ... 反之亦然 data.a = 3 vm.a // -> 3
全部Vue实例自己暴露的属性和方法,都以$为头来区别,对应Vue.set global API
例如:vm.$data,vm.$elvm.$watch,这个有利于和data属性对象的数据来区分;javascript
全部directive都以v-xxx形式存在:
<p v-if="greeting">Hello!</p> //根据greeting表达式的值真假来插入或者删除p元素; <a v-bind:href="url"></a> //href是argument,表示将a元素的href属性和url表达式绑定起来 其对应的简写形式为: <a :href="url"></a> //这个是一种缩写的形式 <a v-on:click="doSomething"> //click是参数,表示on click时,执行doSomething这个表达式(函数) <!-- 完整语法 --> <a v-on:click="doSomething"></a> <!-- 缩写 --> <a @click="doSomething"></a> <a v-bind:href.literal="/a/b/c"></a> //href为argument,literal为修饰符,表示后面"/a/b/c"为字面值而不是表达式!! <button :disabled="someDynamicCondition">Button</button> // 绑定到一个布尔值,若是真则disabled属性就加在button上
directive object context暴露出来供update/bind函数使用的属性:
el: the element the directive is bound to. vm: the context ViewModel that owns this directive. expression: the expression of the binding, excluding arguments and filters. arg: the argument, if present. name: the name of the directive, without the prefix. modifiers: an object containing modifiers, if any. descriptor: an object that contains the parsing result of the entire directive. params: an object containing param attributes.
好比下面的directive例子中:css
<div id="demo" v-demo:arghello.modifiera.modifierb="expmsg" :parax="xdata"></div> // 注意须要watch directive的parameter才能实现xdata变化就能触发directive内部的变化 Vue.directive('example', { params: ['parax'], paramWatchers: { parax: function (val, oldVal) { console.log('parax changed!') } } })
绑定css class和style:html
<div v-bind:style="styleObject"></div> <li v-bind:class="{'j_current': currenttab=='login'}"> 这是class绑定的演示</li> data: { styleObject: { color: 'red', fontSize: '13px' } } <div v-bind:class="classObject"></div> data: { classObject: { 'class-a': true, 'class-b': false } }
v-if directive:条件渲染:
注意v-if会直接在DOM中插入删除对应的元素前端
<h1 v-if="ok">Yes</h1> <h1 v-else>No</h1> <template v-if="ok"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </template>
v-show条件显示vue
不会删除元素,只会使用display:none css的方式java
<h1 v-show="ok">Hello!</h1>
v-for列表数据渲染(注意在v-for子块内能够访问父组件做用域内的属性,还有一个$index)
<ul id="example-2"> <li v-for="(index, item) of items"> {{index}} {{ parentMessage }} - {{ $index }} - {{ item.message }} </li> </ul> var example2 = new Vue({ el: '#example-2', data: { parentMessage: 'Parent', items: [ { message: 'Foo' }, { message: 'Bar' } ] } }) //结果: 0 Parent - 0 -Foo 1 Parent - 1 -Foo //v-for也能够应用在template标签上,这样作的好处是:不用额外的无心义的tag,template tag不会被渲染出来 <ul> <template v-for="item in items"> <li>{{ item.msg }}</li> <li class="divider"></li> </template> </ul>
vuejs能够被应用在数组的push,pop,shift,unshift,splice,sort,reverse方法改变数组的场景,可是若是你使用下面的语法1. vm.items[0]={}; 2.vm.items.length=0改变数组vuejs则没法感知这个变化,vuejs推荐的解决方案是:1.使用$set方法: example1.items.$set(0,{});node
2.使用空数组来替换items便可 example1.items = [];react
vuejs也提供了直接删除一个数组一个元素的简单方法 this.items.$remove(item)jquery
v-for应用在对象上面(而不是数组)webpack
<ul id="repeat-object" class="demo"> <li v-for="value in object"> {{ $key }} : {{ value }} </li> </ul> new Vue({ el: '#repeat-object', data: { object: { FirstName: 'John', LastName: 'Doe', Age: 30 } } }) //输出一下结果 <ul id="repeat-object" class="demo"> <li> FirstName : John </li><li> LastName : Doe </li><li> Age : 30 </li> </ul>
v-on内联语句访问event参数:若是是一个函数做为v-on绑定的表达式的话,该函数自动带有(event参数),这个和普通的js事件处理函数是同样的。
<button v-on:click="say('hello!', $event)">Submit</button> // ... methods: { say: function (msg, event) { // 如今咱们能够访问原生事件对象 event.preventDefault() } }
事件修饰符:
v-on:click.stop/v-on:submit.prevent/v-on:click.stop/v-on:click.capture/v-on:click.self="dothat"
v-model表单控件绑定:
http://vuejs.org.cn/guide/forms.html
过渡动画
http://vuejs.org.cn/guide/transitions.html
组件:
须要确保在初始化root instance以前注册了组件!
<div id="example"> <my-component></my-component> </div> // 定义 var MyComponent = Vue.extend({ template: '<div>A custom component!</div>' }) // 注册 Vue.component('my-component', MyComponent) // 建立根实例 new Vue({ el: '#example' }) //结果: <div id="example"> <div>A custom component!</div> </div>
注意:组件的模板会替换自定义元素my-component标签,也就是说my-component只是做为一个挂载点而已,固然,这能够经过replace选项来改变这个缺省行为
组件的局部注册:
有时咱们可能但愿一个组件只能在其余组件内使用,那么能够用实例选项components来注册:
var Child = Vue.extend({ template: '<div>A custom component!</div>' }) var Parent = Vue.extend({ template: '...', components: { // <my-component> 只能用在父组件模板内 'my-component': Child } })
组件注册语法糖
上面全局和局方方式注册组件老是使用Vue.extend来定义组件,随后传入Vue.component()或者components选项,有时显得很啰嗦,vuejs提供了一种简化方式来声明定义组件(参数传入仍是分两种状况:全局和局部)
// 在一个步骤中扩展与注册 Vue.component('my-component', { template: '<div>A custom component!</div>' }) // 局部注册也能够这么作 var Parent = Vue.extend({ components: { 'my-component': { template: '<div>A custom component!</div>' } } })
组件选项数据隔离问题(data和el选项)
传入Vue构造器的多数选项(new Vue({el,data,components,prop...}))均可以用在Vue.extend()中,可是data和el是两个特例,不能直接简单地把一个对象做为data选项传给Vue.extend(),缘由以下:
var data = { a: 1 } var MyComponent = Vue.extend({ data: data })
若是直接传入data对象给data选项,那么全部的MyComponent组件的实例都将共享同一个data对象!!所以咱们正确的作法是利用javascript的闭包的概念,使用一个函数来返回对应的数据:
var MyComponent = Vue.extend({ data: function () { return { a: 1 } } })
watch
watch: { // whenever question changes, this function will run question: function (newQuestion) { this.answer = 'Waiting for you to stop typing...' this.getAnswer() }, deepwachedArrayOrObject: { handler: function(nv,ov){ // watch handler function body }, deep: true // 指示深度侦测 }
须要注意的是在vuejs2.0中初次mount的组件并不会调用这个watch,而只有数据变化后才会调用,不像1.0中初次mount时也会调用
注意: 对于object对象或者array对象最好使用deep: true的参数,不然可能不会对对象key值变动作出反应
模板解析问题:
vue的模板是DOM模板,使用的是浏览器原生的解析器,DOM模板必须是有效的HTML片断。咱们必须注意有一些HTML元素对于什么元素可以放在他里面是有限制的,好比:
a不能包含其余的交互元素(好比按钮,连接)
ul/ol只能包含li
select只能包含option和optgroup,
table只能包含thead,tbody,tfoot,tr,caption,col,colgroup,
tr只能包含th,td
咱们若是违反这些规则,好比把<ul> <my-component>这种方式来组织的话,浏览器会把my-component提到元素的外面,致使渲染不正确。
这时,若是咱们又必须使用这样的结构,总么办?
咱们使用is属性吧!!!
<table> <tr is="my-component"></tr> </table>
组件component实例做用域
组件实例的scope做用域是孤立的,这意味着不能而且也不该该在子组件的模板内直接引用父组件的数据,可是咱们可使用props属性来吧数据传给子组件:
prop是组件数据的一个字段,指望从父组件传下来。子组件须要显式地用props选项声明props:
Vue.component('child', { // 声明 props props: ['msg'], // prop 能够用在模板内 // 能够用 `this.msg` 设置 template: '<span>{{ msg }}</span>' }) //传入一个普通的字符串给child组件的msg属性 <child msg="hello!"></child>
camelCase和kebab-case的转换
因为HTML属性是不区分大小写的,所以咱们组件的prop使用camelCase来定义时,须要转为短横线隔开(这个和angular相似:
Vue.component('child', { // camelCase in JavaScript props: ['myMessage'], template: '<span>{{ myMessage }}</span>' }) <!-- kebab-case in HTML --> <child my-message="hello!"></child> 心法:camelCase in JavaScript : kebab-case in HTML Vue.component('child', { // camelCase in JavaScript props: ['myMessage'], template: '<span>{{ myMessage }}</span>' }) <!-- kebab-case in HTML --> <child my-message="hello!"></child>
绑定自定义组件的属性
相似于绑定一个普通的属性到一个表达式,咱们也可使用v-bind来绑定自定义组件的props到父组件的数据,这样每当父组件的数据发生变化时,也会传导这个数据给子组件:
<div> <input v-model="parentMsg"> <br> <child v-bind:my-message="parentMsg"></child> </div> //简化版本: <child :my-message="parentMsg"></child>
传递值给prop
初学者每每犯错的是直接传递数值给prop,可是其实是传递了一个字符串,咱们必须使用:前缀,这样告诉vue,咱们后面的是一个表达式,而不是字面量:
<!-- 传递了一个字符串 "1" --> <comp some-prop="1"></comp> <!-- 传递实际的数字 --> <comp :some-prop="1"></comp>
prop从父亲到儿子双向绑定(在vuejs2.0中反向回传是严正反对的!)
默认状况下prop是单向绑定:当父组件属性变化时,传递给子组件,可是反过来不会。你可使用.sync或者.once绑定修饰符来显式地强制双向绑定或者单次绑定:
<!-- 默认为单向绑定 --> <child :msg="parentMsg"></child> <!-- 双向绑定 --> <child :msg.sync="parentMsg"></child> <!-- 单次绑定 :注意单次绑定在数据传入后就不会同步后面的任何变化了,适合传入初始化数据的场景--> <child :msg.once="parentMsg"></child>
须要注意的是:若是prop自己是一个对象或者数组的话,因为javascript对象是引用方式,不管是什么绑定方式都会是双向绑定!!
父子组件通讯:
子组件可硬用this.$parent访问父组件,this.$root访问祖根实例,每一个父组件都有一个数组this.$children来包含全部子元素。
可是在vuejs2.0中,任何试图在组件内修改经过props传入的父组件数据都被认为是anti-pattern的,报如下错误:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders
咱们必须从新思考如何架构咱们的应用:
1. 不要在组件内试图修改props传进来的父组件数据,数据只应该由该数据的源头组件来负责app内的CRUD;
2. 数据永久性功能(就是和后台数据库交互保存)最好是由须要处理数据的子组件来和数据库交互,可是经过$emit一个事件通知该数据的源头组件来更新web app的数据;(在子组件tag引用处以@resource-deleted来引用父组件的事件函数) 这种模式带来的问题是数据来源和数据消费者有事件交互的强烈耦合。
还有一种小方案是将数据永久性功能以及web app内部的数据一致两个功能合为一处,即:子组件须要修改源数据时,$emit消息给父亲,由owner来同时完成数据永久性保存和内部app数据一致
3.对于大型web app可使用vuex来实现解耦:父子之间经过vuex store state tree来保持联系,任什么时候候须要获取数据则getter,若是须要修改数据则setter,数据修改后的reactive则由vuejs处理,这种方式最大限度地实现了解耦
4. 可是有时也须要一些平衡:难道每一片小数据都得经过$emit事件给数据源头来作修改吗?因为若是传入的数据是Object类型或者array类型的,则自己因为是reference传参的,也就是说子组件prop传入供子组件读/写的object/array和“源头object/array”其实是一个对象,所以只要咱们不直接简单粗暴以
this.objFromParent = newObjectCreatedByChild //这样会致使vuejs报错 Avoid mutating a prop directly ...
可是若是咱们这样操做,则是能够的,而且数据彻底是同步的!
this.objFromParent.propertyChangedByChild = newObjectCreatedByChild.propertyChangedByChild //这样数据就很轻松保持了一致性,而不用$emit消息到数据源来维护数据了!!!
5.第4点和vuex的机制比较相似。你能够在须要共享给儿子组件的数据(起源于hosted by本组件)存放在一个localStore对象的属性中,将localStore对象做为属性值传给儿子组件,好比:pstore="localStore",在儿子组件中,则能够直接操做pstore.parentData = somedatahandledbyson ,曲线救国,绕过了vuejs2.0不容许对属性赋值操做的限制。
6. 若是是须要共享给后代子孙的数据,则能够引入一种thisrootstore机制,全部这类数据做为thisrootstore的属性对象存在,后代以this.rootstore.xxx来引用它。
5和6的好处是剔除大部分不须要$emit事件来sync数据的冗余代码,更加易于实现组件功能的抽象和重用
7. 也能够考虑利用slot机制的特性: slot自己所在html js scope属于父组件,这样就能够如下面的形式来解决这个数据同步问题:
<!-- within parent template removeThisSon() resonsible for remove data which hosted by parent --> <son v-for="son in sons"> <div @click="removeThisSon(son)"> some operation affected parent data</div> </son>
心法:对整个属性对象替换(赋值),新增,或者删除操做必须由数据源来操做,可是对传入的属性对象的某些property修正,则能够在子组件内部直接操做,数据就同步反映到父组件中
心法:数据优先,你首先抽象驱动你的app的state,全部代码就围绕着这些个state的变迁,而让vuejs自己来执行构建和更新DOM的工做。
The whole idea is "data first". you define what the state of your application should look like, and let Vue build and update the DOM accordingly
自定义事件
Vue实例的事件系统独立于DOM事件系统,作法有不一样:
使用$on()监听事件;
使用$emit()在这个组件上面触发事件;
使用$dispatch()来派发事件,事件沿着父链冒泡;
$broadcast()广播事件,从父亲向下到全部后代;
子组件索引v-ref
<div id="parent"> <user-profile v-ref:profile></user-profile> </div> var parent = new Vue({ el: '#parent' }) // 访问子组件 var child = parent.$refs.profile
transclusion: slot分发内容
学过angular,你可能知道有一个很是晦涩难懂的概念:transclusion,在vuejs中也有相似的说法slot
咱们先搞清楚编译做用域吧,看看下面的代码:
<child> {{ msg }} </child>
这个代码中的msg究竟是绑定到父组件的数据,仍是绑定到子组件的数据呢??正确答案是父组件。关于组件的做用域有如下心法,紧紧记住:
父组件模板的内容在父组件的做用域内编译;子组件模板的内容在子组件做用域内编译。
一个常见的错误是试图在父组件的模板内将一个directive绑定到子组件的属性和方法:
<!-- 无效:缘由是这个模板是父组件的,而父组件模板不知道子组件的数据状态! --> <child v-show="someChildProperty"></child>
注意:分发内容是在父组件的做用域内编译
v-for和组件共用:
v-for能够像普通元素上同样在compent tag上面使用:
<my-component v-for="item in items" :item="item" :index="$index"> </my-component>
上例中每一个my-component实例将会传入item数据/index索引以便使用
async components:
有时候,咱们的组件在render以前,可能须要一些数据准备的工做:好比从后端ajax过来数据,而且feed到组件中去,这时就须要使用async组件的概念了。
http://jsbin.com/maqagocise/edit?html,js,output
上面的代码例子能够参考:
Vue.component('async-example', function (resolve, reject) { setTimeout(function () { resolve({ template: '<div>I am async!</div>' }); }, 1000); }); new Vue({ el: '#body' }); <async-example></async-example>
动态插入和删除一个vue组件
你虽然能够经过如下代码实现动态插入和删除vue组件的需求,可是这种方式的问题是:在vue devtool中并未看到数据链的关系,咱们仍是建议使用v-if来实现这种应用场景。
methods:{ attachNewPermission: function(){ var vm = this; var html = '<async-example roleid='+this.roleid+' ></async-example> '; var vmtemp = Vue.extend({ template: html, replace: false, el: function(){ return vm.$el.getElementsByClassName('asyncloadednode')[0]; } }); new vmtemp(); } }
经过prop向子组件传入父组件的函数做为callback,而且访问子组件的数据
http://012.vuejs.org/guide/components.html#Passing_Callbacks_as_Props
prop传入父组件数据例子
1 <!-- url为update-text-inplace组件的属性,其值为/admin/roles/xx,其中role.id为在父组件的template可见的数据 --> 2 <update-text-inplace :url='"/admin/roles/"+role.id' fieldname="name"> 3 <div class="role-name">@{{ role.name }}</div> 4 </update-text-inplace>
VUEJS 2.0新feature及变化
https://github.com/vuejs/vue/issues/2873
vue component定义及引用命名约定
// PascalCase import TextBox from './components/text-box'; import DropdownMenu from './components/dropdown-menu'; export default { components: { // use in templates as <text-box> and <dropdown-menu> TextBox, DropdownMenu } } // in a component definition components: { // register using camelCase myComponent: { /*... */ } } <!-- use dash case in templates --> <my-component></my-component>
何为fragment instance
http://vuejs.org/guide/components.html#Fragment-Instance
ES6 moudle export/import
// flash-message.js function alertMessage(message) { alert(message); } function logMessage(message) { console.log(message); } export {alertMessage, logMessage}; //app.js import {alertMessage, logMessage} from './flash-message'; alertMessage("Hello"); logMessage("Hello"); //flash-message.js export default function(message){ alert(message); } //app.js import flashMessage from './flast-message'; flashMessage("Hello");
http://www.cnblogs.com/Answer1215/p/5131548.html
注意root Vue instance不能屡次初始化,不然可能会出现组件找不到的状况
javascript模块化开发模式:
每一个文件都组织为一个模块;
文件的开头经过import(es6)/require(cmd,amd)方式声明须要从外部导入的依赖;
每一个文件须要输出的好比component defination object, function,object等经过export定义;
第三方组件经过npm install --save-dev或者bower install --save下载安装,经过require('jquery')(这种方式是经过npm安装的,能够不用传入路径)或者require('path/to/jquery/jquery')(这种是非npm安装模式从本地文件require)来引入
全部第三方组件(若是自己不支持CMD,AMD,ES6模块化加载的话)或者本身写的过程式js文件须要作简单的改造,改形成ES6/CMD/AMD模块格式,以支持模块化开发模式
关于vuejs大小写,camcase等
在使用vueify时,须要import一个组件的配置对象,这时建议所有使用首字母大写的命名方式,下例:
import MyComponent from './my-component' export default { components: { MyComponent // es2015 shorhand } } //而后在template中使用-代替非首单词大写字母: <my-component></my-component>
以js模块化方式写一个export一个singleton object:
var Spinner = require('spin.js'); // export singleton:这是因为require会cache一个object module.exports = exports = new Spinner; 在须要引用该singleton object的地方: var spin = require('./supportlibs/spinner'); var spin2 = require('./supportlibs/spinner'); spin===spin2 //返回true!
hot reloading
咱们在使用browsersync这个工具作前端开发时,能够只对页面的css进行注入,这个概念在vueify的组件开发中也是能够的
参考: http://vuex.vuejs.org/zh-cn/hot-reload.html
在browserify工具链下有如下plugin实现相似的功能: https://github.com/AgentME/browserify-hmr/
如何在Html inspection pannel中清楚查看一段html属于哪一个组件?
在组件式开发模式下,咱们的页面就是一堆component组件按照逻辑关系堆砌出来的,不少时候咱们发现找到对应的html片断属于哪一个组件的模版定义的不是一件容易的事情,如何处理这种棘手的问题使得开发更容易和有趣?
我总结下来一个简单有效的方法是:在组件的root node下增长一行html注释: <!-- yourcomponent-name -->, 这样在html inspection界面就一眼看出是什么组件了,对应你想修改的话,打开那个组件.vue文件修改便可。
vuejs组件vueify开发模式下的依赖问题
咱们使用vueify开发模式来开发vuejs组件,将全部的代码:html+javascript+css都放在component.vue文件中,这对于前端开发能够说是一种革命,大大地便利了组件的迭代式开发,大大增长了代码的可重用性,可是同时也带来一些“问题”,其中一个问题就是:当咱们更改一个component.vue后,必须从新编译全部引用过这个vue文件的bundle.js文件才能自动使用最新的component逻辑,这在以往纯粹<script>tag引入js的开发模式是不可想象的。之因此必须从新编译那是由于每个bundle.js文件都是独立编译相应模版及components数组中定义的依赖组件而最终造成一个包含全部js/css的bundle的。
Vuejs directive对应this.vm指向谁?
customized directive的bind函数中若是返回this.vm到底指向的是谁呢?这有时候仍是容易混淆不清的。通常性原则:若是该directive attached到一个component的template内部,则该值指向VueComponent,若是该directive attached dom node并不属于任何component,则这个值就直接指向root Vue instance.
Js开发中引用第三方库的几种方式
在js组件开发中,咱们常常须要引用一些成熟的js组件,例如jquery datepicker, jquery select2, vue-mdl等库,引用他们的方式有如下几种:
首先咱们经过npm install vue-strap来安装vue-strap库;
1. CommonJS:
var alert = require('vue-strap/src/alert'); // or var alert = require('vue-strap').alert; new Vue({ components: { 'alert': alert } })
2.ES6
import alert from 'vue-strap/src/alert' // or import { alert } from 'vue-strap' new Vue({ components: { alert } })
3.AMD(这种方式会异步加载js,所以每每经过bower安装,后序会加载到浏览器)
$ bower install vue-strap define(['vue-strap'], function(VueStrap) { var alert = VueStrap.alert; ... });
4. Browser globals
以vue-strap为例,它会被以window.VueStrap全局对象来暴露相应的接口:
<script src="path/to/vue.js"></script> <script src="path/to/vue-strap.js"></script> <script> var vm = new Vue({ components: { alert: VueStrap.alert }, el: "#app", data: { showRight: false, showTop: false } }) </script>
强烈建议参考 segmentfault.com/a/119000000… 该文章对各类模块化的写法解释的很清楚
动态load html而且编译
若是你有一个组件,它会动态加载一部分html代码,而这个代码中包含一些vuejs代码,你须要使用$compile功能:
_self.vm.$compile(_self.vm.$el); //这个是不行的,由于dom已是编译过的 //下面的代码是获取须要recompile的html node,而且$compile,这样就造成了活动的代码了! _.each($('[recompile]'), function(el){ _self.vm.$compile(el); });
以上方法未通过验证,也没有彻底搞清楚,可是替代方案我是亲手测试过的: 1. 经过async component;2.经过v-if async组件的方式动态向后端获取数据和模版,在resolve方法中将对应数据和模版及数据来绑定;经过v-if的方式能够将html partial做为一个变量方式绑定在模版中,当该partial数据ready时,v-if天然会启动编译过程
Dynamic components with prop data传入
若是你须要在同一个mount point显示不一样的component,这时Dynamic components就很合适了。
<!-- html --> <div id="app"> by dynamic Component: <component v-for="item in items" :is="item.component" //这里根据item.component来决定渲染哪类组件 :opts="item.options"> //同时经过opts传入要渲染组件的props </component> </div> Vue.component('node', { template: "<div>node: {{ opts }}</div>", props: ['opts'] }); Vue.component('node2', { template: "<div>node2: {{ opts }}</div>", props: ['opts'] }); new Vue({ el: '#app', data() { return { items: [{ component: "node", //node节点类型 options: 'node节点数据' }, { component: "node2", //node2节点类型 options: 'node2节点数据' }] }; } methods: { addItem() { this.items.push(this.newItem); this.newItem = { component: "", options: "" } } } });
https://jsfiddle.net/matiascx/qn29r3vt/
https://jsbin.com/nikimigaju/edit?html,output
webpack构建环境相关内容
什么是webpack:
webpack是和browserify/gulp/grunt等类似的构建工具(Webpack is a module bundler. It takes a bunch of files, treating each as a module, figuring out the dependencies between them, and bundle them into static assets that are ready for deployment.),webpack比较完美地解决了前端模块化开发的工具链支持。特别是webpack的loader插件机制使得能够任意加载第三方开发的插件来扩展其支持的功能。接下来要说的vue-loader就是其中的一个典型案例;
什么是loader:
Webpack 因为自己只能处理 JavaScript 模块(commonJS,AMD,ES6),若是要处理其余类型的文件,就须要使用 loader 进行转换。
Loader 能够理解为是模块和资源的转换器,它自己是一个函数,接受源文件做为参数,返回转换的结果。这样,咱们就能够经过 require
来加载任何类型的模块或文件,好比 CoffeeScript、 JSX、 LESS 或图片。
先来看看 loader 有哪些特性?
- Loader 能够经过管道方式链式调用,每一个 loader 能够把资源转换成任意格式并传递给下一个 loader ,可是最后一个 loader 必须返回 JavaScript。
- Loader 能够同步或异步执行。
- Loader 运行在 node.js 环境中,因此能够作任何可能的事情。
- Loader 能够接受参数,以此来传递配置项给 loader。
- Loader 能够经过文件扩展名(或正则表达式)绑定给不一样类型的文件。
- Loader 能够经过
npm
发布和安装。 - 除了经过
package.json
的main
指定,一般的模块也能够导出一个 loader 来使用。 - Loader 能够访问配置。
- 插件可让 loader 拥有更多特性。
- Loader 能够分发出附加的任意文件。
Loader 自己也是运行在 node.js 环境中的 JavaScript 模块,它一般会返回一个函数。大多数状况下,咱们经过 npm 来管理 loader,可是你也能够在项目中本身写 loader 模块。
按照惯例,而非必须,loader 通常以 xxx-loader
的方式命名,xxx
表明了这个 loader 要作的转换功能,好比 json-loader
。
在引用 loader 的时候可使用全名 json-loader
,或者使用短名 json
。这个命名规则和搜索优先级顺序在 webpack 的 resolveLoader.moduleTemplates
api 中定义。
什么是vue-loader(browerify下对应vueify工具):
vue-loader是even you为了支持web组件在一个.vue文件中组织js,css,html的梦幻开发模式,首创性地定义了一种文件类型component.vue, 这个vue文件中用script,style, template来分别表明js,css,html,这种文件格式是浏览器不认识的哦,webpack构建工具也是不认识的哦,要能使用必须先编译打包成webpakc bundle,而在webpack生态系统中,vue-loader就是干这个用的。一旦webpack遇到.vue文件就会调用这个vue-loader分别将js,css,html抽取出来,而且调用对应的代码transpiler工具:好比css可能用less,也可能用sass;js可能用coffee,也可能用jsx;html可能用yaml等都须要转换。这个工做就是vue-loader来完成的。
简单一句话,vue-loader就是将一个.vue文件转换为一个js模块的
chrome下vue dev-tool没法显示问题
Vue.config.devtools = true; //在new Vue()以前执行 //Vue.config.debug = true; //window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue = Vue;
directive的webpack引用
// diretive定义 module.exports = { update: function(newValue, oldValue) { console.log(newValue) console.log(oldValue) }, bind: function() { }, unbind: function() { } } // component定义应用directive module.exports = { template: require('./template.html'), directives: { currency: require('./directives/currency.js') } } //在template中 <div v-currency>$1.00</div>
VueX
http://skyronic.com/2016/01/03/vuex-basics-tutorial/
vuex对于构建大型应用是很是重要的,主要解决数据状态维护和传递的问题(好比不相干的多个组件须要更新数据,同时须要通知多个不相干的组件来应对这些数据的更新来更新视图):整个应用只有一个地方负责对状态数据的更新,任何一个地方均可以引用这个数据,须要更新则发消息给vuex来完成更新操做,其余任何引用了这个状态的地方都会reactive更新(computed属性)
主要的思路是保持single source of truth,好比有两个vue instance vmA和vmB,都依赖于一个状态,这时如何保持状态的同步就会是一个问题
var sourceOfTruth = {} var vmA = new Vue({ data: sourceOfTruth }) var vmB = new Vue({ data: sourceOfTruth })
动态建立而且mount组件到dom中
import ExecPlayer from './components/pages/exercise/exec-player.vue'; //这里exec-player只是export了一个component option object var execplayercomp = Vue.extend(ExecPlayer); //这里经过传入定义好的component option object做为Vue.extend的参数,以写程序的方式得到了component的构造函数 var execplayer = new execplayercomp({ // 'el': '#execplayer', //调用new xxx的方式来建立组件,若是给了el配置项,则无需再调用$mount // 须要注意的是这种方式建立的组件在devtool中并不会显示为rootvm的子组件,可是实际上他们是有父子关系的!!! // 若是在html代码中直接以 <exec-player></exec-player>调用的方式来composite组件的话,则在devtool中可以正确显示父子关系~! created(){ this.exercises = rootvm.exercises; } }).$mount(document.getElementById('execplayer'));
vm.$set vs Vue.set vs Object.assign
1. For Vue instances, you can use the $set(path, value) instance method: vm.$set('b', 2) // `vm.b` and `data.b` are now reactive 2. For plain data objects, you can use the global Vue.set(object, key, value) method Vue.set(data, 'c', 3) // `vm.c` and `data.c` are now reactive 3. 若是想使用.assign来一次性给一个object添加多个属性和value,须要注意: // instead of `Object.assign(this.someObject, { a: 1, b: 2 })` this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
vm.$nextTick() vs Vue.nextTick(callback)
到底nextTick是干什么的:
看一个牛人的回答: it's a way to execute a callback function after the data is set.
To add an example to that, lets say you have a jQuery plugin that creates a pie chart. The data on those charts are fetched and set by vuejs. You can't initialize the charts until after the data is set / until the "next tick". Here's a quick example...
If you try to initialize the charts without nextTick()
, it won't work because the data has not been changed yet.
<div id="example">{{msg}}</div> var vm = new Vue({ el: '#example', data: { msg: '123' } }) vm.msg = 'new message' // change data vm.$el.textContent === 'new message' // false Vue.nextTick(function () { vm.$el.textContent === 'new message' // true }) // 对于component下面的调用方法 Vue.component('example', { template: '<span>{{msg}}</span>', data: function () { return { msg: 'not updated' } }, methods: { updateMessage: function () { this.msg = 'updated' console.log(this.$el.textContent) // => 'not updated' this.$nextTick(function () { console.log(this.$el.textContent) // => 'updated' }) } } })
经过extends选项继承基础组件,使用js代码建立新组件,做为独立.vue文件方式构建新组建的替代方案,无须多个.vue文件
var CompA = { ... } // extend CompA without having to call Vue.extend on either var CompB = { extends: CompA, ... }
心法:在同一类组件须要在同一个mount point加载时,这种场景下最好的方案就是使用dynamic component。而使用动态组件又有两种方案:
1. 直接生成多个.vue组件,好比component-typeA.vue,component-typeB.vue,这种方式比较适合组件代码差别巨大的场景,具体应用时在使用该dynamic组件的父组件template中直接import这些.vue文件,而且components option中引用这些组件的定义
import CompTypeA from 'comp-type-a.vue' import CompTypeB from 'comp-type-b.vue' export default { ... components: [ CompTypeA, CompTypeB ] } // html: <component :is="comptype" :optionstoComp></component>
2. 只生成一个generic的.vue组件,随后其余组件extends这个generic组件,在引用该dynamic组件的父组件的script中直接使用js代码方式"声明"新的组件,并在父组件的template中使用:
import Comp from 'generic-comp.vue' var CompTypeA = { extends: Comp, templates: 'A', methods: ..A.. } var CompTypeB = { extends: Comp, templates: 'B', methods: ..B.. } export default { ... components: [ CompTypeA, CompTypeB ] } // html: <component :is="comptype" :optionstoComp></component>
vuejs中重用代码的几种方案
1. 建立自包含的组件,能够任意重用
2. 建立抽象组件,好比node,实体组件extends这个抽象组件,再添加本身的options,造成新的concret组件
3. mixins: 部分代码能够被任意无关的类,组件共用,这部分最适合使用mixin
// mixin引用方法 import VuexStateGetterMixin from './mixin.js' export default { mixins: [ VuexStateGetterMixin ], }
4. 只负责对DOM操做的功能,则能够抽象为directive来重用
javascript中用到的各个版本术语 ES5,ES6,ES2016,ECMAScript
- ECMAScript:一个由 ECMA International 进行标准化,TC39 委员会进行监督的语言。一般用于指代标准自己。
- JavaScript:ECMAScript 标准的各类实现的最经常使用称呼。这个术语并不局限于某个特定版本的 ECMAScript 规范,而且可能被用于任何不一样程度的任意版本的 ECMAScript 的实现。
- ECMAScript 5 (ES5):ECMAScript 的第五版修订,于 2009 年完成标准化。这个规范在全部现代浏览器中都至关彻底的实现了。
- ECMAScript 6 (ES6) / ECMAScript 2015 (ES2015):ECMAScript 的第六版修订,于 2015 年完成标准化。这个标准被部分实现于大部分现代浏览器。能够查阅这张兼容性表来查看不一样浏览器和工具的实现状况。
- ECMAScript 2016:预计的第七版 ECMAScript 修订,计划于明年夏季发布。这份规范具体将包含哪些特性尚未最终肯定
- ECMAScript Proposals:被考虑加入将来版本 ECMAScript 标准的特性与语法提案,他们须要经历五个阶段:Strawman(稻草人),Proposal(提议),Draft(草案),Candidate(候选)以及 Finished (完成)。
ES6中的=> arrow function
// ES5 var total = values.reduce(function (a, b) { return a + b; }, 0); // ES6 var total = values.reduce((a, b) => a + b, 0); // ES5 $("#confetti-btn").click(function (event) { playTrumpet(); fireConfettiCannon(); }); // ES6 $("#confetti-btn").click(event => { playTrumpet(); fireConfettiCannon(); });
arrow函数在vuejs event bus handler中的应用
vuejs2.0开始提倡使用event bus这个机制来实现代码解耦和任何模块之间的数据通讯,很是强大,可是使用中可能会遇到这样的困惑:在event handler中的this指针怎么才可以引用到event handler所在的vue instance呢?这时ES6的=>arrow函数就起到了做用,由于它不会改变this指针:
this.pcomBus.$on('toggle-checked-all-for-role', e => { // 这里this指针就指向的是定义这个pcomBus.$on调用的那个vue instance了! }
ES6 destruturing解构
const obj = { first: 'Jane', last: 'Doe' }; const {first: f, last: l} = obj; // f = 'Jane'; l = 'Doe' // {prop} 是 {prop: prop} 的缩写 const {first, last} = obj; // first = 'Jane'; last = 'Doe'
强烈建议参考 segmentfault.com/a/119000000… 讲解ES6基础语法
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions
npm安装vue, vuex等next分支上的最新版本:
npm install vue@next --save npm install vuex@next --save
vue2.0版本文件以及开发环境工具不匹配带来的问题
$ npm run dev Vue packages version mismatch: - vue@2.0.0-rc.2 - vue-template-compiler@2.0.0-beta.7 This may cause things to work incorrectly. Make sure to use the same version for both. If you are using vue-loader or vueify, re-installing them should bump vue-template-compiler to the latest.
解决办法npm install vue-loader@next --save-dev
vuex mapActions导入methods和local methods混用的语法
import { mapActions } from 'vuex' export default { methods:{ doJob: function(){ this.changeme(content) }, ...mapActions([ 'changeme' ]) } }
vuejs1.0 v-for track-by vs vuejs2.0 :key="item.id"
在vue v-for渲染一个列表时,若是不用被操做的item数据id做为key的话,那么vuejs会默认使用数组index做为key,而这时若是数据列表发生变化,好比一个item被删除,则可能会发生数据不一样步的怪异错误,强烈建议永远在v-for处理列表数据时,加上:key="item.id"!!!
v-for="skill in pskills" :key="skill.id"
什么是抽象组件以(transition组件为例)?
抽象组件具备如下特色:
1. 它是以引入功能而不是构建视图为目的而存在的
2. 它不会在DOM中有任何节点
3. 它也不会在inspect component hierarchy中显示出来
抽象组件和无template的组件有什么区别??
很是典型的例子是vuejs2.0中引入的transition组件:
<transition> <div v-if="ok">toggled content</div> </transition>
transition组件支持如下props:
1. name: 用于自动产生transition class name,好比若是name=fade,则对应的transition class将有.fade-enter,.fade-enter-active,.fade-leave,.fade-leave-active
2. appear: 是否在初次渲染时引用transition,默认为false;
3. css: 是否引用css transition class。默认为true,若是是false的话,则仅触发javascript hooks
4. type: 指定须要等待何种event来决定transition的结束。好比: "transition",或者"animation"
5. mode: 控制leaving/entering transition的timing sequence,可能的选项是:in-out或者out-in
6.enterClass,leaveClss,enterActiveClass,leaveActiveClass,appearClass,appearActiveClass分别用于设置对应的css class为客制内容.
好比:
<transition name="fade" mode="out-in" appear> <!-- classname 为 fade,fade-active,fade-leave,fade-leave-active为transition class --> <!-- 模式为先出再入,而且首次渲染时也应用transition --> <component :is="view"></component> </transition>
支持的事件:
before-enter,enter,after-enter,before-leave,leave,after-leave,before-appear,appear,after-appear
例子:
<transition @after-enter="transitionComplete"> <div v-show="ok">toggled content</div> </transition>
<transition-group>组件
若是须要对多个元素执行transition效果,则须要使用transition-group组件。和transition组件所不一样的是该组件须插入dom
Vue.component('fade', { functional: true, render (createElement, { children }) { const data = { props: { name: 'fade' }, on: { beforeEnter () { /* ... */ }, // <-- Note hooks use camelCase in JavaScript (same as 1.x) afterEnter () { /* ... */ } } } return createElement('transition', data, children) } }) <fade> <div v-if="ok">toggled content</div> </fade>
参考 http://vuejs.org/guide/render-function#Functional-Components
vuejs compile和mount过程
当vuejs app启动时,首先入口程序根据#el来找到本应用对应的html片断,该片断做为root的template,开始启动编译连接过程。每找到一个componennt,就开始walk through该组件的template,继续compile,直到编译完成。在vuejs2.0中因为没有了compiled钩子,咱们能够理解created事件就是按照上面的timing来完成的。若是对应有v-for,则开始启动loop过程,就像普通的js代码同样,先把第一次Loop的数据传给对应组件开始编译,直到全部的loop都完成了,这时compile过程能够认为是彻底完成了。mount过程则和compile彻底相反,由compile造成的dom node map反序逐级mount。也就是说先子组件后父组件直到root组件被mounted.
好比,若是咱们有下面的html结构,以及外围loop两次,a组件template内须要loop三次,则会按照下面的timing图来执行:
<!-- html content for root management partial --> <div id="root"> <div class="root" v-for="(item,index) of outerloop2times"><!-- 外围须要loop 2次 --> <a></a> <b></b> </div> <c></c> <!-- 注意c组件是最后一个created和mounted to root的元素 --> </div> <!-- root结束 --> <script type="x/template" id="a-template"> <div class="a-root"> <a-sub v-for="(item,index) of innerloop3times"></a-sub> <!-- 须要loop 3次 --> </div> </script> <script type="x/template" id="b-template"> <div class="b-root"> <!-- standard html partial --> </div> </script>
全部watcher代码都mounted以后才可能被触发。
关于vue组件的styling探讨
1. vuejs组件须要有一个container class来负责该vue组件的position, layout,display,float等style设置;
2. vue-root, vue-root-{skin}做为root元素的class
3. {skin}能够考虑经过global vuex state驱动传入,方便换肤
新增属性reactive注意事项:
新增单个属性,可使用:
Vue.set(vm.someobj,'newprop',propvalue)
新增多个属性对象,可使用:
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
对于对象的操做总体替换,修改等强烈建议使用Vue.set由于这样可以确保vuejs可以感知到这个变化
基于jqueryui select2组件在vuejs2.0(不支持prop.sync)下如何实现v-model模式的功能?
咱们知道vuejs2.0一个重大的变化是再也不支持prop.sync功能,也就是说从外部传入的数据是不能经过vue组件内部逻辑直接来实现更新的。而咱们在封装jquery-ui的select2组件造成咱们本身的select2组件时,又必需要实现v-model的功能,肿么办?还有就是若是销毁vuejs select2组件,而其底层的jqueryuiselect2控件并不会自动销毁,肿么办?
咱们看看官方的封装代码,解读一下:
Vue.component('select2', { props: ['options', 'value'], template: '#select2-template', mounted: function () { var vm = this $(this.$el) .val(this.value) // init select2 .select2({ data: this.options }) // emit event on change. .on('change', function () { vm.$emit('input', this.value) //这个很是重要,经过emit一个input事件来更新v-model数据 }) }, watch: { value: function (value) { // update value $(this.$el).select2('val', value) }, options: function (options) { // update options $(this.$el).select2({ data: options }) } }, destroyed: function () { // 这里也很重要,在vue组件销毁时,jquery ui本身生成的dom并不会销毁,咱们要经过jquery ui select2的api来实现销毁 $(this.$el).off().select2('destroy') } }) html <select2 v-model="selectedValue" <!-- 这里selectedValue会做为value属性传入select2组件, 组件更新数据时经过$emit('input', select2internalValue)传给selectedValue --> > </select2>
v-model internal
v-model不管用在input元素上仍是custom component上,其工做机制是同样的:
<input v-model="message"> <!-- roughly the same as: --> <input v-bind:value="message" @input="message = $event.target.value"> <my-component v-model="message"></my-component> <!-- roughly the same as: --> <my-component v-bind:value="message" @input="setMessage"> </my-component> <!-- setMessage provided by v-model, doing essentially: function (valuefrom$emittedInputEvent) { message = valuefrom$emittedInputEvent } -->
vuejs2.0动态渲染模版
https://cinwell.com/post/vue-2-dynamic-template/
经过router-view给对应组件传递参数
<router-view class="view" :users="users"></router-view>
使用ES6的特性建立动态key值的对象
看下面的代码:
export default{ data: function () { return { ["thisrootstore_"+this.$options.name]: { bus: new Vue() } } } }
上面这段代码能够做为mixin来被每个组件重用,动态建立包含本组件名称的本地全局数据变量 thisrootstore_xxname,其中的bus就能够用于本组件和其子孙组件事件通讯
而且能够被vuejs安全地merge,也就是说你能够再手工建立其余须要共享的数据在这个对象中
如何给webpack构建出来的bundle瘦身?
用过webpack你必定会发现每每就几行代码加到entry里面最终作出来的bundle却好几百k,对于带宽资源很是昂贵的主机来讲是一个噩梦,一方面加载龟速,另外一方面若是是手机访问更是噩梦。如何给他瘦身?首先找到哪些是咱们须要的,哪些是不该该出现的,对解决问题就作对了一半:
source-map-explorer 这个工具能够对build出来的sourcemap作分析,给出依赖关系,你能够慢慢研究
webpack-bundle-analyzer : 这个也很棒,他能够可视化地列出你的bundle最终是由哪些个js代码构成的,都是哪些臃肿的代码致使了你最终的bundle过于庞大了
http://www.tuicool.com/articles/BjIrEj6
如何在production build中自动将console.log清除掉?
strip-loader能够完成这个工做
如何获取vuejs powered plain data?
有时候咱们可能须要一个未经vuejs define propery(get/set)处理过的原始js数据,一个workaround方案:
JSON.parse(JSON.stringify($vm.action.roles))
如何侦听custom component的native事件,好比click? (使用.native修饰符)
<my-component v-on:click.native="doTheThing"></my-component>
如何绑定html元素属性要么为一个值,要么就没有?
<a :href="shouldhavehref ? 'http://xx.com/yy' : null">
上面的代码中判断shouldhavehref data值,若是该值为真则在a元素上存在href="http://xx.com/yy",若是该值为假,则返回null,从而a元素上就不存在href属性!
心法:只要expression的值为null,就会被清除掉!
v-if和v-for共存
若是v-if和v-for在同一个element上出现,则和angular相似,也有一个directive优先级的问题。在vuejs中,v-for的优先级要高于v-if,也就是说先作v-for的loop,随后对每个loop再应用v-if判断,好比下面的代码:对loop后的每个todo,只有在todo没有完成的状况下,咱们才render这个todo!!
<li v-for="todo in todos" v-if="!todo.isComplete"> {{ todo }} </li>
什么时候使用inline-template
一般,组件用于重用,可是有些时候,咱们可能只须要在一个页面增长一些交互性,这时咱们可能使用Inline-template的组件就比较合适了,缘由是其布置很是方便
// 将这段代码放在你的html页面中 <complete-progress inline-template> You have complete {{ count }} lessons <complete-progress> // 这段代码放在html页面引用的js文件中 Vue.component('complete-progress',{ data: function(){ return { count: 50 } } })