目录
组件
页面布局的本质是盒子,盒子就是标签
页面布局的本质是盒子,盒子就是组件
概念:
组件就是功能模块的封装
组件是构成页面中独立结构单元,组件主要以独立页面结构形式存在,不同组件也具有基本交互功能。
功能模块:一个页面分成三个层
结构层、表现层、行为层
一个组件的基本步骤:定义,引入,注册,使用。
组件的定义:
当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue 文件中,这被叫做单文件组件 (简称 SFC)
组件注册:
组件的使用需要用import引入,然后通过components来注册组件,然后组件的使用,和标签一样。组件将会以其注册时的名字作为模板中的标签名。
组件注册还分为全局注册和局部注册:
全局注册
一个全局的变量,可以在任意的组件使用;任何“页面”都可以使用,比如“导航栏”
注册方法:先引入,然后使用app.component()方法,app.component()方法可以被链式调用。
全局注册虽然很方便,但有以下几个问题:
- 全局注册,但并没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。
- 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。
- 相比之下,局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确,并且对 tree-shaking 更加友好
局部注册
方法:使用components选项;
SFC:Single-File-Component
Vue 的单文件组件 (即 *.vue 文件,英文 Single-File Component,简称 SFC) 是一种特殊的文件格式,使我们能够将一个 Vue 组件的模板、逻辑与样式封装在单个文件中。
父向子传值
首选props;
props后的参数可以是数组,也可以是对象
当一个值被传递给 prop 时,它将成为该组件实例上的一个属性。该属性的值可以像其他组件属性一样,在模板和组件的 this 上下文中访问。
一个组件可以有任意多的 props,默认情况下,所有 prop 都接受任意类型的值。
静态&动态传值
动静态都可以传递不同的值:可以传Number、Boolean、Array、Object
动态传输对象:
prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
类型约束&函数校验
props: {
// 基础类型检查
//(给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true,
},
// Number 类型的默认值
propD: {
type: Number,
default: 100,
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或者数组应当用工厂函数返回。
// 工厂函数会收到组件所接收的原始 props
// 作为参数
default(rawProps) {
return { message: "hello" };
},
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ["success", "warning", "danger"].includes(value);
},
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
default() {
return "Default function";
},
},
}
校验选项中的 type 可以是下列这些原生构造函数:
String,Number,Boolean,Array,Object,Date,Function,Symbol
另外,type 也可以是自定义的类或构造函数,Vue 将会通过 instanceof 来检查类型是否匹配。
单向数据流
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。
子向父传值
通过自定义事件来传值
组件实例提供了一个自定义事件系统,我们就通过自定义事件来实现子向父传值:父组件可以通过 v-on 或 @ 来选择性地监听子组件上抛的事件,就像监听原生 DOM 事件那样
子组件可以通过调用内置的 $emit 方法,通过传入事件名称来抛出一个事件:
透传(Attributes)
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id。
当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上。
对 class 和 style 的合并
v-on 监听器继承
如果不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false
透传进来的 attribute 可以在模板的表达式中直接用 $attrs 访问到
插槽
<slot> 元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。
了解到组件能够接收任意类型的 JavaScript 值作为 props,但组件要如何接收模板内容呢?在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。
<slot> 元素可以有一个特殊的 attribute name,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容,这类带 name 的插槽被称为具名插槽 (named slots)。没有提供 name 的 <slot> 出口会隐式地命名为“default”。
要为具名插槽传入内容,我们需要使用一个含 v-slot 指令的 <template> 元素,
v-slot 有对应的简写 #
最终渲染出来的结果为:
动态组件
在上面的例子中,被传给 :is 的值可以是以下几种:
- 被注册的组件名
- 导入的组件对象
你也可以使用 is attribute 来创建一般的 HTML 元素。
当使用 <component :is="..."> 来在多个组件间作切换时,被切换掉的组件会被卸载
路由
服务端路由指的是服务器根据用户访问的 URL 路径返回不同的响应结果。当我们在一个传统的服务端渲染的 web 应用中点击一个链接时,浏览器会从服务端获得全新的 HTML,然后重新加载整个页面。
然而,在单页面应用中,客户端的 JavaScript 可以拦截页面的跳转请求,动态获取新的数据,然后在无需重新加载的情况下更新当前页面。这样通常可以带来更顺滑的用户体验,尤其是在更偏向“应用”的场景下,因为这类场景下用户通常会在很长的一段时间中做出多次交互。
在这类单页应用中,“路由”是在客户端执行的。一个客户端路由器的职责就是利用诸如 History API 或是 hashchange 事件这样的浏览器 API 来管理应用当前应该渲染的视图。
SPA:单页面应用
基于component的原生实现
<template>
<div>
<A href="#about">关于我们</A> | <A href="#contact">联系我们</A> |
<A href="#products">产品中心</A>
<!-- component是内置的组件,用来动态的显示组件 -->
<component :is="currentComponent" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import About from "./views/About.vue";
import Contact from "./views/Contact.vue";
import Products from "./views/Products.vue";
/** 属性值是A链接的hash值*/
const router = {
about: About,
contact: Contact,
products: Products,
};
export default defineComponent({
data() {
return { currentComponent: About };
},
components: {},
methods: {},
created() {
const hash = window.location.hash;
this.currentComponent = router[hash.slice(1)];
},
mounted() {
/** 箭头函数的目的是为了让this指向当前组件*/
const hashChangeFn = () => {
const hash = window.location.hash;
console.log(hash);
this.currentComponent = router[hash.slice(1)];
};
/** 没有监听点击事件*/
window.addEventListener("hashchange", hashChangeFn);
},
});
</script>
<style lang="scss" scoped></style>