Vue環境搭建
方式1 全局安裝 vue-cli $ npm install --global vue-cli # 創建一個基於 webpack 模板的新項目 $ vue init webpack my-project # 安裝依賴 $ cd my-project $ npm install $ npm run dev 方式2: 直接引入對應的js文件
基礎知識
所謂指令,其實本質就是在模板中出現的特殊標記,根據這些標記讓框架知道需要對這里的 DOM 元素進行什么操作。
v-text
- 解釋:更新元素的 textContent,將數據解析為純文本
<h1 v-text="msg"></h1>
這里v是vue的前綴,text是指令ID,msg是expression。 msg作為ViewModel,當它的值發生改變時,就觸發指令text,重新計算標簽的textContent(innerText)。
Mustache{{}}
- 解釋:可以局部更新元素的 textContent,將數據解析為純文本
<div id="app"> <p>蘋果的價格為{{ msg }}元</p> </div>
v-html
- 解釋:更新元素的 innerHTML,將數據解析成html標簽
<h1 v-html="msg"></h1>
v-bind
- 作用:綁定屬性
- 語法:
v-bind:title="msg"
- 簡寫:
:title="msg"
v-on
- 作用:綁定事件
- 語法:
v-on:click="say"
orv-on:click="say('參數', $event)"
- 簡寫:
@click="say"
- 說明:綁定的事件從
methods
中獲取
使用逗號分割綁定多個事件
<div v-on="click:onClick, keyup:onKeyup, keydown:onKeydown"></div>
事件修飾符
.stop
阻止冒泡,調用 event.stopPropagation() // 比如說一個div下有一個彈窗和一個表單 點擊提交按鈕點擊了<input type="submit" />提交表單信息 點擊彈出層周邊空白區域,關閉彈窗 ,當表單顯示在彈窗層上方時,為防止彈窗被意外關閉,需要阻止表單提交按鈕冒泡行為.prevent
阻止默認事件,調用 event.preventDefault() // 比如說點擊了鏈接標簽a,在跳轉之前,要修改一些URL參數.capture
添加事件偵聽器時使用事件捕獲
模式 // 捕獲階段先於冒泡,如果在盡量頂層處理事件,然后阻止傳播,可以略微節約性能開銷。scroll/resize 這類可能連續觸發的事件不冒泡的原因.self
只當事件在該元素本身(比如不是子元素)觸發時觸發回調.once
事件只觸發一次- 按鍵修飾符
<input v-on:keyup.13="submit">
記住所有的 keyCode 比較困難,所以 Vue 為最常用的按鍵提供了別名: <input v-on:keyup.enter="submit"> <input @keyup.enter="submit"> 按鍵別名包括:.enter .tab .delete (捕獲 “刪除” 和 “退格” 鍵) .esc .space .up .down .left .right.ctrl .shift .meta(windows 鍵,mac-command 鍵,)
.native
在父組件中給子組件綁定一個原生的事件,不加'. native'事件是無法觸發的
<div id="app"> <my-component @click.native="clickFun"></my-component> </div>
Vue.component('my-component', { template: `<a href='#'>click me</a>` }) new Vue({ el: '#app', methods: { clickFun: function(){ console.log("message: success") } } })
v-model
- 作用:在表單元素上創建雙向數據綁定
- 說明:監聽用戶的輸入事件以更新數據
<input v-model="message" placeholder="edit me"> <input type='radio' v-model="radioVal" value='單選按鈕' placeholder="edit me"> <!-- checkboxArr的值必須為數組 --> <input type='checkbox' v-model="checkboxArr" value='復選按鈕' placeholder="edit me">
v-for
- 作用:基於源數據多次渲染元素或模板塊
<div v-for="item in items"> {{ item.text }} </div> <!-- 遍歷數組 item 為當前項,index 為索引 --> <p v-for="(item, index) in list">{{item}} -- {{index}}</p> <!--遍歷對象 item 為值,key 為鍵,index 為索引 --> <p v-for="(item, key, index) in obj">{{item}} -- {{key}}</p>
<!-- 遍歷常量 item為從1開始的遞增值 --> <p v-for="item in 10">{{item}}</p>
v-for的key屬性
- 推薦:使用
v-for
的時候提供key
屬性,以獲得性能提升。 - 說明:使用
v-for
更新已渲染的元素列表時,默認用就地復用
策略;列表數據修改的時候,它會根據key值去判斷某個值是否修改,如果修改,則重新渲染這一項,否則復用之前的元素; 我們在使用的使用經常會使用index
(即數組的下標)來作為key
,但其實這是不推薦的一種使用方法;
const list = [ { id: 1, name: 'test1', }, { id: 4, name: '我是插隊的那條數據', } { id: 2, name: 'test2', }, { id: 3, name: 'test3', }, ] 之前的數據 之后的數據 key: 0 index: 0 name: test1 key: 0 index: 0 name: test1 key: 1 index: 1 name: test2 key: 1 index: 1 name: 我是插隊的那條數據 key: 2 index: 2 name: test3 key: 2 index: 2 name: test2 key: 3 index: 3 name: test3
// 不推薦 <div v-for="(item, index) in list" :key="index" >{{item.name}}</div> //推薦 <div v-for="(item, index) in list" :key="item.id" >{{item.name}}</div>
v-class和v-style
- 說明:這兩個都是HTML元素的屬性,使用
v-bind
,只需要通過表達式計算出字符串結果即可 - 表達式的類型:字符串、數組、對象
- 語法:
<!-- 1 對象書寫方式--> <div v-bind:class="{ active: true }"></div> ===> <div class="active"></div> <!-- 2 數組書寫方式--> <div :class="['active', 'text-danger']"></div> ===> <div class="active text-danger"></div> <!-- 3 數組對象結合--> <div v-bind:class="[{ active: true }, errorClass]"></div> ===> <div class="active text-danger"></div> <!-- style --> <!-- 1 對象書寫方式 --> <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> <!-- 2 數組書寫方式--> <div v-bind:style="[baseStyles, overridingStyles]"></div>
v-if 和 v-show
v-if
:根據表達式的值的真假條件,銷毀或重建元素 v-if適合條件不大可能改變的場景v-show
:根據表達式之真假值,切換元素的 display CSS 屬性,dom元素一直在 v-show適合頻繁切換
v-cloak
- 這個指令保持在元素上直到關聯實例結束編譯。和 CSS 規則如 [v-cloak] { display: none } 一起用時,這個指令可以隱藏未編譯的 Mustache 標簽直到實例准備完畢。
- 防止刷新頁面,網速慢的情況下出現{{ message }}等數據格式
<div v-cloak> {{ message }} </div>
v-pre
- 說明:跳過這個元素和它的子元素的編譯過程。可以用來顯示原始 Mustache 標簽。跳過大量沒有指令的節點會加快編譯。
<span v-pre>{{ this will not be compiled }}</span>
v-once
- 說明:只渲染元素和組件一次。隨后的重新渲染,元素/組件及其所有的子節點將被視為靜態內容並跳過。這可以用於優化更新性能。
<span v-once>This will never change: {{msg}}</span>
自定義指令
指令定義函數提供了幾個鈎子函數 (可選):
bind
-只調用一次,指令第一次綁定到元素時調用。insert
-被綁定元素插入父節點時調用。update
-所在組件的 VNode 更新時調用,但是可能發生在其子元素的 VNode 更新之前。componentUpdated
-所在組件的 VNode 及其子元素的 VNode 全部更新時調用。unbind
-只調用一次,指令與元素解綁時調用
這五個鈎子函數中bind
和update
是最有用的。
他們中的每一個都有可以用的el
,binding
和vnode
參數,除了update
和componentUpdated
之外,還會暴露oldVnode
,以區分傳遞的舊值和新值。
el
指令所綁定的元素,可以用來直接操作 DOM 。binding
一個對象,包含以下屬性:name
,value
,oldValue
,expression
,arg
和modifiers(修飾符對象)
。
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div> Vue.directive('demo', { bind: function (el, binding, vnode) { var s = JSON.stringify el.innerHTML = 'name: ' + s(binding.name) + '<br>' + 'value: ' + s(binding.value) + '<br>' + 'expression: ' + s(binding.expression) + '<br>' + 'argument: ' + s(binding.arg) + '<br>' + 'modifiers: ' + s(binding.modifiers) + '<br>' + 'vnode keys: ' + Object.keys(vnode).join(', ') } }) new Vue({ el: '#hook-arguments-example', data: { message: 'hello!' } }) name:'demo', value:'hello', expression:'message', arg:'foo', modifiers:{"a":true,"b":true}
vnode
Vue 編譯生成的虛擬節點。
binding
和vnode
都是只讀。
比如要實現一個設置元素在某個位置的功能
Vue.directive('tack',{ bind(el,binding,vnode){ el.style.position = 'fixed'; el.style.top = binding.value.top+'px'; el.style.left = binding.value.left+'px'; } }) <p v-tack="{top:'40',left:'100'}">我固定在離頂部40px、左側100px的地方</p>
Vue自定義指令優先級順序
-
系統默認指令會先於自定義指令執行
<!-- v-show 先於 v-block 執行 --> <div v-block v-show="false"></div>
-
自定義指令在標簽上的位置越靠前就越早執行
<!-- v-none 先於 v-block 執行 --> <div v-none v-block></div>
常用屬性
1、watch 監聽props或本組件的值
immediate屬性
watch 的一個特點是,最初綁定的時候是不會執行的,要等到數據改變時才執行監聽計算。那我們想要一開始就讓他最初綁定的時候就執行改怎么辦呢?我們需要修改一下我們的 watch 寫法,修改過后的 watch 代碼如下:
watch: { firstName: { handler(newName, oldName) { this.fullName = newName + ' ' + this.lastName; }, // 代表在wacth里聲明了firstName這個方法之后立即先去執行handler方法 immediate: true } }
deep屬性
默認情況下 handler 只監聽obj
這個屬性它的引用的變化,我們只有給obj
賦值的時候它才會監聽到,如果我們需要監聽obj
里的屬性a
的值呢?這時候deep
屬性就派上用場了!
<div> <p>obj.a: {{obj.a}}</p> <p>obj.a: <input type="text" v-model="obj.a"></p> </div> new Vue({ el: '#root', data: { obj: { a: 123 } }, watch: { obj: { handler(newName, oldName) { console.log('obj.a changed'); }, immediate: true,
deep:true, } } })
deep
的意思就是深入觀察,監聽器會一層層的往下遍歷,給對象的所有屬性都加上這個監聽器,但是這樣性能開銷就會非常大了,任何修改obj
里面任何一個屬性都會觸發這個監聽器里的 handler。
優化,我們可以是使用字符串形式監聽
watch: { 'obj.a': { handler(newName, oldName) { console.log('obj.a changed'); }, immediate: true, // deep: true } }
全局watch的注銷方式
app.$watch
調用后會返回一個值,就是unWatch
方法,你要注銷 watch 只要調用unWatch
方法就可以了。
const unWatch = app.$watch('text', (newVal, oldVal) => { console.log(`${newVal} : ${oldVal}`); }) unWatch(); // 手動注銷watch
2、computed 類似於過濾器,對綁定到view的數據進行處理。不能與data中定義的變量重名,具有緩存性,頁面重新渲染值不變化,計算屬性會立即返回之前的計算結果,而不必再次執行函數
<template> <div> <h4>測試</h4> <div> {{didi}} {{family}} </div> <div> {{didiFamily}} </div> </div> </template> <script> export default { data () { return { didi: 'didi', family: 'family' } }, computed: { didiFamily:{ //getter get:function(){ return this.didi + ' ' + this.family }, //setter set:function(newValue){ // 這里由於該計算屬性被賦值,將被調用 console.log(newValue) this.didi = 123 this.family = 456 } } }, mounted () { // 賦值,調用setter函數 this.didiFamily = 'John Doe' } } </script>
Vue實例中被觀察的數據屬性發生了改變時才會重新執行getter,但是我們有時候計算屬性依賴實時的非觀察數據屬性,比如下面例子中的Date.now
<template> <div> <h4>測試</h4> <div> <input type="text" v-model="message" /> <div>{{now}}</div> </div> </div> </template> <script> export default { data () { return { message: 'hello' } }, computed: { now:{ cache: false, get:function(){ return Date.now() + this.message } } }, mounted () { setInterval(() => { // 當緩存開關為false的時候,定時器每次打印的時間都是不一樣的 console.log(this.now) }, 500) } } </script>
- 當一個變量會引起多個變量的變化時,應該用watch,多個變量的變化會引起一個變量變化時,應該用computed
- 雖然方法也能實現computed同樣的效果,但是因為計算屬性可以基於它們的依賴進行緩存,所以選擇計算屬性會比方法更優。
過濾器
過濾器是針對一些數據 進行篩選、過濾、格式化等相關的處理,變成我們想要的數據,過濾器的本質 就是一個帶有參數帶有返回值的方法 ,過濾器只能用在{{}}和v-bind里面
1、過濾器的創建和使用
1.創建 全局過濾器 Vue.filter( 'filterA', function(myInput){ //myInput是在調用過濾器時,管道前表達式執行的結果 //針對myInput,按照業務需求做處理 //返回 return '處理后的結果' }) 局部過濾器 filters: { filterB: function (value) { if (!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) } } 2.使用 <any>{{expression | filterA | filterB}}</any>
2、如何在調用過濾器時,完成參數的發送和接受
1.發送 <any>{{expression | myFilter(參數1,參數2)}}</any> 2.接受 Vue.filter('myFilter',function(myInput,參數1,參數2){ return '處理后的結果' })
混合mixins和繼承extends
mixins接收對象數組(可理解為多繼承),extends接收的是對象或函數(可理解為單繼承)。
在項目中我們經常會遇到多個組件調用同一個方法的問題,為了避免每次都在.vue文件中定義並調用,我們可采用vue的mixin的用法,mixins就是定義一部分公共的方法或者計算屬性,然后混入到各個組件中使用,方便管理與統一修改
const extend = { data() { return { name: 'extend', } }, created() { console.log('extends created') }, } const mixin1 = { data() { return { name: 'mixin1', } }, created() { console.log('mixin1 created') }, methods: { fun() { console.log('mixin1 fun') }, }, } const mixin2 = { data() { return { name: 'mixin2', } }, created() { console.log('mixin2 created') }, methods: { fooB() { console.log('fooB') }, fun() { console.log('mixin2 fun') }, }, } export default { mixins: [mixin1, mixin2], extends: extend, name: 'app', created() { console.log('created') }, }
生命周期
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue生命周期</title> <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script> </head> <body> <div id="app"> <h1>{{message}}</h1> </div> </body> <script> var vm = new Vue({ el: '#app', data: { message: 'Vue的生命周期' }, // template: "<h1>{{message +'這是在template中的'}}</h1>", //在vue配置項中修改的 // render: function(createElement) { // return createElement('h1', 'this is createElement') // }, beforeCreate: function() { console.group('------beforeCreate創建前狀態------'); console.log("%c%s", "color:red" , "el : " ,this.$el); //undefined console.log("%c%s", "color:red","data : " ,this.$data); //undefined console.log("%c%s", "color:red","message: " + this.message) this.test('beforeCreate'); }, created: function() { console.group('------created創建完畢狀態------'); console.log("%c%s", "color:red","el : " ,this.$el); //undefined console.log("%c%s", "color:red","data : " ,this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 this.test('created'); }, beforeMount: function() { console.group('------beforeMount掛載前狀態------'); console.log("%c%s", "color:red","el : " + (this.$el)); //已被初始化 console.log(this.$el); console.log("%c%s", "color:red","data : " ,this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, mounted: function() { console.group('------mounted 掛載結束狀態------'); console.log("%c%s", "color:red","el : " ,this.$el); //已被初始化 console.log(this.$el); console.log("%c%s", "color:red","data : " ,this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, beforeUpdate: function () { console.group('beforeUpdate 更新前狀態===============》'); console.log("%c%s", "color:red","el : " ,this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " ,this.$data); console.log("%c%s", "color:red","message: " + this.message); }, updated: function () { console.group('updated 更新完成狀態===============》'); console.log("%c%s", "color:red","el : " ,this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " ,this.$data); console.log("%c%s", "color:red","message: " + this.message); }, beforeDestroy: function () { alert(); console.group('beforeDestroy 銷毀前狀態===============》'); console.log("%c%s", "color:red","el : " ,this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " ,this.$data); console.log("%c%s", "color:red","message: " + this.message); }, destroyed: function () { console.group('destroyed 銷毀完成狀態===============》'); console.log("%c%s", "color:red","el : " ,this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " ,this.$data); console.log("%c%s", "color:red","message: " + this.message) }, methods:{ test(stage){ console.log(stage+' called test fun') } } }) </script> </html>
每個階段可以做的事情 beforeCreate 可以在這加個loading事件, created 在這里結束loading事件,發送異步請求 beforeMount mounted 操作dom beforeUpdate updated 對更新后的數據統一處理 beforeDestroy 清除定時器,清除無用的全局緩存數據 這一步,實例仍然完全可用。 destroyed Vue 實例指示的所有東西都會解綁定,所有的事件監聽器會被移除,所有的子實例也會被銷毀。
組件化
組件:組件就是可被反復使用的,帶有特定功能的視圖
所謂的組件化,就像玩積木一樣,把封裝的組件進行復用,把積木(組件)拼接在一起,構成一個復雜的頁面應用程序。
組件樹就是由各個組件構成的一種數據結構,它存在的意義是為了幫梳理應用程序
1、組件的創建
1.1、直接在template屬性中指定模板內容 1.全局組件 Vue.component
Vue.component('my-com',{ template:` <h2>it is a global components</h2> ` })
2.局部組件
new Vue({ components:{ 'my-footer':{ template:'<h1>it is local components</h1>' } } })
1.2、.vue結尾的文件 <template></template> <script></script> <style></style> 1.3、單獨指定一個模板內容 <script id='myContent' type='text/x-template'> </script> Vue.component('my-component',{ template:'#myContent' })
2、組件使用 作為普通的標簽去使用 <my-com></my-com> 3、注意事項 1.組件命名方式使用ComponentName和component-name均可,但是當應用到dom的時候,應該使用短橫線分割命名方式。 2.如果一個組件 要渲染多個元素,將多個元素放在一個頂層標簽中,比如div、form
Vue.component("my-component", { template: "<div> <h1>我是全局組件元素1</h1> <h1>我是全局組件元素2</h1> </div>" });
3.全局組件必須寫在Vue實例創建之前,才在該根元素下面生效;局部組件只能在父模板中直接調用
組件間通信
1、父與子通信 (props down) 1.發送 <son myName='zhangsan'> </son> 2.接受 到son組件: Vue.component('son',{ props:['myName'], template:` <p>{{myName}}</p> ` }) 2、子與父通信 (events up) 1.綁定 methods:{ handleEvent:function(msg){
console.log('子組件傳過來的值:',msg);
} } <son @customEvent="handleEvent"></son> 2.觸發 子組件內部: this.$emit(‘customEvent’,100); 3、ref(reference 引用/參考) 幫助父組件得到子組件中的數據、方法。 1.指定ref屬性 <son ref="mySon"></son> 2.根據ref得到子組件實例 this.$refs.mySon 4、$parent 幫助子組件拿到父組件的實例中的數據,方法 this.$parent得到父組件的實例 this.$root得到根組件的實例 5、兄弟組件通信 1.var bus = new Vue(); 2.接收方 bus.$on('eventName',function(msg){}) 3.發送方 bus.$emit('eventName',123);
路由模塊
路由模塊的本質 就是建立起url和頁面之間的映射關系
1、SPA的基本概念和工作原理
SPA:single page application 單一頁面應用程序,只有一個完整的頁面; 它在加載頁面時,不會加載整個頁面,而是只更新某個指定的容器中內容。 工作原理: 1.解析地址欄 完整的頁面地址、路由地址 2.根據路由地址 從路由詞典中找到真正的要加載的頁面 3.發起ajax請求 請求要加載的頁面 4.像指定的容器中 插入加載來的頁面
2、路由模塊的基本使用
專業術語: router路由容器 route路由 routes 路由數組(路由詞典) 1.引入vue.js vue-router.js 2.指定一個容器 <router-view></router-view> 3.創建業務所需要用到的組件類 var MyLogin = Vue.component() 4.配置路由詞典 const myRoutes = [ {
path:'base',component:MyLogin,
// 路由嵌套
children:[
{path:'/a',component:A}
]
}, {path:'/login',component:MyLogin} ]; const myRouter = new VueRouter({ routes:myRoutes }) new Vue({
mode:'hash', router:myRouter }) 5.測試 修改地址欄中的路由地址,測試看加載的組件是否正確 注意事項: 1.先引入vue,再引入插件 2.一定要指定router-view
3、使用路由模塊來實現頁面跳轉的方式
方式1:直接修改地址欄 方式2:this.$router.push(‘路由地址’); 方式3:<router-link to="路由地址"></router-link>
4、完成參數的傳遞
在頁面之間跳轉的時候,在有些場景下,比如說list --> detail 需要同時指定參數
1.配置接收方的路由地址 /detail --> /detail/:index
2.發送
<router-link to="/detail/20" />
this.$router.push('/detail/20')
3.接收
this.$route.params.index
5.傳參生成url
const { href } = this.$router.resolve(
{
path: '/dataQuery/decisionDetail', query: { productCode, applyNo } }); window.open(href, "_blank");
6.登陸驗證
router.beforeEach((to, from, next) => { if (!cookie.get("isLogin")) { to.path !== "/login" ? next({ path: "/login" }) : next(); }else { next(); } })
7.草稿保存
beforeRouteLeave (to, from, next) { if(用戶已經輸入信息){ //出現彈窗提醒保存草稿,或者自動后台為其保存 }else{ next(true);//用戶離開 } }
Vuex
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態。
vuex解決了組件之間同一狀態的共享問題。多個組件依賴於同一狀態。傳參的方法對於多層嵌套的組件將會變得很繁瑣,不同組件的行為需要變更同一狀態,父子組件直接引用或者通過事件來變更和同步狀態的多份拷貝,通常會導致無法維護代碼。這時就出現了Vuex,這是Vuex出現的背景。,而現在有了vuex,這意味着本來需要共享狀態的更新需要組件之間的通訊,現在組件就都和store通訊了
• Vue Components:Vue組件。HTML頁面上,負責接收用戶操作等交互行為,執行dispatch方法觸發對應action進行回應。
• dispatch:操作行為觸發方法,是唯一能執行action的方法。
• actions:操作行為處理模塊。負責處理Vue Components接收到的所有交互行為。包含同步/異步操作,支持多個同名方法,按照注冊的順序依次觸發。向后台API請求的操作就在這個模塊中進行,包括觸發其他action以及提交mutation的操作。該模塊提供了Promise的封裝,以支持action的鏈式觸發。
• commit:狀態改變提交操作方法。對mutation進行提交,是唯一能執行mutation的方法。
• mutations:狀態改變操作方法。是Vuex修改state的唯一推薦方法,其他修改方式在嚴格模式下將會報錯。該方法只能進行同步操作,且方法名只能全局唯一。操作之中會有一些hook暴露出來,以進行state的監控等。
• state:頁面狀態管理容器對象。集中存儲Vue components中data對象的零散數據,全局唯一,以進行統一的狀態管理。頁面顯示所需的數據從該對象中進行讀取,利用Vue的細粒度數據響應機制來進行高效的狀態更新。
• getters:state對象讀取方法。圖中沒有單獨列出該模塊,這個方法包含在了render中,Vue Components通過該方法讀取全局state對象。
下面來看一下如何使用vuex
1. 首先建立一個store分支文件,如建立一個city_store.js的文件,通常設計store對象都包含4個屬性:state,getters,actions,mutations。
state (類似存儲全局變量的數據)
getters (提供用來獲取state數據的方法)
actions (提供跟后台接口打交道的方法,並調用mutations提供的方法)
mutations (提供存儲設置state數據的方法)
export default { // 1. state state:{ city:"城市名" }, // // 2. getters getters:{ // 參數列表state指的是state數據 getCityFn(state){ return state.city; } }, // 3. actions // 通常跟api接口打交道 actions:{ // 設置城市信息 // 參數列表:{commit, state} // state指的是state數據 // commit調用mutations的方法 // name就是調用此方法時要傳的參數 setCityName({commit,state}, name){ // 跟后台打交道 // 調用mutaions里面的方法 commit("setCity", name); } }, // 4. mutations mutations:{ // state指的是state的數據 // name傳遞過來的數據 setCity(state, name){ state.city = name;//將傳參設置給state的city } } }
2.在store主文件中引入
import Vue from 'vue' import vuex from 'vuex' Vue.use(vuex); import city_store from '../store/modules/city_store.js';//引入某個store對象 export default new vuex.Store({ modules: { dialog: city_store } })
3.在入口文件main.js中引用vuex
//vuex import store from './store' new Vue({ el: '#app', router, store,//使用store template: '<App/>', components: { App } })
4.在頁面中使用store中的數據和方法
<template> <div class="city"> <h1>{{city}}</h1> <ul> <li v-for="(item,index) in cityArr" @click="backFn(index)"> <h2>{{item}}</h2> </li> </ul> </div> </template>
<script> export default { name: 'HelloWorld', data () { return { cityArr:['北京','上海','廣州','深圳','茂名','張家界','清遠','汕頭','佛山'] } }, computed:{ city:function() { // 通過vuex的getters方法來獲取state里面的數據 return this.$store.getters.getCityFn; } }, methods:{ backFn : function(index){ // 調用vuex的ations設置城市的值 this.$store.dispatch("setCityName", this.cityArr[index]); //返回到首頁 this.$router.push("/"); } } } </script>
mapState、mapGetters、mapActions,mapMutations的作用
很多時候 , this.$store.state.city
、this.$store.dispatch("setCityName", this.cityArr[index]);這種寫法不方便 ,使用 mapState、mapGetters、mapActions
,mapMutations就不會這么復雜了。
<script> import { mapGetters, mapActions, mapState, mapMutations } from "vuex"; export default { name: 'HelloWorld', data () { return { cityArr:['北京','上海','廣州','深圳','茂名','張家界','清遠','汕頭','佛山'] } }, computed:{ ...mapGetters(["getCityFn"]), city:function() { // 通過vuex的getters方法來獲取state里面的數據 return this.getCityFn; } }, methods:{ ...mapActions(["setCityName"]), backFn : function(index){ // 調用vuex的ations設置城市的值 this.setCityName(this.cityArr[index]); //返回到首頁 this.$router.push("/"); } } } </script>
mapGetters
mapState一般寫在 computed
中 , mapMutations, mapActions
一般寫在methods
中。
keep-alive屬性
在Vue構建的單頁面應用(SPA)中,路由模塊一般使用vue-router。vue-router不保存被切換組件的狀態,它進行push或者replace時,舊組件會被銷毀,而新組件會被新建,走一遍完整的生命周期。
但有時候,我們有一些需求,比如跳轉到詳情頁面時,需要保持列表頁的滾動條的深度,等返回的時候依然在這個位置,這樣可以提高用戶體驗。在Vue中,對於這種“頁面緩存”的需求,我們可以使用keep-alive組件來解決這個需求。
假如有下面這個場景:
- 現有頁面:首頁(A)、列表頁(B)、詳情頁(C),一般可以從:A->B->C;
- B到C再返回B時,B要保持列表滾動的距離;
- B返回A再進入B時,B不需要保持狀態,是全新的。
在根頁面中定義keep-alive,並傳入全局的緩存數組:在Vuex中定義一個全局的緩存數組,待傳給include:
// App.vue <div class="app"> <!--傳入include數組--> <keep-alive :include="keepAliveComponents"> <router-view></router-view> </keep-alive> </div> export default { computed: { ...mapState({ keepAliveComponents: state => state.keepAliveComponents }) } }
// global.js export default { namespaced: true, state: { keepAliveComponents: [] // 緩存數組 }, mutations: { keepAlive (state, component) { // 注:防止重復添加 !state.keepAliveComponents.includes(component) && state.keepAliveComponents.push(component) }, noKeepAlive (state, component) { const index = state.keepAliveComponents.indexOf(component) index !== -1 && state.keepAliveComponents.splice(index, 1) } } }
緩存:在路由配置頁中,約定使用meta屬性keepAlive,值為true表示組件需要緩存。在全局路由鈎子beforeEach中對該屬性進行處理,這樣一來,每次進入該組件,都進行緩存:
const router = new Router({ routes: [ { path: '/B', name: 'B', component: B, meta: { title: 'B頁面', keepAlive: true // 這里指定B組件的緩存性 } } ] }) router.beforeEach((to, from, next) => { // 在路由全局鈎子beforeEach中,根據keepAlive屬性,統一設置頁面的緩存性 // 作用是每次進入該組件,就將它緩存 if (to.meta.keepAlive) { store.commit('keepAlive', to.name) } })
取消緩存的時機:對緩存組件使用路由的組件層鈎子beforeRouteLeave。因為B->A->B時不需要緩存B,所以可以認為:當B的下一個頁面不是C時取消B的緩存,那么下次進入B組件時B就是全新的:
export default { name: 'B', created () { // ...設置滾動條在最頂部 }, beforeRouteLeave (to, from, next) { // 如果下一個頁面不是詳情頁(C),則取消列表頁(B)的緩存 if (to.name !== 'C') { this.$store.commit('noKeepAlive', from.name) } next() } }
slot 插槽
當某個組件被多個地方使用 , 每個地方使用該組件時對該組件的內部有一部分需要特殊定制 , 這個時候slot可以讓我們更好的復用組件的同時並對其定制。 插槽,也就是slot,是組件的一塊HTML模板,這塊模板顯示不顯示、以及怎樣顯示由父組件來決定, 插槽顯示的位置卻由子組件自身決定,slot寫在組件template的什么位置,父組件傳過來的模板將來就顯示在什么位置。
<template> <div id="app"> <children> <span>子組件內部元素</span> </children> </div> </template> <script> export default { name: 'hello', components: { children: { template: '<div>這里是子組件</div>' } } } </script> <!--渲染結果--> <div id="app"> <div>這里是子組件</div> </div>
<template> <div id="app"> <children> <span>子組件內部元素</span> </children> </div> </template> <script> export default { name: 'hello', components: { children: { template: '<div><slot><p>默認效果</p></slot>這里是子組件</div>' } } } </script> <!--渲染結果--> <div id="app"> <span>子組件內部元素</span> "這里是子組件" </div>
具名插槽可以有多個,匿名插槽只能有一個
<template> <div class="father"> <h3>這里是父組件</h3> <child> <div slot="up"> <span>菜單1</span> </div> <div slot="down"> <span>菜單-1</span> </div> <div> <span>菜單->1</span> </div> </child> </div> </template>
<template> <div class="child"> // 具名插槽 <slot name="up"></slot> // 具名插槽 <slot name="down"></slot> // 匿名插槽 <slot></slot> </div> </template>
作用域插槽-- 作用域插槽的關鍵之處就在於,父組件能接收來自子組件的slot傳遞過來的參數
<div id="root"> <child> <template slot-scope="props"><!--定義一個插槽,該插槽必須放在template標簽內--> <li>{{props.value}}</li> </template> </child> </div> <script> Vue.component('child',{ data: function(){ return { list:[1,2,3,4] } }, template: `<div> <ul> <slot v-for="value in list" :value='value'>//使用slot占位 </slot> </ul> </div>` }) var vm=new Vue({ el: '#root' }) </script>
axios
1.axios的get方法
export const getAjax= function (getUrl,getAjaxData) { return axios.get(getUrl, { params: { 'getAjaxDataObj1': getAjaxData.obj1, 'getAjaxDataObj2': getAjaxData.obj2 } }) }
2.axios的post方法
export const postAjax= function (getUrl,postAjaxData) { return axios.post(postUrl, { data:{
'postAjaxDataObj1': postAjaxData.obj1, 'postAjaxDataObj2': postAjaxData.obj2
} }) }
3.axios的攔截器
主要分為請求和響應兩種攔截器,請求攔截一般就是配置對應的請求頭信息(適用與常見請求方法,雖然ajax的get方法沒有請求頭,但是axios里面進行啦封裝),響應一般就是對reponse進行攔截處理,如果返回結果為[]可以轉化為0
Vue.use(Vuex) Vue.use(VueAxios, axios) Vue.use(qs) // 注:qs,使用axios,必須得安裝 qs,所有的Post 請求,我們都需要 qs,對參數進行序列化。 在 request 攔截器實現 axios.interceptors.request.use( config => { config.baseURL = '/api/' config.withCredentials = true // 允許攜帶token ,這個是解決跨域產生的相關問題 config.timeout = 6000 let token = sessionStorage.getItem('access_token') config.headers = { 'access-token': token, 'Content-Type': 'application/x-www-form-urlencoded' } return config }, error => { return Promise.reject(error) } ) //在 response 攔截器實現 axios.interceptors.response.use( response => { // 定時刷新access-token if (!response.data.value && response.data.data.message === 'token invalid') { // 刷新token store.dispatch('refresh').then(response => { sessionStorage.setItem('access_token', response.data) }).catch(error => { throw new Error('token刷新' + error) }) } return response }, error => { return Promise.reject(error) } )
踩坑經驗
1.修改了數據某項內容,或修改了對象的新增的某個屬性,視圖沒有同步更新
解決方法:Vue.set( target, key, value )
Vue.set(this.items,0,{message:"Change Test",id:'10'})
2.組件命名與html標簽重名
[Vue warn]: Do not use built-in or reserved HTML elements as component id: Table
不要使用內建或預留的HTML 標簽作為組件ID,改成其它名稱
3. action中commit的行為名稱不能重名,重名會造成mutations動作執行覆蓋
4.vue中使用$refs的時候,有時候要用[0]區獲取,有時候不需要
這是由於vue中的v-for去讀取ref的是讀取的是domlist,而單獨的ref讀取的是實體單獨的dom
5.vue中使用vue-lazyload的時候數據改變,但是圖片沒有改變,這是由於懶加載的機制導致的,需要給load的img加上一個:key就會更新了
<el-image :key="url" :src="url" lazy></el-image>
6.vue-router跳轉的時候用path的時候參數傳遞只能用query,而用params傳參的時候只能用name來設置跳轉的連接
this.$router.push({ name:'router1',params: { id: status ,id2: status3},query: { queryId: status2 }});
query刷新不會丟失query里面的數據, params刷新 會 丟失 params里面的數據
7.vue加載圖片
1、絕對路徑訪問
<template> <div> <img :src="src" alt="圖片" /> </div> </template> <script> export default{ data(){ return { src:`/static/img/user.png` } } } </script>
2、使用require
如果想在不調整目錄結構的情況下讀取圖片,還可以使用require:
data(){ return { src:require('../assets/user.png') //重點看這里 } }
3、使用import
也可以用import引入圖片路徑:
<script> import userPath from '../assets/user.png' export default{ data(){ return { src:userPath } } } </script>
vue性能優化
1.非頁面中用到的變量不要定義在data中或者使用freeze屬性
new Vue({ data: { // vue不會對list里的object做getter、setter綁定 list: Object.freeze([ { value: 1 }, { value: 2 }, }, created() { // 界面不會有響應 this.list[0].value = 100 // 下面兩種做法,界面都會響應 this.list = [ { value: 100 }, { value: 200 }, ] this.list = Object.freeze([ { value: 100 }, { value: 200 }, ]) } })
2.組件要使用懶加載
const Foo = resolve => {
// require.ensure 是 Webpack 的特殊語法,用來設置 code-split point
require.ensure(['./Foo.vue'], () => {
resolve(require('./Foo.vue'))
})
}
簡寫為
const Foo = resolve => require(['./Foo.vue'], resolve)
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
3.引入生產環境的 Vue 文件
開發環境下,Vue 會提供很多警告來幫你對付常見的錯誤與陷阱。而在生產環境下,這些警告語句沒有用,反而會增加應用的體積。有些警告檢查還有一些小的運行時開銷。
4.提取組件的 CSS 到單獨到文件
當使用單文件組件時,組件內的 CSS 會以 <style>
標簽的方式通過 JavaScript 動態注入。這有一些小小的運行時開銷,將所有組件的 CSS 提取到同一個文件可以避免這個問題,也會讓 CSS 更好地進行壓縮和緩存。
5.使用單文件組件預編譯模板
這種定義組件的方式不能在構建階段使用預處理器,如 Pug (formerly Jade) 和 Babel,模板在運行時才被編譯為渲染函數,使用單文件組件在構建好的代碼已經包含了編譯出來的渲染函數而不是原始的模板字符串
Vue.component('my-com',{ template:` <h2>it is a global components</h2> ` })
6.采用預渲染(Prerender)來優化加載性能
采用vue-router history模式+webpack的PrerenderSpaPlugin插件,生成的html文件有內容和數據
1.無預渲染
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>vue-lz-2.0</title> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"> </head> <body> <div id="app"></div> <script src="dist/build.js"></script> </body> </html>
2.有預渲染