• 解構賦值 (Destructuring assignment) 語法是一種 JavaScript 運算式,可以把陣列或物件中的資料解開擷取成為獨立變數。 const user = { name: "Tad", age: 48, address: "Tainan", //解構(可以只抽出某幾個) const { name, age, address } = user;

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

    const user_data = { user, sex: "男",
  • 箭頭函式運算式 (arrow function expression)擁有比 函式運算式 還簡短的語法。 它沒有自己的 this arguments super new.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]));
  • 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> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vue3 基本頁面</title> </head> <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>

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

  • 響應式資料 ref 用法,修改其值必須用 變數.value 才行,可以接受任何型態的資料,但是不會對物件或陣列內部的屬性變動做監聽。
  • 響應式資料 reactive 用法,只能用 [陣列] {物件} 。可以做深層的監聽,以及訪問資料不需要 .value。
  • 利用 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"> <script src="./js/vue.js"></script> <script> const { 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> <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> <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 { roNum, addFn, removeFn, addRoFn, removeRoFn, Vue.createApp(App).mount("#app"); </script>

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

  • v-for="(變數, 第幾個) in 陣列" 迴圈,務必用 v-bind:key="唯一值" 綁定 key(可簡寫成 :key ),避免每次改值都要重新渲染整個迴圈,太耗資源 v-for="(新變數, 第幾個) in 陣列" :key="唯一值"
  • v-bind:屬性 可綁定任何屬性,簡寫為 :屬性 :class
  • <ul class="box" :class="{open: isOpen}"> 可簡寫成 <ul :class="['box', {open: isOpen}]">
  • computed 是一個函式,用來進行計算或資料重組,其結果會緩存,例如下例中的計算 BoxHeight 高度。
  • 若需要計算的值需要傳入參數,則建議改用自訂函式,否則都建議用 computed
  • 只要是透過 computed 計算包裝過的 reactive 物件,都要用 .value 來取得 computed 中的資料
  • filter() 方法會建立一個經指定之函式運算後,由原陣列中通過該函式檢驗之元素所構成的新陣列。如: listArr.filter((item) => item.show).length
  • v-show 只是用CSS將元件隱藏(資源耗損較小)
  • v-if 可真的隱藏動元素(資源耗損較高)
  • 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>
  • 若是監聽 ref 的值,那就直接監聽該值即可
  • 若是監聽 ref 物件的值,那要針對其中的資料,並將之改成 getter 可讀取的值,也就是 () => refObj.value.idx 這種方式
  • 若是監聽 ref 整個物件,那要加入第三個參數來做深層監控,也就是 {deep: true} 才行,無法取得舊值
  • 若是監聽 reactive 物件的值,那要針對其中的資料,並將之改成 getter 可讀取的值,也就是 () => reactiveObj.idx 這種方式
  • 若是監聽 reactive 整個物件,無法取得舊值
  • watchEffect(()=>{}) 用來監聽,且不須傳入欲監聽參數,只要在{}中直接使用參數值,就會自動監聽 watchEffect(() => { console.log(num.value); <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>
  • onUnmounted => 組件銷毀後執行
  • onErrorCaptured => 當組件發出錯誤時後調用
  • onRenderTracked => 監控 virtual DOM 重新選染時調用 ( 此事件告訴你操作什麼監聽了組件以及該操作的物件)
  • onRenderTriggered => 監控 virtual DOM 重新選染時調用 ( 此事件告訴你操作什麼觸發了重新渲染,以及該操作的物件)
  • v-on.passive - { passive: true } 模式添加偵聽器
  • v-on.middle - 只當點擊鼠標中鍵時觸發。
  • v-on.right - 只當點擊鼠標右鍵時觸發。
  • v-on.left - 只當點擊鼠標左鍵時觸發。
  • v-on.once - 只觸發一次回調。
  • v-on.{keyAlias} - 僅當事件是從特定鍵觸發時才觸發回調。
  • v-on.self - 只當事件是從偵聽器綁定的元素本身觸發時才觸發回調。
  • v-on.capture - 添加事件偵聽器時使用 capture 模式。
  • v-on.prevent - 調用 event.preventDefault() 讓自身預設功能喪失
  • v-on.stop - 調用 event.stopPropagation() 避免泡泡事件
  • v-model .lazy - 滑鼠焦點離開該元件才會顯示綁定內容
  • v-model .number - 輸入字符串轉為有效的數字
  • v-model .trim - 輸入首尾空格過濾
  • <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> <img v-show="!isLoad" class="load" src="https://i.giphy.com/media/52qtwCtj9OLTi/giphy.webp" alt="" /> <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. 安裝 node.js 及 vue-cli

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

    更新 vue-cli

    npm update -g @vue/cli
  • 建立 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 (依賴)是被哪個函式庫所要求的等等,不過這個我們通常就放著不太會管它。

    import { createApp } from 'vue' import App2 from './App2.vue' import router from './router' import '@/assets/css/reset.css' createApp(App2).use(router).mount('#app')
  • 其中 App2.vue 就是一個 Component 組件
  • import { createApp } from 'vue' 若有 {} 表示是從組建中拆分出來的
  • import App2 from './App2.vue' 若沒有表示是該組件預設匯出的(一般和檔名一致)
  • 亦可 import 共用的 css 檔 @ 來代表以組件的 src 目錄為起點
  • 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>
  • export default 組件名稱{} 若是沒寫名稱,預設就是檔名
  • import { ref } from "vue"; 中的 {ref} 表示從 vue 組建中拆分出 ref 函式
  • return { isOpen, HandOpenMenu }; 中放常數、函式等,以便讓 <template> 樣板中的 {{文字樣板}} v-bind v-model v-if v-on ...等修飾符使用
  • <style lang="scss" scoped> 代表要用 scss 預編譯器,且樣式設定只限定在此組件中
  • <script> import Header from "@/components/Header.vue"; import Footer from "@/components/Footer.vue"; export default { components: { Header, Footer, </script> <template> <Header></Header> <Footer></Footer> </template> <style lang="scss"> margin: 0; padding: 0; background-image: url("~@/assets/images/rightbtn2.jpg"); </style>
  • import Header from "@/components/Header.vue"; 中用 import 來引入 Header 表示組件預設匯出的內容,所以無須放到 {} 中,路徑的 @ 來代表以組件的 src 目錄為起點(若是放在 <style> 中要引入素材的話,要用 ~@ 來代表 src 目錄為起點)
  • components:{ Header } 中的 Header 表示要放到 <template> 樣板中去使用的組件名稱,可用 <Header></Header> <Header /> 來呈現
  • SCSS用法:
  • 練習網站: https://www.sassmeister.com/
  • 用法: https://blog.techbridge.cc/2017/06/30/sass-scss-tutorial-introduction/
  • validator: function(value) { // 這個值必須匹配下列字符串中的一個 return ['success', 'warning', 'danger'].indexOf(value) !== -1
  • 若是有參數要在兩個子組件中傳遞(或共用),可以將該參數設到父組件中,然後再由父組件傳遞給個別子組件。
  • 可以先在 emits:{} 中定義要傳什麼東西到父組件(沒設也行),也就是要送到父組件的函式,可以在裡面做參數的驗證。
  • setup(props, { emit }) {} 中從 context 解構出 emit 功能,並在掛載後將 TimeOut 函數及 num 值透過 emit 傳送到父組件
  • <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>
  • 當子組件將 TimeOut 函數及 num emit 到父組件時,就會觸發 <TimerBox @TimeOut ="handleTimeOut" /> 中的 @TimeOut 事件,同時會執行 handleTimeOut 函式
  • 父組件要在 setup(){} 中定義好 handleTimeOut 函式的內容並 return 出來
  • .名稱-enter-active {} /* 整個進入動畫期間的狀態 */ .名稱-enter-from {} /* 進入動畫從 */ .名稱-enter-to {} /* 進入動畫止 */ .名稱-leave-active {} /* 整個離開動畫期間的狀態 */ .名稱-leave-from {} /* 離開動畫從 */ .名稱-leave-to {} /* 離開動畫止 */
  • <EventBack @click="handleEventBack(100, 'test', $event)" /> 利用 $event 可以取得點擊的所有事件
  • 前面可以接一堆參數, $event 一定要放在最後面
  • 收到時, $event 就有包含所有資訊
  • $event.target 就是點擊的實體本身
  • 子元件:@/components/EventBack.vue <script> export default { setup() { </script> <template> <a href="javascript:;">點我</a> </template> <style></style>
  • 設定一個 const txtInput = ref(null) ,然後在樣板中用 ref="txtInput" 綁定即可
  • 如此,在 setup 裡面就可以取得被綁定的動元素,並做一些動作,例如取得焦點: txtInput.value.focus();
  • 完整範例:

    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')
  • 引入 lib/tools.js 時,不要加副檔名
  • 利用 app.directive('名稱', {生命週期}) 就可以定義一組樣板語法
  • mounted(el, binding) 中的 el 就是代表該元件實體, binding 就是傳進來的資料
  • 若是用 <div v-xxx="值"></div> 的,其值要用 binding.value
  • 若是用 <div v-xxx>{{值}}</div> ,其值要用 el.innerHTML ,也就是取得包著的內容
  • el.innerHTML = 新值 可以替換該元件的顯示值
  • 此外,資料改變時,也要處理其值,所以記得加入 updated() 事件,內容和 mounted() 一致
  • 子組件:@/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>
  • 直接套用定義好的樣板語法即可
  • 完整範例:

  • 修改 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
  • 全部載入(簡單架構適用):要先 import Home from '../views/Home.vue' ,然後設為 component: Home 即可
  • 動態載入(複雜架構才需要):不事先載入: component: () => import('@/components/Chat.vue')
  • 若是有傳入變數的,用「 :變數 」來設定 path
  • 利用 children:[] 來做路由套嵌(在某個頁面裡面的一堆連結)
  • 預設的頁面要設為 path: "",
  • 預設history模式為 createWebHistory(process.env.BASE_URL) ,若遇到 http://xxx/index.html 會失效(請後端重新設定網站設定)
  • 若將history模式改為 createWebHashHistory() ,網只會變成 http://xxx/index.html#/,可解決上方問題,但會跟原始錨點#相衝,SEO也不好(所以不建議,常用於後台界面)
  • 例外處理 path: '/:pathMatch(.*)*',
  • 接著修改主架構,以及個別頁面的內容。
  • 修改 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> <Header></Header> <router-view></router-view> <Footer></Footer> </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> <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>
  • src\views\NotFound.vue 內容(配合例外處理用) <script> export default {}; </script> <template> <div id="NotFoundpage"> <h3>404 Not Found</h3> </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 { text-align: center; font-size: 48px; color: #fff; @media screen and (max-width: 1044px) { font-size: 30px; 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>

    9-2 加入連結

  • 方法一 <router-link to="xx"> :會自動變成 <a href="/Rwd" class="nav-link router-link-active">RWD</a> <router-link to="/Rwd">RWD</router-link> <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 v-if="!isError"> <h1>{{ pageDetal.data.name }}</h1> <h2>NTD: {{ pageDetal.data.money }}</h2> <img :src="pageDetal.data.photo" alt="" /> <img :src="pageDetal.data.teacher?.img" alt="" /> <p>{{ pageDetal.data.teacher?.name }}</p> <h1 v-if="isError">{{ pageDetal.data.error_message }}</h1> </template> <style></style> const openNewTab = (id) => { const routeData = router.resolve({ path: `/courses/${id}` }); window.open(routeData.href, "_blank");
  • Vuex就是把資料拉出來,統一在外部存取,不需用props或emmits傳來傳去。
  • 啟用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: {},
  • state:{} 用來設定初始變數
  • actions:{} 用來設定讓組件用 dispatch() 呼叫的函數
  • 函式可以傳入 context 參數(用 context.state 就可取得 state 中的變數值),也可以有第二個參數,也就是外部傳進來的值
  • context.commit 觸發 muations 中的函式以改變資料值
  • muations:{} 用來改變 state 中資料值
  • 函式一般會傳入 state ,用來取得 state 中的變數值及第二個參數,也就是 actions 傳進來的值
  • 元件可以直接用 store.commit() 來執行 muations 中的函式(但不是好的作法)
  • getters:{} 用來重組資料(類似computed),函式一般會傳入 state ,用來取得 state 中的變數值
  • 從 Vuex 取出 useStore() 函式,用 store.state.isOpen 便可取到定義在state中的變數,但建議改用 store.getters.isOpen (或 store.getters['isOpen'] const isOpen = computed(() => { //return store.getters.isOpen; return store.getters["isOpen"];
  • 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> 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, handOpenState(context) { const isOpen = !context.state.isOpen; context.commit("OpenState", isOpen); 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, onMounted(() => { store.dispatch("Auth/handSetToken", "Acbz1x3WQw4eq9qilpFjregn"); console.log("TOKEN =>", store.getters["Auth/getToken"]); return {}; </script> 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')

    12-1 配置版面

  • 利用布局容器來配置版面( https://element-plus.gitee.io/#/zh-CN/component/container ),例如( src\App.vue ): <template> <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> </template>
  • 利用 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>

    13. 使用 websocket

  • 安裝 websocket 套件( https://www.npmjs.com/package/vue-native-websocket-vue3 npm install vue-native-websocket-vue3 --save
  • 在 main.js 加入設定以使用websocket 套件: import VueNativeSock from "vue-native-websocket-vue3"; // 使用VueNativeSock插件,並進行相關配置 app.use(VueNativeSock, "ws://120.115.2.76:8443", { store: store, format: 'json',
  • nuxt 是一個 vue 用來做 SSR 的框架(建構在Node之上)
  • SSR:Server Side Rendering 每一個不同頁面就回傳一份不同的 html 檔案 ,SEO好,但 loading 較大
  • SPA:Single Page Application 用 JavaScript 把畫面 render 出來,不會載入新的 HTML 檔案,不利于SEO ,先慢後快
  • nuxt 官網: https://nuxtjs.org/
  • 用 npx 或 npm 來建立專案 npx create-nuxt-app <project-name> npm init nuxt-app <project-name>
  • assets:用來放靜態元件,但此資料夾仍會被 nuxt 處理,例如壓縮圖片、CSS檔等
  • static:也是用來放靜態元件,但不會被 nuxt 處理,例如影片檔、pdf檔
  • middleware:中間層,從路由到頁面之間的中間層,可以用來做一些共同的檢查、驗證
  • plugins:全局套件,會自動注入到nuxt.config.js中,全局可用
  • store:vuex的部份
  • components:頁面最小的單位組件,可以直接使用,無須 import
  • 讓 nuxt 支援 scss,需安裝以下套件 npm install --save-dev sass sass-loader fibers
  • 伺服器啟動 ( nuxt start ) 當使用靜態網站產生時,伺服器步驟只在構建時執行,但每一個將要產生的頁面都會執行一次。
  • 產生過程開始 ( nuxt generate )
  • Nuxt hooks
  • serverMiddleware
  • Server-side Nuxt plugins:按照在 nuxt.config.js 中定義的順序進行。
  • nuxtServerInit
  • 僅在伺服器端呼叫的 Vuex 操作,用於預載 store。
  • 第一個參數是 Vuex context ,第二個參數是 Nuxt.js context
  • 從這裡  Dispatch 其他動作→伺服器端隨後的 store 唯一 "入口點"
  • 只能在 store/index.js 中定義。
  • Middleware
  • Global middleware
  • Layout middleware
  • Route middleware
  • asyncData
  • beforeCreate Vue生命週期方法
  • created Vue生命週期方法
  • 新的 fetch(從上到下,同胞元素=平行)
  • 狀態的序列化( render:routeContext Nuxt.js hook
  • HTML渲染( render:route Nuxt.js hook
  • render:routeDone hook 當HTML被髮送到瀏覽器時
  • generate:before Nuxt.js hook
  • 產生HTML檔案
  • 全靜態產生
  • generate:page (可編輯的HTML)
  • generate:routeCreated (Route 生成)
  • generate:done 當所有的HTML檔案都產生后
  • Client端的生命週期

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

  • 接收 HTML
  • 載入 assets (如: JavaScript)
  • Vue 激活
  • Middleware
  • Global middleware
  • Layout middleware
  • Route middleware
  • asyncData(blocking 鎖定)
  • client-side Nuxt.js plugin:按照在 nuxt.config.js 中定義的順序進行。
  • beforeCreate Vue生命週期方法
  • created Vue生命週期方法
  • 新的 fetch(從上到下,同胞元素=平行,non-blocking,無鎖定)
  • beforeMount Vue生命週期方法
  • mounted Vue生命週期方法
  • 14-2 後台的 asyncData

  • asyncData 是在 Server 端 處理非同步的生命週期(所以前端的各種函數或物件都不能用,例如:$this、alert()),只會執行一次,通常用來擷取API結果,其內容會覆蓋 data 中的東西 asyncData() { const name = 'Tad'; return {name};
  • data 的值會被 asyncData 覆蓋,所以除非有要再次修改 asyncData 中的變數,否則一般不會在 data 中去設一個一樣名稱的變數 data() { return { name = ''; methods{ handName() { this.name = 'Kai';
  • asyncData 只有在 page 中能用,其餘目錄沒有這個生命週期
  • 搭配 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>
  • fetch 可以在任何元件目錄下執行, asyncData 只能在 page 目錄下執行
  • fetch 在 vue 實體產生後才執行(所以可以取得 this) ,也就是在asyncData 之後
  • 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>
  • 若是加入 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>
  • $fetchState.pending 可以取得 fetch 是否執行完畢的狀態,例如: <template> <div class="container"> <Logo /> <h1 v-if="$fetchState.pending">載入中...</h1> <h1 v-if="!$fetchState.pending" class="title"> <ul v-if="!$fetchState.pending"> <li v-for="news in res" key="news.id">{{ news.title }}</li> </template> <Logo /> <h1 v-if="$fetchState.pending">載入中...</h1> <h1 v-if="$fetchState.error">出錯了...{{ $fetchState.error }}</h1> </template>
  • $fetchState.timestamp 可搭配 keep-alive 使用於 activated 生命週期中使用( activated 生命週期只有有用 keep-alive 時才會有),讓資料可以進行緩存,例如: <template> <Nuxt keep-alive /> </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> 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' },
  • 若是放在各個頁面,則可以做該頁面設定,例如: <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>
  • 也可以將 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: [ "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>

    14-5 Nuxt 使用 Router

  • 連結可以用 <NuxtLink to=""></NuxtLink > 或 <nuxt-link to=""></nuxt-link>,等同<router-link to=""></router-link>
  • 巢狀路由(嵌套式路由),例如: /about/aaa 只要在 pages 下建立 about 目錄,底下建立 aaa.vue 即可(其中 <Nuxt /> 要換成 <NuxtChild />)
  • 透過 vue router 去切換頁面的內容的時候在 nuxtjs 中我們使用 < Nuxt / > 這個組件來切換,而不是 <router-view /> <router-view /> -> <Nuxt />
  • 在 nuxtjs 裡面我們需要點擊連結然後切換router的頁面的時候,我們會使用 <NuxtLink to="/about">about </NuxtLink> 這種方式來做為我們的連結 <router-link to="/">Home</router-link> -> <NuxtLink to="/">Home</NuxtLink>
  • 當我們在做 嵌套網址 的時候,我們會使用 <NuxtChild /> 來切換我們頁面的內容,而不是用原本的 <router-view />,這樣在識別的時候也會比較好識別這個組件是不是用嵌套網址 <router-view /> -> <NuxtChild />
  • 如果要取得參數來切換內容,例如 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>
  • 若要在method中使用router,必須寫成 this.$router 的寫法,如: methods: { goToNews(id) { this.$router.push("/show/"+id); export default { props: ["error"], // layout: 'blog' // you can set a custom layout for the error page </script> <template> <h1 v-if="error.statusCode === 404">沒有此頁面</h1> <h1 v-else>喔喔~我秀斗了</h1> </template> export default ({ app }, inject) => { // 注入 $hello(msg) in Vue, context and store. inject('hello', msg => console.log(`Hello ${msg}!`)) 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}
  • 若是axios無法取得資料,可以用 try{}.catch(){} 方式來呈現錯誤,但一旦用到axios的地方很多,這樣會顯得麻煩,所以,可以做一個 plugin來處理。
  • 先建立 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')
  • 加入設定檔nuxt.config.js plugins: ["~/plugins/hello.js", "~/plugins/axios.js"],
  • 如此當api傳回404或500時就會自動導向到 page/404.vue或500.vue(要自己做) <script> export default { </script> <template> <h1>404 咩有~</h1> </template> <style lang="scss" scoped> </style>

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

  • 先安裝此套件 https://www.npmjs.com/package/vue-notification (要有支援SSR的套件才不會有問題) npm install --save vue-notification
  • 建立/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); 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;
  • https://developer.mozilla.org/zh-TW/docs/Web/API/Window/localStorage
  • 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
  • 建立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(); mounted() { this.$hello("我在前端被mounted了"); this.$localStorage.set("userData", { name: "tad", age: 48 }); console.log('userData', this.$localStorage.get("userData"));
  • store目錄內的每一個.js檔案都會被轉換為一個 namespaced module (index為根模組)。你的 state 應該始終是一個函式,以避免在伺服器端出現不必要的共享狀態。
  • 加入methods,以便觸發vuex actions中的方法,並加入 computed 以取得 getters 的值 methods: { handCounter() { this.$store.dispatch('handleAddCounter'); // console.log(this.$store.getters.getCounter); computed:{ getCount(){ return this.$store.getters.getCounter;
  • 樣板部份也加入按鈕並顯示之 <button @click="handCounter">按我計數+1 {{getCount}}</button>
  • 也可以從後端直接取得 state 的值 async asyncData({ app }) { return { counter: app.store.state.counter };
  • 從後端也可以利用 dispatch 將值塞進 state 中,若要如此,就不要 return,而是在 computed 中用 getters 去取得 state 的值,不然會造成兩份資料可能會不一致的問題。
  • 若要做成  Vuex module,只要在 store/ 下建立一個目錄即可,如 store/User/index.js
  • 若要拆分 Vuex,只要分別存成 store/User/state.js、store/User/actions.js、store/User/mutations.js、store/User/getters.js 即可
  •