使用 Vue 开发项目时,我们将项目中的内容按照模块划分,但是有时候模块和模块之间会存在数据交互。在真正的项目开发中,父子、兄弟组件之间需要互相传值。最传统的传值方式就是 props 和 $emit。
Prop 是你可以在组件上注册的一些自定义特性。当一个值传递给一个 prop 特性的时候,它就变成了那个组件实例的一个属性。为了给博文组件传递一个标题,我们可以用一个 props 选项将其包含在该组件可接受的 prop 列表中。
1. props传值的示例
<template>
<div id="app">
<Home msg="hello world!" />
<Home msg="tom" />
<Home msg="lion king" />
div>
template>
<script>
import Home from './components/Home'
export default {
name: 'App',
components: { Home },
data() {
return { }
},
}
script>
<template>
<div class="home">
<h3>{{ msg }}h3>
div>
template>
<script>
export default {
name: "Home",
// 通过props接受外界在调用当前组建时,传入的参数
props: ['msg']
}
script>
也可以向子组件传不同的参数:
<Home msg="hello world!" />
<Home name="tom" />
<Home ani="lion king" />
<script>
export default {
name: "Home",
// props接受多个参数
props: ['msg','name','ani']
}
script>
2. props类型验证
如果我们希望每个 prop 都有指定的值类型,并且以对象形式列出 prop,这些属性的名称和值分别是 prop 各自的名称和类型。
// ...
// 使用props接收数据并规定数据的类型
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
// ...
为了让传值变得更有灵活性,vue 提供了以下 props 的验证方式:
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
}
}
}
如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。这在开发一个会被别人用到的组件时尤其有帮助。
注:
一个子组件被一个父组件调用,当子组件触发某个函数时,需要父组件响应,就要使用到 $emit
。
1. $emit
的简单示例
<template>
<div class="child">
<button @click="ev">子组件中的按钮button>
div>
template>
<script>
export default {
name: 'Child',
data() {
return {
msg: 'I am a data from child component'
}
},
methods: {
ev() {
// 当前组件的按钮被点击时,向外抛出一个事件,谁调用当前组件,谁就在当前组件的按钮被点击时响应这个事件
this.$emit('clickBtn');
}
}
}
script>
<template>
<div id="app">
<Child v-on:clickBtn="evInFather" />
div>
template>
<script>
import Child from './components/Child'
export default {
name: 'App',
components: {
Child
},
data() {
return {
flag: true
}
},
methods: {
//当Child子组件中的 clickBtn 事件被emit时,evInFather 也会触发
evInFather() {
console.log('father up');
}
}
}
script>
我们也可以在抛出事件时,同时向外界抛出一些数据。
// Child的methods
// ...
methods: {
ev() {
// 向外抛出数据
this.$emit('clickBtn', this.msg);
}
}
// ...
// 父组件中的methods
// ...
methods: {
evInFather(data) {
console.log(data);
}
}
// ...
如果组件在抛出事件时抛出的数据不止一个,可以以参数列表的形式继续向 $emit
传值,然后在父组件中再以参数列表的形式调用。但是在数据很多的情况下会让代码显得和冗长,所以我们建议将所有要抛出的数据整合在一个对象中,向 $emit
函数的第二个参数传入一个对象。
插槽是组件被调用的第二种方式,更好的提高了组件的复用性。
子组件在调用父组件并向其传值时,更偏向于数据层的传递,但是有时候我们希望子组件在结构渲染层也能更好的响应其某个父组件,可以通过父组件的数据控制让子组件中的内容渲染。
如我们需要一个操作结果提示框,弹框中的标题,图片和按钮栏全部根据操作结果渲染具体结果。使用 props
或者 $emit
也能实现这样的效果,但是如果多种渲染结果的差异很大,那代码冗余就又上去了,所以 Vue 提供了一种更简洁的方式,就是插槽。
插槽相当于将组件封装成一个大的框架,至于具体显示什么,怎么显示,可以在调用时传数据,通过数据控制,也可以在调用时传入内容,显示传入的内容。
1. 插槽的小示例
<template>
<div class="alert">
<p>我是组件中本来就有的内容p>
<slot>slot>
div>
template>
<script>
export default {
name: "Alert"
}
script>
<template>
<div id="app">
<Alert>
<p>
hello,我是father调用时分发给子组件的内容
p>
Alert>
div>
template>
<script>
// ...
script>
注:
如果 Alert
组件中没有包含一个
元素,则该组件被调用时起始标签和结束标签之间的任何内容都会被抛弃。
2. 为组件设置插槽的后备内容
如果一个组件中预留了插槽,但是在组件被调用时并没有为其分发内容,可能会导致结构、逻辑出现一些无法预知的错误,所以有时候为插槽设置一个默认内容是很有必要的。
<div class="alert">
<slot>
<p>default messagep>
slot>
div>
3. 具名插槽
要想封装一个具有高复用性的组件,一个插槽可能还不够,我们可以在组件结构的不同位置为其预留多个插槽,为每个插槽命名,在调用组建时,向不同的插槽分发对应的内容。这就是具名插槽。
<div class="alert">
<div class="title">
<slot name="title">温馨提示slot>
div>
<div class="content">
<slot name="con">您确定要做这样的操作吗slot>
div>
<div class="btn">
<slot name="btn">
<button>确定button>
slot>
div>
div>
调用 Alert
组件:
<Alert>
<template v-slot:title>
提示
template>
<template v-slot:con>
<p>这个操作很危险,是否要进行?p>
template>
<template v-slot:btn>
<button>确定button>
<button>取消button>
template>
Alert>
具名插槽的简写:
<template #btn>template>
注:
,默认 name 值是 default。
上(除了独占默认插槽情况)。4. 作用域插槽
有时我们需要在调用组件并向组件的插槽分发内容时,访问组件内部的数据。可以将组件中的数据绑定成 插槽 prop,然后就可以在组件被调用时,同时访问插槽 prop 上的数据。
<template>
<div class="Alert">
<slot name="userMsg" v-bind:user="user">{{ user.name }}slot>
div>
template>
<script>
export default {
name: "Alert",
data() {
return {
user: {
name: 'tom',
age: 18
}
}
}
}
script>
调用插槽并使用插槽 prop 的值:
<Alert>
<template v-slot:userMsg="um">
<i>{{ um.user.name }}i>
今年
<mark>{{ um.user.age }}mark>
岁啦
template>
Alert>
可以为插槽设置多个插槽 prop:
<slot name="userMsg"
v-bind:user="user"
v-bing:num="100000"
>
{{ user.name }}
slot>
接收:
<template v-slot:userMsg="um">
<i>{{ um.user.name }}i>
今年
<mark>{{ um.user.age }}mark>
岁啦
<strong>{{ um.num }}strong>
template>
插槽 prop 也可以被解构使用:
<template v-slot:userMsg="{user}">template>
注:
如果插槽 prop 很多,建议将所有数据整合成一个对象,传递一个插槽 prop 即可。
render 函数 跟 template 一样都是创建 html 模板的,但是有些场景中用 template 实现起来代码冗长繁琐而且有大量重复,这时候就可以用 render 函数。
如果在组件中的使用 render
函数渲染,那就可以不使用 标签。组件文件中只需要
标签和
标签。
在了解 render
函数之前,我们要先明确一个 Vnode
的概念。
1. Vnode(虚拟节点)
在使用 Vue 开发项目时,渲染在浏览器上的结构是 Vue 通过底层机制的各种条件、循环、计算等操作之后最终渲染在浏览器上的,我们把浏览器上渲染的最终结果看做真实的 DOM 树。
但是 Vue 对于数据的响应很高效,面对这样高效的数据响应,也要同样高效的更新页面中的节点,但是 DOM 结构非常庞大且复杂,要完成所有 DOM 的更新特别困难。
比如我们渲染了一个商品管理列表,当某个商品的某个值发生变化时,页面的列表渲染也要更新,如果要使用原生JS去渲染,我们可能需要重新渲染整个表格,或者某一行。如果要精确地定位到某个单元格,对于代码的要求很高。
好在使用 Vue 时,我们不用手动的使用JS去更新DOM树,Vue 提供了一棵虚拟 DOM 树,它通过这个虚拟 DOM树 来追踪自己要如何改变真实 DOM。
我们在 vue 文件中写的 DOM 节点和 render
函数中的 DOM 都是虚拟 DOM,浏览器渲染时,会对所有的虚拟 DOM 进行计算,最终渲染在浏览器上。
我们在创建虚拟 DOM 时,包含了虚拟 DOM 的所有信息,如子元素,类名,样式,位置等等。
Vue 实例提供了 render
函数来渲染虚拟 DOM,render
函数的参数(也是一个函数)来创建虚拟DOM。
2. render 函数示例
<script>
export default {
name: "Home",
data() {
return {
}
},
render(ce) {
return ce(
'div'
)
}
}
script>
3. render函数的参数-createElement函数 createElement 函数接受三个参数: 下面是 hello world 4. JSX语法 如果一个模板中的结构较为简单,我们使用 如果想继续使用 JSX 语法在 React 中的使用可能会更广泛一点。
组件可以被正常调用,调用时拿到是 render
函数渲染出来的这个 render
函数:
ce
函数(很多地方会将其写成createElement)。render
函数的参数就是 createElement
函数,它创建 VNode
,作为 render
的返回值。
String | Object | Function
一个 HTML 标签名、组件选项对象,或者 resolve
了上述任何一种的一个 async
函数。必填项。
Object
一个包含这个标签(或模板)所有相关属性的对象。可选。
String | Array
由 createElement
创建的子节点或列表。createElement
第二个参数和第三个参数的详细介绍及示例:// 参数二
{
// 与 `v-bind:class` 的 API 相同,
// 接受一个字符串、对象或字符串和对象组成的数组
'class': {
foo: true,
bar: false
},
// 与 `v-bind:style` 的 API 相同,
// 接受一个字符串、对象,或对象组成的数组
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML 特性
attrs: {
id: 'foo'
},
// 组件 prop
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
// 参数三:
// 如果参数三是一个字符串,会被渲染成元素的innerText
render(ce) {
return ce(
'div',
{},
'
createElement
函数就可以了,但是一旦结构稍稍复杂一点点,代码就会变得特别冗长。render
函数,就要使用到 JSX 语法。因为JSX 语法允许在JS代码中书写HTML结构。render(ce) {
return (
<div class="home">
这里面可以任意写结构
</div>
);
}