Vue 常見 API 及問題

組件通信


父子組件通信通過 props$emit 相信小伙伴們都清楚,那么毫無關(guān)聯(lián)的兩個(gè)組件如果需要實(shí)現(xiàn)通信,又有哪些方法呢?相信小伙伴們可能第一時(shí)間想到了 vuex,但是如果只是簡(jiǎn)單的業(yè)務(wù)邏輯,vuex 的引入和維護(hù)就沒有那么必要~~~我百度了一下,大部分有說通過 使用 Vue事件總線(EventBus) 來進(jìn)行處理無關(guān)聯(lián)組件之間的通信,當(dāng)然也有說 vue 自身就有自定義事件 event.$on、event.$emit、event.$off 來進(jìn)行相關(guān)邏輯處理,其實(shí)兩者殊途同歸,用法差異不大,來看栗子:

  • 初始化 event
// event.js
import Vue from 'vue'
export default new Vue()
  • 組件 A,通過 event.$emit 發(fā)送事件
<template>
  <div class="index">
    <button @click="sendMsg()">按鈕</button>
  </div>
</template>

<script>
import event from './event'
export default {
  methods: {
    sendMsg() {
      event.$emit('aMsg', '來自A頁面的消息')
    }
  }
}
</script>
  • 組件 B,通過 event.$on 接收事件
<template>
  <div class="list">{{ msg }}</div>
</template>

<script>
import event from './event'
export default {
  data() {
    return {
      msg: ""
    }
  },
  methods: {
    addMsgFromA(msg) {
      this.msg = msg
      console.log(msg) // 來自A頁面的消息
    }
  },
  mounted() {
    // event.$on('aMsg', (msg) => {
    //   this.msg = msg
    //   console.log(msg)
    // })
    event.$on('aMsg', this.addMsgFromA)
  },
  beforeDestroy() {
    // 及時(shí)銷毀,否則可能造成內(nèi)存泄漏
    event.$off('aMsg', this.addMsgFromA)
  }
}
</script>

上述代碼中,特意將組件 B 中的 mounted 函數(shù)內(nèi)的方法進(jìn)行了封裝,而不是直接寫,是為了保證在頁面銷毀時(shí)對(duì)該方法進(jìn)行及時(shí)移除,我們也可以使用 event.$off('aMsg', this.addMsgFromA) 來移除應(yīng)用內(nèi)所有對(duì)此某個(gè)事件的監(jiān)聽?;蛘咧苯诱{(diào)用 event.$off() 來移除所有事件頻道,不需要添加任何參數(shù) 。

生命周期


Vue 的聲明周期基本可以分為三個(gè)階段:

  • 掛載階段
    beforeCreate、createdbeforeMount、mounted
  • 更新階段
    beforeUpdate、updated
  • 銷毀階段
    beforeDestroy、destroyed

createdmounted 的區(qū)別?

  • created 頁面還沒有開始渲染,但是頁面的實(shí)例已經(jīng)初始化完成(此時(shí)可以獲取到 datamethods 中的數(shù)據(jù),無法獲取 DOM 節(jié)點(diǎn));mounted 頁面完成渲染,此時(shí)組件在網(wǎng)頁上繪制完成。此時(shí)可獲取到 DOM 節(jié)點(diǎn)。

父子組件生命周期調(diào)用順序


寫一個(gè)簡(jiǎn)略版的 todoList ,查看父子組件的生命周期調(diào)用順序。

  • 父組件
<template>
  <div class="index">
    <Input @addTitle="addTitleHandler" />
    <List :list="list" @delete="deleteHandler" />
  </div>
</template>

<script>
import Input from './Input'
import List from './List'
export default {
  components: { Input, List },
  data() {
    return {
      list: [
        {
          id: 'id-1',
          title: '標(biāo)題1'
        },
        {
          id: 'id-2',
          title: '標(biāo)題2'
        }
      ]
    }
  },
  methods: {
    addTitleHandler(title) {
      this.list.push({
        id: `id-${Date.now()}`,
        title
      })
    },
    deleteHandler(id) {
      this.list = this.list.filter(item => item.id !== id)
    }
  },
  created() {
    console.log('index created')
  },
  mounted() {
    console.log('index mounted')
  },
  beforeUpdate() {
    console.log('index beforeUpdate')
  },
  updated() {
    console.log('index updated')
  },
  beforeDestroy() {
    console.log('index beforeDestroy')
  },
  destroyed() {
    console.log('index destroyed')
  }
}
</script>
  • 子組件 List
<template>
  <div class="list">
    <ul>
      <li v-for="item in list" :key="item.id">
        {{ item }}
        <button @click.stop="deleteItem(item.id)">刪除</button>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    list: {
      type: Array,
      default() {
        return []
      }
    }
  },
  methods: {
    deleteItem(id) {
      this.$emit('delete', id)
    }
  },
  created() {
    console.log('list created')
  },
  mounted() {
    console.log('list mounted')
  },
  beforeUpdate() {
    console.log('list beforeUpdate')
  },
  updated() {
    console.log('list updated')
  },
  beforeDestroy() {
    console.log('list beforeDestroy')
  },
  destroyed() {
    console.log('list destroyed')
  }
}
</script>
  • 子組件 Input
<template>
  <div>
    <input type="text" v-model="title" />
    <button @click="addTitle">add</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: ''
    }
  },
  methods: {
    addTitle() {
      this.$emit('addTitle', this.title)
      this.title = ''
    }
  }
}
</script>

掛載階段的執(zhí)行渲染結(jié)果為:index created => list created => list mounted => index mounted,基本可以說明:父子組件渲染的掛載階段是由外向內(nèi)在向外進(jìn)行執(zhí)行的。而更新階段渲染結(jié)果為:index beforeUpdate => list beforeUpdate => list updated => index updated,基本邏輯流程和掛載階段差不多。

$nextTick


  • Vue 是異步渲染

  • data 改變之后,DOM 不會(huì)立刻渲染

  • $nextTick 會(huì)在 DOM 渲染之后被觸發(fā),以獲取最新 DOM 節(jié)點(diǎn)

其實(shí)很好理解,因?yàn)?Vue 中所有 DOM 的渲染都是異步的,所以我們?cè)?DOM 渲染之后直接獲取 DOM 元素可能會(huì)有偏差,使用 $nextTick 可以保證所有異步渲染完成之后再來執(zhí)行,如下栗子:

<template>
  <div>
    <ul ref="ul1">
      <li v-for="(item, index) in list" :key="index">{{ item }}</li>
    </ul>
    <button @click="addItem">添加一項(xiàng)</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      list: ['a', 'b', 'c']
    }
  },
  methods: {
    addItem() {
      this.list.push(`${Date.now()}`)
      this.list.push(`${Date.now()}`)
      this.list.push(`${Date.now()}`)

      // 獲取 DOM 元素
      const ulElem = this.$refs.ul1
      console.log(ulElem.childNodes.length) // 3
      // 使用 $nextTick
      this.$nextTick(() => {
        const ulElem = this.$refs.ul1
        console.log(ulElem.childNodes.length) // 6
      })
    }
  }
}
</script>

slot


  • 基本使用 (父組件往子組件中插入一段內(nèi)容)
// 父組件
<template>
  <div id="app">
    <slot-demo :url="website.url">
      {{ website.title }}
    </slot-demo>
  </div>
</template>

<script>
import SlotDemo from './components/SlotDemo'
export default {
  name: "App",
  components: { SlotDemo },
  data() {
    return {
      name: '張三',
      website: {
        url: 'https://www.baidu.com',
        title: '百度',
        subTitle: '百度一下,你就知道'
      }
    }
  },
};
</script>

// 子組件
<template>
  <div>
    <a :href="url">
      <slot>默認(rèn)內(nèi)容,父組件沒設(shè)置內(nèi)容,我就會(huì)被顯示出來</slot>
    </a>
  </div>
</template>
<script>
export default {
  props: {
    url: {
      type: String,
      default() {
        return ''
      }
    }
  }
}
</script>
  • 作用域插槽 (子組件 data 中的數(shù)據(jù)傳遞給父組件)。子組件通過 :slotData 綁定要傳遞的 data 中的數(shù)據(jù),父組件通過 template 包裹定義 v-slot 接收,然后直接插值調(diào)用。如下栗子:
// 父組件
<div id="app">
  <slot-demo :url="website.url">
    <template v-slot="slotProps">
      {{ slotProps.slotData.title }}
    </template>
  </slot-demo>
</div>
<script>
data() {
  return {
    name: '張三',
    website: {
      url: 'https://www.baidu.com',
      title: '百度',
      subTitle: '百度一下,你就知道'
    }
  }
}
</script>
// 子組件
<div>
  <a :href="url">
    <slot :slotData="website">{{ website.subTitle }}</slot>
  </a>
</div>
<script>
data() {
  return {
    website: {
      url: 'https://www.qq.com',
      title: 'QQ',
      subTitle: '每一天,樂在溝通'
    }
  }
}
</script>
  • 具名插槽 (父組件通過 v-slot 與子組件 slot 中的 name 值進(jìn)行綁定)
// 父組件
<slot-demo>
  <template v-slot:header>
    <h1>這里的內(nèi)容會(huì)被插入到子組件的 header 中</h1>
  </template>
  <p>這里的內(nèi)容會(huì)被插入到子組件的 main 中</p>
  <template v-slot:footer>
    <p>這里的內(nèi)容會(huì)被插入到 footer slot 中</p>
  </template>
</slot-demo>
// 子組件
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

mixin


  • 多個(gè)組件有相同的邏輯,抽離出來

  • mixin 并不是完美的解決方案,會(huì)有一些問題

  • 變量來源不明確,不利于閱讀

  • mixin 可能會(huì)造成命名沖突

  • mixin 和組件可能會(huì)出現(xiàn)多對(duì)多的關(guān)系,復(fù)雜度較高

  • Vue 3 提出的 Composition API 旨在解決這些問題

當(dāng)然缺點(diǎn)固然很多,但是日常開發(fā)中使用 mixin 還是非常符合 真香定律 的。基礎(chǔ)用法:

<template>
  <div>
    <p>{{ name }} {{ major }} {{ city }}</p>
    <button @click="showName">顯示姓名</button>
  </div>
</template>

<script>
import myMixin from './mixin'
export default {
  mixins: [myMixin], // 可添加多個(gè),如[myMixin, myMixin1, myMixin2]
  data() {
    return {
      name: '張三',
      major: 'web 前端'
    }
  },
  methods: {},
  mounted() {
    console.log('mixin mounted', this.name)
  }
}
</script>
//mixin.js
export default {
  data() {
    return {
      city: '上海'
    }
  },
  methods: {
    showName() {
      console.log(this.name)
    }
  },
  mounted() {
    console.log('mixin.js mounted', this.name) // 先于 vue 模板中的 mounted 執(zhí)行
  }
}

cityshowName 我們都沒有在模板中直接定義,而是定義在 mixin.js 中,但是我們卻可以直接使用,這是因?yàn)?mixins 會(huì)將 mixin.js 中的內(nèi)容和我們 vue 模板中的內(nèi)容進(jìn)行融合,從而導(dǎo)致多個(gè)地方都可以直接使用 mixin.js 中的內(nèi)容。

動(dòng)態(tài)組件


  • :is = "component-name" 用法

  • 需要根據(jù)數(shù)據(jù),動(dòng)態(tài)渲染的場(chǎng)景。即組件類型不確定。

光說可能有點(diǎn)混,但是開發(fā)中還真的遇到過~~~舉個(gè)栗子:

<template>
  <div id="app">
    <!-- 使用 :is 和 data 中的值進(jìn)行綁定 -->
    <component :is="nextTickName"></component>
  </div>
</template>
<script>
import NextTick from './components/NextTick'
export default {
  name: "App",
  components: { NextTick }, // 此處還是要引入和注冊(cè)組件
  data() {
    return {
      nextTickName: NextTick // 將組件賦值到 data 中
    }
  },
};
</script>

有沒有感覺多此一舉,但是 vue 將其作為 API 獨(dú)立出來還是有一定意義的,開發(fā)中很多位置還是會(huì)用到的,像組件切換,Tab 等確實(shí)會(huì)用到,具體實(shí)用場(chǎng)景小伙伴可自行斟酌哦~~~

異步組件


  • import() 函數(shù)

  • 按需加載,異步加載大組件

感覺這個(gè)用法和 vue-router 差不多,具體看栗子一目了然:

<template>
  <div id="app">
    <next-tick></next-tick>
  </div>
</template>

<script>
export default {
  name: "App",
  components: {
    NextTick: () => import('./components/NextTick') // import 按需加載組件
  },
  data() {
    return {
    }
  },
};
</script>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容