本文的 Demo 和源代码已放到 GitHub,如果您觉得本篇内容不错,请点个赞,或在 GitHub 上加个星星!
https://github.com/zwl-jasmine95/Vue_test
以下所有知识都是基于 vue.js 2.0 版本
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以
特性扩展。
- is
组件的使用有两个步骤:注册组件 和使用组件。
(1)要注册一个全局组件,可以使用 :
- Vue.component(tagName, options)
例如:
- Vue.component('my-component', {
- // 选项
- })
(2)创建根实例:
- //创建根实例
- var vm = new Vue({
- el:'#component-demo'
- });
案例:
- 1 < !DOCTYPE html > 2 < html lang = "en" > 3 < head > 4 < meta charset = "UTF-8" > 5 < title > 全局组件(component)的基本使用 < /title> 6 </head > 7 < body > 8 < div id = "component-demo" > 9 < !--2.#component - demo是Vue实例挂载的元素,应该在挂载元素范围内使用组件-->10 < hello - component > </hello-component> 11 </div > 12 13 < script type = "text/javascript"src = "../lib/js/vue.js" > </script> 14 <script type="text/javascript "> 15 16 //1.全局组件注册,并指定组件的标签,组件的HTML标签为<hello-component> 17 Vue.component('hello-component',{ 18 template:'<h1>hello component!</h1>' 19 }); 20 21 //创建根实例 22 var vm = new Vue({ 23 el:'#component-demo' 24 }); 25 26 </script> 27 </body> 28 </html>"
注意事项:
- 对于自定义标签名,Vue.js 不强制要求遵循 W3C 规则 (小写,并且包含一个短杠),尽管遵循这个规则比较好。
- 注册组件必须发生在根实例初始化前。
- 如果自定义标签名使用的是驼峰式命名,在使用的时候仍然要在大写字母处加上短杠,并将大写字母改为小写。例如:命名为'helloWorldComponent',在使用时变为 <hello-world-component></hello-world-component>
调用
注册组件时,组件的注册是全局的,这意味着该组件可以在任意 Vue 示例下使用。如果不需要全局注册,或者是让组件使用在其它组件内,可以用选项对象的 components 属性实现局部注册。
- Vue.component()
- 1 <div id="component-demo">
- 2 <local-component></local-component>
- 3 </div>
- 4 <script type="text/javascript">
- 5
- 6 var child = {
- 7 template:'<h1>局部组件的基本使用!</h1>'
- 8 };
- 9
- 10 var vm = new Vue({
- 11 el:'#component-demo',
- 12 components:{
- 13 'local-component':child
- 14 }
- 15 });
- 16 </script>
由于 local-component 组件是注册在 #component-demo 元素对应的 Vue 实例下的,所以它不能在其它 Vue 实例下使用。如果你这样做了,浏览器会提示一个错误:
- 1 < !DOCTYPE html > 2 < html lang = "en" > 3 < head > 4 < meta charset = "UTF-8" > 5 < title > 局部组件 < /title> 6 </head > 7 < body > 8 9 < div id = "component-demo" > 10 < local - component > </local-component> 11 </div > 12 13 < div id = "component-demo2" > 14 < !--这里会报错,因为local - component是#component - demo的局部组件,不能在其他地方使用-->15 < local - component > </local-component> 16 </div > 17 18 < script type = "text/javascript"src = "../lib/js/vue.js" > </script> 19 <script type="text/javascript "> 20 21 var child = { 22 template:'<h1>局部组件的基本使用!</h1>' 23 }; 24 25 var vm = new Vue({ 26 el:'#component-demo', 27 components:{ 28 'local-component':child 29 } 30 }); 31 32 var vm2 = new Vue({ 33 el:'#component-demo2' 34 }); 35 36 37 </script> 38 </body> 39 </html>"
组件意味着协同工作,通常父子组件会是这样的关系:组件 A 在它的模版中使用了组件 B。它们之间必然需要相互通信:父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件。然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性。
在 Vue 中,父子组件的关系可以总结为 props down, events up。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。
当使用 DOM 作为模版时 (例如,将
选项挂载到一个已存在的元素上), 你会受到 HTML 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模版内容。尤其像这些元素
- el
,
- <ul>
,
- <ol>
,
- <table>
限制了能被它包裹的元素,而一些像
- <select>
这样的元素只能出现在某些其它元素内部。
- <option>
在自定义组件中使用这些受限制的元素时会导致一些问题,例如:
- <table>
- <my-row>...</my-row>
- </table>
自定义组件
被认为是无效的内容,因此在渲染的时候会导致错误。变通的方案是使用特殊的
- <my-row>
属性:
- is
- <table>
- <tr is="my-row"></tr>
- </table>
应当注意,如果您使用来自以下来源之一的字符串模板,这些限制将不适用:
- <script type="text/x-template">
- JavaScript 内联模版字符串
组件
- .vue
因此,有必要的话请使用字符串模版。
上述组件注册方法在 template 选项中拼接 HTML 元素比较麻烦,这也导致了 HTML 和 JavaScript 的高耦合性。庆幸的是,Vue.js 提供了两种方式将定义在 JavaScript 中的 HTML 模板分离出来。
- 1 < !DOCTYPE html > 2 < html lang = "en" > 3 < head > 4 < meta charset = "UTF-8" > 5 < title > 使用script标签 < /title> 6 </head > 7 < body > 8 < div id = "demo" > 9 < my - component > </my-component> 10 </div > 11 12 < script type = "text/x-template"id = "myComponent" > 13 < h1 > 组件使用之使用script标签 < /h1> 14 </script > 15 16 < script type = "text/javascript"src = "../lib/js/vue.js" > </script> 17 <script type="text/javascript "> 18 Vue.component('my-component',{ 19 template:'#myComponent' 20 }); 21 22 var vm = new Vue({ 23 el:'#demo' 24 }); 25 </script> 26 </body> 27 </html>"
template 选项现在不再是 HTML 元素,而是一个 id,Vue.js 根据这个 id 查找对应的元素,然后将这个元素内的 HTML 作为模板进行编译。
注意:使用
如果使用
标签,则不需要指定 type 属性。
- <template>
- 1 < !DOCTYPE html > 2 < html lang = "en" > 3 < head > 4 < meta charset = "UTF-8" > 5 < title > 使用template标签 < /title> 6 </head > 7 < body > 8 < div id = "demo" > 9 < my - component > </my-component> 10 </div > 11 12 < template id = "myComponent" > 13 < h1 > 组件使用之使用template标签 < /h1> 14 </template > 15 16 < script type = "text/javascript"src = "../lib/js/vue.js" > </script> 17 <script type="text/javascript "> 18 Vue.component('my-component',{ 19 template:'#myComponent' 20 }); 21 22 var vm = new Vue({ 23 el:'#demo' 24 }); 25 </script> 26 </body> 27 </html>"
建议使用 <script> 或 <template> 标签来定义组件的 HTML 模板。这使得 HTML 代码和 JavaScript 代码是分离的,便于阅读和维护。
通过 Vue 构造器传入的各种选项大多数都可以在组件里用。
是一个例外,它必须是函数。
- data
如果这样写:
- Vue.component('hello-component',{
- template:'<h1>{{message}}</h1>',
- data:{
- message:1
- }
- });
会报错:
所以应该改为:
- Vue.component('my-component',{
- template:'<h1>{{message}}</h1>',
- data:function () {
- return {message : 'data必须为函数'};
- }
- });
组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。要让子组件使用父组件的数据,我们需要通过子组件的 props 选项。
- 1 <div id="demo">
- 2 <child-component message="hello props!"></child-component>
- 3 </div>
- 4
- 5 <template id="myComponent">
- 6 <h1>{{message}}</h1>
- 7 </template>
- 8
- 9 <script type="text/javascript" src="../lib/js/vue.js"></script>
- 10 <script type="text/javascript">
- 11 var child = {
- 12 // 声明 props
- 13 props: ['message'],
- 14 // 就像 data 一样,prop 可以用在模板内
- 15 // 同样也可以在 vm 实例中像"this.message"这样使用
- 16 template:'#myComponent'
- 17 };
- 18
- 19 var vm = new Vue({
- 20 el:'#demo',
- 21 components:{
- 22 'child-component':child
- 23 }
- 24 });
- 25 </script>
HTML 特性是不区分大小写的。所以,当使用的不是字符串模版,camelCased (驼峰式) 命名的 prop 需要转换为相对应的 kebab-case (短横线隔开式) 命名
在模板中,要动态地绑定父组件的数据到子模板的 props,与绑定到任何普通的 HTML 特性相类似,就是用
。每当父组件的数据变化时,该变化也会传导给子组件。
- v-bind
- 1 <body>
- 2 <div id="demo">
- 3 父组件信息:<input type="text" v-model="parentMessage">
- 4 <child-component v-bind:message="parentMessage"></child-component>
- 5 </div>
- 6
- 7 <template id="myComponent">
- 8 <h1>子组件获取的信息为:{{message}}</h1>
- 9 </template>
- 10
- 11 <script type="text/javascript" src="../lib/js/vue.js"></script>
- 12 <script type="text/javascript">
- 13
- 14 var child = {
- 15 props: ['message'],
- 16 template:'#myComponent'
- 17 };
- 18
- 19 var vm = new Vue({
- 20 el:'#demo',
- 21 data:{
- 22 parentMessage:'这里是父组件信息!'
- 23 },
- 24 components:{
- 25 'child-component':child
- 26 }
- 27 });
- 28 </script>
- 29 </body>
demo
初学者常犯的一个错误是使用字面量语法传递数值:
- <!-- 传递了一个字符串 "1" -->
- <comp some-prop="1"></comp>
因为它是一个字面 prop,它的值是字符串
而不是 number。如果想传递一个实际的 number,需要使用
- "1"
,从而让它的值被当作 JavaScript 表达式计算:
- v-bind
- <!-- 传递实际的 number -->
- <comp v-bind:some-prop="1"></comp>
prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态。
- 1 < !DOCTYPE html > 2 < html lang = "en" > 3 < head > 4 < meta charset = "UTF-8" > 5 < title > props单项数据绑定 < /title> 6 <link rel="stylesheet" href="../lib / css / bootstrap.min.css "> 7 </head> 8 <body> 9 <div id="demo "> 10 <table class="table table - striped "> 11 <tr> 12 <td colspan="3 ">父组件数据</td> 13 </tr> 14 <tr> 15 <td>姓名</td> 16 <td>{{parentName}}</td> 17 <td><input type="text " v-model="parentName "/></td> 18 </tr> 19 <tr> 20 <td>年龄</td> 21 <td>{{parentAge}}</td> 22 <td><input type="text " v-model="parentAge "/></td> 23 </tr> 24 </table> 25 26 <!--注意这里camelCased (驼峰式) 命名的 prop 需要转换为相对应的 kebab-case (短横线隔开式) 命名--> 27 <child-component v-bind:child-name="parentName " v-bind:child-age="parentAge "></child-component> 28 29 </div> 30 31 <template id="myComponent "> 32 <table class="table table - striped "> 33 <tr> 34 <td colspan="3 ">子组件数据</td> 35 </tr> 36 <tr> 37 <td>姓名</td> 38 <td>{{childName}}</td> 39 </tr> 40 <tr> 41 <td>年龄</td> 42 <td>{{childAge}}</td> 43 </tr> 44 </table> 45 </template> 46 47 <script type="text / javascript " src=".. / lib / js / vue.js "></script> 48 <script type="text / javascript "> 49 50 var child = { 51 template:'#myComponent', 52 props: ['childName','childAge'] 53 54 }; 55 56 var vm = new Vue({ 57 el:'#demo', 58 data:{ 59 parentName:'*茉莉花开*', 60 parentAge:22 61 }, 62 components:{ 63 'child-component':child 64 } 65 }); 66 </script> 67 </body> 68 </html>"
父组件的数据更改时,子组件的数据也跟着修改:
每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。
为什么我们会有修改 prop 中数据的冲动呢?通常是这两种原因:
对这两种原因,正确的应对方式是:
- props: ['initialCounter'],
- data: function () {
- return { counter: this.initialCounter }
- }
- props: ['size'],
- computed: {
- normalizedSize: function () {
- return this.size.trim().toLowerCase()
- }
- }
针对上述 demo 代码,我们可以稍作修改,将子组件从父组件获取的姓名变为新的姓名:
我们可以看到效果:
!!!在这里,父组件的姓名改变时,子组件的姓名不会跟着改变。
demo
注意:
1. 在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。
2.Vue 2.x 相比较 Vue 1.x 而言,升级变化除了实现了 Virtual-Dom 以外,给使用者最大不适就是移除的组件的
的双向绑定功能。以往在 Vue1.x 中利用
- props
的
- props
和
- twoWay
绑定修饰符就可以实现 props 的双向绑定功能,但是在 Vue2 中彻底废弃了此功能,如果需要双向绑定需要自己来实现。
- .sync
我们可以为组件的 props 指定验证规格。如果传入的数据不符合规格,Vue 会发出警告。当组件给其他人使用时,这很有用。
要指定验证规格,需要用 对象的形式,而不能用字符串数组:
- 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
也可以是一个自定义构造器函数,使用
- type
检测。
- instanceof
当 prop 验证失败,Vue 会在抛出警告 (如果使用的是开发版本)。注意 props 会在组件实例创建之前进行校验,所以在
或
- default
函数里,诸如
- validator
、
- data
或
- computed
等实例属性还无法使用。
- methods
实例:将第三小节中的 demo 代码修改,给传递的数据姓名和年龄加上验证,姓名必须为字符串类型,年龄必须为数字且不为空:
在年龄那输入 "m",可以看见控制台报错了。因为传递的数据是字符串类型,而年龄 props 验证要求的是数字。
demo
父组件是使用 props 传递数据给子组件,但子组件怎么跟父组件通信呢?这个时候 Vue 的自定义事件系统就派得上用场了。
每个 Vue 实例都实现了事件接口 (Events interface),即:
监听事件
- $on(eventName)
触发事件
- $emit(eventName)
另外,父组件可以在使用子组件的地方直接用
来监听子组件触发的事件。不能用
- v-on
侦听子组件抛出的事件,而必须在模板里直接用
- $on
绑定。
- v-on
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>v-on绑定自定义事件</title>
- <script type="text/javascript" src="../lib/js/vue.js"></script>
- </head>
- <body>
- <div id="counter-event-example">
- <p>{{ total }}</p>
- <button-counter v-on:add="incrementTotal"></button-counter>
- </div>
- <script type="text/javascript">
- Vue.component('button-counter', {
- template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
- data: function () {
- return {
- counter: 0
- }
- },
- methods: {
- incrementCounter: function () {
- this.counter += 1;
- this.$emit('add'); //向父组件报告,自己发生了'add'事件
- }
- }
- });
- new Vue({
- el: '#counter-event-example',
- data: {
- total: 0
- },
- methods: {
- incrementTotal: function () {
- this.total += 1
- }
- }
- })
- </script>
- </body>
- </html>
分析:
第一步:在子组件里面把点击事件(click)绑定给了函数 incrementCounter(即图片里面的步骤 1),这里容易理解,即点击了子组件的按钮将会触发位于子组件的 incrementCounter 函数。
第二步:在触发 incrementCounter 函数的时候,子组件的数字在原来值的基础上加 1,并且表示向父组件报告自己触发了 add 事件(至于发生的事件叫什么名字,可以随意取名,只要在父组件中绑定时名称一致即可。)。
第三步: 在子组件触发 add 事件的时候,父组件调用 incrementTotal 函数来响应子组件。
这时我们回想步骤 2,在子组件我们已经使用 emit 来进行通知,所以,这样就形成父子组件间的相互呼应传递信息,其实在开发的过程中父子组件通讯也都是使用这样的方法,父组件传递信息给子组件的时候会通过 props 参数,通常不会直接在子组件中修改父组件传递下来的信息,而且通过这样的一个钩子去通知父组件对某些参数进行改变。
第四步:定义父组件的 incrementTotal 函数,数值在原基础上加 1。
有时候,你可能想在某个组件的根元素上监听一个原生事件。可以使用
修饰
- .native
。例如:
- v-on
- <my-component v-on:click.native="doTheThing"></my-component>
自定义事件可以用来创建自定义的表单输入组件,使用
来进行数据双向绑定。看看这个:
- 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>
接受一个
属性所以要让组件的
- value
生效,它应该 (在 2.2.0+ 这是可配置的):
- v-model
属性
- value
事件
- input
demo:
- 1 <div id="demo">
- 2 <currency-input v-model="price"></currency-input>
- 3 </div>
- 4
- 5 <template id="currency">
- 6 <span>$<input ref="input" v-bind:value="value" v-on:input="updateValue($event.target.value)" /></span>
- 7 </template>
- 8
- 9 <script type="text/javascript">
- 10 Vue.component('currency-input',{
- 11 template:'#currency',
- 12 props: ['value'],
- 13 methods: {
- 14 // 不是直接更新值,而是使用此方法来对输入值进行格式化和位数限制
- 15 updateValue: function (value) {
- 16 var formattedValue = value
- 17 // 删除两侧的空格符
- 18 .trim()
- 19 // 保留 2 小数位
- 20 .slice(0,value.indexOf('.') === -1 ? value.length: value.indexOf('.') + 3);
- 21 // 如果值不统一,手动覆盖以保持一致
- 22 if (formattedValue !== value) {
- 23 this.$refs.input.value = formattedValue
- 24 }
- 25 // 通过 input 事件发出数值
- 26 this.$emit('input', Number(formattedValue))
- 27 }
- 28 }
- 29 });
- 30
- 31 var vm = new Vue({
- 32 el:'#demo',
- 33 data:{
- 34 price:''
- 35 }
- 36 })
- 37 </script>
(只能输入小数点后两位,只能输入数字)
特殊属性 ref:
- (预期:
- string)
被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的
- ref
对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素; 如果用在子组件上,引用就指向组件实例:
- $refs
- <!-- vm.$refs.p will be the DOM node -->
- <p ref="p">hello</p>
- <!-- vm.$refs.child will be the child comp instance -->
- <child-comp ref="child"></child-comp>
当
用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实例的数组。
- v-for
关于 ref 注册时间的重要说明: 因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在!
也不是响应式的,因此你不应该试图用它在模版中做数据绑定。
- $refs
(2.2.0 新增)
默认情况下,一个组件的
会使用
- v-model
属性和
- value
事件,但是诸如单选框、复选框之类的输入类型可能把
- input
属性用作了别的目的。
- value
选项可以回避这样的冲突:
- model
- Vue.component('my-checkbox', {
- model: {
- prop: 'checked',
- event: 'change'
- },
- props: {
- checked: Boolean,
- // this allows using the `value` prop for a different purpose
- value: String
- },
- // ...
- })
- <my-checkbox v-model="foo" value="some value"></my-checkbox>
上述代码等价于:
- <my-checkbox :checked="foo" @change="val => { foo = val }" value="some value"></my-checkbox>
在一些情况下,我们可能会需要对一个 prop 进行『双向绑定』。事实上,这正是 Vue 1.x 中的
修饰符所提供的功能。当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定的值。这很方便,但也会导致问题,因为它破坏了『单向数据流』的假设。由于子组件改变 prop 的代码和普通的状态改动代码毫无区别,当光看子组件的代码时,你完全不知道它何时悄悄地改变了父组件的状态。这在 debug 复杂结构的应用时会带来很高的维护成本。
- .sync
上面所说的正是我们在 2.0 中移除
的理由。但是在 2.0 发布之后的实际应用中,我们发现
- .sync
还是有其适用之处,比如在开发可复用的组件库时。我们需要做的只是让子组件改变父组件状态的代码更容易被区分。
- .sync
从 2.3.0 起我们重新引入了
修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的
- .sync
侦听器。
- v-on
如下代码
- <comp :foo.sync="bar"></comp>
会被扩展为:
- <comp :foo="bar" @update:foo="val => bar = val"></comp>
当子组件需要更新
的值时,它需要显式地触发一个更新事件:
- foo
- this.$emit('update:foo', newValue)
有时候两个组件也需要通信 (非父子关系)。在简单的场景下,可以使用一个空的 Vue 实例作为中央事件总线,相当于中转站,可以用它来传递事件和接收事件。
- var bus = new Vue()
- // 触发组件 A 中的事件
- bus.$emit('id-selected', 1)
- // 在组件 B 创建的钩子中监听事件
- bus.$on('id-selected', function (id) {
- // ...
- })
下面用一个实例来说明:
- 1 < div id = "demo" > 2 < a - component > </a-component> 3 <b-component></b - component > 4 < /div> 5 6 7 <script type="text/javascript "> 8 Vue.component('a-component',{ 9 template:'<button @click="submit ">提交</button>', 10 methods: { 11 submit() { 12 // 事件名字自定义,用不同的名字区别事件 13 this.$root.Bus.$emit('eventName', 123) 14 } 15 } 16 }); 17 18 Vue.component('b-component',{ 19 template:'<p>{{message}}</p>', 20 data:function () { 21 return { 22 message:'b子组件' 23 } 24 }, 25 26 // 当前实例创建完成就监听这个事件 27 created(){ 28 this.$root.Bus.$on('eventName', function (value) { 29 console.log(value); 30 }); 31 }, 32 33 // 在组件销毁时别忘了解除事件绑定 34 beforeDestroy() { 35 this.$root.Bus.$off('eventName') 36 } 37 }); 38 39 var vm = new Vue({ 40 el:'#demo', 41 data: { 42 // 空的实例放到根组件下,所有的子组件都能调用 43 Bus: new Vue() 44 } 45 }) 46 </script>"
本文的 Demo 和源代码已放到 GitHub https://github.com/zwl-jasmine95/Vue_test
点个赞呗~~┑( ̄▽  ̄)┍
来源: http://www.cnblogs.com/jasmine-95/p/7275178.html