Learning Vue 读书笔记 Chapter 4
4.1 Vue中的嵌套组件和数据流
我们将嵌套的组件称为子组件
,而包含它们的组件则称为它们的父组件
。
父组件可以通过 props
向子组件传递数据,而子组件则可以通过自定义事件(emits
)向父组件发送事件。
4.1.1 使用Props向子组件传递数据
在 Vue 组件中,props 字段以对象或数组的形式存在,它包含了该组件可从父组件接收的所有可用数据属性。props 对象的每个属性都对应着目标组件的一个 prop(属性)。若要使组件能够接收父组件传递的数据,您需要在组件的选项对象中声明 props 字段。
在一个子组件中定义props
export default {
name: 'ChildComponent',
props: {
name: String
}
}
将动态变量作为属性传递给子组件。每当children[0]的值发生变化时,Vue也会更新ChildComponent中的name属性,如果需要,子组件将重新渲染其内容。
<template>
<ChildComponent :name="children[0]" />
</template>
<script lang="ts">
import ChildComponent from './ChildComponent.vue';
export default {
data() {
return {
children: ['Red Sweater', 'Blue T-Shirt', 'Green Hat'],
};
},
};
</script>
如果name属性不是字符串类型,需要使用v-bind属性(或:)来向子组件传递静态数据,例如:对于布尔类型使用 :name=“true”
,对于数组类型使用 :name=“[‘hello’, ‘world’]”
。
使用v-bind(不是:)
来传递整个对象,并将其属性绑定到相关子组件的props上:
<template>
<ProductComp v-bind="product" />
</template>
4.1.2 声明带验证和默认值的Prop类型
将prop定义为带有默认值的字符串
export default {
name: 'ChildComponent',
props: {
name: {
type: String,
default: 'Child component'
}
}
}
4.1.3 使用自定义类型检查声明Props
直接将pizza prop 的类型声明为Pizza类
class Pizza {
title: string;
description: string;
image: string;
quantity: number;
price: number;
constructor(
title: string,
description: string,
image: string,
quantity: number,
price: number
) {
this.title = title;
this.description = description;
this.image = image;
this.quantity = quantity;
this.price = price;
}
}
export default {
name: 'PizzaComponent',
props: {
pizza: {
type: Pizza,
required: true
}
}
}
或者,你可以使用TypeScript的接口
或类型来定义你的自定义类型,而不是使用类。然而,在这种情况下,你必须使用vue包中的PropType类型,并按照以下语法,将声明的类型映射到目标prop
:
type: Object as PropType<Your-Custom-Type>
import type { PropType } from 'vue';
interface Pizza {
title: string;
description: string;
image: string;
quantity: number;
price: number;
}
export default {
name: 'PizzaComponent',
props: {
pizza: {
type: Object as PropType<Pizza>,
required: true,
},
},
};
4.1.4 使用defineProps()和withDefaults() 定义Props
使用defineProps和
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
name: {
type: String,
default: "Hello from the child component.",
},
});
</script>
使用defineProps()和TypeScript 类型声明Props (?代表该type中name属性可有可无)
<script setup>
import { defineProps } from 'vue';
type ChildProps = {
name?: string;
};
const props = defineProps<ChildProps>();
</script>
使用defineProps() 和withDefaults()声明Props
import { defineProps, withDefaults } from 'vue';
type ChildProps = {
name?: string;
};
const props = withDefaults(defineProps<ChildProps>(), {
name: 'Hello from the child component.',
});
4.2 使用自定义事件进行组件交流
Vue将传递给子组件的props数据视为只读的原始数据。单向数据流确保只有父组件可以更新数据属性。我们通常希望更新特定的数据属性并将其与父组件同步。为此,我们使用组件选项中的emits
字段来声明自定义事件。
以ToDoList组件为例。这个ToDoList将使用ToDoItem作为其子组件来渲染任务列表,代码如下:
ToDoList Component
<template>
<ul style="list-style: none;">
<li v-for="task in tasks" :key="task.id">
<ToDoItem :task="task"
@task-completed-toggle="onTaskCompleted"/>
</li>
</ul>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import ToDoItem from './ToDoItem.vue';
import type { Task } from './ToDoItem';
export default defineComponent({
name: 'ToDoList',
components: {
ToDoItem,
},
data() {
return {
tasks: [
{ id: 1, title: 'Learn Vue', completed: false },
{ id: 2, title: 'Learn TypeScript', completed: false },
{ id: 3, title: 'Learn Vite', completed: false },
] as Task[],
};
},
methods: {
onTaskCompleted(payload: { id: number; completed: boolean }) {
const index = this.tasks.findIndex(t => t.id === payload.id);
if (index < 0) {
return;
}
this.tasks[index].completed = payload.completed;
}
}
});
</script>
ToDoItem Componet
<template>
<div>
<input type="checkbox"
:checked="task.completed"
@change="onTaskCompleted"/>
<span>{{ task.title }}</span>
</div>
</template>
<script lang="ts">
import { defineComponent, type PropType } from 'vue';
export interface Task {
id: number;
title: string;
completed: boolean;
}
export default defineComponent({
name: 'ToDoItem',
props: {
task: {
type: Object as PropType<Task>,
required: true,
},
},
emits: ['task-completed-toggle'],
methods: {
onTaskCompleted(event: Event) {
this.$emit("task-completed-toggle", {
...this.task,
completed: (event.target as HTMLInputElement)?.checked,
});
}
}
});
</script>
4.3 使用defineEmits()定义自定义事件
ToDoTtem使用defineEmits() 定义自定义事件
<script lang="ts" setup>
//...
const props = defineProps({
task: {
type: Object as PropType<Task>,
required: true,
},
});
const emits = defineEmits(['task-completed-toggle']);
const onTaskCompleted = (event: Event) => {
emits("task-completed-toggle", {
id: props.task.id,
completed: (event.target as HTMLInputElement)?.checked,
});
}
</script>
4.4 使用provide/inject 进行组件沟通
4.4.1 使用provide 传递数据
组件的option 领域 provide 接受两种格式:数据对象
或函数
。 provide 可以是一个包含要注入数据的对象,每个属性代表一个(键,值)数据类型。在以下示例中,ProductList 向其所有后代提供了数据值 selectedIds,其值为 [1]。
<script>
export default {
name: 'ProductList',
// ...
provide: {
selectedIds: [1]
},
}
</script>
provide 的另一种格式类型是一个返回对象的函数,该对象包含可用于注入后代的数据。这种格式类型的一个好处是,我们可以访问 this 实例,并将动态数据或组件方法映射到返回对象的相应字段。
<script>
export default {
// ...
provide() {
return {
selectedIds: [1]
};
},
// ...
}
</script>
4.4.2 使用 inject接收数据
在这段代码中,Vue将获取注入的selectedIds的值并将其赋给一个本地数据字段currentSelectedIds,如果没有注入值,则使用其默认值[]。
<script lang='ts'>
export default {
// ...
inject: {
currentSelectedIds: {
from: 'selectedIds',
default: [],
},
},
}
</script>