vue.js了解篇4(自定义组件)

您所在的位置:网站首页 vue使用自定义组件 vue.js了解篇4(自定义组件)

vue.js了解篇4(自定义组件)

#vue.js了解篇4(自定义组件)| 来源: 网络整理| 查看: 265

目录 1. 注册组件(全局注册/局部注册) 2. (向组件模版)传递数据 3. (响应组件模版中的)事件 4. 组件添加v-model 5. is (解决特殊限制) 6. 插槽 7. 动态组件

前言

除了可使用普通html元素外,Vue还可以自定义组件。

使用自定义组件的好处: 1. 便于维护:可以使代码结构清晰,分散到各文件中。 2. 可复用:减少代码冗余。 注意: 1. 组件只能有一个根元素 2. 组件可以使用指令 3. 组件有和Vue相同的选项(因为组件就是可复用的Vue实例),例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。 4. 每添加一个组件,就会有一个它的新实例被创建。data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。 6. 一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。 7. 多行template更易读,但它们在 IE 下并没有被支持,所以如果需要在不 (经过 Babel 或 TypeScript 之类的工具) 编译的情况下支持 IE,请使用折行转义字符。 例 ... 1、全局/局部 注册组件 Vue.component('my-component-name', { /* ... */ }) /* 第一个参数:组件名。2种命名方式 第一种命名方式:全小写 加 连字符-(W3C规范,建议使用),使用 第二种命名方式:HelloWorld,使用或 */

格式一:全局(可在本页中使用)

全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例。 例1 // 第1步:注册全局组件 Vue.component('hello-world', { template: '自定义组件!' }) // 创建根实例 new Vue({ el: '#app' }) 例2 ... // 定义一个名为 button-counter 的新组件 Vue.component('button-counter', { data: function () { return { count: 0 } }, template: 'You clicked me {{ count }} times.' }) new Vue({ el: '#components-demo' })

格式二:局部(仅在该Vue实例下可用)

注意:局部注册的组件作为其他组件的子组件时不可用,解决如下 var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, // ... } 例 var Child = { template: '自定义组件!' } // 创建根实例 new Vue({ el: '#app', // 第1步、注册局部组件(仅在id为app中使用) components: { 'hello': Child, 'hello2': { template: '自定义组件!' }, } }) 2. 向组件模版传递数据

父组件的数据通过props传给子组件

// 注册 Vue.component('child', { // 声明 props props: ['message'], // 同样也可以在 vm 实例中像 "this.message" 这样使用 template: '{{ message }}' }) // 创建根实例 new Vue({ el: '#app' })

单向动态绑定(当父组件的数据变化时,该变化会传导给子组件)

// 注册 Vue.component('child', { // 声明 props props: ['message'], // 同样也可以在 vm 实例中像 "this.message" 这样使用 template: '{{ message }}' }) // 创建根实例 new Vue({ el: '#app', data: { parentMsg: '父组件内容' } }) 循环 Vue.component('todo-item', { props: ['todo'], template: '{{ todo.text }}' }) new Vue({ el: '#app', data: { sites: [ { text: 'Runoob' }, { text: 'Google' }, { text: 'Taobao' } ] } })

属性详解

单向动态绑定 props 使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致应用的数据流向难以理解(即不应该在一个子组件内部改变 prop)。 例-prop 用来传递一个初始值: props: ['initialCounter'], data: function () { return { counter: this.initialCounter } } 例-prop 以一种原始的值传入且需要进行转换: props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } } 1、Vue中的驼峰属性名在html中要使用连字符-(因为html大小写不敏感)。但{{}}中不受限制。如下: Vue.component('blog-post', { props: ['postTitle'], template: '{{ postTitle }}' }) 2、属性类型验证。属性类型type可以是:String、Number、Boolean、Function、Object、Array、Date、Symbol、一个自定义的构造函数。 1、prop 会在一个组件实例创建之前进行验证,所以实例的属性 (如 data、computed 等) 在 default 或 validator验证 函数中是不可用的; 2、如果有一个属性类型验证失败,(开发环境构建版本的) Vue 将会产生一个控制台的警告。 Vue.component('example', { props: { // 基础类型检测 (`null` 和 `undefined` 会通过任何类型验证) 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 } } } }) 3、传值(静态、动态) 等价于 数字 bool 数组 对象 4、组件可以接受任意特性(动态添加属性) 1、因为组件并不总能预见组件会被用于怎样的场景。 2、这些特性会被添加到这个组件的根元素上。 title会被加在根元素上 5、对于绝大多数特性来说,从外部提供给组件的值会替换掉(覆盖)组件内部设置好的值。但class 和 style 则会合并。 6、禁止 根元素继承特性 注意 inheritAttrs: false 选项并不会影响 style 和 class 的绑定。 Vue.component('my-component', { inheritAttrs: false, props: ['label', 'value'], // ... }) label、value不再从中获取 $attrs 属性决定将一个组件的特性名和特性值传递给指定元素 Vue.component('base-input', { inheritAttrs: false, props: ['label', 'value'], template: ` {{ label }} ` }) 3. 响应组件模版中的事件

父组件可以使用props传递数据给子组件,但如果子组件要把数据传递回去,就需要使用事件

每个 Vue 实例都实现了事件接口: 使用 $on(eventName) 监听事件 使用 $emit(eventName) 触发事件 组件内部使用 $emit方法并传入事件名称来触发一个外部事件。使用 $emit 的第二个参数来传递参数,外部可以通过 $event 访问到。 事件名不存在任何自动化的大小写转换,必须完全一致。 注意:v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。所以极力推荐使用全小写加连字符-。

举例1

Vue.component('blog-post', { props: ['post'], template: ` {{ post.title }} Enlarge text Enlarge text ` }) ========================== ========================== 传值1 Enlarge text ... ========================== ========================== 传值2 ... methods: { onEnlargeText: function (enlargeAmount) { this.postFontSize += enlargeAmount } }

举例2

{{ total }}

Vue.component('button-counter', { template: '{{ counter }}', data: function () { // data 必须是一个函数。这样的好处就是每个实例可以维护一份被返回对象的独立的拷贝,如果 data 是一个对象则会影响到其他实例 return { counter: 0 } }, methods: { incrementHandler: function () { this.counter += 1 this.$emit('increment') // this.$emit('increment',1) 带参数 ,在function (count)可获取 或 html 元素中 $event可获取 } }, }) new Vue({ el: '#counter-event-example', data: { total: 0 }, methods: { incrementTotal: function () { this.total += 1 } } })

.sync(2.3.0+ 新增)

在有些情况下可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题。推荐以 update:myPropName 的模式触发事件来达到效果: this.$emit('update:title', newTitle) 简写为 整个对象的属性 注意: 注意带有 .sync 修饰符的 v-bind 使用表达式、对象。只能提供想要绑定的属性名。

在一个组件的根元素上直接监听一个原生事件,而不是监听子组件时

注意:在尝试监听一个类似 的非常特定的元素时,且根元素并不是input时会失效,listeners 属性(一个对象,里面包含了作用在这个组件上的所有监听器)可以解决这个问题: 例: Vue.component('base-input', { inheritAttrs: false, props: ['label', 'value'], computed: { inputListeners: function () { var vm = this // `Object.assign` 将所有的对象合并为一个新对象 return Object.assign({}, // 从父级添加所有的监听器 this.$listeners, // 然后添加自定义监听器, // 或覆写一些监听器的行为 { // 这里确保组件配合 `v-model` 的工作 input: function (event) { vm.$emit('input', event.target.value) } } ) } }, template: ` {{ label }} ` }) 4. 组件添加v-model Vue.component('custom-input', { props: ['value'], template: ` ` }) 组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件。 但是像单选框、复选框(使用的是checked而不是value)等类型的输入控件可能会将 value特性用于不同的目的。可以使用model选项来解决(仍然需要在组件的 props 选项里声明 checked 这个 prop): Vue.component('base-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` ` }) 5. is (解决特殊限制) 有些 HTML 元素,诸如 、、 和 ,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 、 和 ,只能出现在其它某些特定的元素内部。 使用这些有约束条件的元素时会遇到一些问题。例如: 这个自定义组件 会被作为无效的内容提升到外部,并导致最终渲染结果出错。 注意:三种情况不存在这个限制:字符串、单文件组件.vue、 解决 6. 插槽 组件开始标签和结束标签之间的内容(包括html元素、纯文本、组件)称之为插槽,插槽 prop 可以将插槽转换为可复用的模板。 在 2.6.0 中,为具名插槽和作用域插槽引入了一个新的统一的语法 (即 `v-slot` 指令)。它取代了 `slot` 和 `slot-scope` 这两个目前已被废弃但未被移除且仍在[文档中]的特性。 Something bad happened. Vue.component('alert-box', { template: ' Error! ' }) 说明: 1、 会替换组件起始标签和结束标签之间的任何内容,这里会直接将文本‘Something bad happened.’替换过来。 2、如果没有 ,则起始标签和结束标签之间的任何内容会被忽略。 作用域插槽 父级模板里的所有内容都是在父级作用域中编译的; 子模板里的所有内容都是在子作用域中编译的。 {{ url: 访问不到。不能访问父级组件的作用域}} {{ user.firstName }} 模版 {{ user.lastName }} 上述代码不会正常工作,解决: {{ hello.user.firstName }} {{ user.lastName }} 只有默认插槽时的缩写(注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确): {{ hello.user.firstName }} 多个插槽 {{ slotProps.user.firstName }} ... 作用域插槽工作原理: 将插槽内容包括在一个传入单个参数的函数里function (slotProps) { // 插槽内容 }。这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式 {{ user.firstName }} 重命名 {{ person.firstName }} 默认值 {{ user.firstName }} 会显示默认的Submit Submit 具名插槽(多个插槽时分别指定名字) 有时需要多个插槽。例: Here might be a page title

A paragraph for the main content.

And another one.

Here's some contact info

模版: 动态插槽名(2.6.0) ... 7. 动态组件 8. 异步组件 在大型应用中,可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。 示例1 Vue.component('async-webpack-example', function (resolve) { // 这个特殊的 `require` 语法将会告诉 webpack,自动将你的构建代码切割成多个包,这些包会通过 Ajax 请求加载 require(['./my-async-component'], resolve) }) 示例2 // 全局注册 Vue.component( 'async-webpack-example', // 这个 `import` 函数会返回一个 `Promise` 对象。 () => import('./my-async-component') ) // 局部注册 new Vue({ // ... components: { 'my-component': () => import('./my-async-component') // webpack 2 和 ES2015 语法加在一起,异步加载“并不会被 Browserify 支持” } }) 示例3(仅作演示,模拟) Vue.component('async-example', function (resolve, reject) { setTimeout(function () { // 向 `resolve` 回调传递组件定义 resolve({ template: 'I am async!' }) }, 1000) }) 异步组件工厂函数也可以返回一个如下格式的对象: const AsyncComponent = () => ({ // 需要加载的组件 (应该是一个 `Promise` 对象) component: import('./MyComponent.vue'), // 异步组件加载时使用的组件 loading: LoadingComponent, // 加载失败时使用的组件 error: ErrorComponent, // 展示加载时组件的延时时间。默认值是 200 (毫秒) delay: 200, // 如果提供了超时时间且组件加载也超时了, // 则使用加载失败时使用的组件。默认值是:`Infinity` timeout: 3000 }) 9. render渲染函数 Vue的模板最终会被编译成render渲染函数。 Vue推荐在绝大多数情况下使用模板来创建HTML,但在某些情况下使用渲染函数更高效。 举例(render的好处) Hello world!

使用模版(冗余)

Vue.component('anchored-heading', { template: '#anchored-heading-template', props: { level: { type: Number, required: true } } })

使用render函数

Vue.component('anchored-heading', { render: function (createElement) { // "虚拟 DOM”是对由 Vue 组件树建立起来的整个 VNode 树的称呼 return createElement( // 虚拟节点 (virtual node)VNode 'h' + this.level, // 标签名称 this.$slots.default // 子元素数组,子节点被存储在组件实例中的 $slots.default 中 ) }, props: { level: { type: Number, required: true } } }) 组件树中的所有 VNodes 必须是唯一的,以下的做法是错误的 render: function (createElement) { var myParagraphVNode = createElement('p', 'hi') return createElement('div', [ // 错误-重复的 VNodes myParagraphVNode, myParagraphVNode ]) } 解决:使用工厂函数 render: function (createElement) { return createElement('div', Array.apply(null, { length: 20 }).map(function () { return createElement('p', 'hi') }) ) } createElement 方法说明 // Vue通过建立一个虚拟 DOM 对真实 DOM 发生的变化保持追踪。 // @returns {VNode} createElement( // 1、{String | Object | Function} 必填项。 // 一个 HTML 标签字符串,或组件选项对象,或解析上述任何一种的一个 async 异步函数。 'div', // 2、{Object} 可选参数。 // 一个包含模板相关属性的数据对象,可以在模版中使用这些特性。 { }, // 3、{String | Array} 可选参数。 // 子虚拟节点 (VNodes),由 `createElement()` 构建而成,也可以使用字符串来生成“文本虚拟节点”。 [ // createElement('h1', '一则头条'), createElement(MyComponent, { props: { someProp: 'foobar' } }) ] )

createElement 第二个参数(数据对象)详解

正如在模板语法中,v-bind:class 和 v-bind:style,会被特别对待一样,在 VNode 数据对象中,下列属性名是级别最高的字段。 { // 和`v-bind:class`一样的 API,接收一个字符串、对象、数组(字符串和对象组成的) 'class': { foo: true, bar: false }, // 和`v-bind:style`一样的 API,接收一个字符串、对象或对象组成的数组 style: { color: 'red', fontSize: '14px' }, // 普通的 HTML 特性 attrs: { id: 'foo' }, // 组件 props props: { myProp: 'bar' }, // DOM 属性 domProps: { innerHTML: 'baz' }, // 事件监听器基于 `on`,但不再支持如 `v-on:keyup.enter` 修饰器,需要手动匹配 keyCode。 on: { click: this.clickHandler }, // 仅用于组件,用于监听原生事件,而不是组件内部使用 // `vm.$emit` 触发的事件。 nativeOn: { click: this.nativeClickHandler }, // 自定义指令。注意,无法对 `binding` 中的 `oldValue` 赋值,因为 Vue 已经自动进行了同步。 directives: [ { name: 'my-custom-directive', value: '2', expression: '1 + 1', arg: 'foo', modifiers: { bar: true } } ], // 作用域插槽格式 // { name: props => VNode | Array } scopedSlots: { default: props => createElement('span', props.text) }, // 如果组件是其他组件的子组件,需为插槽指定名称 slot: 'name-of-slot', // 其他特殊顶层属性 key: 'myKey', ref: 'myRef', // 如果在渲染函数中向多个元素都应用了相同的 ref 名, // 那么 `$refs.myRef` 会变成一个数组。 refInFor: true }

完整示例

var getChildrenTextContent = function (children) { return children.map(function (node) { return node.children ? getChildrenTextContent(node.children) : node.text }).join('') } Vue.component('anchored-heading', { render: function (createElement) { // 创建 kebab-case 风格的 ID var headingId = getChildrenTextContent(this.$slots.default) .toLowerCase() .replace(/\W+/g, '-') .replace(/(^-|-$)/g, '') return createElement( 'h' + this.level, [ createElement('a', { attrs: { name: headingId, href: '#' + headingId } }, this.$slots.default) ] ) }, props: { level: { type: Number, required: true } } }) javascript替换指令

v-if 和 v-for 在render函数里则使用 if、map语句 替换

{{ item.name }}

No items found.

... props: ['items'], render: function (createElement) { if (this.items.length) { return createElement('ul', this.items.map(function (item) { return createElement('li', item.name) })) } else { return createElement('p', 'No items found.') } }

render 函数中没有与 v-model 的直接对应 - 必须自己实现相应的逻辑:

props: ['value'], render: function (createElement) { var self = this return createElement('input', { domProps: { value: self.value }, on: { input: function (event) { self.$emit('input', event.target.value) } } }) }

事件修饰符

on .passive & .capture ! .once ~ .capture.once or .once.capture ~! 举例 on: { '!click': this.doThisInCapturingMode, '~keyup': this.doThisOnce, '~!mouseover': this.doThisOnceInCapturingMode } .stop event.stopPropagation() .prevent event.preventDefault() .self if (event.target !== event.currentTarget) return .enter, .13 if (event.keyCode !== 13) return .ctrl, .alt, .shift, .meta if (!event.ctrlKey) return (change ctrlKey to altKey, shiftKey, or metaKey, respectively) 示例 on: { keyup: function (event) { // 如果触发事件的元素不是事件绑定的元素 // 则返回 if (event.target !== event.currentTarget) return // 如果按下去的不是 enter 键或者 // 没有同时按下 shift 键 // 则返回 if (!event.shiftKey || event.keyCode !== 13) return // 阻止 事件冒泡 event.stopPropagation() // 阻止该元素默认的 keyup 事件 event.preventDefault() // ... } } 插槽 this.$slots.default 访问静态插槽的内容,得到的是一个 VNodes 数组. render: function (createElement) { // `` return createElement('div', this.$slots.default) } this.$scopedSlots.default访问作用域插槽,得到的是一个返回 VNodes 的函数 props: ['message'], render: function (createElement) { // `` return createElement('div', [ this.$scopedSlots.default({ text: this.message }) ]) } 如果要用渲染函数向子组件中传递作用域插槽,可以利用 VNode 数据对象中的 scopedSlots 域 render: function (createElement) { return createElement('div', [ createElement('child', { // 在数据对象中传递 `scopedSlots` // 格式:{ name: props => VNode | Array } scopedSlots: { default: function (props) { return createElement('span', props.text) } } }) ]) } JSX 使用Babel插件:用于在 Vue 中使用 JSX 语法,它可以让我们回到更接近于模板的语法上。 import AnchoredHeading from './AnchoredHeading.vue' ... new Vue({ el: '#demo', render: function (h) {// 将 h 作为 createElement 的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。从 Vue 的 Babel 插件的 3.4.0 版本开始,会在以 ES2015 语法声明的含有 JSX 的任何方法和 getter 中 (不是函数或箭头函数中) 自动注入 const h = this.$createElement,这样就可以去掉 (h) 参数了。对于更早版本的插件,如果 h 在当前作用域中不可用,应用会抛错。 return ( Hello world! ) } }) 函数式组件(functional: true) 当使用函数式组件时,该引用将会是 HTMLElement,因为他们是无状态的也是无实例的。 注意:在 2.3.0 之前的版本中,如果一个函数式组件想要接收 prop,则 props 选项是必须的。在 2.3.0 或以上的版本中,可以省略 props 选项,所有组件上的特性都会被自动隐式解析为 prop。 Vue.component('my-component', { functional: true, props: { // ... }, // 为了弥补缺少的实例 // 提供第二个参数作为上下文 render: function (createElement, context) { // ... } }) 因为函数式组件只是一个函数,所以渲染开销也低很多。 组件需要的一切都是通过上下文传递,包括 props:提供所有 prop 的对象 children: VNode 子节点的数组 slots: 一个函数,返回了包含所有插槽的对象 scopedSlots: (2.6.0+) 一个暴露传入的作用域插槽以及函数形式的普通插槽的对象。 data:传递给组件的数据对象 parent:对父组件的引用 listeners: (2.3.0+) 一个包含了所有在父组件上注册的事件侦听器的对象。这只是一个指向 `data.on`的别名。 injections: (2.3.0+) 如果使用了inject选项,则该对象包含了应当被注入的属性。 this.$slots.default 更新为 context.children, this.level 更新为 context.props.level

向子元素或子组件传递数据

在普通组件中,没有被定义为 prop 的特性会自动添加到组件的根元素上,将现有的同名特性替换或与其合并。 然而函数式组件要求显式定义该行为: Vue.component('my-functional-button', { functional: true, render: function (createElement, context) { // 完全透明的传入任何特性、事件监听器、子结点等。 return createElement('button', context.data, context.children) } }) 如果使用基于模板的函数式组件,那么还需要手动添加特性和监听器。因为我们可以访问到其独立的上下文内容,所以可以使用 data.attrs 传递任何 HTML 特性,也可以使用 listeners (即 data.on 的别名) 传递任何事件监听器。

示例(根据传入 prop 的值来代为渲染更具体的组件)

var EmptyList = { /* ... */ } var TableList = { /* ... */ } var OrderedList = { /* ... */ } var UnorderedList = { /* ... */ } Vue.component('smart-list', { functional: true, props: { items: { type: Array, required: true }, isOrdered: Boolean }, render: function (createElement, context) { function appropriateListComponent () { var items = context.props.items if (items.length === 0) return EmptyList if (typeof items[0] === 'object') return TableList if (context.props.isOrdered) return OrderedList return UnorderedList } return createElement( appropriateListComponent(), context.data, context.children ) } })

slots().default 和 children 对比

first

second

对于这个组件,children 会给你两个段落标签,而 slots().default 只会传递第二个匿名段落标签,slots().foo 会传递第一个具名段落标签。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3