“Vue从入门到实战:杂项”的版本间的差异
第274行: | 第274行: | ||
在create钩子中使用$on()方法监听当前组件实例的自定义事件greet,在beforeDestory钩子中使用$off()方法删除greet事件的所有监听器。 | 在create钩子中使用$on()方法监听当前组件实例的自定义事件greet,在beforeDestory钩子中使用$off()方法删除greet事件的所有监听器。 | ||
+ | |||
+ | ===递归组件=== | ||
+ | 组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事: | ||
+ | |||
+ | name: 'unique-name-of-my-component' | ||
+ | |||
+ | 当你使用 Vue.component 全局注册一个组件时,这个全局的 ID 会自动设置为该组件的 name 选项。 | ||
+ | |||
+ | <nowiki>Vue.component('unique-name-of-my-component', { | ||
+ | // ... | ||
+ | })</nowiki> | ||
+ | |||
+ | 稍有不慎,递归组件就可能导致无限循环: | ||
+ | |||
+ | <nowiki>name: 'stack-overflow', | ||
+ | template: '<div><stack-overflow></stack-overflow></div>' | ||
+ | </nowiki> | ||
+ | |||
+ | 类似上述的组件将会导致“max stack size exceeded”错误,所以请确保递归调用是条件性的 (例如使用一个最终会得到 false 的 v-if)。 | ||
+ | |||
+ | <nowiki> | ||
+ | <!DOCTYPE html> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta charset="UTF-8"> | ||
+ | <script src="vue.js"></script> | ||
+ | </head> | ||
+ | <body> | ||
+ | <div id="app"> | ||
+ | <category-component :list="categories"></category-component> | ||
+ | </div> | ||
+ | |||
+ | <script> | ||
+ | |||
+ | new Vue({ | ||
+ | el: '#app', | ||
+ | data: { | ||
+ | categories: [ | ||
+ | { | ||
+ | name: '程序设计', | ||
+ | children: [ | ||
+ | { | ||
+ | name: 'Java', | ||
+ | children: [ | ||
+ | {name: 'Java SE'}, | ||
+ | {name: 'Java EE'} | ||
+ | ] | ||
+ | }, | ||
+ | { | ||
+ | name: 'C++' | ||
+ | } | ||
+ | ] | ||
+ | }, | ||
+ | { | ||
+ | name: '前端框架', | ||
+ | children: [ | ||
+ | {name: 'Vue.js'}, | ||
+ | {name: 'React'} | ||
+ | ] | ||
+ | } | ||
+ | ] | ||
+ | }, | ||
+ | components: { | ||
+ | CategoryComponent : { | ||
+ | name: 'catComp', | ||
+ | props: { | ||
+ | list: { | ||
+ | type: Array | ||
+ | } | ||
+ | }, | ||
+ | data: function(){ | ||
+ | return { | ||
+ | count: 0 | ||
+ | } | ||
+ | }, | ||
+ | template: ` | ||
+ | <ul> | ||
+ | <!-- 如果list为空,表示没有子分类了,结束递归 --> | ||
+ | <template v-if="list"> | ||
+ | <li v-for="cat in list"> | ||
+ | {{cat.name}} | ||
+ | <catComp :list="cat.children"/> | ||
+ | </li> | ||
+ | </template> | ||
+ | </ul>` | ||
+ | } | ||
+ | } | ||
+ | }) | ||
+ | </script> | ||
+ | </body> | ||
+ | </html></nowiki> |
2021年2月18日 (四) 15:00的版本
组件通信的其他方式
前面介绍的组件通信的三种方式:
- 父组件通过prop向子组件传递数据
- 子组件通过自定义事件向父组件发起通知或进行数据传递
- 子组件通过<slot>元素充当占位符,获取父组件分发的内容,也可以在子组件的<slot>元素上使用v-bind指令绑定一个插槽prop,向父组件提供数据。
除此之外,还有其他实现方式
访问根实例
在每一个new Vue实例的子组件中,都可以通过$root属性来访问根实例。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script src="vue.js"></script> </head> <body> <div id="app"> <parent></parent> </div> <script> Vue.component('parent', { template: '<child></child>' }); Vue.component('child', { methods: { accessRoot(){ console.log("单价:" + this.$root.price); console.log("总价:" + this.$root.totalPrice); console.log(this.$root.hello()); } }, template: '<button @click="accessRoot">访问根实例</button>' }) new Vue({ el: '#app', data: { price: 98 }, computed: { totalPrice(){ return this.price * 10; } }, methods: { hello(){ return "Hello, Vue.js无难事"; } } }) </script> </body> </html>
不管组件是根实例的子组件,还是更深层级的后代组件,$root属性总是代表了根实例。
访问父组件实例
和$root类似,$parent属性用于在一个子组件访问父组件的实例。这可以替代父组件将数据以 prop 的方式传入子组件的方式。
在绝大多数情况下,触达父级组件会使得你的应用更难调试和理解,尤其是当你变更了父级组件的数据的时候。当我们稍后回看那个组件的时候,很难找出那个变更是从哪里发起的。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script src="vue.js"></script> </head> <body> <div id="app"> <parent></parent> </div> <script> Vue.component('parent', { data(){ return { price: 98 } }, computed: { totalPrice(){ return this.price * 10; } }, methods: { hello(){ return "Hello, Vue.js无难事"; } }, template: '<child></child>', }); Vue.component('child', { methods: { accessParent(){ console.log("单价:" + this.$parent.price); console.log("总价:" + this.$parent.totalPrice); console.log(this.$parent.hello()); } }, template: '<button @click="accessParent">访问父组件实例</button>' }) new Vue({ el: '#app', }) </script> </body> </html>
访问子组件实例或子元素
在Vue.js中,父组件要访问子组件实例或子元素,可以给子组件或子元素添加一个特殊的属性ref, 为子组件或子元素分配一个引用ID ,然后父组件就可以通过$ref属性来访问子组件实例或子元素。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script src="vue.js"></script> </head> <body> <div id="app"> <parent></parent> </div> <script> Vue.component('parent', { mounted(){ // 访问子元素<input>,让其具有焦点 this.$refs.inputElement.focus(); // 访问子组件<child>的message数据属性 console.log(this.$refs.childComponent.message) }, template: ` <div> <input ref="inputElement"><br> <!--子元素--> <child ref="childComponent"></child> <!-- 子组件--> </div>` }); Vue.component('child', { data(){ return { message: 'Vue.js无难事' } }, template: '<p>{{message}}</p>' }) new Vue({ el: '#app', }) </script> </body> </html>
要注意,$ref属性只在组件渲染完成之后生效,并且他们不是响应式的。要避免在模板和计算属性中访问$refs 。
依赖注入
$root属性用于访问根实例,$parent属性用于访问父组件实例,但如果组件嵌套的层级不确定,某个组件的数据或方法需要被后代组件所访问,又该如何实现呢 ?
这时需要用到两个新的实例选项:provide和inject .provide选项允许我们指定要提供给后代组件的数据或方法,在后代组件中使用inject选项来接收要添加到该实例中的特定属性。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script src="vue.js"></script> </head> <body> <div id="app"> <parent></parent> </div> <script> Vue.component('parent', { methods: { sayHello(name){ console.log("Hello, " + name); } }, provide(){ return { // 数据属性message和sayHello方法可供后代组件访问 message: 'Vue.js无难事', hello : this.sayHello } }, template: '<child/>' }); Vue.component('child', { // 接收message数据属性和hello方法 inject: ['message', 'hello'], mounted(){ // 当自身的方法来访问 this.hello('zhangsan'); }, // 当自身的数据属性来访问 template: '<p>{{message}}</p>' }) new Vue({ el: '#app', }) </script> </body> </html>
手动监听
现在,你已经知道了 $emit 的用法,它可以被 v-on 侦听,但是 Vue 实例同时在其事件接口中提供了其它的方法。我们可以:
- 通过 $on(eventName, eventHandler) 侦听一个事件
- 通过 $once(eventName, eventHandler) 一次性侦听一个事件
- 通过 $off(eventName, eventHandler) 停止侦听一个事件
你通常不会用到这些,但是当你需要在一个组件实例上手动侦听事件时,它们是派得上用场的。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script src="vue.js"></script> </head> <body> <div id="app"> <child></child> </div> <script> Vue.component('child', { created(){ // 监听当前实例的greet事件 this.$on("greet", function(){ this.$parent.sayHello(); }) }, beforeDestroy(){ // 删除greet事件的所有监听器 this.$off("greet"); }, methods: { handleClick(){ // 触发自定义事件greet this.$emit('greet'); } }, template: '<button @click="handleClick">手动监听事件</button>' }) var vm = new Vue({ el: '#app', methods: { sayHello(){ alert("Hello, Vue.js"); } } }) </script> </body> </html>
在create钩子中使用$on()方法监听当前组件实例的自定义事件greet,在beforeDestory钩子中使用$off()方法删除greet事件的所有监听器。
递归组件
组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事:
name: 'unique-name-of-my-component'
当你使用 Vue.component 全局注册一个组件时,这个全局的 ID 会自动设置为该组件的 name 选项。
Vue.component('unique-name-of-my-component', { // ... })
稍有不慎,递归组件就可能导致无限循环:
name: 'stack-overflow', template: '<div><stack-overflow></stack-overflow></div>'
类似上述的组件将会导致“max stack size exceeded”错误,所以请确保递归调用是条件性的 (例如使用一个最终会得到 false 的 v-if)。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script src="vue.js"></script> </head> <body> <div id="app"> <category-component :list="categories"></category-component> </div> <script> new Vue({ el: '#app', data: { categories: [ { name: '程序设计', children: [ { name: 'Java', children: [ {name: 'Java SE'}, {name: 'Java EE'} ] }, { name: 'C++' } ] }, { name: '前端框架', children: [ {name: 'Vue.js'}, {name: 'React'} ] } ] }, components: { CategoryComponent : { name: 'catComp', props: { list: { type: Array } }, data: function(){ return { count: 0 } }, template: ` <ul> <!-- 如果list为空,表示没有子分类了,结束递归 --> <template v-if="list"> <li v-for="cat in list"> {{cat.name}} <catComp :list="cat.children"/> </li> </template> </ul>` } } }) </script> </body> </html>