Message组件讲解

目录

message方法

1. 首先对传来的参数进行处理

2. 判断是不是需要分组

3. 开始创建message

4. 添加实例,并返回含有关闭方法的对象

message组件

repeatNum部分

其他部分

网盘地址


以前在公司做组件封装,当时封装过message组件,对elementPlus的message组件也进行过研究。最近闲来无事,正好又看一下。

message方法

先看message方法,接收一个option参数,就是我们使用message方法时配置的内容

1. 首先对传来的参数进行处理

1.1 会对传来的配置进行判断,判断传来的字符串还是vnode还是正常的配置

字符串:

vnode:

正常配置:

1.2 normalized变量的normalized内容

1.3 判断是否有appendTo属性,没有则默认插入到body中,有则插入到指定DOM内

1.4 返回normalized这个处理后的变量

2. 判断是不是需要分组

2.1 查看配置处理后的对象中是否有grouping属性,并且实例数组不能为空

  • 这里的实例数组,每次创建message,实例数组都会新增一个实例进去(除了grouping需要分组的,分组的只需要添加一次),先留意一下,下面会讲每个实例中都有那些内容

3. 开始创建message

const createMessage = ({ appendTo, ...options }) => {
  const id = `message_${seed++}`
  // 如果传入了onClose方法,就是传入的onClose,如果没传,则就是undefined。所以下面的用到了?.
  const userOnClose = options.onClose
  // 创建一个div元素
  const container = document.createElement('div')
  // 创建虚拟dom用,传给虚拟dom的props
  const props: any = {
    ...options, // 用不到appendTo,所以上面进行了结构
    id,
    // 在 leave 钩子之前调用(message组件里面的Transition组件要用)
    onClose: () => {
      userOnClose?.() // 执行用户传入的onClose逻辑
      closeMessage(instance) // 关闭message逻辑
    },
    // 在离开过渡完成、且元素已从 DOM 中移除时调用(message组件里面的Transition组件要用)
    onDestroy: () => {
      render(null, container)
    },
  }
  // 创建虚拟DOM节点
  const vnode = createVNode(
    MessageConstructor, // 组件名
    props, // props
    // 插槽,如果是VNode用法,h函数自己就创建了,不需要生成虚拟DOM
    isVNode(props.message) ? { default: () => props.message } : null
  )
  // 渲染成真实DOM(插入到上面创建的div中了)
  render(vnode, container)
  // 并插入到指定的DOM结构内(container内最外层只有一个div)
  appendTo.appendChild(container.firstElementChild!)
  // vnode.component就是生成这个虚拟DOM(message)元素
  const vm = vnode.component!
  // handler对象,存放close方法。共message组件消失的时候调用
  const handler = {
    close: () => {
      // 把message组件往外抛出的visible变为false,组件内通过这个控制显示和隐藏(v-show)
      vm.exposed!.visible.value = false
    },
  }
  // 实例中存放的元素
  const instance = {
    id,
    vnode,
    vm,
    handler,
    props: (vnode.component as any).props,
  }
  // 返回实例
  return instance
}

3.1 在外面需要一个seed变量,每次新增message,都让seed加1。刷新则重置

3.2 首先给每个message定义一个唯一的id

3.3 从剩余参数中拿出来onClose方法

3.4 创建div元素,一会生成的message组件就放在这个div里

3.5 创建vnode需要的props

  • 这里的onClose和onDestroy就是message组件中Transition标签上用到的,先留意一下

  • closeMessage方法

  • 每个实例里面都会有handler对象,这个对象里面会有关闭的方法,留意一下

3.6 然后生成虚拟DOM并渲染真实DOM

  • MessageConstructor就是message组件

3.7 插入到指定的DOM中去

3.8 把当前message组件实例获取过来,并赋值给vm

  • 可以打印看一下vnode(其实就是vue的getCurrentInstance方法获取到的内容)

3.9 生成handler对象,并写入close方法

组件抛出部分:由于抛出的是整个ref对象,所以还保留着响应式

3.10 到这一步,就看到了组件实例存放的是什么了。有id,vnode,vm,handler,props

3.11 最后返回实例instance

4. 添加实例,并返回含有关闭方法的对象

message组件

  • 看一下组件结构(这里我把引入的饿了吗组件都去掉了,换成了span)
<template>
  <transition
    :name="ns.b('fade')"
    @before-leave="onClose"
    @after-leave="$emit('destroy')"
  >
    <div
      v-show="visible"
      :id="id"
      ref="messageRef"
      :class="[
        ns.b(),
        { [ns.m(type)]: type },
        ns.is('center', center),
        ns.is('closable', showClose),
        ns.is('plain', plain),
        customClass,
      ]"
      :style="customStyle"
      role="alert"
      @mouseenter="clearTimer"
      @mouseleave="startTimer"
    >
      <span v-if="repeatNum > 1" :class="ns.e('badge')">{{ repeatNum }} </span>
      <span v-if="iconComponent">【{{ iconComponent }}】</span>
      <slot>
        <p v-if="!dangerouslyUseHTMLString" :class="ns.e('content')">
          {{ message }}
        </p>
        <p v-else :class="ns.e('content')" v-html="message" />
      </slot>
      <span v-if="showClose" :class="ns.e('closeBtn')" @click.stop="close"
        >close
      </span>
    </div>
  </transition>
</template>
  • 最外层使用Transition组件包裹,可以对内部v-show的div应用过渡效果。name属性命名,可以通过此命名来进行过渡或者动画效果,可以看官网说明:Transition | Vue.js (vuejs.org)
  • 最主要的下面这两个事件,会在内部div隐藏的时候触发

  • 但是发现当前页面这个事件只有一个,那使用的就是props传来的了

  • onClose使用的是这里props传来的(上面有讲)

  • 然后接着看下面的div

  • 看下ns,是怎么来的

  • 然后看下useNamespace方法,这里我直接把elementPlus的粘过来了。有兴趣的可以研究下
import { computed, getCurrentInstance, inject, ref, unref } from 'vue'
 
import type { InjectionKey, Ref } from 'vue'
 
export const defaultNamespace = 'el'
const statePrefix = 'is-'
 
const _bem = (
  namespace: string,
  block: string,
  blockSuffix: string,
  element: string,
  modifier: string
) => {
  let cls = `${namespace}-${block}`
  if (blockSuffix) {
    cls += `-${blockSuffix}`
  }
  if (element) {
    cls += `__${element}`
  }
  if (modifier) {
    cls += `--${modifier}`
  }
  return cls
}
 
export const namespaceContextKey: InjectionKey<Ref<string | undefined>> =
  Symbol('namespaceContextKey')
 
export const useGetDerivedNamespace = (
  namespaceOverrides?: Ref<string | undefined>
) => {
  const derivedNamespace =
    namespaceOverrides ||
    (getCurrentInstance()
      ? inject(namespaceContextKey, ref(defaultNamespace))
      : ref(defaultNamespace))
  const namespace = computed(() => {
    return unref(derivedNamespace) || defaultNamespace
  })
  return namespace
}
 
export const useNamespace = (
  block: string,
  namespaceOverrides?: Ref<string | undefined>
) => {
  const namespace = useGetDerivedNamespace(namespaceOverrides)
  const b = (blockSuffix = '') =>
    _bem(namespace.value, block, blockSuffix, '', '')
  const e = (element?: string) =>
    element ? _bem(namespace.value, block, '', element, '') : ''
  const m = (modifier?: string) =>
    modifier ? _bem(namespace.value, block, '', '', modifier) : ''
  const be = (blockSuffix?: string, element?: string) =>
    blockSuffix && element
      ? _bem(namespace.value, block, blockSuffix, element, '')
      : ''
  const em = (element?: string, modifier?: string) =>
    element && modifier
      ? _bem(namespace.value, block, '', element, modifier)
      : ''
  const bm = (blockSuffix?: string, modifier?: string) =>
    blockSuffix && modifier
      ? _bem(namespace.value, block, blockSuffix, '', modifier)
      : ''
  const bem = (blockSuffix?: string, element?: string, modifier?: string) =>
    blockSuffix && element && modifier
      ? _bem(namespace.value, block, blockSuffix, element, modifier)
      : ''
  const is: {
    (name: string, state: boolean | undefined): string
    (name: string): string
  } = (name: string, ...args: [boolean | undefined] | []) => {
    const state = args.length >= 1 ? args[0]! : true
    return name && state ? `${statePrefix}${name}` : ''
  }
 
  // for css var
  // --el-xxx: value;
  const cssVar = (object: Record<string, string>) => {
    const styles: Record<string, string> = {}
    for (const key in object) {
      if (object[key]) {
        styles[`--${namespace.value}-${key}`] = object[key]
      }
    }
    return styles
  }
  // with block
  const cssVarBlock = (object: Record<string, string>) => {
    const styles: Record<string, string> = {}
    for (const key in object) {
      if (object[key]) {
        styles[`--${namespace.value}-${block}-${key}`] = object[key]
      }
    }
    return styles
  }
 
  const cssVarName = (name: string) => `--${namespace.value}-${name}`
  const cssVarBlockName = (name: string) =>
    `--${namespace.value}-${block}-${name}`
 
  return {
    namespace,
    b,
    e,
    m,
    be,
    em,
    bm,
    bem,
    is,
    // css
    cssVar,
    cssVarName,
    cssVarBlock,
    cssVarBlockName,
  }
}
 
export type UseNamespaceReturn = ReturnType<typeof useNamespace>
  • 根据传入的props属性,来进行对应类名的添加

  • 动态类名customStyle,主要是设置组件定位的top高度

getLastOffset方法

组件导出的bottom就是这项最终的top高度(组件本身的高度 + offset)

getOffsetOrSpace方法

这个bottom,如果是第一次添加message

不是第一次添加message

而元素的高度,又是怎么监听的呢?其实用到了useResizeObserver这个hook,页面一进来的时候,就会对指定的dom进行尺寸监听,只要尺寸变化了就会触发,第一次进来也会触发。这样就能得到元素最新的高度

然后接着往下看结构

一个是鼠标经过事件,一个是鼠标离开事件

  • 分别控制清理定时和开始定时
  • stopTimer有定义。开始定时的时候,会把结构出来的stop函数赋值给stopTimer

可以查看下useTimeoutFn的用法

useTimeoutFn | VueUse中文文档

这样的话,鼠标经过message,就会清理定时,鼠标离开message,就会重新开始计时

如果看下面逻辑的话,会发现它一进来就会调用计时,并显示message组件

然后如果鼠标不经过message,那么在设置时间过去后,就会调用close方法

组件v-show=false,就会关闭,就会触发Transition的这两个方法,并触发过渡动画效果

由于message组件是虚拟dom创建出来的,所以创建虚拟dom的props部分onClose和destory方法就会进行执行: 渲染函数 & JSX | Vue.js

执行onClose就会执行用户传入的onClose逻辑(如果用户传了的话),然后执行closeMessage方法。从实例数组中移除该项,并调用这项实例中关闭方法关闭(每项实例中都有handler对象,上面有讲)

执行onDestory方法,就是把message组件从结构中移除。onDestory执行时机要比onClose要晚一点

至此,一个mesage创建到销毁就执行完了

repeatNum部分

  • repeatNum是props传来的。message组件也对它进行了侦听

如果变化了(上面message方法的2.1有讲repeatNum,只要从实例数组中查到了同样的显示内容,repeatNum就会+1)。首先开始清理定时,然后重新开始定时

  • 结构部分,原先是el-badge组件,我给换成了span

其他部分

网盘地址

  • 把主要逻辑都写里面了,样式没加,可以自己加一下。原有组件进行了删减,zIndex没有处理,还有键盘esc事件

链接:百度网盘 请输入提取码

提取码:ynbc

  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 小程序WebView是一种框架,可以帮助开发者在小程序中嵌入网页。它允许开发者在小程序中使用HTML、CSS和JavaScript等Web技术,使用WebView可以轻松实现小程序的跨平台开发,从而实现更加优雅的用户体验。 ### 回答2: 小程序WebView是微信小程序中的一种组件,它能够在小程序中嵌入网页内容。通过使用WebView,开发者可以在小程序中展示网页、进行网页交互、加载HTML5页面等。 小程序WebView的使用非常简便。通过在小程序的页面配置文件中引入WebView组件,开发者可以在小程序的视图层中嵌入WebView,并通过指定URL属性来加载网页内容。加载的网页可以是远程的HTML页面或者本地的HTML文件。 小程序WebView有很多常用的属性可以调整,以满足不同的需求。比如,开发者可以设置WebView的URL属性,用于加载指定网页;可以设置WebView的用户代理属性,修改请求头信息;可以设置WebView的缩放属性,控制网页缩放效果等。 小程序WebView还提供了一些常用的方法,用于控制网页的加载和交互。通过调用WebView组件的方法,开发者可以加载指定URL的网页、重新加载网页内容、调用JavaScript操作网页元素等。并且,WebView还支持网页跳转监听、错误监听等事件,以及通过postMessage方法实现小程序与网页的数据交互。 总之,小程序WebView是一种非常重要的组件,它为小程序提供了更多的表现形式和功能扩展。开发者可以通过使用WebView,将网页内容无缝地嵌入到小程序中,为用户提供更加丰富多样的交互体验。同时,小程序WebView也需要注意合理使用,防止恶意网页的入侵和滥用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值