Vue.js 3.0 筆記

1. ES6 重點

  1. 變數宣告:
    1. var 是宣告全域變數;
    2. let 是宣告只存活在 {} 中的變數;
    3. const 是是宣告常數(無法再修改,但是 [array]陣列及 {object} 物件可以塞值進去)
  2. 解構賦值 (Destructuring assignment) 語法是一種 JavaScript 運算式,可以把陣列或物件中的資料解開擷取成為獨立變數。
    const user = {
      name: "Tad",
      age: 48,
      address: "Tainan",
    };
    //解構(可以只抽出某幾個)
    const { name, age, address } = user;
    

    亦可直接將整個物件塞入:

    const user_data = {
      user,
      sex: "男",
    };
    

     

  3. 箭頭函式運算式(arrow function expression)擁有比函式運算式還簡短的語法。它沒有自己的 thisargumentssupernew.target 等語法。本函式運算式適用於非方法的函式,但不能被用作建構式(constructor)。
    //一般用法
    document.getElementById("aLink1").addEventListener("click", function () {
      console.log(this);
    });
    //因為沒有 this,所以要用 e 來抓取目前元件資訊
    document.getElementById("aLink2").addEventListener("click", (e) => {
      console.log(e.target);
    });

    設定常數才能用箭頭函式

    //原始寫法(加入參數預設值,避免錯誤)
    const Add = function(a=0, b=0) {
      return a + b
    }
    //箭頭函式
    const Add = (a=0, b=0) => {
      return a + b
    }
    //若只有返回可以更簡化
    const Add = (a=0, b=0) => a + b
    //呼叫函式用法
    add(1, 3)

    另一個例子(將數字陣列轉為文字)

    const ArrtoStr = (arr = []) => {
        const mapStr = arr.map((item) => item + '')
        return mapStr
    }
    
    console.log(ArrtoStr([1, 3]));

     

  4. ES module
    //tools.js
    const Add = (a, b) => {
      return a + b;
    };
    
    //一定要用 const 才能 export
    export Name = "Mike";
    export const Age = 12;
    //預設被匯出的
    export default Add;
    

    接收的部份:

    //一定要有 type="module"
    <script type="module">
      //預設匯出的不需 {},Add名稱可以任意改
      import Add, { Name, Age } from "./tools.js";
      console.log(Add(4, 2));
      console.log(Name);
      console.log(Age);
    </script>

     

  5.  

Vue.js 3.0 筆記

1-1 Array 陣列存取

方法 描述
concat() 連接兩個或更多的數組,並返回結果。
join() 把數組的所有元素放入一個字符串。元素通過指定的分隔符進行分隔。
pop() 刪除並返回數組的最後一個元素
push(新元素, [新元素, ...]) 向數組的末尾添加一個或更多元素,並返回新的長度。
reverse() 顛倒數組中元素的順序。
shift() 刪除並返回數組的第一個元素
slice() 從某個已有的數組返回選定的元素
sort() 對數組的元素進行排序
splice(開始索引, 數量, [新元素, ...]) 刪除元素,並向數組添加新元素。
toSource() 返回該對象的源代碼。
toString() 把數組轉換為字符串,並返回結果。
toLocaleString() 把數組轉換為本地數組,並返回結果。
unshift() 向數組的開頭添加一個或更多元素,並返回新的長度。
valueOf() 返回數組對象的原始值

Vue.js 3.0 筆記

1-2 String 對象方法

方法 描述
anchor() 創建 HTML 錨。
big() 用大號字體顯示字符串。
blink() 顯示閃動字符串。
bold() 使用粗體顯示字符串。
charAt() 返回在指定位置的字符。
charCodeAt() 返回在指定的位置的字符的 Unicode 編碼。
concat() 連接字符串。
fixed() 以打字機文本顯示字符串。
fontcolor() 使用指定的顏色來顯示字符串。
fontsize() 使用指定的尺寸來顯示字符串。
fromCharCode() 從字符編碼創建一個字符串。
indexOf() 檢索字符串。
italics() 使用斜體顯示字符串。
lastIndexOf() 從後向前搜索字符串。
link() 將字符串顯示為鏈接。
localeCompare() 用本地特定的順序來比較兩個字符串。
match() 找到一個或多個正則表達式的匹配。
replace() 替換與正則表達式匹配的子串。
search() 檢索與正則表達式相匹配的值。
slice() 提取字符串的片斷,並在新的字符串中返回被提取的部分。
small() 使用小字號來顯示字符串。
split(分隔符號[, 返回的數組的最大長度]) 把字符串分割為字符串數組。
strike() 使用刪除線來顯示字符串。
sub() 把字符串顯示為下標。
substr(起始[, 長度]) 從起始索引號提取字符串中指定數目的字符。
substring() 提取字符串中兩個指定的索引號之間的字符。
sup() 把字符串顯示為上標。
toLocaleLowerCase() 把字符串轉換為小寫。
toLocaleUpperCase() 把字符串轉換為大寫。
toLowerCase() 把字符串轉換為小寫。
toUpperCase() 把字符串轉換為大寫。
toSource() 代表對象的源代碼。
toString() 返回字符串。
valueOf() 返回某個字符串對象的原始值。

Vue.js 3.0 筆記

1-3 setTimeout 倒數計時

  1. 要使用 setTimeout(),必須在卸載時去 clearTimeout()才行
    <script>
    export default {
      setup() {
        let timer = null;
    
        onMounted(() => {
          timer = setTimeout(() => {
            router.go(-1);
          }, 3000);
        });
    
        onUnmounted(() => {
          clearTimeout(timer);
        });
      },
    };
    </script>

     

  2.  

Vue.js 3.0 筆記

2. Vue 入門

  1. 基本架構(只有被 mount 的元件才能被Vue控制,以外的元件不行)
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Vue3 基本頁面</title>
    </head>
    
    <body>
        <div id="app"></div>
        <script src="https://unpkg.com/vue@next"></script>
        <script>
            const App = {
                setup() {
                    return {};
                },
            };
            Vue.createApp(App).mount("#app");
        </script>
    </body>
    
    </html>

 

Vue.js 3.0 筆記

2-1 響應式資料 ref、reactive及 v-model 雙向綁定

  1. 響應式資料 ref 用法,修改其值必須用 變數.value 才行,可以接受任何型態的資料,但是不會對物件或陣列內部的屬性變動做監聽。
  2. 響應式資料 reactive 用法,只能用[陣列]{物件}。可以做深層的監聽,以及訪問資料不需要 .value。
  3. 利用 v-model 可以做到雙向綁定(修改input,h1內容也會修改)。
    <div id="app">
        <h1>{{refMsg}}</h1>
        <input type="text" v-model="refMsg">
        <h1>{{reactiveMsg.Text}}</h1>
        <input type="text" v-model="reactiveMsg.Text">
    </div>
    <script src="./js/vue.js"></script>
    <script>
        const {
            ref,
            reactive
        } = Vue;
        const App = {
            setup() {
                const refMsg = ref("Hello Vue ref!");
                const reactiveMsg = reactive({
                    Text: "Hello Vue reactive!"
                });
                setTimeout(() => {
                    refMsg.value = "Hi ref!"
                    reactiveMsg.Text = "Hi reactive!"
                }, 3000);
                return {
                    refMsg,
                    reactiveMsg,
                };
            },
        };
        Vue.createApp(App).mount("#app");
    </script>

 

完整範例

Vue.js 3.0 筆記

2-2 v-on 事件綁定及readonly 唯讀

  1. 可以利用 v-on:事件="函式" 來進行事件綁定,簡寫:@事件="函式"
  2. readonly 讓該變數值無法做任何調整

    <div id="app">
      <h1>一般:{{Num}}</h1>
      <button v-on:click="addFn">新增</button>
      <button v-on:click="removeFn">減少</button>
      <h1>唯讀:{{roNum}}</h1>
      <button @click="addRoFn">新增</button>
      <button @click="removeRoFn">減少</button>
    </div>
    <script src="./js/vue.js"></script>
    <script>
      const { ref, readonly } = Vue;
      const App = {
        setup() {
          const Num = ref(0);
          const roNum = readonly(Num);
          const addFn = () => {
            Num.value++;
          };
          const removeFn = () => {
            Num.value--;
          };
          const addRoFn = () => {
            roNum.value++;
          };
          const removeRoFn = () => {
            roNum.value--;
          };
          return {
            Num,
            roNum,
            addFn,
            removeFn,
            addRoFn,
            removeRoFn,
          };
        },
      };
      Vue.createApp(App).mount("#app");
    </script>
    

 

完整範例

Vue.js 3.0 筆記

2-3 v-for 迴圈、v-bind 屬性綁定、v-show及v-if、computed

  1. v-for="(變數, 第幾個) in 陣列" 迴圈,務必用 v-bind:key="唯一值" 綁定 key(可簡寫成 :key),避免每次改值都要重新渲染整個迴圈,太耗資源
    v-for="(新變數, 第幾個) in 陣列" :key="唯一值" 

     

  2. v-bind:屬性可綁定任何屬性,簡寫為 :屬性 :class
  3. <ul class="box" :class="{open: isOpen}"> 可簡寫成 <ul :class="['box', {open: isOpen}]">
  4. computed是一個函式,用來進行計算或資料重組,其結果會緩存,例如下例中的計算 BoxHeight 高度。
  5. 若需要計算的值需要傳入參數,則建議改用自訂函式,否則都建議用computed
  6. 只要是透過 computed 計算包裝過的 reactive 物件,都要用.value來取得 computed 中的資料
  7. filter() 方法會建立一個經指定之函式運算後,由原陣列中通過該函式檢驗之元素所構成的新陣列。如:listArr.filter((item) => item.show).length
  8. v-show 只是用CSS將元件隱藏(資源耗損較小)
  9. v-if 可真的隱藏動元素(資源耗損較高)
  10. v-if v-for 不能同時使用,v-if 的執行順序比 v-for 高。
<script>
  const { ref, reactive, computed } = Vue;
  const App = {
    setup() {
      const isOpen = ref(true);
      const listArr = reactive([
        { name: "項目A", show: true, css: "red" },
        { name: "項目B", show: false, css: "red" },
        { name: "項目C", show: true, css: "blue" },
        { name: "項目D", show: true, css: "red" },
        { name: "項目E", show: false, css: "red" },
        { name: "項目F", show: true, css: "blue" },
        { name: "項目G", show: true, css: "blue" },
        { name: "項目H", show: true, css: "blue" },
        { name: "項目I", show: true, css: "red" },
      ]);

      const ItemArr = computed(() => {
        return listArr.filter((item) => item.show === true);
      });

      const BoxHeight = computed(() => {
        return isOpen.value ? `${ItemArr.value.length * 40}px` : "0px";
      });

      const HandListShow = () => {
        isOpen.value = !isOpen.value;
      };
      return {
        listArr,
        HandListShow,
        isOpen,
        BoxHeight,
      };
    },
  };
  Vue.createApp(App).mount("#app");
</script>

完整範例

Vue.js 3.0 筆記

2-4 watch 及 watchEffect 監聽

  1. watch用來監聽某個值
    watch(被監聽變數, (新值, 舊值) => {
      要做的事...
    });

     

    1. 若是監聽 ref 的值,那就直接監聽該值即可
    2. 若是監聽 ref 物件的值,那要針對其中的資料,並將之改成 getter 可讀取的值,也就是 () => refObj.value.idx 這種方式
    3. 若是監聽 ref 整個物件,那要加入第三個參數來做深層監控,也就是 {deep: true} 才行,無法取得舊值
    4. 若是監聽 reactive 物件的值,那要針對其中的資料,並將之改成 getter 可讀取的值,也就是 () => reactiveObj.idx 這種方式
    5. 若是監聽 reactive 整個物件,無法取得舊值
  2. watchEffect(()=>{})用來監聽,且不須傳入欲監聽參數,只要在{}中直接使用參數值,就會自動監聽
    watchEffect(() => {
      console.log(num.value);
    });

     

  3. 例如:
    <script>
    const { ref, reactive, watch, watchEffect } = Vue;
    const App = {
      setup() {
        const num = ref(0);
        const refObj = ref({ idx: 0 });
        const reactiveObj = reactive({ idx: 0 });
        let timer = null;
    
        watch(num, (newNum, oldNum) => {
          console.log(
            "ref 資料" + num.value + " 的監控",
            "新:" + newNum + ",舊:" + oldNum
          );
        });
    
        watch(
          () => refObj.value.idx,
          (newNum, oldNum) => {
            console.log(
              "ref 單一物件 getter=" + refObj.value.idx + " 的監控",
              "新:" + newNum + ",舊:" + oldNum
            );
          }
        );
    
        watch(
          refObj,
          (newNum, oldNum) => {
            console.log(
              "ref 整個物件=" + refObj.value.idx + " 的深層監控",
              "新:" + newNum.idx + ",舊:" + oldNum.idx
            );
          },
          {
            deep: true,
          }
        );
    
        watch(
          () => reactiveObj.idx,
          (newIdx, oldIdx) => {
            console.log(
              "reactive 單一物件 getter=" + reactiveObj.idx + " 的監控",
              "新:" + newIdx + ",舊:" + oldIdx
            );
          }
        );
    
        watch(reactiveObj, (newObj, oldObj) => {
          console.log(
            "reactive 整個物件=" + reactiveObj.idx + " 的監控",
            "新:" + newObj.idx + ",舊:" + oldObj.idx
          );
        });
    
        watchEffect(() => {
          console.log("watchEffect 監控 ref 資料 = " + num.value);
          console.log("watchEffect 監控 ref 物件 = " + refObj.value.idx);
          console.log("watchEffect 監控 reactive 物件 = " + reactiveObj.idx);
        });
    
        timer = setInterval(() => {
          console.log("-------");
          num.value++;
          refObj.value.idx++;
          reactiveObj.idx++;
          if (num.value > 4) {
            clearInterval(timer);
          }
        }, 2000);
    
        return {};
      },
    };
    
    Vue.createApp(App).mount("#app");
    </script>
    

     

完整範例:

Vue.js 3.0 筆記

2-5 常用生命週期

https://vue3js.cn/docs/zh/api/options-lifecycle-hooks.html#beforecreate

Vue.js 3.0 筆記

2-6 事件修飾符

Vue.js 3.0 筆記

3. axios 存取資料

  1. 中文手冊:https://juejin.cn/post/6844903919907258382#articleHeader19
  2. 安裝
    npm install --save axios vue-axios 

     

  3. 一般會等動元素都渲染完成才去執行api,以免資料抓回來沒地方塞,也就是 onMounted(()=>{}) 中去執行api
    <div id="app">
      <ul class="box" v-show="isLoad">
        <li v-for="(news, i) in allNews.data" :key="news.nsn">
          {{i + 1}}. <a :href="news.url">{{news.news_title}}</a>
        </li>
      </ul>
      <img v-show="!isLoad" class="load" src="https://i.giphy.com/media/52qtwCtj9OLTi/giphy.webp" alt="" />
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.20.0/axios.min.js"></script>
    <script src="https://unpkg.com/vue@next"></script>
    <script>
      const { ref, reactive, onMounted } = Vue;
      const app = {
        setup() {
          const allNews = reactive({ data: [] });
          const isLoad = ref(false);
    
          onMounted(() => {
            axios.defaults.baseURL = "https://www.lces.tn.edu.tw";
            axios
              .get("/modules/tadnews/app_api.php?op=list_all_news")
              .then((res) => {
                allNews.data = res.data;
                isLoad.value = true;
              })
              .catch((error) => {
                console.dir(error);
              });
          });
    
          return {
            allNews,
            isLoad,
          };
        },
      };
      Vue.createApp(app).mount("#app");
    </script>

     

  4.  

完整範例:

Vue.js 3.0 筆記

4. 安裝 node.js 及 vue-cli

  1. NVM 是用來管理 node.js 版本的工具: https://github.com/coreybutler/nvm-windows
    1. nvm -v:目前該nvm版本
    2. nvm list:列出目前電腦有安裝的nodejs版本
    3. nvm list available:目前網路上可用的nodejs版本列表
    4. nvm install v12.19.0:該nodejs版本下載安裝
    5. nvm uninstall v12.19.0:移除該nodejs版本
    6. nvm use v12.19.0:使用該nodejs版本
  2. 安裝完 node.js 就會有 npm(node.js套件管理工具)
    1. npm -v:目前npm的版本
    2. npm init:新增 package.json
    3. npm install [套件名稱]:安裝 NPM 套件
    4. npm install [套件名稱] -g:安裝全域 NPM 套件(C:\Users\[使用者名稱]\AppData\Roaming\npm\node_modules)
    5. npm install [套件名稱] -S:安裝套件並寫入 package.json 的 dependencies(-S 等同 --save
    6. npm install [套件名稱] -D:安裝套件並寫入 package.json 的 devDependencies(-D 等同 --save-dev
    7. npm uninstall [套件名稱]:移除 NPM 套件
    8. npm uninstall [套件名稱] -g:移除全域 NPM 套件
    9. npm list:顯示已套件列表
    10. npm install:還原套件
  3. 安裝 vue-cli
    npm install -g @vue/cli

    檢查版本

    vue --version

    更新 vue-cli

    npm update -g @vue/cli

     

  4. 建立 vue-cli 專案(記得先切換到欲儲存該專案的位置)
    vue create 專案名稱

    亦可用圖形界面:

    vue ui

    啟動專案

    cd 專案名稱
    npm run serve

    node_modules :就是我們透過npm下載下來的套件跟工具都會放在這個資料夾裡面。
    package.json:關於這整包專案所有的資訊,包含我們安裝的套件版本,專案版本,npm指令都可以在這個json檔案裡面找得到,之後要搬移專案重新安裝套件也需要靠這個json檔案(裡面的 script 就是給 npm run 用的)
    package-lock.json:package-lock.json是npm5版本新增的,是專門紀錄package.json裡面更細節的內容,例如安裝的套件的詳細版本,或是確認你的dependency (依賴)是被哪個函式庫所要求的等等,不過這個我們通常就放著不太會管它。

Vue.js 3.0 筆記

4-1 Component 組件開發

  1. main.js:程式的進入點
    import { createApp } from 'vue'
    import App2 from './App2.vue'
    import router from './router'
    import '@/assets/css/reset.css'
    
    createApp(App2).use(router).mount('#app')
    1. 其中 App2.vue 就是一個 Component 組件
    2. import { createApp } from 'vue' 若有{}表示是從組建中拆分出來的
    3. import App2 from './App2.vue' 若沒有表示是該組件預設匯出的(一般和檔名一致)
    4. 亦可 import 共用的 css 檔 @ 來代表以組件的 src 目錄為起點
  2. Component 的基本架構(可放在 components 目錄下,檔案字首大寫 )
    <script>
    import { ref } from "vue";
    // import some from "@/components/some.vue";
    export default {
      props: {},
      emits: {},
      components: {},
      setup(props, {emit}) {
        return {};
      },
    };
    </script>
    
    <template>
    </template>
    
    <style lang="scss" scoped>
    </style>
    1. export default 組件名稱{} 若是沒寫名稱,預設就是檔名
    2. import { ref } from "vue"; 中的 {ref} 表示從 vue 組建中拆分出 ref 函式
    3. return { isOpen, HandOpenMenu }; 中放常數、函式等,以便讓 <template> 樣板中的 {{文字樣板}}v-bindv-modelv-ifv-on...等修飾符使用
    4. <style lang="scss" scoped> 代表要用 scss 預編譯器,且樣式設定只限定在此組件中
  3. 引入組件
    <script>
    import Header from "@/components/Header.vue";
    import Footer from "@/components/Footer.vue";
    export default {
      components: {
        Header,
        Footer,
      },
    };
    </script>
    
    <template>
      <div>
        <Header></Header>
        <Footer></Footer>
      </div>
    </template>
    <style lang="scss">
    * {
      margin: 0;
      padding: 0;
      background-image: url("~@/assets/images/rightbtn2.jpg");
    }
    </style>
    
    1. import Header from "@/components/Header.vue"; 中用 import 來引入 Header 表示組件預設匯出的內容,所以無須放到 {} 中,路徑的 @ 來代表以組件的 src 目錄為起點(若是放在<style>中要引入素材的話,要用 ~@ 來代表 src 目錄為起點)
    2. components:{ Header } 中的 Header 表示要放到 <template> 樣板中去使用的組件名稱,可用<Header></Header><Header />來呈現
  4. SCSS用法:
    1. 練習網站:https://www.sassmeister.com/
    2. 用法:https://blog.techbridge.cc/2017/06/30/sass-scss-tutorial-introduction/

Vue.js 3.0 筆記

4-2 scss 用法

  1. 練習網站:https://www.sassmeister.com/
  2. 用法:https://blog.techbridge.cc/2017/06/30/sass-scss-tutorial-introduction/

Vue.js 3.0 筆記

5. 父組件→子組件間的參數傳遞(props)

  1. 父組件:利用 <PropsTest :msg="data" /> 將參數傳到 PropsTest 組件
    <script>
    import PropsTestfrom "@/components/PropsTest.vue";
    import { ref } from "vue";
    
    export default {
      components: {
        PropsTest,
      },
      setup() {
        const data= ref('我要傳到子組件');
        return { data };
      },
    };
    </script>
    
    <template>
      <PropsTest :msg="data" />
    </template>
  2. 子組件:在組建中定義 props 物件,並傳入 setup() 中,再 return 給樣板中呼叫使用
    <script>
    export default {
      props:{
        msg:{
          type: String,
          default: '我是預設文字'
        }
      },
      setup(props) {
        return { props };
      },
    };
    </script>
    
    <template>
      <h1>{{ props.msg }}</h1>
    </template>
  3. 定義 props 有以下方法:(注意,若型別是 [陣列] {物件},要用函式的方式去設定預設值,{物件} 更必須 return 一個預設的物件才行)
    props: {
        // 基礎的類型檢查 (`null` 和 `undefined` 會通過任何類型驗證)
        propA: Number,
    
        // 多個可能的類型
        propB: [String, Number],
    
        // 必填的字符串
        propC: {
            type: String,
            required: true
        },
    
        // 帶有默認值的數字
        propD: {
            type: Number,
            default: 100
        },
    
        // 帶有默認值的陣列
        propE: {
            type: Array,
            // 對象或數組默認值必須從一個工廠函數獲取
            default: () => []
        },
    
        // 帶有默認值的對象
        propF: {
            type: Object,
            // 對象或數組默認值必須從一個工廠函數獲取
            default: ()=> ({})
        },
    
        // 具有默認值的函數
        propG: {
            type: Function,
            // 與對象或數組默認值不同,這不是一個工廠函數 —— 這是一個用作默認值的函數
            default: ()=> {}
        },
    
        // 自定義驗證函數
        propH: {
            validator: function(value) {
                // 這個值必須匹配下列字符串中的一個
                return ['success', 'warning', 'danger'].indexOf(value) !== -1
            }
        }
    }

     

  4.  若是有參數要在兩個子組件中傳遞(或共用),可以將該參數設到父組件中,然後再由父組件傳遞給個別子組件。

Vue.js 3.0 筆記

5-1 子組件→父組件間的參數傳遞(emits)

  1. 子組件:倒數計時組件,預設5秒,0秒時停止
    <script>
    import { onMounted, ref } from "vue";
    
    export default {
      emits: {
        TimeOut: (num) => {
          return num.value === 0;
        },
      },
      setup(props, { emit }) {
        const num = ref(5);
        let timer = null;
        onMounted(() => {
          timer = setInterval(() => {
            num.value--;
            if (num.value === 0) {
              clearInterval(timer);
              emit("TimeOut", num);
            }
          }, 1000);
        });
        return {
          num,
        };
      },
    };
    </script>
    
    <template>
      <h1>{{ num }}</h1>
    </template>
    
    <style lang="scss" scoped>
    </style>
    1. 可以先在 emits:{} 中定義要傳什麼東西到父組件(沒設也行),也就是要送到父組件的函式,可以在裡面做參數的驗證。
    2. setup(props, { emit }) {} 中從 context 解構出 emit 功能,並在掛載後將 TimeOut 函數及 num 值透過 emit 傳送到父組件
  2. 父組件
    <script>
    import TimerBox from "@/components/TimerBox.vue";
    
    export default {
      components: {
        TimerBox,
      },
      setup() {
        const handleTimeOut = (num) => {
          if (num.value === 0) {
            console.log("時間到:", num.value);
          }
        };
        return {
          handleTimeOut,
        };
      },
    };
    </script>
    
    <template>
      <TimerBox @TimeOut="handleTimeOut" />
    </template>
    
    <style>
    </style>
    
    1. 當子組件將 TimeOut 函數及 numemit 到父組件時,就會觸發 <TimerBox @TimeOut ="handleTimeOut" /> 中的 @TimeOut事件,同時會執行 handleTimeOut 函式
    2. 父組件要在 setup(){} 中定義好 handleTimeOut 函式的內容並 return 出來

實際範例:

Vue.js 3.0 筆記

6. transition 動畫

  1. 範例(利用<transition name="tad"></transition>來勾住 組件中進入和離開 DOM )
    <script>
    import { ref } from "vue";
    export default {
      setup() {
        const isAmin = ref(false);
        const goAmin = () => {
          isAmin.value = !isAmin.value;
        };
        return { isAmin, goAmin };
      },
    };
    </script>
    
    <template>
      <button @click="goAmin">click</button>
      <transition name="tad">
        <div id="box" v-if="isAmin"></div>
      </transition>
    </template>
    
    <style>
    .tad-enter-active,
    .tad-leave-active {
      transition: opacity 1s ease;
    }
    
    .tad-enter-from,
    .tad-leave-to {
      opacity: 0;
    }
    #box {
      width: 100px;
      height: 100px;
      background-color: red;
    }
    </style>
    

     

  2. 動畫名稱會對應到 style中的 名稱
    <transition name="名稱"></transition> 

    在 style中有六種狀態(名稱規則是固定的)

    .名稱-enter-active {} /* 整個進入動畫期間的狀態 */
    .名稱-enter-from {} /* 進入動畫從 */
    .名稱-enter-to {} /* 進入動畫止 */
    
    .名稱-leave-active {} /* 整個離開動畫期間的狀態 */
    .名稱-leave-from {} /* 離開動畫從 */
    .名稱-leave-to {} /* 離開動畫止 */

完整範例:

Vue.js 3.0 筆記

6-1 transition + keyframes

  1. 範例:
    <script>
    import { ref } from "vue";
    export default {
      setup() {
        const isAmin = ref(false);
        const goAmin = () => {
          isAmin.value = !isAmin.value;
        };
        return { isAmin, goAmin };
      },
    };
    </script>
    
    <template>
      <button @click="goAmin">click</button>
      <transition name="tad">
        <div id="box" v-if="isAmin"></div>
      </transition>
    </template>
    
    <style>
    .tad-enter-active {
      animation: tad-in 0.5s;
    }
    .tad-leave-active {
      animation: tad-in 0.5s reverse;
    }
    @keyframes tad-in {
      0% {
        transform: scale(0);
      }
      50% {
        transform: scale(1.25);
      }
      100% {
        transform: scale(1);
      }
    }
    
    #box {
      width: 100px;
      height: 100px;
      background-color: rgb(109, 148, 111);
    }
    </style>
    

     

  2. reverse 是反轉之意

範例內容:

Vue.js 3.0 筆記

7. 點擊時,利用 $event 取得點擊事件及參數

  1. 父元件 App.vue
    <script>
    import EventBack from "@/components/EventBack.vue";
    
    export default {
      components: {
        EventBack,
      },
      setup() {
        const handleEventBack = (n, m, e) => {
          console.log(n);
          console.log(m);
          console.log(e);
          console.log(e.target);
        };
    
        return { handleEventBack };
      },
    };
    </script>
    
    <template>
      <EventBack @click="handleEventBack(100, 'test', $event)" />
    </template>
    
    <style lang="scss">
    </style>
    
    1. <EventBack @click="handleEventBack(100, 'test', $event)" /> 利用$event可以取得點擊的所有事件
    2. 前面可以接一堆參數,$event一定要放在最後面
    3. 收到時,$event 就有包含所有資訊
    4. $event.target 就是點擊的實體本身
  2. 子元件:@/components/EventBack.vue
    <script>
    export default {
      setup() {
      },
    };
    </script>
    
    <template>
      <a href="javascript:;">點我</a>
    </template>
    
    <style></style>

     

Vue.js 3.0 筆記

8. Template Refs 模板引用

  1. 子元件:@/components/TemplateRef.vue
    <script>
    import { onMounted, ref } from "vue";
    export default {
      setup() {
        const txtInput = ref(null);
        onMounted(() => {
          txtInput.value.focus();
        });
        const num = ref(12345);
        return { txtInput, num };
      },
    };
    </script>
    
    <template>
      <input v-model="num" ref="txtInput" type="text" />
    </template>
    
    <style>
    </style>

     

  2. 設定一個 const txtInput = ref(null),然後在樣板中用 ref="txtInput" 綁定即可
  3. 如此,在 setup 裡面就可以取得被綁定的動元素,並做一些動作,例如取得焦點:txtInput.value.focus();

完整範例:

Vue.js 3.0 筆記

8-1 自定義模板語法 v-xx

  1. 可以自己定義 v-xxx 的模板語法(不需要再用 ref="xxx" 去綁定)
  2. 先修改 main.js
    import { createApp } from 'vue'
    import { numPrice } from './lib/tools'
    import App from './App.vue'
    
    const app = createApp(App)
    app.directive('focus', {
        mounted(el) {
            el.focus()
        }
    })
    
    app.directive('money', {
        mounted(el, binding) {
            const p = numPrice(binding.value)
            el.innerHTML = p
        },
        updated(el, binding) {
            const p = numPrice(binding.value)
            el.innerHTML = p
        }
    })
    app.directive('price', {
        mounted(el) {
            const p = numPrice(el.innerHTML)
            el.innerHTML = p
        },
        updated(el) {
            const p = numPrice(el.innerHTML)
            el.innerHTML = p
        }
    })
    app.mount('#app')
    
    1. 引入 lib/tools.js 時,不要加副檔名
    2. 利用 app.directive('名稱', {生命週期}) 就可以定義一組樣板語法
    3. mounted(el, binding) 中的 el 就是代表該元件實體,binding 就是傳進來的資料
      1. 若是用 <div v-xxx="值"></div> 的,其值要用 binding.value
      2. 若是用 <div v-xxx>{{值}}</div>,其值要用 el.innerHTML,也就是取得包著的內容
    4. el.innerHTML = 新值 可以替換該元件的顯示值
    5. 此外,資料改變時,也要處理其值,所以記得加入updated() 事件,內容和 mounted()一致
  3. 子組件:@/components/TemplateRef.vue
    <script>
    import { ref } from "vue";
    export default {
      setup() {
        const num = ref(12345678);
        return { num };
      },
    };
    </script>
    
    <template>
      <input v-focus v-model="num" type="text" placeholder="請輸入文字" />
      <h1 v-money="num"></h1>
      <h2 v-price>{{ num }}</h2>
    </template>
    
    <style></style>
  4. 直接套用定義好的樣板語法即可

完整範例:

Vue.js 3.0 筆記

8-2 slot 用法

  1. 子組件:components/SlotTest.vue
    <script>
    export default {
      setup() {
        return {};
      },
    };
    </script>
    
    <template>
      <div class="alert">
        <slot>我是預設內容</slot>
      </div>
    </template>
    
    <style lang="scss" scoped>
    .alert {
      padding: 10px;
      margin: 10px;
      background: rgb(179, 228, 230);
      border: 1px solid rgb(51, 158, 161);
    }
    </style>
    1. 將預設內容用<slot></slot>包起來,未來要替換用的,也就是做成一個插槽
  2. 父組件:
    <script>
    import SlotTest from "@/components/SlotTest.vue";
    
    export default {
      components: {
        SlotTest,
      },
      setup() {
        return {};
      },
    };
    </script>
    
    <template>
      <slot-test>我是插槽1</slot-test>
      <slot-test>插槽2在這裡</slot-test>
      <slot-test>插槽3不一樣</slot-test>
    </template>
    
    <style></style>
    
  3. 使用插槽時,<SlotTest />要改成<slot-test>欲插入內容</slot-test>

完整內容:

Vue.js 3.0 筆記

9. Vue Router

  1. 官網:https://router.vuejs.org/zh/installation.html
  2. 安裝:
    npm install vue-router

     

  3. 使用:
    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router'
    
    createApp(App).use(router).mount('#app')
    

     

  4.  

Vue.js 3.0 筆記

9-1 配置路由

  1. 修改 src\router\index.js,配置需要的路由,例如:
    import { createRouter, createWebHistory } from 'vue-router'
    import Home from '../views/Home.vue'
    import About from "../views/About.vue";
    import AboutHome from "../views/About/index.vue";
    import Guide from "../views/About/Guide.vue";
    import Reference from "../views/About/Reference.vue";
    import Changelog from "../views/About/Changelog.vue";
    import GitHub from "../views/About/GitHub.vue";
    import NotFound from '@/views/NotFound.vue'
    
    const routes = [
      {
        path: '/',
        name: 'Home',
        component: Home
      },
      {
        path: '/Chat',
        name: 'Chat',
        component: () => import('@/components/Chat.vue')
      },
      {
        path: "/Courses/:id",
        name: "Courses_id",
        component: () => import("../views/Courses/_id.vue"),
      },
      {
        path: "/about",
        name: "About",
        component: About,
        children: [
          {
            path: "",
            component: AboutHome,
          },
          {
            path: "guide",
            component: Guide,
          },
          {
            path: "reference",
            component: Reference,
          },
          {
            path: "changelog",
            component: Changelog,
          },
          {
            path: "gitHub",
            component: GitHub,
          },
        ],
      },
      {
        path: '/:pathMatch(.*)*',
        name: 'not-found',
        component: NotFound
      },
    
    ]
    
    const router = createRouter({
      history: createWebHistory(process.env.BASE_URL),
      routes
    })
    
    export default router
    

     

    1. 全部載入(簡單架構適用):要先import Home from '../views/Home.vue',然後設為component: Home即可
    2. 動態載入(複雜架構才需要):不事先載入:component: () => import('@/components/Chat.vue')
    3. 若是有傳入變數的,用「:變數」來設定 path
    4. 利用 children:[] 來做路由套嵌(在某個頁面裡面的一堆連結)
      1. 預設的頁面要設為 path: "",
    5. 預設history模式為 createWebHistory(process.env.BASE_URL),若遇到 http://xxx/index.html 會失效(請後端重新設定網站設定)
    6. 若將history模式改為 createWebHashHistory(),網只會變成 http://xxx/index.html#/,可解決上方問題,但會跟原始錨點#相衝,SEO也不好(所以不建議,常用於後台界面)
    7. 例外處理path: '/:pathMatch(.*)*',
  2. 接著修改主架構,以及個別頁面的內容。
  3. 修改 App.vue,設定頁面欲呈現的主要架構,其中用<router-view></router-view>來顯示路由內容,其餘部份就是頁首、頁尾等固定的呈現區域
    <script>
    import Header from "@/components/Header.vue";
    import Footer from "@/components/Footer.vue";
    export default {
      components: {
        Header,
        Footer,
      },
    };
    </script>
    
    <template>
      <div>
        <Header></Header>
        <router-view></router-view>
        <Footer></Footer>
      </div>
    </template>
    <style lang="scss">
    * {
      margin: 0;
      padding: 0;
      -webkit-box-sizing: border-box;
      -moz-box-sizing: border-box;
      box-sizing: border-box;
      font-family: "Microsoft JhengHei", "Heiti TC", "sans-serif";
    }
    img {
      display: block;
    }
    html,
    body {
      width: 100%;
      height: 100%;
    }
    </style>
    

     

  4. 修改其中的 src\views\Home.vue 的頁面內容
    <script>
    import Article from "@/components/Article.vue";
    import Aside from "@/components/Aside.vue";
    import Main from "@/components/Main.vue";
    
    export default {
      name: 'Home',
      components: {
        Article,
        Aside,
        Main,
      }
    }
    </script>
    
    <template>
        <Article></Article>
        <Aside></Aside>
        <Main></Main>
    </template>

     

  5. src\views\NotFound.vue 內容(配合例外處理用)
    <script>
    export default {};
    </script>
    
    <template>
      <div id="NotFoundpage">
        <div>
          <h3>404 Not Found</h3>
        </div>
      </div>
    </template>
    
    <style lang="scss" scoped>
    #NotFoundpage {
      width: 100%;
      height: 578px;
      background: rgb(211, 118, 118);
      background-size: cover;
    
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      align-items: center;
      > div {
        > h3 {
          text-align: center;
          font-size: 48px;
          color: #fff;
          @media screen and (max-width: 1044px) {
            font-size: 30px;
          }
        }
        > p {
          text-align: center;
          font-size: 14px;
          color: #fff;
          line-height: 3em;
          @media screen and (max-width: 1044px) {
            font-size: 14px;
            line-height: 25px;
          }
        }
        @media screen and (max-width: 1044px) {
          width: 90%;
          height: auto;
          margin: 0 auto;
        }
      }
      @media screen and (max-width: 730px) {
        height: 349px;
      }
      @media screen and (max-width: 640px) {
        height: 175px;
      }
    }
    </style>

     

Vue.js 3.0 筆記

9-2 加入連結

  1. 方法一 <router-link to="xx">:會自動變成 <a href="/Rwd" class="nav-link router-link-active">RWD</a>
    <router-link to="/Rwd">RWD</router-link>

     

  2.  

Vue.js 3.0 筆記

9-3 useRoute 及 useRouter

  1. 範例(\src\views\Courses\_id.vue):
    <script>
    import axios from "axios";
    import { onMounted, onUnmounted, reactive, ref } from "vue";
    import { useRoute, useRouter } from "vue-router";
    export default {
      setup() {
        const route = useRoute();
        const router = useRouter();
        const pageDetal = reactive({ data: {} });
        const isError = ref(false);
        let timer = null;
    
        onMounted(() => {
          axios
            .get(`https://vue-lessons-api.herokuapp.com/courses/${route.params.id}`)
            .then((res) => {
              pageDetal.data = res.data.data[0];
            })
            .catch((error) => {
              isError.value = true;
              pageDetal.data["error_message"] = error.response.data.error_message;
              timer = setTimeout(() => {
                router.go(-1);
              }, 3000);
            });
        });
    
        onUnmounted(() => {
          clearTimeout(timer);
        });
    
        return { pageDetal, isError };
      },
    };
    </script>
    <template>
      <div>
        <div v-if="!isError">
          <h1>{{ pageDetal.data.name }}</h1>
          <h2>NTD: {{ pageDetal.data.money }}</h2>
          <img :src="pageDetal.data.photo" alt="" />
          <div>
            <img :src="pageDetal.data.teacher?.img" alt="" />
            <p>{{ pageDetal.data.teacher?.name }}</p>
          </div>
        </div>
        <h1 v-if="isError">{{ pageDetal.data.error_message }}</h1>
      </div>
    </template>
    
    <style></style>
    

     

  2. useRoute()取得所有 route 傳入的資料(在 route.params 中,例如 route.params.id):
    1. 傳入的參數:route.params
    2. 傳庫的網址:route.path
  3. useRouter()提供操作網址用的函式(例如: router.push("\Home") 用來轉向):
    1. 簡易轉向:
      router.push("\Home") 

       

    2. 轉向還可以這麼寫,未來可以塞入更多參數:
      router.push({
        path: "\Home"
      })

       

    3. 回上頁:
      router.go(-1)

       

    4. 儲存網址(下例為開新分頁的函式):
      const openNewTab = (id) => {
        const routeData = router.resolve({ path: `/courses/${id}` });
        window.open(routeData.href, "_blank");
      };

       

    5.  
  4.  

Vue.js 3.0 筆記

10. Vuex 資料傳遞

  1. Vuex就是把資料拉出來,統一在外部存取,不需用props或emmits傳來傳去。
  2. 啟用Vuex後會多一個\src\store\資料夾,其中index.js可以定義各種資料的存取
    import { createStore } from "vuex";
    export default createStore({
      state:{
        isOpen: false,
      },
      actions:{
        handOpenState(context) {
          const isOpen = !context.state.isOpen;
          context.commit("OpenState", isOpen);
        },
      },
      mutations:{
        OpenState(state, payload) {
          state.isOpen = payload;
        },
      },
      getters(
        isOpen(state) {
          return state.isOpen;
        },
      ),
      modules: {},
    });
    
    1. state:{} 用來設定初始變數
    2. actions:{} 用來設定讓組件用 dispatch() 呼叫的函數
      1. 函式可以傳入 context 參數(用context.state 就可取得 state 中的變數值),也可以有第二個參數,也就是外部傳進來的值
      2. context.commit 觸發 muations 中的函式以改變資料值
    3. muations:{} 用來改變 state 中資料值
      1. 函式一般會傳入 state ,用來取得 state 中的變數值及第二個參數,也就是 actions 傳進來的值
      2. 元件可以直接用 store.commit() 來執行 muations 中的函式(但不是好的作法)
    4. getters:{}  用來重組資料(類似computed),函式一般會傳入 state ,用來取得 state 中的變數值
  3. 從 Vuex 取出 useStore() 函式,用 store.state.isOpen 便可取到定義在state中的變數,但建議改用store.getters.isOpen(或store.getters['isOpen']
    const isOpen = computed(() => {
      //return store.getters.isOpen;
      return store.getters["isOpen"];
    });

     

  4. store.dispatch() 觸發 actions 中的函式(盡量不要用store.commit() 觸發 muations 中的函式直接來改值,這樣流程不一致不太好)
    <script>
    import { useStore } from "vuex";
    export default {
      setup() {
        const store = useStore();
    
        const handClickMenu = () => {
          store.dispatch("handOpenState");
        };
    
        return { handClickMenu };
      },
    };
    </script>

     

  5.  

Vue.js 3.0 筆記

10-1 拆分 Vuex

  1. 若架構變大時,可以將 Vuex 項目都獨立出來
    import { createStore } from "vuex";
    import state from "./state.js";
    import actions from "./actions.js";
    import mutations from "./mutations.js";
    import getters from "./getters.js";
    import Auth from "./Auth";
    export default createStore({
      state,
      actions,
      mutations,
      getters,
      modules: {
        Auth,
      },
    });
    

     

  2. \src\store\state.js
    export default {
      isOpen: false,
    };
    

     

  3. \src\store\actions.js
    export default {
      handOpenState(context) {
        const isOpen = !context.state.isOpen;
        context.commit("OpenState", isOpen);
      },
    };
    

     

  4. \src\store\mutations.js
    export default {
      OpenState(state, payload) {
        state.isOpen = payload;
      },
    };
    

     

  5. \src\store\getters.js
    export default {
      isOpen(state) {
        return state.isOpen;
      },
    };
    

     

  6.  

Vue.js 3.0 筆記

10-2 modules 用法

  1. 可以把特定功能獨立出來,避免 store 太擁擠
  2. 例如做一個Auth認證功能,先在 store 下建立 \Auth\index.js
    export default {
      namespaced: true,
      state: {
        token: "",
      },
      actions: {
        handSetToken({ commit }, token) {
          commit("setToken", token);
        },
      },
      mutations: {
        setToken(state, token) {
          state.token = token;
        },
      },
      getters: {
        getToken(state) {
          return state.token;
        },
      },
    };
    

    namespaced 一旦設為 true,在使用時就要加上模組名稱,如「Auth/xxxx」

    store.dispatch("Auth/handSetToken", "Acbz1x3WQw4eq9qilpFjregn");
    console.log("TOKEN =>", store.getters["Auth/getToken"]);

     

  3. 在 \store\index.js 引入之
    import { createStore } from "vuex";
    import state from "./state.js";
    import actions from "./actions.js";
    import mutations from "./mutations.js";
    import getters from "./getters.js";
    
    import Auth from "./Auth";
    
    export default createStore({
      state,
      actions,
      mutations,
      getters,
      modules: {
        Auth,
      },
    });
    

     

  4. 在 App.vue 使用該 modules
    <script>
    import { onMounted } from "vue";
    import { useStore } from "vuex";
    export default {
    
      setup() {
        const store = useStore();
    
        onMounted(() => {
          store.dispatch("Auth/handSetToken", "Acbz1x3WQw4eq9qilpFjregn");
          console.log("TOKEN =>", store.getters["Auth/getToken"]);
        });
    
        return {};
      },
    };
    </script>

     

  5.  

Vue.js 3.0 筆記

11. Composition API

  1. 可以把共用的功能做成 Composition API
  2. 新增 API 資料夾,例如:my-api
  3. 新增檔案,例如:my-api/useWebSocket.js
    // 連線 websocket
    export function webSocket(url) {
      const ws = new WebSocket("ws://" + url);
      return ws;
    }
    

     

  4. 新增API主檔案,例如:my-api/index.js
    import { webSocket } from "./useWebSocket.js";
    export const useWebSocket = webSocket;
    

     

  5. 在元件中使用
    import { useWebSocket } from "../my-api";
    export default {
      setup() {
        const ws = useWebSocket("120.115.2.76:8443/");
      },
    };

     

  6.  

Vue.js 3.0 筆記

12. 使用 Element plus

  1. 安裝 Element plus
    npm install element-plus --save

     

  2. 安裝主題機制以及白堊主題
    npm i element-theme -g
    npm i element-theme-chalk -D

     

  3. 完整引入(並套用正體中文語系) src\main.js
    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    
    import ElementPlus from 'element-plus';
    import locale from 'element-plus/lib/locale/lang/zh-tw'
    import 'element-plus/lib/theme-chalk/index.css';
    
    const app = createApp(App)
    app.use(ElementPlus, { locale })
    app.use(store)
    app.use(router)
    app.mount('#app')
    

     

  4.  

Vue.js 3.0 筆記

12-1 配置版面

  1. 利用布局容器來配置版面(https://element-plus.gitee.io/#/zh-CN/component/container),例如(src\App.vue):
    <template>
      <div>
        <el-container>
          <el-aside width="200px">Aside</el-aside>
          <el-container>
            <el-header><Header/></el-header>
            <el-main>
              <router-view></router-view>
            </el-main>
            <el-footer><Footer/></el-footer>
          </el-container>
        </el-container>
      </div>
    </template>

     

  2. 利用https://element-plus.gitee.io/#/zh-CN/component/menu來製作選單,例如(src\components\Header.vue):
    <script>
    import { ref } from "vue";
    export default {
      setup() {
        const activeIndex = ref(1);
    
        const handleSelect = (key, keyPath) => {
          console.log(key, keyPath);
        };
        return { activeIndex, handleSelect };
      },
    };
    </script>
    
    <template>
      <el-menu
        :default-active="activeIndex"
        class="el-menu-demo"
        mode="horizontal"
        @select="handleSelect"
        background-color="#545c64"
        text-color="#fff"
        active-text-color="#ffd04b"
      >
        <el-menu-item index="1">回首頁</el-menu-item>
        <el-submenu index="2">
          <template #title>各種功能</template>
          <el-menu-item index="2-1">選項1</el-menu-item>
          <el-menu-item index="2-2">選項2</el-menu-item>
          <el-menu-item index="2-3">選項3</el-menu-item>
          <el-submenu index="2-4">
            <template #title>選項4</template>
            <el-menu-item index="2-4-1">選項1</el-menu-item>
            <el-menu-item index="2-4-2">選項2</el-menu-item>
            <el-menu-item index="2-4-3">選項3</el-menu-item>
          </el-submenu>
        </el-submenu>
        <el-menu-item
          index="3"
          disabled
        >消息中心</el-menu-item>
        <el-menu-item index="4"><a
            href="https://www.ele.me"
            target="_blank"
          >後臺管理</a></el-menu-item>
      </el-menu>
    </template>
    <style lang="scss" scoped>
    </style>
    

     

  3.  

Vue.js 3.0 筆記

13. 使用 websocket

  1. 安裝 websocket 套件(https://www.npmjs.com/package/vue-native-websocket-vue3
    npm install vue-native-websocket-vue3 --save

     

  2. 在 main.js 加入設定以使用websocket 套件:
    import VueNativeSock from "vue-native-websocket-vue3";
     
    // 使用VueNativeSock插件,並進行相關配置
    app.use(VueNativeSock, "ws://120.115.2.76:8443", {
      store: store,
      format: 'json',
    });

     

    1. 第二個參數為websocket 位址
    2. 第三個參數用來配置其他項目,如使用 vuex
    3. 如此會自動連接,若不想自動連,要加入 connectManually: true
  3.  

Vue.js 3.0 筆記

14. nuxt 框架

  1. nuxt 是一個 vue 用來做 SSR 的框架(建構在Node之上)
    1. SSR:Server Side Rendering 每一個不同頁面就回傳一份不同的 html 檔案 ,SEO好,但 loading 較大
    2. SPA:Single Page Application 用 JavaScript 把畫面 render 出來,不會載入新的 HTML 檔案,不利于SEO ,先慢後快
  2. nuxt 官網:https://nuxtjs.org/
  3. 用 npx 或 npm 來建立專案
    npx create-nuxt-app <project-name>
    npm init nuxt-app <project-name>
    

     

  4. 專案建立後,可以用 dev 啟動專案
    cd <project-name>
    npm run dev
    

     

  5. 專案建完後,幾個重要目錄
    1. pages:建立頁面,例如建立 about.vue,會有自動路由 http://網址/about
    2. layout:佈景,編輯 default.vue,會影響所有頁面
      • 其中 <Nuxt /> 就是 nuxt 產生的內容(等同 router-view)
      • <Nuxt />只能在 layout 中使用。
    3. assets:用來放靜態元件,但此資料夾仍會被 nuxt 處理,例如壓縮圖片、CSS檔等
    4. static:也是用來放靜態元件,但不會被 nuxt 處理,例如影片檔、pdf檔
    5. middleware:中間層,從路由到頁面之間的中間層,可以用來做一些共同的檢查、驗證
    6. plugins:全局套件,會自動注入到nuxt.config.js中,全局可用
    7. store:vuex的部份
    8. components:頁面最小的單位組件,可以直接使用,無須 import
       
  6. 讓 nuxt 支援 scss,需安裝以下套件
    npm install --save-dev sass sass-loader fibers

     

Vue.js 3.0 筆記

14-1 nuxt 生命週期

Server 端的生命週期

Client端的生命週期

無論你選擇哪種Nuxt.js模式,這部分的生命週期都會在瀏覽器中完全執行。

Vue.js 3.0 筆記

14-2 後台的 asyncData

  1. asyncData 是在 Server 端處理非同步的生命週期(所以前端的各種函數或物件都不能用,例如:$this、alert()),只會執行一次,通常用來擷取API結果,其內容會覆蓋 data 中的東西
    asyncData() {
      const name = 'Tad';
      return {name};
    },

     

  2. data 的值會被 asyncData 覆蓋,所以除非有要再次修改 asyncData 中的變數,否則一般不會在 data 中去設一個一樣名稱的變數
    data() {
      return {
        name = '';
      };
    },
    methods{
      handName() {
        this.name = 'Kai';
      }
    }

     

  3. asyncData 只有在 page 中能用,其餘目錄沒有這個生命週期
  4. 搭配 async、await 使用:
    <script>
    import axios from "axios";
    export default {
      async asyncData() {
        const res = await axios.get("http://blog.lces.tn.edu.tw/api.php");
        return { res: res.data.data };
      }
    };
    </script>

     

Vue.js 3.0 筆記

14-3 後台的 fetch

  1. fetch 可以在任何元件目錄下執行, asyncData 只能在 page 目錄下執行
  2. fetch 在 vue 實體產生後才執行(所以可以取得 this) ,也就是在asyncData 之後
  3. fetch 無法直接 return,所以傲用 this.變數來指定新值,覆蓋 data 中的值,例如:
    <script>
    import axios from "axios";
    export default {
      // async asyncData() {
      //   const res = await axios.get("http://blog.lces.tn.edu.tw/api.php");
      //   return { res: res.data.data };
      // }
      data() {
        return {
          res: []
        };
      },
      async fetch() {
        this.res = await axios
          .get("http://blog.lces.tn.edu.tw/api.php")
          .then(respon => respon.data.data);
      }
    };
    </script>

     

  4. 若是加入fetchOnServer: false,則會變成在前端執行,如:
    <script>
    import axios from "axios";
    export default {
      data() {
        return {
          res: []
        };
      },
      fetchOnServer: false,
      async fetch() {
        this.res = await axios
          .get("http://blog.lces.tn.edu.tw/api.php")
          .then(respon => respon.data.data);
      }
    };
    </script>

     

  5. $fetchState.pending 可以取得 fetch 是否執行完畢的狀態,例如:
    <template>
      <div class="container">
        <div>
          <Logo />
          <h1 v-if="$fetchState.pending">載入中...</h1>
          <h1 v-if="!$fetchState.pending" class="title">
            校園日誌
          </h1>
          <ul v-if="!$fetchState.pending">
            <li v-for="news in res" key="news.id">{{ news.title }}</li>
          </ul>
        </div>
      </div>
    </template>
    

     

  6. $fetchState.error 可以取得 fetch 是否執行出錯的訊息,例如:
    <template>
      <div class="container">
        <div>
          <Logo />
          <h1 v-if="$fetchState.pending">載入中...</h1>
          <h1 v-if="$fetchState.error">出錯了...{{ $fetchState.error }}</h1>
      </div>
    </template>

     

  7. $fetchState.timestamp 可搭配 keep-alive 使用於activated生命週期中使用(activated生命週期只有有用 keep-alive 時才會有),讓資料可以進行緩存,例如:
    <template>
      <div>
          <Nuxt keep-alive />
      </div>
    </template>

    page/index.vue

    <script>
    import axios from "axios";
    export default {
      data() {
        return {
          res: [],
        };
      },
      activated() {
        // Call fetch again if last fetch more than 30 sec ago
        if (this.$fetchState.timestamp <= Date.now() - 30000) {
          this.$fetch();
        }
      },
      fetchOnServer: false,
      async fetch() {
        this.res = await axios
          .get("http://blog.lces.tn.edu.tw/api.php")
          .then((respon) => respon.data.data);
      },
    };
    </script>
    

     

  8.  

Vue.js 3.0 筆記

14-4 nuxt.config.js

  1. 若是放在 nuxt.config.js 中,表示是全域的(可以自己在 meta 中加 property 屬性):
      ssr: true,
      // Global page headers (https://go.nuxtjs.dev/config-head)
      head: {
        title: "我的 nuxt 測試專案",
        meta: [
          { charset: "utf-8" },
          { name: "viewport", content: "width=device-width, initial-scale=1" },
          { property: "op:url", content: "https://go.nuxtjs.dev/config-head" },
          { property: "op:image", content: "https://nuxtjs.org/logos/nuxt.svg" },
          { hid: "description", name: "description", content: "" }
        ],
        link: [
          { rel: "icon", type: "image/x-icon", href: "/favicon.ico" },
          { rel: "stylesheet", type: "text/css", href: "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.0-beta1/css/bootstrap.min.css" },
          { rel: "stylesheet", type: "text/css", href: "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" }
        ],
        script: [
          { src: 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.0-beta1/js/bootstrap.bundle.min.js' },
          { src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js' },
        ]
      },

     

  2. 若是放在各個頁面,則可以做該頁面設定,例如:
    <script>
    export default {
      head: {
        title: '關於我們',
        meta: [
          { charset: 'utf-8' },
          { name: 'viewport', content: 'width=device-width, initial-scale=1' },
          { property: 'op:url', content: 'https://go.nuxtjs.dev/config-head' },
          { property: 'op:image', content: 'https://nuxtjs.org/logos/nuxt.svg' },
          { hid: 'description', name: 'description', content: '' }
        ],
        link: [
          { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
        ]
      },
    };
    </script>

     

  3. 也可以將 head 改為函式,如此可以接收 data 的內容來動態改變值,如:
    <script>
    import axios from "axios";
    export default {
      head() {
        return {
          title: this.news.title,
          link: [
            {
              rel: "stylesheet",
              type: "text/css",
              href:
                "https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.css",
            },
          ],
          script: [
            {
              src:
                "https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js",
            },
          ],
        };
      },
      async asyncData(context) {
        const res = await axios.get(
          "http://blog.lces.tn.edu.tw/api.php?op=show&id=" + context.params.id
        );
        return { news: res.data };
      },
    </script>

     

關閉SSR變成SPA

  1. 只要設定 ssr 為 false 即可
    export default {
      ssr: false,
      ...略...
    };
    

     

Vue.js 3.0 筆記

14-5 Nuxt 使用 Router

  1. 連結可以用 <NuxtLink to=""></NuxtLink > 或 <nuxt-link to=""></nuxt-link>,等同<router-link to=""></router-link>
  2. 巢狀路由(嵌套式路由),例如: /about/aaa 只要在 pages 下建立 about 目錄,底下建立 aaa.vue 即可(其中 <Nuxt /> 要換成 <NuxtChild />)
  3. 透過 vue router 去切換頁面的內容的時候在 nuxtjs 中我們使用 <Nuxt /> 這個組件來切換,而不是 <router-view />
    <router-view /> -> <Nuxt />
  4. 在 nuxtjs 裡面我們需要點擊連結然後切換router的頁面的時候,我們會使用 <NuxtLink to="/about">about </NuxtLink> 這種方式來做為我們的連結
    <router-link to="/">Home</router-link> -> <NuxtLink to="/">Home</NuxtLink>
  5. 當我們在做嵌套網址的時候,我們會使用  <NuxtChild /> 來切換我們頁面的內容,而不是用原本的 <router-view />,這樣在識別的時候也會比較好識別這個組件是不是用嵌套網址
    <router-view /> -> <NuxtChild />
  6. 如果要取得參數來切換內容,例如 show/1、show/2 可以建立 pages/show/_id.vue,asyncData可用context取得路由的參數:
    <script>
    import axios from "axios";
    export default {
      async asyncData(context) {
        const res = await axios.get(
          "http://blog.lces.tn.edu.tw/api.php?op=show&id=" + context.params.id
        );
        return { news: res.data };
      },
    };
    </script>

     

  7. 若要在method中使用router,必須寫成 this.$router 的寫法,如:
      methods: {
        goToNews(id) {
          this.$router.push("/show/"+id);
        },
      },

     

  8.  

 

 

Vue.js 3.0 筆記

14-6 錯誤頁面

  1. 在 layout 下建立 error.vue,內容為:
    <script>
    export default {
      props: ["error"],
      // layout: 'blog' // you can set a custom layout for the error page
    };
    </script>
    
    <template>
        <div>
          <h1 v-if="error.statusCode === 404">沒有此頁面</h1>
          <h1 v-else>喔喔~我秀斗了</h1>
        </div>
    </template>
    

     

Vue.js 3.0 筆記

14-7 plugin

  1. plugin 的三種方式

Vue.js 3.0 筆記

14-7-1 注入 plugin

  1. plugin用法,先在 plugins 下建立一個 js 檔,如:plugins/hello.js
    export default ({ app }, inject) => {
      // 注入 $hello(msg) in Vue, context and store.
      inject('hello', msg => console.log(`Hello ${msg}!`))
    }
    

     

  2. 接著修改nuxt.config.js,加入該plugin:
    export default {
      plugins: ['~/plugins/hello.js']
    }
    

     

  3. 使用方法,前端用 this.$外掛名稱,後端用$外掛名稱,如:
    export default {
      mounted() {
        this.$hello('我在前端被mounted了')
      },
      asyncData({ app, $hello }) {
        $hello('我在後端被asyncData了')
      }
    }
    

     

  4. 後端用這樣也行
    asyncData(context) {
      context.$hello('我在後端被asyncData了')
    },

     

Vue.js 3.0 筆記

14-7-2 整合現有的nuxt套件 plugin

  1. 先安裝一個外部套件
    npm install @nuxtjs/axios

     

  2. 將外部套件加入設定檔nuxt.config.js
    modules: ['@nuxtjs/axios'],

     

  3. 然就可以直接用外部套件了。
    async asyncData({$hello, $axios}) {
      $hello('我在後端被asyncData了')
      const res =await $axios.get('http://blog.lces.tn.edu.tw/api.php?op=index')
      return {res: res.data.data}
    },

     

  4. 若是axios無法取得資料,可以用 try{}.catch(){} 方式來呈現錯誤,但一旦用到axios的地方很多,這樣會顯得麻煩,所以,可以做一個 plugin來處理。
  5. 先建立 plugins/axios.js
    export default function ({ $axios, redirect }) {
      $axios.onError(error => {
        if (error.response.status === 500) {
          console.log('500');
          redirect('/500')
        }
    
        if(error.response.status === 404) {
          console.log('404');
          redirect('/404')
        }
      })
    }
    
  6.  加入設定檔nuxt.config.js
    plugins: ["~/plugins/hello.js", "~/plugins/axios.js"],

     

  7. 如此當api傳回404或500時就會自動導向到 page/404.vue或500.vue(要自己做)
    <script>
    export default {
    };
    </script>
    
    <template>
      <h1>404 咩有~</h1>
    </template>
    
    <style lang="scss" scoped>
    </style>
    

     

Vue.js 3.0 筆記

14-7-3 整合一般npm套件 plugin

  1. 先安裝此套件 https://www.npmjs.com/package/vue-notification (要有支援SSR的套件才不會有問題)
    npm install --save vue-notification
  2. 建立/plugins/notification.js
    import Vue from "vue";
    // for SPA:
    // import Notifications from 'vue-notification'
    // for SSR:
    import Notifications from "vue-notification/dist/ssr.js";
    
    Vue.use(Notifications);
    

     

  3. 加到 nuxt.config.js 設定
    plugins: ["~/plugins/hello.js", "~/plugins/axios.js","~/plugins/notification.js"],

     

  4. 將套件載入語法<notifications group="foo" />加到每頁都會執行到的地方,如:layouts\default.vue
    <template>
      <div>
        <Nav />
        <!-- 主畫面 -->
        <div class="container">
          <div class="row">
            <!-- 主內容區 -->
            <div class="col-md-9">
              <Nuxt />
              <notifications group="foo" />
            </div>
            <!-- 側邊欄 -->
            <div class="col-md-3">
              <Aside />
            </div>
          </div>
        </div>
    
      </div>
    </template>
    

     

  5. 接著加入方法
      methods: {
        handNotify() {
          this.$notify({
            group: "foo",
            title: "Important message",
            text: "Hello user! This is a notification!",
          });
        }
      },

     

  6. 加個按鈕以呼叫該方法
    <template>
      <div>
        <h1>關於</h1>
        <button @click="handNotify">按我</button>
      </div>
    </template>

     

  7. 由於該套件的CSS是放在src/Notifications.vue中,無法被plugin讀取,所以,從中取出CSS,並另存至assets\notification.css
    
    .vue-notification-group {
      display: block;
      position: fixed;
      z-index: 5000;
    }
    .vue-notification-wrapper {
      display: block;
      overflow: hidden;
      width: 100%;
      margin: 0;
      padding: 0;
    }
    .notification-title {
      font-weight: 600;
    }
    .vue-notification-template {
      display: block;
      box-sizing: border-box;
      background: white;
      text-align: left;
    }
    .vue-notification {
      display: block;
      box-sizing: border-box;
      text-align: left;
      font-size: 12px;
      padding: 10px;
      margin: 0 5px 5px;
      color: white;
      background: #44A4FC;
      border-left: 5px solid #187FE7;
    }
    .vue-notification.warn {
      background: #ffb648;
      border-left-color: #f48a06;
    }
    .vue-notification.error {
      background: #E54D42;
      border-left-color: #B82E24;
    }
    .vue-notification.success {
      background: #68CD86;
      border-left-color: #42A85F;
    }
    .vn-fade-enter-active, .vn-fade-leave-active, .vn-fade-move  {
      transition: all .5s;
    }
    .vn-fade-enter, .vn-fade-leave-to {
      opacity: 0;
    }
    

     

  8. 接著在nuxt.config.js中加入CSS設定
    css: ['~/assets/notification.css'],

     

  9.  

Vue.js 3.0 筆記

14-7-4 將 localStorage 做成 plugin

  1. localStorage的用法:
    1. https://developer.mozilla.org/zh-TW/docs/Web/API/Window/localStorage
    2. https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/javascript-localstorage-%E7%9A%84%E4%BD%BF%E7%94%A8-e0da6f402453
  2. 建立plugin/localStorage.js
    export default ({ app }, inject) => {
      inject("localStorage", {
        set(key = "", val = {}) {
          localStorage.setItem(key, JSON.stringify(val));
        },
        get(key = "") {
          const obj = JSON.parse(localStorage.getItem(key));
          // 避免傳回 null
          if (!obj) {
            return {};
          }
          return obj;
        },
        remove(key = "") {
          localStorage.removeItem(key);
        },
        removeAll() {
          localStorage.clear();
        }
      });
    };
    

     

  3. 加入nuxt.config.js設定
      plugins: [
        "~/plugins/hello.js",
        "~/plugins/axios.js",
        "~/plugins/notification.js",
        "~/plugins/localStorage.js"
      ],

     

  4. 如此,便可方便的localStorage使用
      mounted() {
        this.$hello("我在前端被mounted了");
        this.$localStorage.set("userData", { name: "tad", age: 48 });
        console.log('userData', this.$localStorage.get("userData"));
      },

     

Vue.js 3.0 筆記

14-8 使用vuex

  1. 先建立 store/index.js,如:
    export const state = () => ({
      counter: 0
    });
    
    export const actions =  {
      handleAddCounter({ commit }) {
        commit("addCounter");
      }
    };
    
    export const mutations = {
      addCounter(state) {
        state.counter++;
      }
    };
    
    export const getters =  {
      getCounter: state => {
        return `counter: ${state.counter}`;
      }
    };
    
  2. store目錄內的每一個.js檔案都會被轉換為一個 namespaced module (index為根模組)。你的 state  應該始終是一個函式,以避免在伺服器端出現不必要的共享狀態。
  3. 加入methods,以便觸發vuex actions中的方法,並加入 computed 以取得 getters 的值
      methods: {
        handCounter() {
          this.$store.dispatch('handleAddCounter');
          // console.log(this.$store.getters.getCounter);
        },
      },
      computed:{
        getCount(){
          return this.$store.getters.getCounter;
        }
      }

     

  4. 樣板部份也加入按鈕並顯示之
    <button @click="handCounter">按我計數+1 {{getCount}}</button>

     

  5. 也可以從後端直接取得 state 的值
    async asyncData({ app }) {
      return { counter: app.store.state.counter };
    },

     

  6. 從後端也可以利用 dispatch 將值塞進 state 中,若要如此,就不要 return,而是在 computed 中用 getters 去取得 state 的值,不然會造成兩份資料可能會不一致的問題。
  7. 若要做成  Vuex module,只要在 store/ 下建立一個目錄即可,如 store/User/index.js
  8. 若要拆分 Vuex,只要分別存成 store/User/state.js、store/User/actions.js、store/User/mutations.js、store/User/getters.js 即可