Vue笔记系列
一、Vue.js入门
三、Vue.js进阶
如下会随用随记一些API,可能会不按期更新。html
注册或获取全局组件。注册还会自动使用给定的id设置组件的名称。vue
// 注册组件,传入一个扩展过的构造器 Vue.component('my-component', Vue.extend({ /* ... */ })) // 注册组件,传入一个选项对象(自动调用 Vue.extend) Vue.component('my-component', { /* ... */ }) // 获取注册的组件(始终返回构造器) var MyComponent = Vue.component('my-component')
使用基础 Vue 构造器,建立一个“子类”。参数是一个包含组件选项的对象。注意:data 选项是特例,在 Vue.extend() 中它必须是函数。git
<!-- HTML --> <div id="mount-point"></div>
// 建立构造器 var Profile = Vue.extend({ template: '<p v-text="name"></p>', data: function () { return { name: '第一个构造器!' } } }) // 建立 Profile 实例,并挂载到一个元素上(会替换#mount-pointer)。挂载的组件会把被挂载的元素替换掉。 new Profile().$mount('#mount-pointer');
结果以下:github
<p>第一个构造器!</p>
若是挂载元素不想被替换掉,能够用如下方法:segmentfault
var component = new Profile().$mount() document.getElementById('mount-pointer').appendChild(component.$el)
Vue 不能检测到对象属性的添加或删除。因为 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,因此属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。Vue 不容许在已经建立的实例上动态添加新的根级响应式属性(因此,set方法的object参数也不能是 Vue 实例,或者 Vue 实例的根数据对象)。可使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象
以前说过的v-for指令,当你利用索引直接设置一个项时,例如上文的example1.words[0] = {text: 'A'},若是想让视图更新,其中一种方法就是用set。api
Vue.set(example1.items, 0, {text: 'A'})
在下次 DOM 更新循环结束以后执行延迟回调。在修改数据以后当即使用这个方法,获取更新后的 DOM。数组
<div id="example">{{message}}</div> var vm = new Vue({ el: '#example', data: { message: '123' } }) vm.message = 'new message' // 更改数据 vm.$el.textContent === 'new message' // false Vue.nextTick(function () { vm.$el.textContent === 'new message' // true })
为毛第一次DOM里面的内容没有变,拿不到改变的内容,通过nextTick方法后才才能拿到改变的内容。
这是由于,当你设置 vm.someData = 'new value' ,该组件不会当即从新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。浏览器
全局配置——Vue.config
是一个对象,包含 Vue 的全局配置。有如下属性:服务器
Vue.config.silent = true;
取消 Vue 全部的日志与警告,false时开启。Vue.config.devtools = true;
配置是否容许 vue-devtools 检查代码。开发版本默认为 true,生产版本默认为 false。vue-devtools指的是一个浏览器插件,在谷歌应用里面有。安装以后,是在google 开发者工具的这里找到。app
一、全局注册
注册一个全局组件,可使用 Vue.component(tagName, options)
。
注意:对于自定义标签名,Vue.js 不强制要求遵循 W3C规则 (小写,而且包含一个短杠),可是建议这样写。
组件在注册以后,即可以在父实例的模块中以自定义元素的形式使用。谨记要确保在初始化根实例以前注册了组件。而且, el 和 data 选项必须是函数。
<!-- HTML --> <div id="example"> <my-component></my-component> </div>
// 注册,这就是所谓的语法糖,由于下面的方法有点麻烦。 Vue.component('my-component', { template: '<div>个人第一个组件!</div>' }) // 建立父实例 new Vue({ el: '#example' })
渲染为:
<div id="example"> <div>个人第一个组件!</div> </div>
二、构造器用做组件
可使用 Vue.extend({...})
建立一个组件构造器,extend 方法建立基础Vue构造器的子类,参数是一个对象,包含组件选项,这里要注意的特例是 el 和 data 选项,在 Vue.extend() 中,它们必须是函数。注册组件的component方法也同样。这是由于,若是使用一个数据对象(是一个引用),那么全部的组件实例都共享这一个对象,这样就会牵一发而动全身。
有了这个构造器,咱们既能够用全局注册的方式用 Vue.component(tag, constructor)
注册,也能够利用该构造器构建一个实例,而后用 Vue.$mount() 将该组件实例添加到DOM树上。
<!-- HTML --> <div id="example"> <my-component></my-component> </div>
// 建立构造器 var Profile = Vue.extend({ template: '<p v-text="name"></p>', data: function () { return { name: '第一个构造器组件!' } } }) // 注册 Vue.component('my-component',Profile) // 建立父实例 new Vue({ el: '#example' })
渲染为:
<div id="example"> <p>第一个构造器组件!</p> </div>
三、局部注册
经过使用组件实例选项注册,可使组件仅在另外一个实例/组件的做用域中可用。即在注册的对象参数中添加 components 成员,components成员的标签就只在该组件内使用,不在全局DOM树中使用局部注册的组件。
//实例做用域 var Child = { template: '<div>一个局部组件!</div>' } new Vue({ // ... components: { // <my-component> 将只在父模板可用 'my-component': Child } }) //组件做用域 <div id="comp-ex"> <contact></contact> </div> <script> var Person = Vue.extend({ // constructor template: "<div><span>name:</span> {{name}} <span>Age: </span> {{age}}</div>", data: function() { return { name: ' li ming', age: 22 } } }); var Contact = Vue.extend({ template: "<div><span>Tel: </span> {{tel}}, <span>E-mail: </span> {{email}}<person></person></div>", data: function() { return { tel: '152-0101-1010', email: 'admin#163.com' } }, components: { 'person': Person // 局部注册 person 标签 } }) Vue.component('contact', Contact) var you = new Vue({ // init component, render template el: '#comp-ex' }) </script>
子组件只能在父组件的template中使用。注意下面两种子组件的使用方式是错误的:
<div id="app"> <parent-component> <child-component></child-component> </parent-component> </div>
由于当子组件注册到父组件时,Vue.js会编译好父组件的模板,模板的内容已经决定了父组件将要渲染的HTML。
<parent-component>…</parent-component>至关于运行时,它的一些子标签只会被看成普通的HTML来执行,<child-component></child-component>不是标准的HTML标签,会被浏览器直接忽视掉。
<div id="app"> <parent-component> </parent-component> <child-component> </child-component> </div>
运行这段代码,浏览器会提示如下错误:
四、is特性
一些 HTML 元素,如<ul>
<ol>
<table>
<select>
,限制什么元素能够放在它里面。自定义元素不在白名单上,将被放在元素的外面,于是渲染不正确。这时应当使用 is 特性,指示它是一个自定义元素。
<table> <my-row>...</my-row> </table> <!-- 自定义组件 <my-row> 被认为是无效的内容,所以在渲染的时候会致使错误。须要使用特殊的 is 属性 --> <table> <tr is="my-row"></tr> </table>
良好的流程: Vue.js 中,父子组件的关系能够总结为 props down, events up 。父组件经过 props 向下传递数据给子组件,子组件经过 events 给父组件发送消息。
一、Prop显式声明
组件实例的做用域是孤立的。这意味着不能而且不该该在子组件的模板内直接引用父组件的数据。可使用 props 把数据传给子组件。prop 是父组件用来传递数据的一个自定义属性。子组件须要显式地用 props选项声明 “prop”:
读到这里,咱们掌握的组件的构造选项对象的属性包括了:
- template,要渲染的内容
- data,数据,必须是一个函数,函数返回一个对象
- props,从父组件传递数据到子组件。
<div id="comp-ex"> <!-- 注意在HTML中属性要写kebab-case(短横线隔开) --> <child my-message='qiqihaobenben'></child> <child my-message='qiqihaobenben'></child> </div> <script> Vue.component('child', { // 就像 data 同样,prop 能够用在模板内 template: '<span>{{message}}</span>', //声明 props // HTML特性不区分大小写,名字形式为 camelCase 的prop用做特性时,写在HTML中要用短横线隔开,不然不起做用,如上。 props: ['myMessage'], // 一样也能够在 vm 实例中像 “this.message” 这样使用 data: function (){ return { message: this.myMessage } } }) var me = new Vue({ el: '#comp-ex' }) <script>
输出结果为:
<div id="comp-ex"> <span>qiqihaobenben qiqihaobenben</span> </div>
HTML特性不区分大小写,可是名字形式为 camelCase 的prop用做特性时,须要转为 kebab-case(短横线隔开)。在html中的属性使用短横线隔开,而在js的template中的标识使用的驼峰命名,能够参考上面的例子。
上面的例子使用节点属性方式向子组件传递数据,若是父组件的数据变化,子组件并不会随之变化,这就是其动态性,若是要绑定动态的值,应该使用 v-bind 指令,这样每当父组件的数据变化时,也会传递给子组件
<div> <input v-model="parentMsg"> <br> <child v-bind:my-message="parentMsg"></child> </div>
动态和非动态这两种传递方式在传递数字时是不一样的,以下:
<!-- 传递了一个字符串"1" --> <comp some-prop="1"></comp> <!-- 传递实际的数字1 --> <comp v-bind:some-prop="1"></comp> 虽然html渲染的时候并不太区分字符串和数字,可是注意有这种区别。
prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,可是不会反过来。这是为了防止子组件无心修改了父组件的状态——这会让应用的数据流难以理解。
注意:在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,若是 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。
原本不想说的,但是看文档时怎么用怎么不对,后来想通了,因此就拿出来讲一下。
看官方文档能够知道,type 能够是下面原生构造器:
其中的除了String,其余的类型都须要动态绑定才能验证正确,不然获得的就是一水的String类型。
//js属性验证 props: { myAge: { type: [Number,Boolean,Object,Array,Function] } } <!--如下都会报错,Expected XXX, got String.--> <my-component my-age="12"></my-component> <my-component my-age="true"></my-component> <my-component my-age="{}"></my-component> <my-component my-age="[]"></my-component> <my-component my-age="consoleOne"></my-component> <!--正确的作法是用v-bind来绑定属性--> <my-component v-bind:my-age="12"></my-component> ... ...
另外,default和required这两个验证规则,须要组件的属性彻底不存在时才会生效
//设置默认值 props: { myName: { default: 2 } } //设置必传项 props: { myName: { required: true } } <!--如下,不论是必传项或者默认值都不会有效果--> <my-component my-name="" ></my-component> <!--只有这个属性在组件上真的没有,才会触发验证效果--> <my-component ></my-component>
以上,当 prop 验证失败了,若是使用的是开发版本会抛出一条警告。
二、自定义事件
尽管能够访问父链上任意的实例,不过子组件应当避免直接依赖父组件的数据。另外子组件中修改父组件的状态是很是糟糕的,由于:
父组件是使用 props 传递数据给子组件,若是子组件要把数据传递回去,那就是自定义事件!
每一个 Vue 实例都实现了事件接口(Events interface),即:
父组件——使用 $on(eventName) 监听事件
子组件——使用 $emit(eventName) 触发事件
下面的列子跟官网的几乎同样,可是区别在于,是经过传参来改变父组件的状态的赋值。
<div id="counter-event-example"> <p>{{ total }}</p> <button-counter v-on:increment="incrementTotal"></button-counter> <button-counter v-on:increment="incrementTotal"></button-counter> </div>
var allTotal = {number: 0} //这个是统计两个按钮的点击的次数,这样就能直接赋给父组件 Vue.component('button-counter', { template: '<button v-on:click="increment">{{ counter }}</button>', data: function () { return { counter: 0, allCounter: allTotal } }, methods: { increment: function () { this.counter += 1; this.allCounter.number += 1; // 准备给父组件的自定义事件方法传参 this.$emit('increment',this.allCounter.number); } }, }) new Vue({ el: '#counter-event-example', data: { total: 0 }, methods: { incrementTotal: function (value) { this.total = value; } } })
在某个组件的根元素上监听一个原生事件。可使用 .native 修饰 v-on 。这就是说全部的原生事件若是在组件的根元素上不加.native,vue会自动认为它是自定义事件
使用 v-model 来进行数据双向绑定。牢记:这个指令仅仅是一个语法糖。
<input v-model="something"> <!--上下两种方式是等价的--> <input v-bind:value="something" v-on:input="something = $event.target.value">
因此在组件中使用时,它至关于下面的简写:
<custom-input v-bind:value="something" v-on:input="something = arguments[0]"></custom-input> <!--改写成语法糖以下--> <custom-input v-model="something"></custom-input>
尤为注意,这个地方有点绕:若是是自定义的表单组件,而且父组件在加载这个表单组件时使用了v-model指令,那么做为子组件,接收到的prop应该是value而不是something。
<div id="parent"> <user-profile ref:profile></user-profile> </div> <script> var parent = new Vue({el: '#parent'}); var child = parent.$refs.profile; // 能够获得子组件 </script>
父组件模板的内容在父组件做用域内编译;子组件模板的内容在子组件做用域内编译。说白了,就是一眼看上去,在谁里面就是谁的。
<div id="app"> <my-component v-show="display"></my-component> </div> <template id="myComponent"> <table> <tr> <th colspan="3">{{msg}}</td> </tr> </table> </template> <script> var my = Vue.extend({ template: '#myComponent', data : function (){ return { msg : '这是子组件', display: false } } }) var vm = new Vue({ el: '#app', data: { display: true }, components: { 'myComponent': my } }) </script>
在my-component标签上使用指令v-show="display",这个display数据是来源于Vue实例vm ,仍是my-component组件呢?
答案是Vue实例
下面的代码在定义my-component组件的模板时,指定了一个<slot></slot>元素。
<div id="app"> <my-component> <h1>这是父组件的内容!</h1> </my-component> <my-component> </my-component> </div> <template id="myComponent"> <div class="content"> <h2>这是一个子组件!</h2> <slot>若是没有分发内容,则显示slot中的内容</slot> <p>Hello,Vue.js</p> </div> </template> <script> Vue.component('my-component', { template: '#myComponent' }) new Vue({ el: '#app' }) </script>
第一个<my-component>标签有一段分发内容 <h1>这是父组件的内容!</h1>
,渲染组件时显示了这段内容。
第二个<my-component>标签则没有,渲染组件时则显示了slot标签中的内容。
<slot> 元素能够用一个特殊的属性 name 来配置如何分发内容。多个 slot 能够有不一样的名字。具名 slot 将匹配内容片断中有对应 slot 特性的元素。
<div id="app"> <my-component> <div slot="header"> 这是一个头部 </div> <p>neirong</p> <p>neirong</p> <div slot="footer"> 这是一个底部 </div> </my-component> </div> <template id="myComponent"> <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> </template> <script> Vue.component('my-component', { template: '#myComponent' }) new Vue({ el: '#app' }) </script>
能够看出仍然能够有一个匿名 slot ,它是默认 slot ,做为找不到匹配的内容片断的备用插槽。若是没有默认的 slot ,这些找不到匹配的内容片断将被抛弃。
做用域插槽是一种特殊类型的插槽,用做使用一个(可以传递数据到)可重用模板替换已渲染元素。
数据传递,可重用,天然而然的想到循环
<div id="app"> <my-awesome-list :items="items"> <template slot="item" scope="props"> <li>{{ props.text }}</li> </template> </my-awesome-list> </div> <script> Vue.component('my-awesome-list',{ template: `<ul> <slot name="item" v-for="item in items" :text="item.text"> </slot> </ul>`, props: ['items'] }) var demo = new Vue({ el: '#app', data: { items: [ {text: 'aaaaa'}, {text: 'bbbbb'}, {text: 'ccccc'}, {text: 'ddddd'}, {text: 'eeeee'} ] } }) </script>
当注册组件(或者 props)时,可使用 kebab-case ,camelCase ,或 TitleCase。可是,在 HTML 模版中,请使用 kebab-case 形式:
// 在组件定义中 components: { // 使用 kebab-case 形式注册 'kebab-cased-component': { /* ... */ }, // register using camelCase 'camelCasedComponent': { /* ... */ }, // register using TitleCase 'TitleCasedComponent': { /* ... */ } } <!-- 在HTML模版中始终使用 kebab-case --> <kebab-cased-component></kebab-cased-component> <camel-cased-component></camel-cased-component> <title-cased-component></title-cased-component>
注意:当使用字符串模式时,能够不受 HTML 的 case-insensitive 限制。
在实例初始化以后,数据观测(data observer) 和 event/watcher 事件配置以前被调用。
实例已经建立完成以后被调用。在这一步,实例已完成如下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
在挂载开始以前被调用:相关的 render 函数首次被调用。该钩子在服务器端渲染期间不被调用。
el 被新建立的 vm.$el 替换,并挂载到实例上去以后调用该钩子。若是 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。该钩子在服务器端渲染期间不被调用。
数据更新时调用,发生在虚拟 DOM 从新渲染和打补丁以前。
你能够在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
该钩子在服务器端渲染期间不被调用。
因为数据更改致使的虚拟 DOM 从新渲染和打补丁,在这以后会调用该钩子。
当这个钩子被调用时,组件 DOM 已经更新,因此你如今能够执行依赖于 DOM 的操做。然而在大多数状况下,你应该避免在此期间更改状态,由于这可能会致使更新无限循环。
该钩子在服务器端渲染期间不被调用。
keep-alive 组件激活时调用。该钩子在服务器端渲染期间不被调用。
keep-alive 组件停用时调用。该钩子在服务器端渲染期间不被调用。
实例销毁以前调用。在这一步,实例仍然彻底可用。该钩子在服务器端渲染期间不被调用。
Vue 实例销毁后调用。调用后,Vue 实例指示的全部东西都会解绑定,全部的事件监听器会被移除,全部的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
手动地挂载一个未挂载的实例,返回值是实例自身。于是能够链式调用其它实例方法。
若是没有提供 elementOrSelector 参数,模板将被渲染为文档以外的的元素,而且你必须使用原生DOM API把它插入文档中。
var MyComponent = Vue.extend({ template: '<div>Hello!</div>' }) // 建立并挂载到 #app (会替换 #app) new MyComponent().$mount('#app') // 同上 new MyComponent({ el: '#app' }) // 或者,在文档以外渲染而且随后挂载,这种方式不会替换#app var component = new MyComponent().$mount() document.getElementById('app').appendChild(component.$el)
彻底销毁一个实例。清理它与其它实例的链接,解绑它的所有指令及事件监听器。触发 beforeDestroy 和 destroyed 的钩子。
注意:在大多数场景中你不该该调用这个方法。最好使用 v-if 和 v-for 指令以数据驱动的方式控制子组件的生命周期。
将回调延迟到下次 DOM 更新循环以后执行。在修改数据以后当即使用它,而后等待 DOM 更新。它跟全局方法 Vue.nextTick 同样,不一样的是回调的 this 自动绑定到调用它的实例上。
应用上,在组件内使用 vm.$nextTick() 实例方法特别方便,由于它不须要全局 Vue ,而且回调函数中的 this 将自动绑定到当前的 Vue 实例上:
Vue.component('example', { template: '<span>{{ message }}</span>', data: function () { return { message: 'not updated' } }, methods: { updateMessage: function () { this.message = 'updated' console.log(this.$el.textContent) // => '没有更新' this.$nextTick(function () { console.log(this.$el.textContent) // => '更新完成' }) } } })