前言
寫了 Vue心得 之後,在這年又開始寫Vue了
版本
$ node -v
v16.13.1
$ npm -v
8.1.2
$ 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"
https://stackoverflow.com/questions/32106155/can-you-force-vue-js-to-reload-re-render#comment94975841_48755228 Can you force Vue.js to reload/re-render?
https://stackoverflow.com/a/51170320 Do we have router.reload in vue-router?
https://stackoverflow.com/a/49646063 Vue-router reload components
在組件上新增 :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"
https://stackoverflow.com/a/54367510 Can you force Vue.js to reload/re-render?
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()
https://www.zhihu.com/question/49863095/answer/289157209 请问vue组件如何reload或者说vue-router如何刷新当前的route??
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
特定路由被緩存
https://www.jianshu.com/p/0b0222954483 vue-router 之 keep-alive
指定首頁(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>
https://stackoverflow.com/a/43858500 Why doesn't the data get updated in Vue Dev Tools?
為什麼Vue Dev Tools裡面的data不更新?
因為頁面沒變,所以加入 <div>{{parentCount}}</div> 把畫面更新,Vue Dev Tools裡面的data才會更新
:key
https://stackoverflow.com/a/51541950 What does the colon represent inside a VueJS/Vuetify/HTML component tag
:key 是 v-bind:key 的簡寫
@符號
https://stackoverflow.com/a/58313986 What does the @ symbol do in Vue.js?
標籤裡的@符號是 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
https://stackoverflow.com/a/43096892 Checking if a key exists in a JavaScript object?
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)
https://vuejs.org/examples/#modal Modal with Transitions
https://v2.cn.vuejs.org/v2/guide/transitions.html 进入/离开 & 列表过渡
或是直接用現成插件 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'
https://stackoverflow.com/a/72144014 Component name "Temp" should always be multi-word vue/multi-word-component-names
<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標籤
https://stackoverflow.com/a/49218055 How to use v-if and v-else without any html tag or else
<template v-if="condition">
</template>
<template v-else>
</template>
倒計時
https://stackoverflow.com/a/59923858 How do I create a simple 10 seconds countdown in Vue.js
<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
沒有留言:
張貼留言