likes
comments
collection
share

如何写一个函数式组件(vue2,vue3版)

作者站长头像
站长
· 阅读数 35

最近迷上了函数式组件,主要是最近接到一个需求,就是公司的产品原来的loading效果都是用uniapp提供的方法,所以这次希望全部统一一种loading效果。不过这次就不带uniapp框架的限制,只考虑vue2、vue3怎么实现一个函数式组件。

准备

这次实现的组件是loading组件,实现的功能主要下面:

// 1、能直接调用
Loading()

// 2、能直接传入文字调用
Loading('加载中...')

// 3、有相关配置调用
Loading({
    text: '加载中...',
    mask: true
})

// 4、关闭实现
Loading.close()

为了方便使用,可以直接挂载在Vue的实例里面,但是Vue2,Vue3的方式不太相同,实现也不太相同,所以下面会分开实现。

vue2

首先是展示vue文件,这里不做过多的解释,这种程度难不了大家。

<template>
  <div class="loading-container" :class="{ pe: !mask }" v-if="visible">
    <div class="loading-wrapper" :class="{ mask: mask, pe: !mask }">
      <img
        src="https://zhanchi-static.oss-cn-shenzhen.aliyuncs.com/zhancchi_recruit/loading.gif"
      />
      <div class="loading-text">{{ text }}</div>
    </div>
  </div>
</template>
<script>
export default {
  name: "globalLoading",
  data() {
    return {
      text: "加载中",
      visible: false,
      mask: false
    };
  },
  methods: {
    close() {
      this.visible = false;
    }
  },
  watch: {
    visible(newVal) {
      this.visible = newVal;
    }
  }
};
</script>
<style lang="scss" scoped>

.pe {
  pointer-events: none;
}
.loading-container {
  position: fixed;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 999;

  .loading-wrapper {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 999;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;

    img {
      width: 32px;
      height: 32px;
    }

    .loading-text {
      display: inline-block;
      max-width: 100px;
      margin-top: 5px;
      font-size: 12px;
      color: #666
    }
  }

  .mask {
    background: rgba(0, 0, 0, 0.5);
    .loading-text {
      color: #fff;
    }
  }
}
</style>

大家可以根据自己的需求设计,下来就是怎么改成函数式组件。

首先,我们需要Vue2里面给我提供的一个方法——extend。给予我们可以构建“子类”的办法,接着创建index.js文件。

import Vue from 'vue';
import Main from './index.vue'
let LoadingConstructor = Vue.extend(Main)
// 保存Loading构造实例
let instance

接着我们需要Loading方法,实现如下:

const Loading = function(options) {
    options = options || {}
    // 实现功能点2
    if(typeof options === 'string') {
        options =  {
            text: options
        }
    }
    // 合并options跟data的属性
    instance = new LoadingConstructor({
        data: options
    })
    // 挂载在VNode上面
    instance.$mount()
    // 插入到页面
    document.body.appendChild(instance.$el)
}

在上面将options给data赋值时,会将属性逐一合并,由于一开始我们设置了初始值,所以没有传options的时候,会默认使用组件设置的初始值。那么实现关闭loading的方法就简单了:

Loading.close = function() {
    instance && (instance.visible = false)
}

export default Loading

接着只需要在Vue实例挂载即可

// 导入的是index.js的Loading
Vue.prototype.$Loading = Loading

vue3

其实vue3的实现大同小异,只不过由于vue3写法等方面不一样了,所以实现方式也会有不一样。先看template文件:

<script setup>
import { ref } from 'vue'

defineProps({
  text: {
    type: String,
    default: '加载中...',
  },
  mask: {
    type: Boolean,
    default: false,
  },
})

const visible = ref(false)

function open() {
  visible.value = true
}

function close() {
  visible.value = false
}

defineExpose({
  close,
  open,
})
</script>
<template>
  <div v-show="visible" class="loading-container" :class="{ pe: !mask }">
    <div class="loading-wrapper" :class="{ mask: mask, pe: !mask }">
      <img
        src="https://zhanchi-static.oss-cn-shenzhen.aliyuncs.com/zhancchi_recruit/loading.gif"
        alt=""
      />
      <span class="loading-text">{{ text }}</span>
    </div>
  </div>
</template>
<style lang="scss" scoped>
.loading-container {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 999;

  .loading-wrapper {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 999;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;

    img {
      width: 32px;
      height: 32px;
    }

    .loading-text {
      display: inline-block;
      max-width: 100px;
      margin-top: 5px;
      font-size: 12px;
      color: #666;
    }
  }

  .mask {
    background: rgb(0 0 0 / 50%);

    .loading-text {
      color: #fff;
    }
  }
}

.pe {
  pointer-events: none;
}
</style>

在vue2中,我们可以将参数定义在data,通过构造器合并属性的方式来传递参数,但是在vue3中已经不提供这样的方法了,所以需要将参数定义为props,并且通过defineExpose暴露方法在外部使用(其实直接修改visible值也可以,但是使用者来说,还是希望更语义化点吧)。

接着是index.js文件

import { createVNode, render } from 'vue'
import Main from './index.vue'

let instance
const container = document.createElement('div')
const Loading = options => {
  if (instance) {
    document.body.removeChild(container.firstElementChild)
  }
  let props = options || {}
  if (typeof options === 'string') {
    props = {
      text: options,
    }
  }
  // 创建vnode
  instance = createVNode(Main, props)
  // 渲染成到容器
  render(instance, container)
  // container.firstElementChild:实际上我们只挂在了Loading组件
  document.body.appendChild(container.firstElementChild)
  // 展示loading
  const vm = instance.component
  // 调用展示方法
  vm.exposed.open()
}

Loading.close = () => {
  if (instance) {
      // 调用关闭方法
    instance.component.exposed.close()
  }
}

export default Loading

不同于vue2挂载在实例的方法,vue3是下面方式挂载,但调用就是少写个this罢了(因为vue3没有this概念)

import { createApp } from 'vue'
import App from './App.vue'
import Loading from '@/component/loading/index.js'

const app = createApp(App)
app.config.globalProperties.$Loading = Loading

总结

至此就完成了函数式组件的写法,当然这是Loading组件只展示的了最简单实现,比如你可以添加一些回调函数,如:success,faild等等,套路都一样,实现起来也没啥难度,文章到此为止。

转载自:https://juejin.cn/post/7254027262885642299
评论
请登录