iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 5
0
Modern Web

VueJS 從前端到後端系列 第 5

Component 魔術方法 Day 4

部落格同步刊登 [IT 鐵人賽] Component 魔術方法 Day 4

我們講完元件的應用之後,本次篇章我們會來說明一下,關於 importrequire 在元件當中的應用。這兩個方法對於現今的 JavaScript 來說,並不是什麼新奇的東西。而搭配 Vue 的元件又能玩出什麼新的把戲?讓我們繼續看下去。


require 方法

這個方法在一般的 JavaScript 當中很常見,主要目的就是為了將檔案讀取進來。而在 Vue 裡面,他不僅僅是可以引入 JavaScript 檔案,連同圖片檔案也可以利用這種方式來載入。只是圖片檔案利用 require 載入後,會變成以 base64 的方式存在於元件中。

https://ithelp.ithome.com.tw/upload/images/20190906/20001433ULQREVLukb.png

當然,以目前的潮流來說,使用 import 是比較常見的作法,但也不是說 require 這個方法不行。只是要記得,如果是使用 require 的話,最後面必須要加上 .default 才能讓元件可以使用。

那麼,在這邊使用 require 的方法有什麼好處嗎?

沒有(欸)。

有一些比較詭譎的作法,或許你會需要使用 require 的方法把 .vue 檔案給載入,例如說,我如果依照環境變數,來載入元件的時候,那麼使用 require 是個不錯的選擇。

<script>
let component

if (window.env.component !== '') {
  component = require(`@/component/${window.env.component}.vue`)
}

export default {
  name: 'App',
  components: {
    MyComponent: component
  }
}
</script>

我們底下先假裝一下我們有設定 winow.env.component 這件事 XD

https://ithelp.ithome.com.tw/upload/images/20190906/200014330IQouxBWPx.png

至於你說 import 不行嗎?不好意思,這種方法還真的是不行。對於 import 來說,後面是不能使用 `${...}` 這種魔術方法的。

https://ithelp.ithome.com.tw/upload/images/20190906/20001433NuxU4Nygoh.png

所以說,當你忘記設定 window.env 這個全域變數的時候,你的 App 就會壞掉囉~

https://ithelp.ithome.com.tw/upload/images/20190906/20001433xGzyhcP0aj.png

所以,總結來說,我們使用會 require 這個方法,有個考量,就是需要依照某些邏輯條件,來載入不同的元件的時候,我們就能利用 require 的方法來達成。而除了放在元件外面,你也可以放在 components 屬性裡面來載入,記得要給他一個名字。

https://ithelp.ithome.com.tw/upload/images/20190906/20001433Wy27DZvleM.png

使用的方式跟你放在外面差不多,不過這樣的方式你就不能做其他的判斷了。


import 方法

就如同剛剛提到的,import 應該是目前比較流行的方式。當然,他跟 require 不一樣的地方,除了不能使用魔術方法之外。另外,你也不能將 import 放在其他的 JavaScript 裡面,編譯過程中會報錯。

https://ithelp.ithome.com.tw/upload/images/20190906/200014330s2hFqjyGo.png

撇除掉這件事情之外,importrequire 不一樣的地方在於,當你在 components 裡面使用的時候,import 可以應用懶加載的方式來達成。也就是說 components 裡面可以利用函式的方式來載入元件。

<script>
export default {
  name: 'App',
  components: {
    HelloWorld: () => import('@/components/HelloWorld.vue')
  }
}
</script>

這一點是 require 所無法辦到的。有鑑於此,我們就可以在這邊多動一些手腳。

<script>
export default {
  name: 'App',
  components: {
    HelloWorld: () => {
      if (conditions) {
        return import('@/components/HelloWorld.vue')
      } else {
        return import('@/components/HelloKitty.vue')
      }
    }
  }
}
</script>

這樣的方式就很類似我們在 <script> 標籤開始的地方,使用邏輯判斷搭配 require 來載入元件。不過,基本上 import 依舊無法使用 `${...}` 這種魔術方法。


其他用途

題外話,剛剛提到了 require 可以載入圖片,那麼 import 當然也可以用來做其他的事情。例如說,你可以使用這兩個方法來載入 Mixins,

<script>
import MyMixins from '@/mixins/MyMixins'
let OtherMixins = require('@/mixins/OtherMixins').default

export default {
  name: 'MyComponent',
  mixins: [ MyMixins, OtherMixins ],
  data () {
    return {
      avatar: require('@/assets/images/avatar.jpg')
    }
  }
}
</script>

當然這好像只是 JavaScript 基本用法,讓我們回歸到元件本身。一般使用的情境下,其實對於所謂的 動態 元件載入這件事情,需求並不高。而之所以會想這麼做,除了專案架構本身可能有所需求之外,另一點大概就是 開發人員覺得爽 所以這麼做。

https://ithelp.ithome.com.tw/upload/images/20190906/20001433xRC8BitcQh.png


非同步載入

前面提到了 import 可以懶加載這件事,所以相對應的,我們就可以思考一下非同步載入的可行性。所以,我們這邊先 假設 有一個需求,讓我們來寫一點點 User Story 來描述一下:

  1. 小明第一次來到這個網站。
  2. 網站的上方有一個註冊跟登入的按鈕。
  3. 小明點了登入之後,出現了登入畫面。
  4. 但是小明想想好像沒有註冊過帳號,所以又點了註冊的按鈕。
  5. 出現了註冊的畫面,然後小明又按了登入按鈕。
  6. 他想起了他的帳號密碼登入了,然後網站上方出現了他的帳號資訊。
  7. 然後小明就關掉視窗了。

你看,多麼清晰明瞭毫無目的與建樹的使用者故事。這個故事告訴了我們以下的需求(才沒有!

  1. 沒登入的人,網站上方有「登入」以及「註冊」的按鈕。
  2. 登入之後,網站上方會出現「帳號資訊」的區塊。
  3. 先不要管「帳號資訊」是什麼,先做一版出來看看。

就這樣,我們得出了一個 相當明確 的需求量表,接著我們要進入 Sprint Plaining。

好了!這裡沒有 Sprint!

先試想一下我們可以怎麼做,關於「未登入」以及「已登入」這兩件事情。

  • 做一個「未登入」的元件 A。
  • 做一個「已登入」的元件 B。
  • 然後判斷登入狀態,如果登入就顯示 A,否則就顯示 B。

好的,那我們先把他寫在同一個元件內試試看,

<template>
  <header>
    <div class="user" v-if="isLogin">
      <img class="avatar" :src="userAvatar" :alt="userName"/>
      <p v-text="userName"></p>
    </div>
    <div class="not-login" v-else>
      <button type="button" @click="signin">登入</button>
      <button type="button" @click="signup">註冊</button>
    </div>
  </header>
</template>

<script>
export default {
  name: 'App',
  data () {
    return {
      isLogin: false
    },
    user: {
      name: ''
      avatar: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
    }
  },
  computed: {
    userAvatar () {
      if (this.isLogin) {
        return this.user.avatar
      }
      return 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
    },
    userName () {
      if (this.isLogin) {
        return this.user.name
      }
      return ''
    }
  },
  methods: {
    signin () {
      // 這裡執行一個登入的動作
      setTimeout(() => {
        this.isLogin = true
      }, 3000)
    },
    signup () {
      // 這裡執行一個註冊的動作
      setTimeout(() => {
        this.isLogin = true
      }, 3000)
    }
  }
}
</script>

這樣寫下來其實也沒什麼毛病。然後,我們把「未登入」以及「已登入」這兩件事情拆成兩個元件呢,他可能就會變成這個樣子:

<template>
  <header>
    <LoginUser v-if="isLogin" />
    <NotLoginUser v-else />
  </header>
</template>

<script>
import LoginUser from '@/components/LoginUser.vue'
import NotLoginUser from '@/components/NotLoginUser.vue'

export default {
  name: 'App',
  components: {
    LoginUser,
    NotLoginUser
  },
  data () {
    return {
      isLogin: false
    }
  }
}
</script>

這個時候你會發現,你的子元件 <LoginUser><NotLoginUser> 必須要想辦法跟父元件溝通,這樣才能去觸發 isLogin 來改變元件顯示的狀況。而元件溝通這件事情,我們後續還會有章節討論。假設,今天我們連元件溝通這件事情都不想做呢?

那麼,我們就找一個同事來幫我們做元件溝通就好了。

這裡用上非同步讀取的方式,其實有點過於獵奇。不過,為了衝一下字數,我們試著來實作看看,

<template>
  <header>
    <User />
  </header>
</template>

<script>
export default {
  name: 'App',
  components: {
    User: () => {
      if (this.isLogin) {
        return import('@/components/User.vue')
      } else {
        return import('@/components/Geusts.vue')
      }
    }
  },
  data () {
    return {
      isLogin: false
    }
  }
}
</script>

然後你會發現爆炸了!

https://ithelp.ithome.com.tw/upload/images/20190906/20001433VmeGKR7rP6.png

是的,你以為使用了 => 就覺得 this 一定會指向你的元件實體,在 components 裡面是不正確的!那麼你覺得這一個 this 會指向誰呢?我們把他 console.log 出來就會發現!

https://ithelp.ithome.com.tw/upload/images/20190906/20001433yoQS9xR1OK.png

這個 this 什麼都不是!

所以我們就無法採用動態載入了嗎?當然不是,除了 this 之外,你還可以放一個變數到外面去。當然,放到外面去有一個但書,你得確保你這個元件,這個變數,僅只有單一目的、單一功能,否則放在外面的變數,會因為封裝問題,造成元件重用時的污染。關於這個污染我們後續會聊到,簡單來說是個小坑。

<template>
  <header>
    <User />
  </header>
</template>

<script>
let userIsLogin = false

export default {
  name: 'App',
  components: {
    User: () => {
      if (userIsLogin) {
        return import('@/components/User.vue')
      } else {
        return import('@/components/Geusts.vue')
      }
    }
  },
  data () {
    return {
      isLogin: userIsLogin
    }
  }
}
</script>

雖然你拿到外面去了,但是,你還是得告訴父元件,到底這個使用者登入了沒。所以,元件之間的溝通還是勢必要存在。當然,你可以再往外拋給 window 這個全域變數,不過這樣做有點太過於沒節操了點。


說完一大堆 import 的部分,最後提一下關於他自身的事情。import 本身其實是可以支援 Promise 的操作的,也就是說,這兩種寫法是可以被接受的,

import('./a.js').then(...)

/* OR */

let a = await import('./a.js')

那麼有趣的事情來了,我可不可以在 components 裡面這樣做呢?答案是可以的!但是記得要把 .then 傳回來的東西 return 回去,不然你的元件會報錯。

<script>
export default {
  name: 'App',
  components: {
    User: () => {
      return import('@/components/User.vue').then(module => {
        return module
      })
    }
  }
}
</script>

https://ithelp.ithome.com.tw/upload/images/20190906/20001433G7zF6Tlg7e.png

當你把那個 module 印出來,你會發現他其實是你的子元件 Vue 的實例。

https://ithelp.ithome.com.tw/upload/images/20190906/200014330aIZwVdXhV.png

至於這樣可以做什麼事情,就留給大家一個想像的空間啦!

小結

動態載入的地方還有很多可以聊聊,就我們後續搭配到 Vue-Router 的時候再來談。


上一篇
Component 進階應用 Day 3
下一篇
Component 魔術方法 Day 5
系列文
VueJS 從前端到後端31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
kkdayy_55330
iT邦新手 5 級 ‧ 2020-02-04 14:39:12

大大您好

我有很認真學習,不過還是忍不住多看你的 iTerm2 幾眼,
請問那個時間、%進度怎麼設定呢?能夠分享嗎?謝謝!

我要留言

立即登入留言