--- tags: Vue,電商,第一章 --- # Vue電商 官方測試網站 https://play.vuejs.org/#eNo9jDsOwjAQRK+yuDEUENEiJxIdN6Bxg5INRPJnZa/TWL47S5BSvnkzU9Wd6LIWVDdl8pgWYsjIhQYbFk8xMVRIOEODOUUPWqraBhvGGDKDz2/of/6oH+hchGdMbjrokw2m+9/JkQCjJ/diFAIwn+tQ6zZuzXRCW7oEKgzr2ccJXW+VeKtEmW5fq/YFaYM9HQ== 必裝插件 ![](https://hackmd.io/_uploads/SyYudyHUh.png) 要求 node.js 16.0 以上 create vue 指令 ``` npm init vue@latest ``` ![](https://hackmd.io/_uploads/HyTRPuUHn.png) 創建好的項目資料夾內執行 ``` npm install npm run dev ``` # setup ```htmlmixed= <!-- 開關:容許在script書寫組合式API --> <script setup></script> <template> <!-- 不再要求唯一根元素<div id="app"></div> --> </template> <style scoped></style> ``` ![](https://hackmd.io/_uploads/H1hK6_IB3.png) Vue3舊方法 ```javascript= <script> export default { setup() { console.log('setup', this); // undefined // 數據 const message = 'this is message'; // 函數 const logMessage = () => { console.log(message); } return { message, logMessage } }, beforeCreate() { console.log('beforeCreate'); } } </script> <template> <div> {{ message }} <button @click="logMessage">log</button> </div> </template> ``` Vue3新方法 ```javascript= <script setup> console.log('setup', this); // undefined // 數據 const message = 'this is message'; // 函數 const logMessage = () => { console.log(message); } </script> <template> <div> {{ message }} <button @click="logMessage">log</button> </div> </template> ``` ## reactive和ref reactive ```javascript= <script setup> // 1.導入函數 import { reactive } from 'vue'; // 2.執行函數,傳入一個對象類型的參數 const state = reactive({ count: 0 }) const setCount = () => { state.count++; } </script> <template> <div> <button @click="setCount">{{ state.count }}</button> </div> </template> ``` ref ```javascript= <script setup> // 1.導入函數 import { ref } from 'vue'; // 2.執行函數,傳入一個[簡單類型 或 對象類型]的參數 const count = ref(0); const setCount = () => { // 修改ref產生的響應式對象數據,必須通過.value屬性 // ref函數內部還是依賴reactive函數 count.value++; } </script> <template> <div> <button @click="setCount">{{ count }}</button> </div> </template> ``` ## computed ```javascript= <script setup> // 1.導入函數 import { ref } from 'vue'; import { computed } from 'vue'; // 2.執行函數,return計算後的值 const list = ref([1, 2, 3, 4, 5, 6, 7, 8]); // computedList接收用的變量不要直接賦值 const computedList = computed(() => { return list.value.filter(item => item > 2) // 不可以異步請求和修改dom }) // 3.新增定時器來測試看看 setTimeout(() => { list.value.push(9,10) },3000) </script> <template> <div> 原始數組{{ list }} </div> <div> 計算屬性數組{{ computedList }} </div> </template> ``` ## watch 單數據 ```javascript= <script setup> // 1.導入函數 import { ref, watch } from 'vue'; const count = ref(0) const setCount = () => { count.value++ } // 這時ref對象不要加.value watch(count, (newVal, oldVal) => { console.log('count變化了', newVal, oldVal); }) </script> <template> <button @click="setCount">{{ count }}</button> </template> ``` 多數據 ```javascript= <script setup> // 1.導入函數 import { ref, watch } from 'vue'; const count = ref(0) const setCount = () => { count.value++; } const name = ref('cp'); const changeName = () => { name.value = 'pc'; } // 這時ref對象不要加.value watch([count, name], ([newCount, newName], [oldCount, oldName]) => { console.log('count或者name變化了', [newCount, newName],[oldCount, oldName]); }) </script> <template> <button @click="setCount">{{ count }}</button> <button @click="changeName">{{ name }}</button> </template> ``` 立即執行 ```javascript= <script setup> // 1.導入函數 import { ref, watch } from 'vue'; const count = ref(0) const setCount = () => { count.value++ } // 這時ref對象不要加.value watch(count, (newVal, oldVal) => { console.log('count變化了', newVal, oldVal) }, { immediate: true }) </script> <template> <button @click="setCount">{{ count }}</button> </template> ``` 如果是修改對象的屬性值不會觸發偵聽,須改用深度偵聽 深度偵聽很耗內存,工作不建議用 ```javascript= <script setup> // 1.導入函數 import { ref, watch } from 'vue'; const state = ref({count:0}) const change = () => { state.value.count++; } // 這時ref對象不要加.value watch(state, (newVal, oldVal) => { // 這時候newVal和oldVal都會顯示相同值 console.log('count變化了', newVal, oldVal) }, { deep:true }) </script> <template> <button @click="change">{{ state.count }}</button> </template> ``` 精確偵聽 ```javascript= <script setup> // 1.導入函數 import { ref, watch } from 'vue'; const state = ref({ name: 'cp', age:18 }) const changeName = () => { state.value.name = 'pink'; } const changeAge = () => { state.value.age = 20; } // 精確偵聽 watch( () => state.value.age, () => { console.log('age變化了'); } ) </script> <template> <button @click="changeName">{{ state.name }}</button> <button @click="changeAge">{{ state.age }}</button> </template> ``` ## 生命週期 ![](https://hackmd.io/_uploads/Sk35si18h.png) ```javascript= <script setup> // 1.導入函數 import { onMounted } from 'vue'; onMounted(() => { console.log('組件掛載完畢1'); // 前人寫的邏輯 }) onMounted(() => { console.log('組件掛載完畢3'); // 自己寫的邏輯 }) onMounted(() => { console.log('組件掛載完畢2'); }) </script> <template> <div> <!-- 組件掛載完畢1 --> <!-- 組件掛載完畢3 --> <!-- 組件掛載完畢2 --> </div> </template> ``` ## 父子通信 ### 父傳子 ```javascript= <script setup> import { ref } from 'vue'; import SonCom from './son-com.vue' const count = ref(100) setTimeout(() => { count.value = 200 }, 3000) </script> <template> <div class="father"> <h2>父組件app</h2> <!-- 綁屬性 --> <SonCom :count="count" message="father message" /> </div> </template> ``` ```javascript= <script setup> // 使用編譯器宏函數 // 接收數據 const props = defineProps({ message: String, count: Number }) console.log(props); </script> <template> <div class="son"> <h3>子組件app</h3> <div> 父組件傳入{{ message }} - {{ count }} </div> </div> </template> ``` ### 子傳父 ```javascript= <script setup> import SonCom from './son-com.vue' const getMessage = (msg) => { console.log(msg); } </script> <template> <div class="father"> <h2>父組件app</h2> <!-- 綁屬性 --> <SonCom @get-message="getMessage" /> </div> </template> ``` ```javascript= <script setup> const emit = defineEmits(['get-message']); const sendMsg = () => { // 用自定義事件傳數據給父組件 emit('get-message','this is son message') } </script> <template> <div class="son"> <h3>子組件app</h3> <button @click="sendMsg">觸發自定義事件</button> </div> </template> ``` ## 模版引用 ```javascript= <script setup> import { onMounted, ref } from 'vue'; import TestCom from './test-com.vue' // 用ref對象 const h1Ref = ref(null); const comRef = ref(null); // 組件掛載完才能獲取 onMounted(() => { console.log(h1Ref.value); console.log(comRef.value); }) </script> <template> <h1 ref="h1Ref">我是dom標籤h1</h1> <TestCom ref="comRef" /> </template> ``` ```javascript= <script setup> import { ref } from 'vue'; const name = ref('test name') const setName = () => { name.value = 'test new name' } // 暴露給父組件 defineExpose({ name, setName }) </script> <template> <div>我是test組件</div> <div>{{ name }}</div> <button @click="setName">改名</button> </template> ``` ## provide和inject ```javascript= <script setup> import { provide, ref } from 'vue'; import Mid from './mid-page.vue' // 提供數據 provide('data-key', 'this is top data'); // 傳遞響應式數據 const count = ref(0); provide('count-key', count) setTimeout(() => { count.value = 100 }, 3000) // 傳遞方法 const setCount = () => { count.value++ } provide('setCount-key', setCount) </script> <template> <div>頂層組件喔 <Mid /> </div> </template> <style scoped></style> ``` ```javascript= <script setup> import Bot from './bot-page.vue' </script> <template> <div class="main">我是Mid組件 <Bot /> </div> </template> <style scoped> .main{ margin: 20px; } </style> ``` ```javascript= <script setup> import { inject } from 'vue'; // 接收數據 const topData = inject('data-key'); // 接收響應式數據 const countData = inject('count-key'); // 接收方法 const setCount = inject('setCount-key'); </script> <template> <div class="main">底層組件</div> <div>來自頂層的數據{{ topData }}</div> <div>來自頂層的響應數據{{ countData }}</div> <div><button @click="setCount">修改</button></div> </template> <style scoped> .main { margin: 20px; } </style> ``` :::danger 小案例待補充 :fire: ::: ## pinia https://pinia.vuejs.org/zh/core-concepts/ ![](https://hackmd.io/_uploads/rkFRu8mL2.png) `npm install pinia` ### main.js ```javascript= import './assets/main.css' import { createApp } from 'vue' import App from './App.vue' // 1.導入 import { createPinia } from 'pinia' // 2.執行方法得到實例 const pinia = createPinia() // 3.把pinia實例加到app應用裡 createApp(App).use(pinia).mount('#app') ``` ### counter src/stores 新增counter.js ```javascript= // 導入一個方法 import { defineStore } from "pinia"; import { ref } from "vue"; export const useCounterStore = defineStore("counter", () => { // 定義數據(state) const count = ref(0); // 定義修改數據方法(action 同步+異步) const increment = () => { count.value++; }; // 以對象方式return供組件使用 return { count, increment, }; }); ``` 在app.vue裡 ```javascript= <script setup> // 1.導入use打頭的方法 import { useCounterStore } from '@/stores/counter' // 2.執行方法得到store實例對象 const counterStore = useCounterStore(); console.log(counterStore); </script> <template> <button @click="counterStore.increment">{{ counterStore.count }}</button> </template> ``` ### getters和異步action ```javascript= // 導入一個方法 import { defineStore } from "pinia"; import { computed, ref } from "vue"; import axios from "axios"; const API_URL = "http://geek.itheima.net/v1_0/channels"; export const useCounterStore = defineStore("counter", () => { // 定義數據(state) const count = ref(0); // 定義修改數據方法(action 同步+異步) const increment = () => { count.value++; }; // getter定義 const doubleCount = computed(() => count.value * 2); // 定義異步action const list = ref([]); const getList = async () => { const res = await axios.get(API_URL); // 第一個data是axios語法 // 第二個data是接口裡的對象名 list.value = res.data.data.channels; }; // 以對象方式return供組件使用 return { count, doubleCount, increment, list, getList, }; }); ``` ```javascript= <script setup> // 1.導入use打頭的方法 import { useCounterStore } from '@/stores/counter' import { onMounted } from 'vue'; // 2.執行方法得到store實例對象 const counterStore = useCounterStore(); console.log(counterStore); // 觸發action onMounted(() => { counterStore.getList() }) </script> <template> <button @click="counterStore.increment">{{ counterStore.count }}</button> {{ counterStore.doubleCount }} <ul> <li v-for="item in counterStore.list" :key="item.id">{{ item.name }}</li> </ul> </template> ``` ### storeToRefs ```javascript= // 導入一個方法 import { defineStore } from "pinia"; import { computed, ref } from "vue"; import axios from "axios"; const API_URL = "http://geek.itheima.net/v1_0/channels"; export const useCounterStore = defineStore("counter", () => { // 定義數據(state) const count = ref(0); // 定義修改數據方法(action 同步+異步) const increment = () => { count.value++; }; // getter定義 const doubleCount = computed(() => count.value * 2); // 定義異步action const list = ref([]); const getList = async () => { const res = await axios.get(API_URL); // 第一個data是axios語法 // 第二個data是接口裡的對象名 list.value = res.data.data.channels; }; // 以對象方式return供組件使用 return { count, doubleCount, increment, list, getList, }; }); ``` ```javascript= <script setup> // 1.導入use打頭的方法 import { useCounterStore } from '@/stores/counter' import { storeToRefs } from 'pinia'; import { onMounted } from 'vue'; // 2.執行方法得到store實例對象 const counterStore = useCounterStore(); console.log(counterStore); // 直接解構賦值(響應式丟失) // const { count, doubleCount } = counterStore // 方法包裹(保持響應式更新) const { count, doubleCount } = storeToRefs(counterStore) console.log(count, doubleCount); // 函數直接從原來的counterStore解構賦值 const { increment } = counterStore // 觸發action onMounted(() => { counterStore.getList() }) </script> <template> <button @click="increment">{{ count }}</button> {{ doubleCount }} <ul> <li v-for="item in counterStore.list" :key="item.id">{{ item.name }}</li> </ul> </template> ``` ## module-alias應用在vue 不用安裝包 在根目錄新增json文件 命名為jsconfig.json 貼以下代碼 ```json= { "compilerOptions": { "baseUrl": "./", "paths": { "@/*": ["src/*"] } } } ``` 這個只是聯想提示功能而已 實際的路徑轉換在vue本身帶有的vite.config.js檔案中實現