Bootstrap

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>

;