---
# System prepended metadata

title: Vue電商
tags: [電商, 第一章, Vue]

---

---
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檔案中實現
