前言
版本
$ vue --version
@vue/cli 5.0.4
創建一個新項目
$ vue create vue2022
手動選擇
? Please pick a preset:
Default ([Vue 3] babel, eslint)
Default ([Vue 2] babel, eslint)
> Manually select features
選擇需要的feature
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
(*) Router
>(*) Vuex
( ) CSS Pre-processors
(*) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
選擇vue版本
? Choose a version of Vue.js that you want to start the project with
3.x
> 2.x
使用history 模式
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y
選擇linter / formatter配置
? Pick a linter / formatter config:
ESLint with error prevention only
ESLint + Airbnb config
> ESLint + Standard config
ESLint + Prettier
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
>(*) Lint on save
( ) Lint and fix on commit
配置文件
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
> In dedicated config files
In package.json
新建完專案後,會自動提交一個init的git commit
同組件路由切換時不重新渲染組件
如/about2 和 /about 都使用 views/AboutView.vue 組件:
diff --git a/src/App.vue b/src/App.vue
index 240acf4..2995013 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -2,7 +2,8 @@
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
- <router-link to="/about">About</router-link>
+ <router-link to="/about">About</router-link> |
+ <router-link to="/about2">About2</router-link>
</nav>
<router-view/>
</div>
diff --git a/src/router/index.js b/src/router/index.js
index a395a1f..5d09db5 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -17,6 +17,11 @@ const routes = [
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
+ },
+ {
+ path: '/about2',
+ name: 'about2',
+ component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]
diff --git a/src/views/AboutView.vue b/src/views/AboutView.vue
index 3fa2807..16bb285 100644
--- a/src/views/AboutView.vue
+++ b/src/views/AboutView.vue
@@ -1,5 +1,17 @@
<template>
<div class="about">
- <h1>This is an about page</h1>
+ <h1>This is an {{$route.name}} page</h1>
</div>
</template>
+
+<script>
+export default {
+ name: 'AboutView',
+ created () {
+ console.log('AboutView.vue')
+ console.log(this.$router)
+ console.log(this.$route)
+ console.log(this.$route.path)
+ }
+}
+</script>
s
切換路由時,組件的created不被呼叫
自動更新 - :key="$route.fullPath"
在組件上新增 :key="$route.fullPath"
diff --git a/src/App.vue b/src/App.vue
index 69ddd1a..4f3e76c 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -8,6 +8,7 @@
<router-view
+ :key="$route.fullPath"/>
</div>
</template>
s
手動更新 - :key="componentKey"
diff --git a/src/App.vue b/src/App.vue
index 4f3e76c..e4e7a4a 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -8,7 +8,8 @@
<router-view
:parentString="string"
@reloadComponentEvent="emitReloadComponentEvent($event)"
+ :key="componentKey"
+ @forceRerenderEvent="emitForceRerenderEvent($event)"
v-if="isRouterAlive"/>
</div>
</template>
@@ -18,7 +19,8 @@ export default {
data () {
return {
isRouterAlive: true,
+ componentKey: 0
}
},
methods: {
@@ -28,6 +30,14 @@ export default {
},
emitReloadComponentEvent () {
this.reload()
+ },
+ forceRerender () {
+ this.componentKey += 1
+ },
+ emitForceRerenderEvent (name) {
+ console.log('emitForceRerenderEvent')
+ console.log(name)
+ this.forceRerender()
}
}
}
diff --git a/src/views/AboutView.vue b/src/views/AboutView.vue
index fafb0f5..7678eb2 100644
--- a/src/views/AboutView.vue
+++ b/src/views/AboutView.vue
@@ -1,6 +1,7 @@
<template>
<div class="about">
<h1 @click="reloadComponent">This is an {{$route.name}} page:{{parentString}}</h1>
+ <button @click="clickForceRerender">forceRerender</button>
</div>
</template>
@@ -17,6 +18,10 @@ export default {
reloadComponent () {
console.log('reloadComponent')
this.$emit('reloadComponentEvent', this.$route.name)
+ },
+ clickForceRerender () {
+ console.log('forceRerender')
+ this.$emit('forceRerenderEvent', this.$route.name)
}
},
props: {
s
手動更新 - this.$nextTick()
diff --git a/src/App.vue b/src/App.vue
index 2995013..3eb8189 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -5,10 +5,31 @@
<router-link to="/about">About</router-link> |
<router-link to="/about2">About2</router-link>
</nav>
- <router-view/>
+ <router-view
+ @reloadComponentEvent="emitReloadComponentEvent($event)"
+ v-if="isRouterAlive"/>
</div>
</template>
+<script>
+export default {
+ data () {
+ return {
+ isRouterAlive: true
+ }
+ },
+ methods: {
+ reload () {
+ this.isRouterAlive = false
+ this.$nextTick(() => (this.isRouterAlive = true))
+ },
+ emitReloadComponentEvent () {
+ this.reload()
+ }
+ }
+}
+</script>
+
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
diff --git a/src/views/AboutView.vue b/src/views/AboutView.vue
index 16bb285..545956a 100644
--- a/src/views/AboutView.vue
+++ b/src/views/AboutView.vue
@@ -1,6 +1,6 @@
<template>
<div class="about">
- <h1>This is an {{$route.name}} page</h1>
+ <h1 @click="reloadComponent">This is an {{$route.name}} page</h1>
</div>
</template>
@@ -12,6 +12,12 @@ export default {
console.log(this.$router)
console.log(this.$route)
console.log(this.$route.path)
+ },
+ methods: {
+ reloadComponent () {
+ console.log('reloadComponent')
+ this.$emit('reloadComponentEvent', this.$route.name)
+ }
}
}
</script>
s
特定路由被緩存
指定首頁(home)緩存 => 即不重新渲染,HomeView.vue不重複呼叫created()
about、about2不緩存 => about、about2需用不同組件(component)
diff --git a/src/App.vue b/src/App.vue
index eda18d3..588bafb 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -5,12 +5,14 @@
<router-link to="/about">About</router-link> |
<router-link to="/about2">About2</router-link>
</nav>
+ <keep-alive>
+ <router-view v-if="$route.meta.keepAlive">
+ <!-- 这里是会被缓存的视图组件,比如 home -->
+ </router-view>
+ </keep-alive>
+ <router-view v-if="!$route.meta.keepAlive">
+ <!-- 这里是不被缓存的视图组件,比如 about、about2 -->
+ </router-view>
</div>
</template>
diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue
index 1c544cb..f59096c 100644
--- a/src/components/HelloWorld.vue
+++ b/src/components/HelloWorld.vue
@@ -37,6 +37,9 @@ export default {
name: 'HelloWorld',
props: {
msg: String
+ },
+ created () {
+ console.log('HelloWorld.vue')
}
}
</script>
diff --git a/src/router/index.js b/src/router/index.js
index 5d09db5..1b097c7 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -8,7 +8,10 @@ const routes = [
{
path: '/',
name: 'home',
+ component: HomeView,
+ meta: {
+ keepAlive: true // 需要被缓存
+ }
},
{
path: '/about',
@@ -16,12 +19,18 @@ const routes = [
{
path: '/about',
name: 'about',
- component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
+ component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue'),
+ meta: {
+ keepAlive: false // 不需要被缓存
+ }
},
{
path: '/about2',
name: 'about2',
- component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
+ component: () => import(/* webpackChunkName: "about" */ '../views/About2View.vue'),
+ meta: {
+ keepAlive: false // 不需要被缓存
+ }
}
]
diff --git a/src/views/About2View.vue b/src/views/About2View.vue
new file mode 100644
index 0000000..28f3639
--- /dev/null
+++ b/src/views/About2View.vue
@@ -0,0 +1,18 @@
+<template>
+ <div class="about">
+ <h1>This is an {{$route.name}} page:</h1>
+ <button>forceRerender</button>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'About2View',
+ created () {
+ console.log('About2View.vue')
+ console.log(this.$router)
+ console.log(this.$route)
+ console.log(this.$route.path)
+ }
+}
+</script>
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index e8d96d7..2eca718 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -13,6 +13,9 @@ export default {
name: 'HomeView',
components: {
HelloWorld
+ },
+ created () {
+ console.log('HomeView.vue')
}
}
</script>
s
組件間溝通
父傳子(props)
diff --git a/src/components/ChildComponent.vue b/src/components/ChildComponent.vue
index e47d61b..7e2b2fe 100644
--- a/src/components/ChildComponent.vue
+++ b/src/components/ChildComponent.vue
@@ -1,12 +1,22 @@
<template>
<div class="child">
ChildComponent
+ <div>{{obj.title}}</div>
+ <div>{{obj.content}}</div>
</div>
</template>
<script>
export default {
- name: 'ChildComponent'
+ name: 'ChildComponent',
+ props: {
+ obj: {
+ type: Object,
+ default: function () {
+ return {}
+ }
+ }
+ }
}
</script>
diff --git a/src/views/ParentView.vue b/src/views/ParentView.vue
index adb9021..304371a 100644
--- a/src/views/ParentView.vue
+++ b/src/views/ParentView.vue
@@ -6,7 +6,9 @@
<div id="right">right<br>right line 2</div>
<div class="clear"></div>
</div>
- <child-component></child-component>
+ <child-component
+ :obj="singleObj"
+ ></child-component>
</div>
</template>
@@ -16,6 +18,14 @@ import ChildComponent from '@/components/ChildComponent'
export default {
name: 'ParentView',
components: { ChildComponent },
+ data () {
+ return {
+ singleObj: {
+ title: 'props',
+ content: '父傳子'
+ }
+ }
+ },
created () {
console.log('ParentView.vue')
}
s
子傳父(event emitter)
diff --git a/src/components/ChildComponent.vue b/src/components/ChildComponent.vue
index 7e2b2fe..45920ee 100644
--- a/src/components/ChildComponent.vue
+++ b/src/components/ChildComponent.vue
@@ -3,12 +3,18 @@
ChildComponent
<div>{{obj.title}}</div>
<div>{{obj.content}}</div>
+ <button @click="callParent">子傳父</button>
</div>
</template>
<script>
export default {
name: 'ChildComponent',
+ data () {
+ return {
+ count: 0
+ }
+ },
props: {
obj: {
type: Object,
@@ -16,6 +22,12 @@ export default {
return {}
}
}
+ },
+ methods: {
+ callParent () {
+ this.count++
+ this.$emit('callParentEvent', this.count)
+ }
}
}
</script>
diff --git a/src/views/ParentView.vue b/src/views/ParentView.vue
index 304371a..0b2cfdd 100644
--- a/src/views/ParentView.vue
+++ b/src/views/ParentView.vue
@@ -8,7 +8,9 @@
</div>
<child-component
:obj="singleObj"
+ @callParentEvent="callMe($event)"
></child-component>
+ <div>{{parentCount}}</div>
</div>
</template>
@@ -23,11 +25,18 @@ export default {
singleObj: {
title: 'props',
content: '父傳子'
- }
+ },
+ parentCount: null
}
},
created () {
console.log('ParentView.vue')
+ },
+ methods: {
+ callMe (count) {
+ console.log(count)
+ this.parentCount = count
+ }
}
}
</script>
s
<div>{{parentCount}}</div>
為什麼Vue Dev Tools裡面的data不更新?
因為頁面沒變,所以加入 <div>{{parentCount}}</div> 把畫面更新,Vue Dev Tools裡面的data才會更新
:key
:key 是 v-bind:key 的簡寫
@符號
標籤裡的@符號是 v-on 的簡寫
VUEX
版本
"vue": "^2.6.14",
"vuex": "^3.6.2"
設定state
diff --git a/src/store/index.js b/src/store/index.js
index ceffa8e..c0deccd 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,17 +1,26 @@
+import { COUNT_MUTATION } from '@/store/mutation-types'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
+ count: 0
},
getters: {
},
mutations: {
+ [COUNT_MUTATION] (state, n) {
+ state.count += n
+ }
},
actions: {
},
diff --git a/src/store/mutation-types.js b/src/store/mutation-types.js
new file mode 100644
index 0000000..492a305
--- /dev/null
+++ b/src/store/mutation-types.js
@@ -0,0 +1,2 @@
+export const COUNT_MUTATION = 'COUNT_MUTATION'
diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index 0b2cfdd..e3e3708 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -1,21 +1,27 @@
<template>
<div>
<div>TestView.vue</div>
+ <div @click="increase(1)">increase 1</div>
+ <div @click="increase(2)">increase 2</div>
</div>
</template>
<script>
import ChildComponent from '@/components/ChildComponent'
+import store from '@/store'
export default {
name: 'TestView',
@@ -36,7 +42,19 @@ export default {
callMe (count) {
console.log(count)
this.parentCount = count
+ },
+ increase (n) {
+ store.commit('COUNT_MUTATION', n)
+ }
}
}
</script>
s
獲取state
通過屬性訪問(store.getters.countInGetters)或mapGetters輔助函數
diff --git a/src/store/index.js b/src/store/index.js
index a0bfcb7..24dff6e 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -8,6 +8,9 @@ export default new Vuex.Store({
count: 0
},
getters: {
+ countInGetters (state) {
+ return state.count
+ }
},
mutations: {
increment (state, n) {
diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index 3b4e0b9..3e8272f 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -14,12 +14,14 @@
<div>{{parentCount}}</div>
<div @click="increase(1)">increase 1</div>
<div @click="increase(2)">increase 2</div>
+ <div @click="testGetter()">test</div>
</div>
</template>
<script>
import ChildComponent from '@/components/ChildComponent'
import store from '@/store'
+import { mapGetters } from 'vuex'
export default {
name: 'TestView',
@@ -43,7 +45,16 @@ export default {
},
increase (n) {
store.commit('increment', n)
+ },
+ testGetter () {
+ console.log(store.getters.countInGetters)
+ console.log(this.countInGetters)
}
+ },
+ computed: {
+ ...mapGetters([
+ 'countInGetters'
+ ])
}
}
</script>
s
createLogger 插件
diff --git a/src/store/index.js b/src/store/index.js
index 24dff6e..c856f10 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,5 +1,5 @@
import Vue from 'vue'
-import Vuex from 'vuex'
+import Vuex, { createLogger } from 'vuex'
Vue.use(Vuex)
@@ -20,5 +20,6 @@ export default new Vuex.Store({
actions: {
},
modules: {
- }
+ },
+ plugins: [createLogger()]
})
s
lodash
檢查object有沒有key
vue 2 內建lodash,直接import
diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index e3e3708..79024b7 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -22,6 +22,7 @@
import ChildComponent from '@/components/ChildComponent'
import store from '@/store'
import { mapGetters } from 'vuex'
+import _ from 'lodash'
export default {
name: 'TestView',
@@ -32,11 +33,26 @@ export default {
title: 'props',
content: '父傳子'
},
- parentCount: null
+ parentCount: null,
+ todos: [
+ {
+ id: 1,
+ text: 'text 1',
+ done: true
+ },
+ {
+ id: 2,
+ text: 'text 2',
+ done: false
+ }
+ ]
}
},
created () {
console.log('TestView.vue')
+ console.log(_.get(this.todos, '0.text')) // text 1
+ console.log(_.get(this.todos, '1.id')) // 2
+ console.log(_.get(this.todos, '2.id')) // undefined
},
methods: {
callMe (count) {
s
彈窗modal
使用到的特性:組件,prop 傳遞,插槽 (slot),過渡 (transitions)
或是直接用現成插件 vue2-simplert-plugin 或 mint-ui的Message box
ModalComponent.vue
<template>
<Transition name="modal">
<div v-if="show" class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">default header</slot>
</div>
<div class="modal-body">
<slot name="body">default body</slot>
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button
class="modal-default-button"
@click="$emit('close')"
>OK
</button>
</slot>
</div>
</div>
</div>
</div>
</Transition>
</template>
<script>
export default {
name: 'ModalComponent',
props: {
show: Boolean
}
}
</script>
<style scoped>
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: table;
transition: opacity 0.3s ease;
}
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
width: 300px;
margin: 0px auto;
padding: 20px 30px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
transition: all 0.3s ease;
}
.modal-header h3 {
margin-top: 0;
color: #42b983;
}
.modal-body {
margin: 20px 0;
}
.modal-default-button {
float: right;
}
/*
* The following styles are auto-applied to elements with
* transition="modal" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the modal transition by editing
* these styles.
*/
.modal-enter-from {
opacity: 0;
}
.modal-leave-to {
opacity: 0;
}
.modal-enter-from .modal-container,
.modal-leave-to .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
</style>
s
name: 'ModalComponent', 組件名字必須是多個單字,否則要在 .eslintrc.js 加入 'vue/multi-word-component-names': 'off'
<slot name="header">default header</slot> 組件內的具名插槽
TestView.vue
diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index d9c5d70..b79c879 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -12,9 +12,28 @@
@callParentEvent="callMe($event)"
></child-component>
<div>{{parentCount}}</div>
+ <button id="show-modal" @click="showModal = true">Show Modal</button>
+ <!-- use the modal component, pass in the prop -->
+ <modal-component :show="showModal" @close="showModal = false">
+ <template v-slot:header>
+ <h3>custom header1, {{ slotProps.user.firstName }}</h3>
+ </template>
+ </modal-component>
</div>
</template>
@@ -23,10 +42,14 @@ import ChildComponent from '@/components/ChildComponent'
import store from '@/store'
import { mapGetters } from 'vuex'
import _ from 'lodash'
+import ModalComponent from '@/components/ModalComponent'
export default {
name: 'TestView',
+ components: {
+ ModalComponent
+ },
data () {
return {
singleObj: {
@@ -45,7 +68,15 @@ export default {
text: 'text 2',
done: false
}
- ]
+ ],
+ showModal: false
}
},
created () {
s
具名插槽 v-slot:header 可簡寫成 #header (WebStorm都會自動提示插槽名)。slot="header"是2.6.0 起被廢棄的寫法
.eslintrc.js
允許空白行
rules: {
+ 'no-trailing-spaces': 'off',
其他語法
v-if和v-else不要html標籤
<template v-if="condition">
</template>
<template v-else>
</template>
倒計時
<template>
{{ timerCount }}
</template>
<script>
export default {
data() {
return {
timerCount: 30
}
},
watch: {
timerCount: {
handler(value) {
if (value > 0) {
setTimeout(() => {
this.timerCount--;
}, 1000);
}
},
immediate: true // This ensures the watcher is triggered upon creation
}
}
}
</script>
s