Vue2实践(1)之用Vue.extend做一个函数调用的Dialog

前言

在之前的文章中我整理了一些Vue的核心:数据代理、数据劫持、依赖收集、视图更新、diff。Vue的有趣和强大当然不止于此,接下来我会通过一些实践来整理Vue2的其他功能、特性。

通过前面的代码,其实我们能发现,使用Vue2来开发最终还是要通过JS实现的。所以这一节我们来使用JS函数实现一个Dialog,目的是手动地挂载这个组件,使它能在项目中的任何页面上调用。

基础

Dialog组件

xml 复制代码
<!-- /src/components/Dialog.vue -->
<template>
    <div class="container">
        <div>{{ text }}</div>
    </div>
</template>
<script>
export default {
    props: {
        text: {
            type: String,
            default: ''
        }
    },
}
</script>

调用函数

javascript 复制代码
// /src/utils/dialog.js
import Vue from "vue";
import Dialog from "@/components/Dialog.vue";
​
let DialogContructor = Vue.extend(Dialog);
​
export default function openDialog(options) {
    const instance = new DialogContructor({
        props: {
            text: options.text,
        },
        render(h) {
            return h(Dialog, {
                props: {
                    text: this.text
                }
            })
        }
    })
    document.body.appendChild(instance.$mount().$el); // 手动将实例化的元素挂载到body上
}

使用

xml 复制代码
<!-- 组件A -->
<template>
<!-- 省略 -->
</template>
​
<script>
import openDialog from '@/uitls/dialog';
export default {
    // ...
    methods: {
        click() {
            openDialog({
                text: 'Hi~Vue2'
            })
        }
    }
}
</script>

通过以上代码已经可以实现这个简单的功能了

进阶-在Dialog中使用slot

使用slot的组件可以给使用者提供更灵活的调用,比如Dialog有时候仅仅用作一个消息通知,有时候又可以模拟一个弹出输入框。

xml 复制代码
<!-- /src/components/Dialog.vue -->
<template>
    <div class="container">
        <div class="title">{{ title }}</div>
        <div class="content">
            <slot></slot>
        </div>
    </div>
</template>
<script>
export default {
    props: {
        title: {
            type: String,
            default: ''
        }
    },
}
</script>
xml 复制代码
<!-- 组件A -->
<template>
<!-- 省略 -->
</template>
​
<script>
import openDialog from '@/uitls/dialog';
import TextInput from '@/components/FieldTypes/TextInput.vue';
export default {
    // ...
    methods: {
        click() {
            openDialog({
                title: 'Hi~Vue2',
                components: {
                    TextInput
                },
                slotRender: (h) => {
                    props: {
                        field: {
                            type: 'text',
                            name: '选项名称',
                            placeholder: '请输入选项名称',
                            required: true,
                            value: ''
                        }
                    },
                }
            })
        }
    }
}
</script>

在openDialog中传入的options最终在dialog.js中进行处理

javascript 复制代码
// /src/utils/dialog.js
import Vue from "vue";
import Dialog from "@/components/Dialog.vue";
​
let DialogContructor = Vue.extend(Dialog);
​
export default function openDialog(options) {
    const instance = new DialogContructor({
        props: {
            title: options.title,
        },
        components: options.components || {},
        render(h) {
            const slot = slotRender ? slotRender(h) : null;
            return h(Dialog, {
                props: {
                    text: this.text
                },
                scopedSlots: {
                    default: () => slot
                }
            })
        }
    })
    document.body.appendChild(instance.$mount().$el); // 手动将实例化的元素挂载到body上
}

通过这样的方式,我们在组件中可以更灵活地使用这个函数调用组件,往其中塞入自定义的组件结构

进阶-函数调用组件的组件间通信

想要完成组件间通信很重要的一点在于运用JS的引用类型特性、以及$emitsAPI。前者不需要特别赘述,但是$emitsAPI在这里的使用值得注意:

首先我们假设TextInput中已经有$emit('input', $event.target.value)

xml 复制代码
<!-- 组件A -->
<template>
<!-- 省略 -->
</template>
​
<script>
import openDialog from '@/uitls/dialog';
import TextInput from '@/components/FieldTypes/TextInput.vue';
export default {
    // ...
    data() {
        return {
            componentData: {}
        }
    }
    methods: {
        click() {
            openDialog({
                title: 'Hi~Vue2',
                components: {
                    TextInput
                },
                slotRender: (h) => {
                    props: {
                        field: {
                            type: 'text',
                            name: '选项名称',
                            placeholder: '请输入选项名称',
                            required: true,
                            value: ''
                        }
                    },
                    on: {
                        'input': (value) => formData['text'] = value
                    }
                }
            })
        }
    }
}
</script>
javascript 复制代码
// /src/utils/dialog.js
import Vue from "vue";
import Dialog from "@/components/Dialog.vue";
​
let DialogContructor = Vue.extend(Dialog);
​
export default function openDialog(options) {
    const instance = new DialogContructor({
        props: {
            title: options.title,
        },
        components: options.components || {},
        render(h) {
            const slot = slotRender ? slotRender(h) : null;
            return h(Dialog, {
                props: {
                    text: this.text
                },
                on: {
                    'custom-confirm': options.methods.confirm
                },
                scopedSlots: {
                    default: () => slot
                }
            })
        }
    })
    document.body.appendChild(instance.$mount().$el); // 手动将实例化的元素挂载到body上
}
xml 复制代码
<!-- /src/components/Dialog.vue -->
<template>
    <div class="container">
        <div class="title">{{ title }}</div>
        <div class="content">
            <slot></slot>
        </div>
        <div class="actions">
            <button class="confirm" @click="confirm">确定</button>
        </div>
    </div>
</template>
<script>
export default {
    props: {
        title: {
            type: String,
            default: ''
        }
    },
    methods: {
        confirm () {
            this.$emit('custom-confirm')
        },
    }
}
</script>

这里其实通过了两个$emits事件以及引用类型特性,完成了函数调用组件的通信。

总结

根据这个例子,后续会展开整理一下内容:

  1. Vue.extend()API
  2. Vue源码对methods以及事件的处理,补全实例化Vue的一些内容
  3. 手写渲染函数

下一个实例的话会选择动态表单这个经典案例

相关推荐
万少18 小时前
HarmonyOS 开发必会 5 种 Builder 详解
前端·harmonyos
橙序员小站21 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
炫饭第一名1 天前
速通Canvas指北🦮——基础入门篇
前端·javascript·程序员
王晓枫1 天前
flutter接入三方库运行报错:Error running pod install
前端·flutter
符方昊1 天前
React 19 对比 React 16 新特性解析
前端·react.js
ssshooter1 天前
又被 Safari 差异坑了:textContent 拿到的值居然没换行?
前端
曲折1 天前
Cesium-气象要素PNG色斑图叠加
前端·cesium
Forever7_1 天前
Electron 淘汰!新的桌面端框架 更强大、更轻量化
前端·vue.js
不会敲代码11 天前
前端组件化样式隔离实战:React CSS Modules、styled-components 与 Vue scoped 对比
css·vue.js·react.js
Angelial1 天前
Vue3 嵌套路由 KeepAlive:动态缓存与反向配置方案
前端·vue.js