el-messageを模倣するためのElementuiソースコード学習

問題の説明

仕事でツールライブラリを使用することは非常に幸せで効率的ですが、ソースコードではあまり使用されない一部のAPIメソッドが使用されるため、ツールライブラリのソースコードを確認するために時間をかける必要があります。これらのAPIメソッドは、プログラミング能力を向上させることができます。将来、いくつかの要件をより適切に実現するために、独自のツールライブラリをカプセル化すると便利です。

需要分析

コンポーネントをカプセル化する前に、カプセル化するコンポーネントのアプリケーションシナリオと使用要件を検討し、これをブレークスルーとして使用して、コードロジックをより適切に実装する必要があります。

アプリケーションのシナリオと要件:メッセージプロンプト

メッセージは主に情報プロンプトであり、アプリケーションシナリオは、ユーザーが何らかの操作を実行したかどうか、成功したか失敗したかなどのインタラクティブなフィードバックであると思います。したがって、カプセル化されるコンポーネントには次の要件があることを定義できます。

  • 情報テキストメッセージパラメータを入力できる必要があります
  • メッセージ情報を必要とするタイプフィードバック(成功フィードバック、警告フィードバック、エラーフィードバック、通常の情報フィードバック)タイプパラメーター
  • プロンプトが表示されたら、デフォルトの消失時間パラメータを設定できます
  • マウスがホバリングしているときは、このメッセージプロンプトを保持し、タイマータイマーパラメータが消えないようにしてください
  • 小さなアイコンの種類やテキストが中央に配置されているかどうかのプロンプトなどのその他

Ele.me UIの公式コンポーネントを見ると、公式の考慮事項がまだ非常に詳細であり、多くの構成アイテムのオプションパラメーターが指定されていることがわかりますが、実際のパッケージコンポーネントでは、公式のもののような多くの構成項目を実行します。一般的に使用される構成項目を実装するだけで、最も実用的な機能を維持できます。

Eleme UIの公式elメッセージコンポーネント:element.eleme.cn /#/zh-CN/com…

レンダリング

1.gif

理解の仕方

この機能の効果について、個人的な提案は次のように理解できます。

  • まず、あまり使用されないAPIを確認します
  • 次に、コードのクローンを作成して実行します(githubコードリポジトリのアドレスは記事の最後に添付されています)
  • コメントと組み合わせると、すぐに理解できます

ナレッジポイントのレビュー:クラス配列の使用法と:styleの使用法

// html
<div
  :class="[
    'messageBox',   /* .messageBox这个类名确定要加到div这个标签上 */ 
    center ? 'horizontal' : '',   /* 是否给div标签加上.horizontal这个类名取决于center这个变量的值是否为true */
    typeArr.includes(type) ? type : '',   /* 是否给div这个标签加上type变量值的类名,取决于typeArr变量数组是否包含type的值 */
  ]"
  :style="controlTop"   /* 等价于:style={top: `12px`} 等价于 style="top: 12px" 即距离顶部top值为12像素 */
>
</div>

// js
data() {
    return {
      center: false, // 是否让水平文字居中,默认false
      type: "info", // 默认info类型
      typeArr: ["info", "success", "warning", "error"], // 总共4种类型
    };
},
computed: {
    controlTop() {
      return { top: `12px` };
    },
  },

なぜ:classの配列の使用法と:styleの使用法に言及するのですか?

この例では、4種類のスタイル(成功、警告、エラー、情報)をメッセージにバインドするには、以下を使用する必要があります。

用户多次点击触发message的出现,控制下一个message的位置在上一个的下方,就需要让不断的更改下一个message的top值;

知识点复习之transition过渡钩子函数

// html
<transition 
    v-on:before-enter="beforeEnter" /* 过渡出现进入之前 */ 
    v-on:enter="enter" /* 过渡出现进入 */ 
    v-on:after-enter="afterEnter" /* 过渡出现进入之后 */ 
    v-on:enter-cancelled="enterCancelled" /* 取消进入过渡 */
    v-on:before-leave="beforeLeave" /* 过渡消失离开之前 */
    v-on:leave="leave"  /* 过渡消失离开 */
    v-on:after-leave="afterLeave" /* 过渡消失离开之后 */
    v-on:leave-cancelled="leaveCancelled"  /* 取消过渡消失离开 */
>
    <!-- ... --> 
</transition>

// js
methods: {
    // ...
    afterLeave(){
        /* 本例中使用了这个钩子,当过渡消失的时候会触发这个钩子函数,
        我们可以在钩子函数中写一些js逻辑代码,进行相应操作 */ 
    }
    // ...

}

那么,什么时候,过渡消失,什么时候过渡出现?

最明显的就是,v-show由true改为false、由false改为true的时候,会自动触发transition的过渡钩子函数执行

本例中使用过渡钩子函数主要是因为,当消失一个message的时候,需要减少一个message的计数,所以需要通过这个钩子去进行js逻辑代码曹组欧

过渡钩子详见官方文档:cn.vuejs.org/v2/guide/tr…

知识点复习之vue销毁组件的方式

  1. 使用v-if官方推荐最好的方式
  2. 使用key 一般用的不是特别多
  3. this.$destroy(true) vue的1.x版本常常使用,从2.x版本就不支持了,相当于兼容写法
  4. 自己手动移除this.$el.parentNode.removeChild(this.$el); 本例使用之 关于第3点和第4点,尤大佬还亲自回答了关于这个问题的issues。简单截图如下:

snipaste_20220710_144810.png

issues地址如下:github.com/vuejs/vue/i…

为什么提到这个销毁dom方式呢?

因为我们使用v-show加上去transition控制message的隐藏和消失的,这个效果丝滑一些,没有使用v-if直接干掉dom。所以需要手动写代码,在过渡消失以后,当我们看不到message的时候,再偷偷的给message移除掉即可

完整代码

整体代码思路

  1. 搞一个message组件用于继承
  2. 使用Vue.extend继承这个组件形成一个构造器
  3. 定义一个函数,函数一执行,就使用构造器创建一个message显示,默认3秒自动消失
  4. 把这个函数挂载在原型上,并暴露出去,方便访问使用

关于Vue.extend继承不太熟悉的,可以先看看笔者的另外两篇文章哦

继承... juejin.cn/post/702172…

継承... juejin.cn/post/710854…

使用される.vueファイルコード

// html
<button @click="showMessage1">信息弹出</button>
<button @click="showMessage2">成功弹出</button>
<button @click="showMessage3">警告弹出</button>
<button @click="showMessage4">错误弹出</button>
<button @click="showMessage5">弹出5秒关闭</button>
<button @click="showMessage6">文字居中哦</button>
<button @click="showMessage7">引入使用</button>

// 一种是原型链使用方式,另一种是引入使用方式
import MyMessage from "@/components/index.js";
methods: {
showMessage1() {
  this.$myMessage({
    message: "信息弹出",
    type: "info",
  });
},
showMessage2() {
  this.$myMessage({
    message: "成功弹出",
    type: "success",
  });
},
showMessage3() {
  this.$myMessage({
    message: "警告弹出",
    type: "warning",
  });
},
showMessage4() {
  this.$myMessage({
    message: "错误弹出",
    type: "error",
  });
},
showMessage5() {
  this.$myMessage({
    message: "弹出5秒关闭",
  });
},
showMessage6() {
  this.$myMessage({
    message: "文字居中哦",
    center: true,
  });
},
showMessage7() {
  MyMessage({
    message: "引入使用",
    type: "success",
  });
},
},

プロトタイプにマウントされた継承を介してファイルコードを動的に作成するのは簡単

import Vue from 'vue';
import messageComponent from './src/index.vue' // 引入组件,方便继承
let MessageConstructor = Vue.extend(messageComponent); // 引入一个message构造器,方便new之

let instance = null // 定义组件实例
let count = 0 // 定义统计次数,便于知道创建多少个实例

const MyMessage = function (options) {
    if (options.duration & typeof options.duration !== 'number') { // 对于duration数字类型的校验
        console.error('Error! duration Must be a numeric type ') // 用户乱传递非数字类型参数,就抛错不执行后续代码
        return
    }
    count = count + 1 // MyMessage函数调用一次,统计次数加一个
    instance = new MessageConstructor({ // 实例化一个组件实例
        data: options, // data传参数,组件的data接收(即传递配置项)
        propsData: { // propsData传参,
            count: count, // 将统计的次数传递给子组件
            cutCount: cutCount // 传递一个函数,当MyMessage消失的时候,通知外界
        },
    });
    instance.$mount(); // 实例组件挂载
    document.body.appendChild(instance.$el); // 把这个组件实例的dom元素,追加到document文档中
    instance.isShowMyMessage = true; // 将组件的isShowMyMessage属性值置为true,即让实例出现,即消息出现
    return instance; // MyMessage函数执行一次,就会返回一个加工好的实例对象
}

function cutCount() { // 当message消失一个
    count = count - 1 // 就把外界统计的数量减少一个
    let messageBoxDomList = document.querySelectorAll('.messageBox') // 然后选中所有的messageDOM元素
    for (let i = 0; i < messageBoxDomList.length; i++) { // 遍历一下这个DOM伪数组
        let dom = messageBoxDomList[i] // 所有的都往上移动60像素
        dom.style['top'] = parseInt(dom.style['top']) - 60 + 'px'
    }
}

export default MyMessage // 暴露出去
Vue.prototype.$myMessage = MyMessage; // 挂载在vue原型上,方便this.$myMessage调用

継承のためのメッセージコンポーネントコード

<template>
  <transition name="message-fade" @after-leave="handleAfterLeave">
    <div
      :class="[
        'messageBox',
        center ? 'horizontal' : '',
        typeArr.includes(type) ? type : '',
      ]"
      :style="controlTop"
      v-show="isShowMyMessage"
      @mouseenter="clearTimerFn"
      @mouseleave="startTimerFn"
    >
      <span> {{ iconObj[type] }} {{ message }}</span>
    </div>
  </transition>
</template>

<script>
export default {
  name: "myMessage",
  props: {
    count: {
      // 统计次数
      type: Number,
      default: 1,
    },
    cutCount: {
      // dom消失通知外界函数
      type: Function,
    },
  },
  data() {
    return {
      isShowMyMessage: false, // v-show的标识布尔值
      message: "", // 提示的消息文字
      timer: null, // 用来清除的定时器
      duration: 3000, // 默认3秒消失
      center: false, // 是否让水平文字居中,默认false
      type: "info", // 默认info类型
      typeArr: ["info", "success", "warning", "error"], // 总共4种类型
      iconObj: {
        // 这里的对应图标,就以 红桃、黑桃、方块、梅花 为例吧
        info: "♥",
        success: "♠",
        warning: "♦",
        error: "♣",
      },
    };
  },
  computed: {
    controlTop() {
      return {
        // 距离顶部的位置,取决于创建了几个message
        top: `${12 + (this.count - 1) * 60}px`,
      };
    },
  },
  mounted() {
    this.startTimerFn(); // 开启定时器,默认3秒后销毁组件
  },
  methods: {
    // 开始定时器计时,要销毁dom元素
    startTimerFn() {
      // 时间大于0,才做计时消失隐藏
      if (this.duration > 0) {
        this.timer = setTimeout(() => {
          this.close(); // 达到计时时间,就隐藏这个notice
        }, this.duration);
      }
    },
    // 鼠标移入,清除定时器,使dom永远存在;鼠标移出,再重新计时准备移除dom
    clearTimerFn() {
      clearTimeout(this.timer);
    },
    // 过渡动画消失时,会执行此钩子函数,销毁组件,同时移除dom
    handleAfterLeave() {
      // 在移除一个dom之前,要先通知外界的计数count减去一个,并让余下的所有dom都往上移动,即更改位置
      this.cutCount();
      // 然后移除dom
      this.$destroy(true);
      this.$el.parentNode.removeChild(this.$el);
    },
    // 关闭隐藏dom
    close() {
      this.isShowMyMessage = false;
      /**
       * 注意当v-show为false的时候,会触发过渡动画消失钩子handleAfterLeave函数执行
       * 相当于在close函数中,执行了 this.handleAfterLeave()
       * */
    },
  },
};
</script>

<style lang="less" scoped>
// 默认样式
.messageBox {
  min-width: 320px;
  height: auto; // 高度由内容撑开
  padding: 16px; // 加上内边距
  border: 1px solid #e9e9e9;
  position: fixed; // 使用固定定位,使位置靠近顶部并居中
  top: 20px;
  left: 50%;
  transform: translateX(-50%); // 控制居中
  box-sizing: border-box;
  border-radius: 4px; // 加圆角好看一些
  background-color: #edf2fc;
  // 过渡效果
  transition: opacity 0.3s, transform 0.4s, top 0.4s;
  display: flex; // 开启弹性盒垂直居中
  align-items: center;
}
// 文字居中样式
.horizontal {
  justify-content: center;
}
// 成功提示样式
.success {
  color: #67c23a;
  background-color: #f0f9eb;
}
// 警告提示样式
.warning {
  color: #e6a23c;
  background-color: #fdf6ec;
}
// 错误提示样式
.error {
  color: #f56c6c;
  background-color: #fef0f0;
}
// 过渡效果样式
.message-fade-enter,
.message-fade-leave-active {
  opacity: 0;
  -webkit-transform: translate(-50%, -100%);
  transform: translate(-50%, -100%);
}
</style>

githubリポジトリコードアドレス

elementuiのソースコードは、コンポーネントを模倣することを学習します。準備が忙しくないときは、シリーズを書いてください。できるだけ多くのコメントを書きます。進歩し、みんなと一緒に成長します^ _ ^

githubアドレス:github.com/shuirongshu…

おすすめ

転載: juejin.im/post/7118640250049527815