第5章 深入了解Vue组件 5.1什么是组件化开发 组件化开发是Vue.js框架的核心特性之一,也是目前前端技术框架中最常见的一种开发模式。在Vue.js中,组件就是一个可以复用的Vue实例,拥有独一无二的组件名称,可以扩展HTML元素,使用组件名称作为自定义的HTML标签。 在Vue.js项目中,每个组件都是一个Vue实例,所以组件内的属性选项都是相同的,例如data、computed、watch、methods及生命周期钩子等。仅有的例外是像el这样实例特有的选项。 在很多场景下,网页中的某些部分是可以复用的,例如头部导航、猜你喜欢、热点信息等。我们可以将网站中能够重复使用的部分设计成一个个组件,当需要的时候,直接引用这个组件即可。 Vue组件化开发有别于前端传统的模块化开发。模块化是为了实现每个模块、方法的单一功能,一般通过代码逻辑进行划分,而组件化开发,更多的是实现前端UI的重复使用。 5.2Vue自定义组件 在使用Vue CLI工具创建的项目中,src目录是用来存放项目源码的,在src目录下会自动创建两个子目录,一个是src/views 目录,另一个是src/components 目录。这两个子目录都是用来创建组件的,但是为了区分组件的功能,一般在src/views目录下创建的是视图组件,而在src/components目录下创建的是公共UI组件。 我们可以在src/views 目录下创建一个Home.vue的视图组件,代码如下: 这是Home页面! 然后将Home.vue组件引入项目的根组件App.vue中,代码如下: 在App.vue根组件中使用components选项注册自定义组件,完成上面代码后启动项目,在浏览器中运行的效果如图5.1所示。 图5.1在浏览器中运行的效果 5.2.1组件的封装 在项目的开发中,很多时候需要将某些UI部分封装成一个独立的组件,这部分UI可以是一个页面中的模块,例如商品列表,也可以是一个很小的部件,例如按钮。对于这种常用的UI元素可以创建一个组件并放到components目录下。 在src/components目录下新建一个Button.vue文件,代码如下: 按钮 在上面的代码中,标签内只能有一个子节点,如果在标签的同级位置还有其他的标签元素,需要在和同级标签的外部再添加一个父节点。 自定义Button.vue组件创建成功后,在App.vue根组件中引入,代码如下: 在上面的代码中,components选项内使用的elbutton作为自定义组件的别名,在模板中用 作为HTML扩展标签。启动项目,在浏览器中运行的效果如图5.2所示。 图5.2自定义按钮组件 5.2.2自定义组件上的属性 在5.2.1节示例代码中,使用 自定义UI组件就可以在浏览器中渲染出按钮元素,但是在很多时候,自定义组件内部不能直接定义按钮的样式与属性,应该由组件的使用者对按钮组件进行创建并赋予其相关的属性值。我们可以在自定义组件上以使用标签属性的方式,为自定义组件传入参数,并在组件内部渲染。 例如,可以在创建Button.vue组件时,为其设置props选项属性,代码如下: {{text}} 在上面的示例代码中,使用props选项属性为Button.vue自定义组件定义了属性 text,在其他组件内引入该自定义组件时,就可以使用该属性作为标签属性,并为自定义组件传入具体的内容。 在App.vue中引入Button.vue自定义组件,代码如下: 在上面的示例代码中,通过为 标签设置 text 属性的方式设置按钮显示的文本内容,在浏览器中运行的效果如图5.3所示。 图5.3设置自定义按钮文本 5.2.3自定义组件上的事件 自定义组件与预定义的HTML标签是有区别的,预定义的HTML标签能够被浏览器识别并渲染,所以会在浏览器中支持HTML标签上的原始属性和事件,但是自定义组件的标签在浏览器中渲染的是组件内部定义的UI样式,浏览器无法直接识别自定义组件标签上的属性和事件。 在使用自定义组件时,如果要为组件标签设置属性和事件,就要在自定义组件的内部提前声明。在组件上设置事件与设置属性还有所差别,设置属性只需要在组件内部使用props选项,而设置事件,需要考虑事件的执行时机,并在组件内部为原始的HTML标签定义该事件。 例如自定义按钮组件,并为组件设置单击事件,代码如下: {{text}} 上面的示例代码为原生的 标签添加了单击事件,并在单击事件的触发函数中调用了$emit()方法,触发该自定义组件定义的名为click的事件。 在App.vue中引入Button.vue组件,代码如下: 在上面的示例代码中,为标签定义了@click事件,在自定义组件标签中没有默认的单击事件,但是在创建Button.vue组件时,为原生按钮元素添加了单击事件,并使用 $emit('click') 方法触发了click事件,所以在标签上定义@click事件是可以被触发的。 在浏览器中运行的效果如图5.4所示。 图5.4自定义单击事件触发效果 5.3组件属性校验 在自定义组件时,可以在自定义组件文件的内部使用props选项来为组件自定义一些功能。在使用自定义组件标签时,这些预先定义的props属性可以使用标签属性的书写方式在组件标签上声明,此时,标签上的属性值被传入自定义组件内的props选项中,在组件内就可以获取这个值了。 HTML标签中的属性名对大小写不敏感,所以浏览器会把所有大写字符解释为小写字符。这意味着当使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebabcase (短横线分隔命名) 命名,代码如下: Vue.component('blog-post', { //在 JavaScript 中为camelCase props: ['postTitle'], template: '{{ postTitle }}' }) 在 HTML 中属性名使用 kebabcase 格式命名,代码如下: 我们可以为组件的 prop 指定验证要求,如果有一个需求没有被满足,则 Vue 会在浏览器控制台中报出警告。在使用prop验证属性的类型时,值的类型应使用对象,而不能使用字符串,代码如下: Vue.component('my-component', { 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 ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } }) 33min 5.4组 件 通 信 组件化开发是Vue中的核心概念之一,通过设计具有各自状态的UI组件,然后由这些组件拼成更加复杂的UI页面,使代码更加简洁、容易维护。创建自定义组件在Vue开发中是非常常见的,在这种开发场景下必定会涉及组件之间的通信。在本节中将要学习的是如何实现组件之间的数据交互。 5.4.1父组件向子组件通信 因为Vue项目采用单向数据流,所以只能从父组件将数据传递给子组件。具体传递的方式,可以使用5.2.2节讲到的props选项。在子组件的props选项中定义属性,在父组件中就可以向子组件传递值了。 子组件 Son.vue 文件代码如下: 子组件接收父组件传值: {{text}} 父组件 Father.vue 文件代码如下: 父组件向子组件传值 在浏览器中运行的效果如图5.5所示。 图5.5父组件向子组件传值 5.4.2子组件向父组件通信 单向数据流决定了父组件可以影响子组件的数据,但是反之不行。子组件内数据发生更新后,在父组件中无法直接获取更新后的数据。要想实现子组件向父组件传递数据,可以在子组件数据发生变化后,触发一个事件方法,然后由这个事件方法告诉父组件数据更新了。在父组件中只需对这个事件进行监听,当捕获到这个事件运行后,再对父组件的数据进行同步更新。 子组件 Son.vue 文件代码如下: 子组件输入新值: 提交 父组件 Father.vue 文件代码如下: 父组件监听子组件数据更新: {{msg}} 在上面的示例代码中,父组件中使用von事件监听器来监听子组件的事件,在子组件中使用 $emit() 触发当前实例上的事件。在浏览器中运行的效果如图5.6所示。 图5.6子组件向父组件传递数据 5.5插槽 在构建页面时,我们常常会把具有公共特性的部分抽取出来,封装成一个独立的组件,但是在实际使用过程中又会产生一些其他问题,不能完全满足开发的需求。例如,当我们需要在自定义组件内添加一些新的元素时,原来组件的封装方式不能实现。这时,我们就需要使用插槽来分发内容。 2min 5.5.1什么是插槽 Vue为了实现组件的内容分发,在组件的相关内容中提供了一套用于组件内容分发的API,也就是插槽。这套API使用 内置组件作为承载分发内容的出口,代码如下: 创建父组件 Demo.vue,代码如下: 在父组件中使用插槽 这是父组件中添加的元素 创建子组件 Myslot.vue,代码如下: 这是子组件内容 在浏览器中运行的效果如图5.7所示。 图5.7插槽效果 5.5.2具名插槽 在Vue 2.6中,具名插槽和作用域插槽引入了一个新的统一语法的vslot指令。它取代了slot和slotscope特性。 在实际的开发过程中,组件中的插槽不止一个,有时需要多个插槽,代码如下: 这是页面头部 这是页面的主体内容 这是页面的底部 针对上面的示例, 元素有一个name属性,这个属性可以用来定义额外的插槽,代码如下: 当 元素上没有定义name属性时, 出口会带有隐含的 default。在向具名插槽提供内容时,可以在一个 元素上使用 vslot 指令,并以 vslot 的参数形式提供其名称,代码如下: 这是页面头部 这是页面主体部分 这是页面底部 在上面的代码中,所有的内容都被传入对应的插槽,没有使用带有vslot的中的内容会被视为默认插槽的内容。 vslot指令与von、vbind类似,也有自己的缩写形式,把vslot替换为字符 #即可,代码如下: 这是页面头部 这是页面主体部分 这是页面底部 5.5.3作用域插槽 在使用插槽时,经常会有这样的应用场景: 在父组件中定义的数据需要在子组件中也能访问。例如,在父组件中获取的商品列表数据,当父组件中引入商品卡片的组件时,需要在商品卡片组件内部使用插槽,并把商品数据传给子组件进行渲染。 例如,创建商品卡片组件 card.vue 文件,代码如下: {{ goods.title }} 在商品卡片组件中想要显示商品的标题,但是商品数据是在父组件中获取的,所以只有在父组件中使用 组件标签才可以访问 goods 对象。为了让 goods 在父级的插槽内容可用,可以将 goods 作为 元素的一个属性绑定上去。 card.vue文件代码如下: {{ goods.title }} 绑定在 元素上的属性被称为插槽prop。现在,在父级作用域中,可以给vslot赋一个值,来定义提供的插槽prop的名字,代码如下: {{ slotProps.goods.title }}
这是父组件中添加的元素
这是子组件内容
这是页面主体部分