编写简单的 Vue 组件

既然 Vue 是基于组件去构造视图的,那么最重要的事情当然是要掌握组件的知识点了。

Vue 为了极大简化组件的开发,把组件的书写形式精炼为配置的形式,一个组件使用一个 js 对象来描述,然后利用该 js 描述对象可以生成多个该组件实例。

打开 vue/types/index.d.ts 文件,可以看到四个与组件相关的类型:

  • Component
  • ComponentOptions
  • FunctionalComponentOptions
  • AsyncComponent

此处主要关注 AsyncComponentComponentOptionsFunctionalComponentOptions ,分别对应异步组件普通组件函数组件

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 内部的响应式原理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,044评论 0 29
  • Vue 实例 属性和方法 每个 Vue 实例都会代理其 data 对象里所有的属性:var data = { a:...
    云之外阅读 2,198评论 0 6
  • 1.安装 可以简单地在页面引入Vue.js作为独立版本,Vue即被注册为全局变量,可以在页面使用了。 如果希望搭建...
    Awey阅读 10,989评论 4 129
  • 下载安装搭建环境 可以选npm安装,或者简单下载一个开发版的vue.js文件 浏览器打开加载有vue的文档时,控制...
    冥冥2017阅读 6,027评论 0 42
  • “致虚极,守静笃。万物并作,吾以观复。夫物芸芸,各复归其根。归根曰静,静曰复命。复命曰常,知常曰明。不知常...
    钱江潮369阅读 260评论 0 1