yanyue404 / blog

Just blog and not just blog.
https://yanyue404.github.io/blog/
Other
87 stars 13 forks source link

跟着 Vant Dialog 学习函数调用使用组件 #268

Open yanyue404 opened 6 months ago

yanyue404 commented 6 months ago

docs: https://vant-contrib.gitee.io/vant/v2/#/zh-CN/dialog repo: https://github.com/youzan/vant/blob/2.x/src/dialog/index.js

函数调用

Dialog 是一个函数,调用后会直接在页面中弹出相应的模态框。

import { Dialog } from 'vant';

// 基础提示
Dialog({ message: '提示' });

// 消息提示:用于提示一些消息,只包含一个确认按钮。

Dialog.alert({
  title: '标题',
  message: '弹窗内容',
}).then(() => {
  // on close
});

// 消息确认: 用于确认消息,包含取消和确认按钮。
Dialog.confirm({
  title: '标题',
  message: '弹窗内容',
})
  .then(() => {
    // on confirm
  })
  .catch(() => {
    // on cancel
  });

使用场景

  1. 组件的模板是通过调用接口从服务端获取的,需要动态渲染组件;
  2. 实现类似原生 window.alert() 的提示框组件,它的位置是在 <body> 下,而非 <div id="app">,并且不会通过常规的组件自定义标签的形式使用,而是像 JS 调用函数一样使用。

从熟悉 Vue 的构造器——extend 与手动挂载——$mount 开始

创建一个 Vue 实例时,都会有一个选项 el,来指定实例的根节点,如果不写 el 选项,那组件就处于未挂载状态。Vue.extend 的作用,就是基于 Vue 构造器,创建一个“子类”,它的参数跟 new Vue 的基本一样,但 data 要跟组件一样,是个函数,再配合 $mount ,就可以让组件渲染,并且挂载到任意指定的节点上,比如 body。

比如上文的场景,就可以这样写:

import Vue from 'vue';

const AlertComponent = Vue.extend({
  template: '<div>{{ message }}</div>',
  data () {
    return {
      message: 'Hello, Aresn'
    };
  },
});

这一步,我们创建了一个构造器,这个过程就可以解决异步获取 template 模板的问题,下面要手动渲染组件,并把它挂载到 body 下:

const component = new AlertComponent().$mount();

这一步,我们调用了 $mount 方法对组件进行了手动渲染,但它仅仅是被渲染好了,并没有挂载到节点上,也就显示不了组件。此时的 component 已经是一个标准的 Vue 组件实例,因此它的 $el 属性也可以被访问:

document.body.appendChild(component.$el);

当然,除了 body,你还可以挂载到其它节点上。

$mount 也有一些快捷的挂载方式,以下两种都是可以的:

// 在 $mount 里写参数来指定挂载的节点
new AlertComponent().$mount('#app');
// 不用 $mount,直接在创建实例时指定 el 选项
new AlertComponent({ el: '#app' });

实现同样的效果,除了用 extend 外,也可以直接创建 Vue 实例,并且用一个 Render 函数来渲染一个 .vue 文件:

import Vue from 'vue';
import Notification from './notification.vue';

const props = {};  // 这里可以传入一些组件的 props 选项

const Instance = new Vue({
  render (h) {
    return h(Notification, {
      props: props
    });
  }
});

const component = Instance.$mount();
document.body.appendChild(component.$el);

这样既可以使用 .vue 来写复杂的组件(毕竟在 template 里堆字符串很痛苦),还可以根据需要传入适当的 props。渲染后,如果想操作 Render 的 Notification 实例,也是很简单的:

const notification = Instance.$children[0];

因为 Instance 下只 Render 了 Notification 一个子组件,所以可以用 $children[0] 访问到。

如果你还不理解这样做的目的,没有关系,后面小节的两个实战你会感受到它的用武之地。

需要注意的是,我们是用 $mount 手动渲染的组件,如果要销毁,也要用 $destroy 来手动销毁实例,必要时,也可以用 removeChild 把节点从 DOM 中移除。

来个例子

import vue from 'vue'
import vueComponent from './index.vue'

// 组件构造器,构造出一个 vue组件实例
const CreateConstructor = vue.extend(vueComponent)
let instance

function showConfirm(options = {}) {
  return new Promise((resolve, reject) => {
    instance = new CreateConstructor({
      el: document.createElement('div'),
      data() {
        return {
          show: true
        }
      },
      methods: {
        onCancel: (e) => {
          instance.reject()
          // 返回修改
          instance.show = false
        }
      }
    })

    instance.$on('confirm', () => {
      instance.show = false
      instance.resolve()
    })

    Object.assign(instance, options, {
      resolve,
      reject
    })

    // 添加节点
    document.body.insertBefore(instance.$el, document.body.children[0])
  })
}

export default showConfirm
// 使用
showConfirm()
  .then(() => {
    alert('confirm')
  })
  .catch(() => {
    alert('cancel')
  })

支持 Vue2

// 不适用于创建后直接修改 props 的情况, 会报修改 props wraning
export function mount(component, opt, el) {
  if (!component) {
    console.warn('亲,请传入正确的组件');
  }
  if (!el) {
    el = document.createElement('div');
    document.body.appendChild(el);
  }
  return new Vue({
    el,
    render(h) {
      return h(component, opt);
    },
  });
}

// 使用 Vue.extend 的方式可以更好复用实例,从实例修改 props
export function mountComponent(component, option, apply) {
  const root = document.createElement('div');
  const getInstance = () => {
    const CreateConstructor = Vue.extend(component);
    // * option.data(Function) 修改 data
    // * option.propsData(Object) 修改 props
    // * option.methods (Object) 修改 methods
    return new CreateConstructor({ el: root, ...option });
  };
  const instance = getInstance();
  // 可以直接扩展实例,或按返回值自行扩展
  //eg: instance.$on (承接组件内部的 $emit)、instance.$slots (自定义插槽的内容)
  apply(instance);
  // 添加节点
  document.body.appendChild(root);
  return {
    instance,
    unmount() {
      instance.$el.remove();
    },
  };
}

export function destory(vm) {
  vm.$el.remove();
  vm.$destroy();
}

支持 Vue3

注意:vue 组件需要使用 export default { setup : {}} 模式才可获取到 instance 实例上的属性和方法

// form vant@3.0.7
export function mountComponent(RootComponent, props = {}) {
  var app = createApp(RootComponent, props);
  var root = document.createElement('div');
  document.body.appendChild(root);
  return {
    instance: app.mount(root),
    unmount() {
      app.unmount();
      document.body.removeChild(root);
    }

  };
}

其他