既然 Vue 是基于组件去构造视图的,那么最重要的事情当然是要掌握组件的知识点了。
Vue 为了极大简化组件的开发,把组件的书写形式精炼为配置的形式,一个组件使用一个 js 对象来描述,然后利用该 js 描述对象可以生成多个该组件实例。
打开 vue/types/index.d.ts 文件,可以看到四个与组件相关的类型:
- Component
- ComponentOptions
- FunctionalComponentOptions
- AsyncComponent
此处主要关注 AsyncComponent
、 ComponentOptions
和 FunctionalComponentOptions
,分别对应异步组件
、普通组件
、函数组件
。
ComponentOptions
在 vue/types/options.d.ts 中可以看到 ComponentOptions 的声明,重点关注如下属性声明:
-
data
组件中使用到的数据声明与初始化。可以是一个对象,也可以是一个方法,不过强烈推荐使用方法,原因将在后续文章讲解。
-
props
组件对外暴露的数据接口,外部可以通过该接口向组件传递数据。
-
methods
组件实例上的方法。
-
template
组件模板,默认使用 HTML 配合一些简单的 Vue 语法来书写,也可以借助于编译时插件去支持其它的一些模板语法(比如 pug )。
-
created
生命周期方法,组件实例化时,在各项数据准备就绪之后,解析
template
之前执行。
以上是构造普通组件的最基础属性,那么定义一个简单普通组件就可以这样写:
import Vue, { ComponentOptions } from 'vue';
const MyComponent: ComponentOptions<Vue> = {
props: {
name: String,
age: number
},
data() {
return {
counter: 0
};
},
created() {
this.setTimer();
},
methods: {
setTimer() {
setInterval(() => this.counter++, 1000);
}
},
template: '<div class="my-component">{{ name }} {{ age > 18 ? 'old' : 'young' }} {{ counter }}</div>'
};
Vue.component('MyComponent', MyComponent);
在上述组件定义中,有若干知识点:
-
data
方法、methods
配置中的方法、created
方法中,注入的this
都指向当前组件实例vm
。遵照 Vue 官方文档的惯例,我们使用
vm
( ViewModel 缩写) 表示 Vue 组件实例。 template
中的{{ expression }}
是 Vue 给模板添加的语法,用于在当前位置输出字符串,其中表达式转换成字符串的方式与'' + expression
行为一致,因此,如果表达式值是undefined
,将会在相应位置输出undefined
字符串。目前,
template
只能且必须返回一个元素根节点(有例外,但是正常用法是碰不到的)。template
中访问外部传入的数据(props
)、data
中声明的数据时,不需要写this
,直接访问就行,因为这些数据都会被直接挂载到vm
实例上去。
上述组件配置通过 Vue.component
方法注册到 Vue 的全局空间中去,其中,第一个参数指定该组件在全局空间中的名字为 MyComponent
,在需要使用该组件的地方通过名字调用就行了:
import Vue, { ComponentOptions } from 'vue';
const App: ComponentOptions<Vue> = {
el: 'app',
data() {
return {
age: 27
};
},
template: '<MyComponent name="yibuyisheng" :age="age"></MyComponent>'
};
Vue.component('MyComponent', MyComponent);
对于 props
属性,直接通过标签属性
(比如 name="yibuyisheng"
)的形式传递给组件。
在执行上述示例的时候,可以发现,{{ counter }}
表达式部分的输出会一秒加一。实际上, data
里面事先声明的数据都是响应式
的,改变 data
中声明的数据,模板中相应部分就会发生变化。
FunctionalComponentOptions
函数式组件的主要特点在于没有本地状态 data
,不会生成组件实例,组件需要的一切信息都从上下文参数中获取。
函数式组件配置项非常少:
-
name
主要用于组件递归调用,以及开发的时候能方便找到针对该组件打印的日志。由于在渲染的时候并不会生成组件实例,因此在 Vue devtools 的组件树中是看不到函数式组件的。
-
props
同普通组件的 props 。
-
inject
配置从上层组件注入的数据,后续文章会详细介绍。
-
functional
标记该组件是否为函数式组件。
-
render
普通组件和函数式组件都会有
render
函数,用于渲染界面。虽然上面在讲解普通组件的时候没有提到render
函数,但是实际上template
会被 Vue 的模板编译器转换成render
函数。函数式组件的
render
函数与普通组件的有很大区别:- 函数式组件不会生成实例,所以在
render
中没有this
。 - 函数式组件的
render
存在第二个参数context
,从context
中可以获取到props
等信息,具体参考官方文档。
- 函数式组件不会生成实例,所以在
编写一个简单的函数式组件:
import Vue, { FunctionalComponentOptions } from 'vue';
const MyFunctionalComponent: FunctionalComponentOptions<Record<string, any>, string[]> = {
props: ['name'],
functional: true,
render(createElement, context) {
return createElement('div', context.data, ['name: ' + context.props.name]);
}
};
Vue.component<string>('MyFunctionalComponent', MyFunctionalComponent);
说明:
createElement
方法用于创建一个 VNode 节点,详细内容将会在后续文章介绍,目前只需要知道createElement('div', ...)
会在 DOM 树中生成一个div
元素节点。- 通过
context.props
访问函数式组件外部传入的props
数据。
AsyncComponent
在开发一些大型( SPA )应用的时候,可能会存在大量前端组件代码,对首屏进入可交互状态造成一定的延迟。
实际上,渲染页面1
的时候,最好不要去加载页面2
的组件,直到用户点击进入页面2
的时候,才去加载页面2
相关的组件。
为了实现这个功能, Vue 组件支持异步写法:
import Vue, { AsyncComponent } from 'vue';
const MyAsyncComponent: AsyncComponent = function (resolve) {
setTimeout(function () {
resolve({
template: '<div>async component.</div>'
});
}, 1000);
};
Vue.component('MyAsyncComponent', MyAsyncComponent);
在一秒后,组件加载完成,相应界面得到渲染。
预告
下一篇文章将会深入介绍 Vue 内部的响应式原理。