Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
yanyue404 opened this issue Jan 4, 2024 · 0 comments
Open

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

yanyue404 opened this issue Jan 4, 2024 · 0 comments

Comments

@yanyue404
Copy link
Owner

yanyue404 commented Jan 4, 2024

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 [email protected]
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);
    }

  };
}

其他

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant