var
是宣告全域變數;let
是宣告只存活在 {}
中的變數;const
是是宣告常數(無法再修改,但是 [array]
陣列及 {object}
物件可以塞值進去)
const user = {
name: "Tad",
age: 48,
address: "Tainan",
};
//解構(可以只抽出某幾個)
const { name, age, address } = user;
亦可直接將整個物件塞入:
const user_data = {
user,
sex: "男",
};
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]));
//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>
方法 | 描述 |
---|---|
concat() | 連接兩個或更多的數組,並返回結果。 |
join() | 把數組的所有元素放入一個字符串。元素通過指定的分隔符進行分隔。 |
pop() | 刪除並返回數組的最後一個元素 |
push(新元素, [新元素, ...]) | 向數組的末尾添加一個或更多元素,並返回新的長度。 |
reverse() | 顛倒數組中元素的順序。 |
shift() | 刪除並返回數組的第一個元素 |
slice() | 從某個已有的數組返回選定的元素 |
sort() | 對數組的元素進行排序 |
splice(開始索引, 數量, [新元素, ...]) | 刪除元素,並向數組添加新元素。 |
toSource() | 返回該對象的源代碼。 |
toString() | 把數組轉換為字符串,並返回結果。 |
toLocaleString() | 把數組轉換為本地數組,並返回結果。 |
unshift() | 向數組的開頭添加一個或更多元素,並返回新的長度。 |
valueOf() | 返回數組對象的原始值 |
方法 | 描述 |
---|---|
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() | 返回某個字符串對象的原始值。 |
setTimeout()
,必須在卸載時去 clearTimeout()
才行
<script>
export default {
setup() {
let timer = null;
onMounted(() => {
timer = setTimeout(() => {
router.go(-1);
}, 3000);
});
onUnmounted(() => {
clearTimeout(timer);
});
},
};
</script>
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>
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">
</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>
完整範例
v-on:事件="函式"
來進行事件綁定,簡寫:@事件="函式"
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>
完整範例
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>
完整範例
watch
用來監聽某個值
watch(被監聽變數, (新值, 舊值) => {
要做的事...
});
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>
完整範例:
https://vue3js.cn/docs/zh/api/options-lifecycle-hooks.html#beforecreate
onBeforeMount
=> DOM 渲染前執行onMounted
=> DOM 渲染完成後執行onBeforeUpdate
=> 資料更新DOM更改前執行onUpdated
=> 資料更新DOM更改後執行onBeforeUnmount
=> 組件銷毀前執行onUnmounted
=> 組件銷毀後執行onErrorCaptured
=> 當組件發出錯誤時後調用onRenderTracked
=> 監控 virtual DOM 重新選染時調用 ( 此事件告訴你操作什麼監聽了組件以及該操作的物件)onRenderTriggered
=> 監控 virtual DOM 重新選染時調用 ( 此事件告訴你操作什麼觸發了重新渲染,以及該操作的物件)
npm install --save axios vue-axios
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>
完整範例:
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版本npm -v
:目前npm的版本npm init
:新增 package.jsonnpm 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
:還原套件
npm install -g @vue/cli
檢查版本
vue --version
更新 vue-cli
npm update -g @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 (依賴)是被哪個函式庫所要求的等等,不過這個我們通常就放著不太會管它。
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')
App2.vue
就是一個 Component 組件import { createApp } from 'vue'
若有{}
表示是從組建中拆分出來的import App2 from './App2.vue'
若沒有表示是該組件預設匯出的(一般和檔名一致)import
共用的 css 檔 @
來代表以組件的 src
目錄為起點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>
<div>
<Header></Header>
<Footer></Footer>
</div>
</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 />
來呈現<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>
props
物件,並傳入 setup()
中,再 return
給樣板中呼叫使用
<script>
export default {
props:{
msg:{
type: String,
default: '我是預設文字'
}
},
setup(props) {
return { props };
},
};
</script>
<template>
<h1>{{ props.msg }}</h1>
</template>
[陣列]
跟 {物件}
,要用函式的方式去設定預設值,{物件}
更必須 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
}
}
}
<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>
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
出來實際範例:
<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>
<transition name="名稱"></transition>
在 style中有六種狀態(名稱規則是固定的)
.名稱-enter-active {} /* 整個進入動畫期間的狀態 */
.名稱-enter-from {} /* 進入動畫從 */
.名稱-enter-to {} /* 進入動畫止 */
.名稱-leave-active {} /* 整個離開動畫期間的狀態 */
.名稱-leave-from {} /* 離開動畫從 */
.名稱-leave-to {} /* 離開動畫止 */
完整範例:
<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>
範例內容:
<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>
<EventBack @click="handleEventBack(100, 'test', $event)" />
利用$event
可以取得點擊的所有事件$event
一定要放在最後面$event
就有包含所有資訊$event.target
就是點擊的實體本身
<script>
export default {
setup() {
},
};
</script>
<template>
<a href="javascript:;">點我</a>
</template>
<style></style>
<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>
const txtInput = ref(null)
,然後在樣板中用 ref="txtInput"
綁定即可txtInput.value.focus();
完整範例:
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')
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()
一致
<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>
完整範例:
<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>
<slot></slot>
包起來,未來要替換用的,也就是做成一個插槽
<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>
<SlotTest />
要改成<slot-test>欲插入內容</slot-test>
完整內容:
npm install vue-router
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
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: "",
createWebHistory(process.env.BASE_URL)
,若遇到 http://xxx/index.html 會失效(請後端重新設定網站設定)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>
<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>
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>
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>
<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>
<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>
useRoute()
取得所有 route 傳入的資料(在 route.params
中,例如 route.params.id
):
route.params
route.path
useRouter()
提供操作網址用的函式(例如: router.push("\Home")
用來轉向):
router.push("\Home")
router.push({
path: "\Home"
})
router.go(-1)
const openNewTab = (id) => {
const routeData = router.resolve({ path: `/courses/${id}` });
window.open(routeData.href, "_blank");
};
\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
傳進來的值muations
中的函式(但不是好的作法)getters:{}
用來重組資料(類似computed),函式一般會傳入 state
,用來取得 state
中的變數值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,
},
});
export default {
isOpen: false,
};
export default {
handOpenState(context) {
const isOpen = !context.state.isOpen;
context.commit("OpenState", isOpen);
},
};
export default {
OpenState(state, payload) {
state.isOpen = payload;
},
};
export default {
isOpen(state) {
return state.isOpen;
},
};
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"]);
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,
},
});
<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>
my-api
my-api/useWebSocket.js
// 連線 websocket
export function webSocket(url) {
const ws = new WebSocket("ws://" + url);
return ws;
}
my-api/index.js
import { webSocket } from "./useWebSocket.js";
export const useWebSocket = webSocket;
import { useWebSocket } from "../my-api";
export default {
setup() {
const ws = useWebSocket("120.115.2.76:8443/");
},
};
npm install element-plus --save
npm i element-theme -g
npm i element-theme-chalk -D
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')
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>
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>
npm install vue-native-websocket-vue3 --save
import VueNativeSock from "vue-native-websocket-vue3";
// 使用VueNativeSock插件,並進行相關配置
app.use(VueNativeSock, "ws://120.115.2.76:8443", {
store: store,
format: 'json',
});
connectManually: true
npx create-nuxt-app <project-name>
npm init nuxt-app <project-name>
cd <project-name>
npm run dev
npm install --save-dev sass sass-loader fibers
nuxt start
) 當使用靜態網站產生時,伺服器步驟只在構建時執行,但每一個將要產生的頁面都會執行一次。nuxt generate
) nuxt.config.js
中定義的順序進行。store/index.js
中定義。render:routeContext
Nuxt.js hook)render:route
Nuxt.js hook)render:routeDone
hook 當HTML被髮送到瀏覽器時generate:before
Nuxt.js hookgenerate:page
(可編輯的HTML)generate:routeCreated
(Route 生成)generate:done
當所有的HTML檔案都產生后無論你選擇哪種Nuxt.js模式,這部分的生命週期都會在瀏覽器中完全執行。
nuxt.config.js
中定義的順序進行。
asyncData() {
const name = 'Tad';
return {name};
},
data() {
return {
name = '';
};
},
methods{
handName() {
this.name = 'Kai';
}
}
<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>
<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">
<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>
$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>
$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>
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' },
]
},
<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>
<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>
export default {
ssr: false,
...略...
};
<router-view /> -> <Nuxt />
<router-link to="/">Home</router-link> -> <NuxtLink to="/">Home</NuxtLink>
<router-view /> -> <NuxtChild />
<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>
methods: {
goToNews(id) {
this.$router.push("/show/"+id);
},
},
<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>
export default ({ app }, inject) => {
// 注入 $hello(msg) in Vue, context and store.
inject('hello', msg => console.log(`Hello ${msg}!`))
}
export default {
plugins: ['~/plugins/hello.js']
}
this.$外掛名稱
,後端用$外掛名稱
,如:
export default {
mounted() {
this.$hello('我在前端被mounted了')
},
asyncData({ app, $hello }) {
$hello('我在後端被asyncData了')
}
}
asyncData(context) {
context.$hello('我在後端被asyncData了')
},
npm install @nuxtjs/axios
modules: ['@nuxtjs/axios'],
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}
},
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')
}
})
}
plugins: ["~/plugins/hello.js", "~/plugins/axios.js"],
<script>
export default {
};
</script>
<template>
<h1>404 咩有~</h1>
</template>
<style lang="scss" scoped>
</style>
npm install --save vue-notification
import Vue from "vue";
// for SPA:
// import Notifications from 'vue-notification'
// for SSR:
import Notifications from "vue-notification/dist/ssr.js";
Vue.use(Notifications);
plugins: ["~/plugins/hello.js", "~/plugins/axios.js","~/plugins/notification.js"],
<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>
methods: {
handNotify() {
this.$notify({
group: "foo",
title: "Important message",
text: "Hello user! This is a notification!",
});
}
},
<template>
<div>
<h1>關於</h1>
<button @click="handNotify">按我</button>
</div>
</template>
.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;
}
css: ['~/assets/notification.css'],
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();
}
});
};
plugins: [
"~/plugins/hello.js",
"~/plugins/axios.js",
"~/plugins/notification.js",
"~/plugins/localStorage.js"
],
mounted() {
this.$hello("我在前端被mounted了");
this.$localStorage.set("userData", { name: "tad", age: 48 });
console.log('userData', this.$localStorage.get("userData"));
},
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}`;
}
};
state
應該始終是一個函式,以避免在伺服器端出現不必要的共享狀態。
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>
async asyncData({ app }) {
return { counter: app.store.state.counter };
},