Vue 學習
Vue擴展
Vue實例參數補充
const vm = new Vue({
el:'#app',
data:{
// 實例數據
},
computed:{
// 計算屬性
},
methods:{
// 實例的方法
},
watch:{
// 監聽器
},
// ...一堆生命週期鉤子函數
router: xxx // 通過new VueRouter() 創建的實例與Vue實例進行綁定
render: (createElement) => {
// 渲染函數,自帶一個參數,這個參數是一個函數
// 返回的結果會直接替換整個 #app 的區域(類似於v-text)
// 而一般的組件是隻替換組件使用處的代碼 相當於 插值表達式(具體可以往後看)
return createElement(組件對象) // 返回值是html代碼
}
})
修飾符補充
.native修飾符
一般註冊的事件 @click @keyup 都是vue的合成事件,不是原生事件,如果想直接越過vue,不進行合成事件的處理,直接原生事件,那麼就在註冊事件的時候,加上 .native修飾符就可以了 @click.native=""
在使用elementUI中的el-input 組件的時候,因爲這個組件沒有提供回車的事件,因此我們想要使用自己定義的回車事件就需要越過Vue的事件處理,執行原生事件就需要用到.native修飾符了
組件
組件是可複用的 Vue 實例,且帶有一個名字。因爲組件是可複用的 Vue 實例,所以它們與 new Vue 接收相同的選項,例如 data、computed、watch、methods 以及生命週期鉤子等。僅有的例外是像 el 這樣根實例特有的選項。並且在組件中,data 必須是一個函數
一個頁面可以是有多個組件組成
組件的優勢:
1. 便於複用
2. 便於維護
3. 便於分工
組件化和模塊化的區別
區別:
組件是具體的:按照一些小功能的通用性和可複用性來抽象組件
組件化更多關注的 UI 部分,頁面的每個部件,比如頭部,內容區,彈出框甚至確認按鈕都可以成爲一個組件,每個組件有獨立的 HTML、css、js 代碼。
模塊是抽象的:按照項目業務劃分的大模塊
模塊化側重的功能的封裝,主要是針對 Javascript 代碼,隔離、組織複製的 javascript 代碼,將它封裝成一個個具有特定功能的的模塊。
模塊可以通過傳遞參數的不同修改這個功能的的相關配置,每個模塊都是一個單獨的作用域,根據需要調用。一個模塊的實現可以依賴其它模塊。
組件註冊
- 當使用 kebab-case (短橫線分隔命名) 定義一個組件時,你也必須在引用這個自定義元素時使用 kebab-case,比如:my-header
- 當使用 PascalCase (首字母大寫命名) 定義一個組件時,在引用這個自定義元素時使用 kebab-case 方式
- template 在如下這樣使用的時候必須有一個 root Element(根元素)進行包裹,不然會報錯
- 全局註冊組件
註冊
// Vue.component('組件名', { 組件對象 })
// 方式一
Vue.component('myheader', {
template: '<h1>hello world!</h1>'
})
// 方式二
Vue.component('my-header', {
template: '<h1>hello world!</h1>'
})
// 方式三
Vue.component('myHeader', {
template: '<h1>hello world!</h1>'
})
使用
<!-- 對應上面的方式一 -->
<div id="app">
<myheader></myheader>
</div>
<!-- 對應方式二和三 -->
<div id="app">
<my-header></my-header>
</div>
- 局部註冊組件
const vm = new Vue({
el: '#app',
components: {
// 也可以寫成 myheader 或者 myHeader 方式與上面一樣
'my-header': {
template: '<h1>hello world</h1>'
}
}
})
組件對象參數
data 必須是一個函數 vue 組件中的 data 必須是個函數的目的:就是爲了保證組件實例之間不會相互影響!
- 如果 data 是個對象,就是引用類型的,任意一個地方發生改變,其他人都會受到影響
- 用了函數之後,每次創建 vue 組件實例,都會調用函數,重新創建一個新的對象,那麼每個實例都有自己的 data 對象,就不會互相影響了
組件嵌套
父子組件——在使用組件的時候,如果一個組件的模板中,用到了另一個組件標籤,則這兩個組件就形成了父子關係
Vue.component('father', {
// 在父組件的模板中引用了子組件,可以使用雙標籤<son></son> 也可以使用單標籤 <son />
template: '<h1> {{msg}} <son></son></h1>',
data() {
return {
msg: 'hello father'
}
}
})
Vue.component('son', {
template: '<h1>{{msg}}</h1>',
data() {
return {
msg: 'hello son'
}
}
})
// 這種並不是父子組件,這樣定義只是在組件中又定義了一個組件,而這個son組件的使用範圍只是在father組件內部,無法在外部使用,是一個局部組件
Vue.component('father', {
template: '<h1>hello father</h1>',
components: {
son: {
template: '<h1>hello son</h1>'
}
}
})
以上這些只是組件的最最最最……基礎的使用,這樣的使用很費勁,但是這部分是基礎,必須會了才能更好的學習之後的
組件通訊 ★
組件之間是相互獨立的,數據是無法直接互相訪問的!!在實際開發當中,我們經常會遇到,在一個組件中,要使用另外一個組件中的數據的情況。這個時候就需要使用組件通訊技術!!即在組件之間傳遞數據
-
父組件 → 子組件(屬性傳值)
Prop 是你可以在組件上註冊的一些自定義特性。當一個值傳遞給一個 prop 特性的時候,它就變成了那個組件實例的一個屬性。爲了給博文組件傳遞一個標題,我們可以用一個 props 選項將其包含在該組件可接受的 prop 列表中。一個組件默認可以擁有任意數量的 prop,任何值都可以傳遞給任何 prop
// 父組件傳值給子組件是在編譯的時候自動的
Vue.component('father', {
// step 1: 給子組件添加通過 v-bind 綁定的屬性(prop),並且把father的數據綁定給屬性(prop)
template: '<h1> {{msg}} <son :msgFromFather="msg"></son></h1>',
data() {
return {
msg: '這是father的msg'
}
}
})
Vue.component('son', {
// step 2: 在子組件中增加屬性 props 這是一個數組,用來接收step1中綁定的屬性(prop)
props: ['msgFromFather'],
// step 3: 在子組件模板中可以直接props中的屬性名
template: '<h1>{{msgFromFather}}</h1>'
})
-
子組件 → 父組件(事件傳值)
單向數據流
所有的 prop 都使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態,從而導致你的應用的數據流向難以理解。額外的,每次父級組件發生更新時,子組件中所有的 prop 都將會刷新爲最新的值。這意味着你不應該在一個子組件內部改變 prop。如果你這樣做了,Vue 會在瀏覽器的控制檯中發出警告。
Vue.component('father', {
// 通過事件綁定的方式,用 @ 給子組件添加事件(事件名自定義),並且與step1的方法綁定,傳遞給子組件
template: '<div>{{sonData}} <son @getData="getSonData"></son></div>',
data() {
return {
// 用於存放子組件的數據
sonData: ''
}
},
methods: {
// step 1: 聲明一個函數,並且有一個形參用於接收子組件的數據
getSonData(value) {
console.log(value)
this.sonData = value
}
}
})
Vue.component('son', {
template: `<div>
<button @click="clickHandler">sendToFather</button>
</div>`,
data() {
return {
msg: '這是son的msg'
}
},
methods: {
// 子組件傳值給父組件是需要自己觸發的
clickHandler() {
// step 3: 通過this.$emit('事件名',子組件數據),調用父組件傳遞的函數把子組件的數據傳遞給父組件
this.$emit('getData', this.msg)
}
}
})
-
父子組件雙向綁定
儘管因爲存在單向數據流,導致我們不能直接在子組件中去修改父組件傳遞過來的數據,但是我們可以通過雙向綁定的方式,在實現子組件中修改父組件中傳遞的數據
方法一:組件間的數據傳遞
這個方法的實現其實就是父傳子,子傳父的結合,父組件把數據傳遞給子組件(屬性傳值),然後子組件通過事件傳值又把修改的數據傳遞給父組件,這樣實現了雙向綁定
Vue.component('father', {
template: `<div>父組件:<input type="text" v-model="msg"><son :val="msg" @xxx="msg = $event"></son></div>`,
data() {
return {
msg: 'felix'
}
},
methods: {
getval(val) {
this.msg = val
}
}
})
Vue.component('son', {
props: ['val'],
template: `<div>子組件:<input type="text" :value=sonMsg @input="changeval"></div>`,
data() {
return {
sonMsg: this.val
}
},
methods: {
changeval(e) {
this.$emit('xxx', e.target.value)
}
},
watch: {
val() {
this.sonMsg = this.val
}
}
})
const vm = new Vue({
el: '#app'
})
方法二:.sync修飾符
使用.sync修飾符其實就是方法一的簡寫
// 方法一中的
<son :val="msg" @xxx="msg = $event"></son>
// 修改成如下這樣
<son :val.sync="msg"></son>
// 方法一種的
this.$emit('xxx', e.target.value)
// 修改成如下這樣,這裏的必須要這麼寫update:xxx,冒號後面的是與上面的綁定,但是update是必不可少的,我們在網上看到的一些雙向綁定使用方法一的時候也是用update這樣的方式寫的,但是實際的邏輯就是通過上面說的來實現的,沒必要使用update,但是這裏必須使用update,調用Vue內部的update事件
this.$emit('update:val', e.target.value)
方法三:v-model綁定
Vue.component('father', {
// step 1: 通過v-model給子組件綁定數據,其實v-model相當於是給value值綁定數據的
template: `<div><son v-model="msg"></son>父組件:<input type="text" v-model="msg">
</div>`,
// 上面的son中的v-model其實是這個的簡寫 <son :value="msg" @input="msg = $event"></son>
data() {
return {
msg: 'felix'
}
}
})
Vue.component('son', {
// step 3:給子組件添加input事件
template: `<div>子組件: <input type="text" v-model="sonMsg" @input="set"></div>`,
// step 2:接收父組件中綁定的數據,因爲v-model相當於是給value綁定數據,因此這裏用value接收
props: ['value'],
data() {
return {
sonMsg: this.value
}
},
methods: {
set(e) {
// step 4:通過文本框的input事件去觸發父組件中用v-model綁定的input事件,並把數據傳遞給父組件,實現雙向數據綁定
this.$emit('input', e.target.value)
}
},
watch: {
value() {
// 添加監視,對傳遞過來的value值,這樣只要父組件中的數據修改了,子組件中的數據sonMsg也可立即修改並響應式的在界面上顯示
this.sonMsg = this.value
}
}
})
const vm = new Vue({
el: '#app'
})
- 非父子組件傳值
同級組件之間的數據傳遞
// bear2 發送數據給 bear1 兩者不是父子組件,是兄弟組件
// step 1 : 定義global-event-bus (全局事件總線) 一個空的Vue實例
const bus = new Vue()
Vue.component('bear1', {
template: `<div>{{msg}}</div>`,
data() {
return {
msg: ''
}
},
methods: {
// step 2 : 定義一個函數用於接收另一個組件傳遞過來的數據通過形參mes
getBear2Msg(mes) {
this.msg = '收到熊二來信:' + mes
}
},
created() {
// step 3 : 在組件剛創建之後就把聲明的函數通過$on 註冊到總線(bus)上,方便另一個組件可以通過總線觸發
bus.$on('getMsg', this.getBear2Msg)
}
})
Vue.component('bear2', {
template: `<div>
<button @click="sendMsg">告訴熊大</button>
</div>`,
data() {
return {
msg: '熊大,光頭強又來砍樹啦!'
}
},
methods: {
sendMsg() {
// step 4 : 通過$emit 觸發接收組件(即bear1組件)的函數,並且把數據傳遞過去,這樣上面的getBear2Msg的mes參數就有了值
bus.$emit('getMsg', this.msg)
}
}
})
const vm = new Vue({
el: '#app'
})
還有一種方法就是使用 Vuex 來解決
組件補充(瞭解)
下面兩個屬性在實際開發中用的不多,作爲了解知道就行
-
$refs 屬性
- 這個屬性可以用來在組件中訪問子組件或者子元素
- 如果要在當前組件中訪問子組件或子元素,那麼就需要給子組件或者子元素標籤添加 ref 屬性,隨便給個名字 ref=名字
- 在當前組件中就可以通過 this.$refs.名字訪問到子組件或子元素了
- 如果是子組件,則獲取到的是 vue 實例
- 如果是子元素,則獲取到的是 dom 對象
Vue.component('father', { template: `<div><son ref="child"></son><button @click="handler">按鈕</button></div>`, methods: { handler() { console.log(this.$refs.child.msg) } } }) Vue.component('son', { template: `<div>這是子組件數據: {{msg}}</div>`, data() { return { msg: '這是son的msg' } } }) const vm = new Vue({ el: '#app' })
-
$parent 屬性
獲取當前組件的父組件或者父元素
Vue.component('father', { template: `<div>{{msg}}<son ref="child"></son></div>`, data() { return { msg: '這是father的msg' } } }) Vue.component('son', { template: `<div><button @click="handler">按鈕</button></div>`, methods: { handler() { console.log(this.$parent.msg) } } }) const vm = new Vue({ el: '#app' })
插槽
插槽,當組件中需要一些內容,這些內容是通過在組件標籤內書寫,傳遞進去的,那麼我們需要使用插槽進行接收
匿名插槽
定義組件
Vue.component("mycomp", {
template:"<div><slot></slot></div>",
data(){
return {
btnText: "按鈕文字"
}
}
});
new Vue({
el: "#app"
});
頁面代碼
<div id="app">
<mycomp>
<div>這是放在組件最前面的內容</div>
<div>這是放在組件最後面的內容</div>
</mycomp>
</div>
最後就會把<mycomp>標籤裏的兩個div替換template模板中的slot標籤
具名插槽
具名插槽: 插槽可以起名字, 將內容和插槽進行對應
我要實現讓一個div放前面,一個放後面,這時候就需要讓給插槽命名,這樣纔可以讓對應的東西放到對應的插槽中去
Vue.component("mycomp", {
template:
"<div>1: <slot name='first'></slot> 2:<slot name='second'></slot></div>",
data(){
return {
btnText: "按鈕文字"
}
}
});
<mycomp>
<div slot="front">這是放在組件最前面的內容</div>
<div slot="back">這是放在組件最後面的內容</div>
</mycomp>
當有很多內容需要放到某個具名插槽中去,如果每個都給添加一個slot屬性來指定名字,這樣是很麻煩的
<mycomp>
<div slot="front">這個要放到前面</div>
<div slot="front">這個也要放到前面</div>
<div slot="back">這個放在後面</div>
<div slot="back">這個也要放在後面</div>
</mycomp>
因此可以如下修改
<mycomp>
<!-- 如果有多個內容要放到同一個插槽裏,那麼就可以使用template標籤把他們包起來 -->
<!-- template標籤不會生成額外的標籤,不會影響標籤結構 ,用div包裹的話,會增加一個div標籤,結構就發生了改變-->
<template slot="front">
<div>這個要放到前面</div>
<div>這個也要放到前面</div>
</template>
<template slot="back">
<div>這個放在後面</div>
<div>這個也要放在後面</div>
</template>
</mycomp>
作用域插槽
作用域插槽: 當我們在給組件中插槽填寫內容的時候,如果想要用到組件中的數據,直接使用拿不到數據 這個時候就可以使用作用域插槽,把組件中的數據給傳遞出來,就可以直接使用了
Vue.component("mycomp", {
// 通過v-bind(或 : )來綁定組件中的數據,綁定的名字自定義
template:
"<div>1: <slot name='first' :btnText='btnText'></slot> 2:<slot name='second' :btnText='btnText'></slot></div>",
data(){
return {
btnText: "按鈕文字"
}
}
});
<!-- v-slot:插槽的名字="組件中傳遞出來的數據對象" -->
<mycomp>
<!-- 因爲傳遞的值是一個對象,這裏可以通過{上面定義的名字}來進行解構,然後就可以用插值表達式-->
<template v-slot:second="{btnText}">
<button>{{btnText}}</button>
</template>
</mycomp>
插槽這東西我們在正常的開發中基本上很難用到,因爲我們一般可以直接用人家封裝的組件,不用自己去寫組件提供給別人用,如果需要提供給別人使用的,那麼就需要用到插槽方便用戶在組件中輸入值來進行傳遞,但是當我們用到別人的組件的時候就會用到插槽,比如ElementUI的el-table組件中,我們要獲取其中某一行的數據,這時候就需要通過v-slot=“scope”來接收。
單頁面應用
單頁面應用就是根據hash值來改變頁面內容的
單頁面應用(single-page application 簡寫SPA)是指一個項目只有一個頁面,頁面的切換效果,是通過組件的切換來完成的。
單頁面應用因爲只有一個頁面,所以頁面不能發生跳轉,但是,我們又需要根據url地址來展示不同的組件
這個時候,只能用哈希值來表示究竟要展示哪個組件了
模擬實現一個單頁面應用
<div id="app">
<!-- 這就相當於一個佔位符,告訴Vue我要在這裏放一個組件,具體放哪個組件通過is屬性來控制 -->
<component :is="currentPage"></component>
</div>
Vue.component('login', {
template: `<div><h1>這是登錄頁</h1><input type="text"><input type="text"><button>登錄</button></div>`
})
Vue.component('register', {
template: `<div><h1>這是註冊頁</h1><input type="text"><input type="text"><input type="text"><button>註冊</button></div>`
})
Vue.component('list', {
template: `<div><h1>這是列表頁</h1><input type="text"><input type="text"><button>登錄</button></div>`
})
const vm = new Vue({
el: '#app',
data: {
currentPage: 'login'
},
created() {
this.goPage()
// 通過這個屬性來監聽地址欄中的hash值是否改變了,hash值就是咱們之前用到的" #/xxx "的
window.onhashchange = () => {
this.goPage()
}
},
methods: {
goPage() {
switch (location.hash) {
case '#/login':
this.currentPage = 'login'
break
case '#/register':
this.currentPage = 'register'
break
case '#/list':
this.currentPage = 'list'
break
}
}
}
})
vue-router
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,讓構建單頁面應用變得易如反掌
用 vue-router 實現上面的單頁面案例
<div id="app">
<!-- vue-router中提供了一個聲明式導航 -->
<ul>
<!-- 使用 router-link 組件來導航. -->
<!-- 通過傳入 `to` 屬性指定鏈接. -->
<!-- <router-link> 默認會被渲染成一個 `<a>` 標籤 可以通過tag 屬性來修改渲染的標籤 -->
<li>
<router-link to="/login" tag="button" active-class="active">登錄頁</router-link>
</li>
<li><router-link to="/register">註冊頁</router-link></li>
<li><router-link to="/list">列表頁</router-link></li>
</ul>
<!-- 路由出口 -->
<!-- 路由匹配到的組件將渲染在這裏 -->
<router-view></router-view>
</div>
// 1. 定義 (路由) 組件,這個就是之前創建組件時候的組件參數對象
const login = {
template: `<div><h3>這是登錄頁</h3><input type="text"><input type="text"><button>登錄</button></div>`
}
const register = {
template: `<div><h3>這是註冊頁</h3><input type="text"><input type="text"><input type="text"><button>註冊</button></div>`
};
const list = {
template: `<div><h3>這是列表頁</h3><input type="text"><input type="text"><button>登錄</button></div>`
};
// 2. 定義路由 每個路由應該映射一個組件。 其中"component" 可以是 通過 Vue.extend() 創建的組件構造器, 或者,只是一個組件配置對象
const router = new VueRouter({
// routes 裏面存放的就是路由規則(hash值和組件的對應關係)
routes: [
// 每一條路由規則就是一個對象
{path: "/",component: login},
{path: "/login",component: login},
{path: "/register",component: register},
{path: "/list",component: list}
]
});
const vm = new Vue({
el: "#app",
// 3. 將Vue根組件實例和路由對象關聯起來
// 完整寫法是 router : router ES6可以簡寫成下面的
router
});
編程式導航
const vm = new Vue({
el: "#app",
router,
methods: {
clickHandler(){
// 在js代碼中如果想要跳轉頁面,有個東西
// router.push("/register");
this.$router.push("/register")
// this.$router.push({path: "/register"})
// this.$router.push({name: "rg"})
}
}
});
<router-link>
常用屬性
1. to
表示目標路由的鏈接。當被點擊後,內部會立刻把
to
的值傳到router.push()
,所以這個值可以是一個字符串或者是描述目標位置的對象。
<!-- 字符串 -->
<router-link to="home">Home</router-link>
<!-- 渲染結果 -->
<a href="home">Home</a>
<!-- 使用 v-bind 的 JS 表達式 -->
<router-link v-bind:to="'home'">Home</router-link>
<!-- 不寫 v-bind 也可以,就像綁定別的屬性一樣 -->
<router-link :to="'home'">Home</router-link>
<!-- 同上 -->
<router-link :to="{ path: 'home' }">Home</router-link>
<!-- 命名的路由 -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
<!-- 帶查詢參數,下面的結果爲 /register?plan=private -->
<router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link>
2. active-class
設置 鏈接激活時使用的 CSS 類名。默認值可以通過路由的構造選項
linkActiveClass
來全局配置
路由參數
- 通過 ? 設置的參數
我們常見的設置參數是 #/detail?id=1&name=zs 這種形式
// 頁面代碼,設置連接
<router-link to="/detail?id=1">詳情頁</router-link>
const detail = {
// 可以通過$route.query獲取對應的值,這裏是可以省略this的
template: '<h1>詳情界面 ----- {{$route.query.id}}</h1>',
created() {
console.log(this.$route)
}
}
const home = {
template: '<h1> home </h1>'
}
const router = new VueRouter({
routes: [
{ path: '/', component: home },
{ path: '/detail', component: detail }
]
})
const vm = new Vue({
el: '#app',
router
})
- 通過動態路由設置參數
還有一種形式是 #/detail/1/zs,直接寫傳遞的值
const detail = {
// 這種形式的就通過params來獲取參數,我們可以直接帶地址欄輸入 #/detail/1
template: '<h1>詳情界面 ----- {{$route.params.id}}</h1>',
created() {
console.log(this.$route)
}
}
const home = {
template: '<h1> home </h1>'
}
const router = new VueRouter({
routes: [
{ path: '/', component: home },
// 我們需要修改路由的匹配規則,這個意思是在/後面用id來佔位,表示這個位置放一個id值
{ path: '/detail/:id', component: detail }
]
})
const vm = new Vue({
el: '#app',
router
})
導航守衛
vue-router
提供的導航守衛主要用來通過跳轉或取消的方式守衛導航。
舉個例子:我們進行用戶沒有登錄,那麼我們就需要這個守衛幫我們進行攔截,然後重定向到登錄界面,登錄後如果用戶的token或者cookie什麼的保存的信息是合法的,就允許跳轉,這就是守衛的一個作用之一。
全局前置守衛
你可以使用 router.beforeEach
註冊一個全局前置守衛:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
當一個導航觸發時,全局前置守衛按照創建順序調用。守衛是異步解析執行,此時導航在所有守衛 resolve 完之前一直處於 等待中。
每個守衛方法接收三個參數:
- to: Route: 即將要進入的目標路由對象
- from: Route: 當前導航正要離開的路由
- next: Function: 一定要調用該方法來 resolve 這個鉤子。執行效果依賴
next
方法的調用參數。- next(): 進行管道中的下一個鉤子。如果全部鉤子執行完了,則導航的狀態就是 confirmed (確認的)。
- next(false): 中斷當前的導航。如果瀏覽器的 URL 改變了 (可能是用戶手動或者瀏覽器後退按鈕),那麼 URL 地址會重置到
from
路由對應的地址。 - next(’/’) 或者 next({ path: ‘/’ }): 跳轉到一個不同的地址。當前的導航被中斷,然後進行一個新的導航。你可以向
next
傳遞任意位置對象,且允許設置諸如replace: true
、name: 'home'
之類的選項以及任何用在 router-link的
toprop或
router.push`中的選項。 - next(error): (2.4.0+) 如果傳入
next
的參數是一個Error
實例,則導航會被終止且該錯誤會被傳遞給router.onError()
註冊過的回調。
確保要調用 next 方法,否則鉤子就不會被 resolved。
next方法簡單的說就是通過next()來讓守衛進行放行
守衛還有些別的,但是基本上類似,參數也類似,當用到的時候去查看就行
路由嵌套
有時我們會遇到這樣一種情形,比如我們的界面如下圖
整個頁面我們用一個router-view進行了展示,但是當我們點擊側邊菜單的時候一些東西是展示在Main中的,這時候如果我們還是通過配置如下的路由,那麼整個界面都是會重新渲染的
{
path:'/xxx',
component: xxxx
}
這顯然不是我們想要的結果,這我們要怎麼實現呢?
這時候就用到了子路由(嵌套路由),假設當前這個頁面的路由是 /index
首先我們需要在Main中再放一個<router-view>,作爲嵌套路由的出口,在這塊區域中進行渲染
{
path:"/index",
component:index,
children:[
{
path:'xxx', // 還有一種形式是 path:'/xxx'
component:xxx
}
]
}
以 / 開頭的嵌套路徑會被當作根路徑。 這讓你充分的使用嵌套組件而無須設置嵌套的路徑。
帶 / 和不帶 /
// 路由配置中設置了 帶 /,則跳轉的連接這樣設置
// 顯示 http://localhost:8080/#/xxx
<router-link to="/xxx" />
// 路由配置中設置了不帶 / ,則如下設置
// 顯示 http://localhost:8080/#/index/xxx
<router-link to="/index/xxx" />
至於你到底要配置帶不帶 / 純看你自己
路由懶加載
當打包構建應用時,JavaScript 包會變得非常大,影響頁面加載。如果我們能把不同路由對應的組件分割成不同的代碼塊,然後當路由被訪問的時候才加載對應組件,這樣就更加高效了
const Foo = () => import('./Foo.vue')
在路由配置中什麼都不需要改變,只需要像往常一樣使用 Foo
:
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
把組件按組分塊
有時候我們想把某個路由下的所有組件都打包在同個異步塊 (chunk) 中。只需要使用 命名 chunk,一個特殊的註釋語法來提供 chunk name (需要 Webpack > 2.4)。
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
Webpack 會將任何一個異步模塊與相同的塊名稱組合到相同的異步塊中。
Vue CLI
這個腳手架工具主要是用於開發單頁面應用的,因此我們可以看到在生成的項目中只有一個HTML頁面,在public文件夾中
單文件組件
在很多 Vue 項目中,我們使用 Vue.component 來定義全局組件,緊接着用 new Vue({ el: '#container '}) 在每個頁面內指定一個容器元素
這種方式在很多中小規模的項目中運作的很好,在這些項目裏 JavaScript 只被用來加強特定的視圖。但當在更復雜的項目中,或者你的前端完全由 JavaScript 驅動的時候,
下面這些缺點將變得非常明顯:
全局定義 (Global definitions) 強制要求每個 component 中的命名不得重複
字符串模板 (String templates) 缺乏語法高亮,在 HTML 有多行的時候,需要用到醜陋的 \
不支持 CSS (No CSS support) 意味着當 HTML 和 JavaScript 組件化時,CSS 明顯被遺漏
沒有構建步驟 (No build step) 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用預處理器,如 Pug (formerly Jade) 和 Babel
文件擴展名爲 .vue 的 single-file components(單文件組件) 爲以上所有問題提供瞭解決方法,並且還可以使用 webpack 或 Browserify 等構建工具
單文件組件默認有下面三部分,分別是HTML、CSS 、JS
<template>
<!-- 通過lang來設置模板語言,默認是HTML 也可以設置爲pug jade ejs等 -->
<div>
<!-- 組件HTML代碼 -->
</div>
</template>
<script>
// 可以通過增加lang屬性來設置使用什麼語言,默認是js 也可以設置爲 ts(typescript)
export default {
// 寫之前組件中的東西,比如data(){return {}} 、 props:[] ……
};
</script>
<style scoped>
/*
寫css樣式,可以通過lang屬性來設置,是css、less、sass 等,
默認情況下樣式是全局作用的,加了scoped表示只在這個組件中起作用
*/
</style>
Vue-CLI使用
通常我們把它叫做Vue腳手架
在使用Vue開發的時候,我們會用到好多工具,webpack,babel,eslint…
在使用這些工具的時候,我們需要自己手動配置好多內容,這些內容配置太過繁瑣。
vue官方就出了一個腳手架工具,這個工具裏面會把所有我們需要的配置,全部幫我們自動處理完畢。
Vue-CLI的使用
- 安裝
npm i -g @vue/cli
- 創建項目
# 終端創建項目
vue create 項目名稱
# 圖形化界面創建
vue ui
- 生成項目目錄結構說明
1. 最外層的,基本上全都是配置文件,不需要過多的關注
2. node_modules 所有的npm下載的包
3. public 公共的靜態資源
4. src目錄(工作中需要關注的重點!)
1. main.js 這是整個項目的入口文件,項目就是從他開始執行的,render函數之前有介紹
2. router.js 這是用來配置路由規則文件
3. App.vue 這是整個項目的根組件
4. assets目錄,靜態資源,所有的組件中用到的靜態資源,全部放到這個目錄中 (圖片 css 字體文件)
5. components目錄,頁面內使用的組件,就都放到這個文件夾中
6. views目錄,所有的頁面組件都放到這個文件夾裏 (頁面組件就是指會有路由規則進行對應的組件)
- 運行項目
# server是在package.json文件中生成項目的時候scripts配置的,根據實際配置的來修改
# "serve": "vue-cli-service serve", package.json中是這樣的
npm run server
- 打包項目
# bulid 同樣是在package.json 中配置的
# "build": "vue-cli-service build",
npm run bulid
通過打包後會在根目錄下生成一個dist文件夾,這個文件就是經過打包壓縮後的,一般都是直接把這個文件夾放到服務器上
配置反向代理
devServer.proxy
如果你的前端應用和後端 API 服務器沒有運行在同一個主機上,你需要在開發環境下將 API 請求代理到 API 服務器。這個問題可以通過 vue.config.js 中的 devServer.proxy 選項來配置。
注意:devServe這個名字不要自己改動,之前自己改動了導致配置的不起作用
devServer.proxy 可以是一個指向開發環境 API 服務器的字符串:
module.exports = {
devServer: {
proxy: 'http://localhost:4000'
}
}
這會告訴開發服務器將任何未知請求 (沒有匹配到靜態文件的請求) 代理到http://localhost:4000。
如果你想要更多的代理控制行爲,也可以使用一個 path: options 成對的對象
module.exports = {
devServer: {
proxy: {
'/api': {
target: '<url>',
ws: true,
changeOrigin: true,
// '/api/home' 路徑重寫爲:'/home'
pathRewrite: { "^/api": "/" }
},
'/foo': {
target: '<other_url>'
}
}
}
}
這裏的vue.config.js這個文件不需要自己去引入哪裏,在運行項目的時候腳手架會自己去讀取這個配置文件,並編譯裏面的代碼,這樣我們就實現了反向代理
至於怎麼使用?我們在axios中寫路徑的時候可以省略上面的target部分,直接以你寫的/api 開始 接後面的路徑就行。
// 假設url http://localhost:8080/home?id=1&name=zs
// 並且上面的targe配置了 http://localhost:8080/
// 那麼我們就可以直接 以 /api/home?id=1&name=zs,這樣的方式去寫
axios補充
在vue開發中存在的問題
-
每個需要用到axios的組件,都需要單獨引入axios
-
axios的基地址每次都要寫
-
headers每次都要寫
解決辦法
- 我們可以把axios加到Vue構造函數的原型中 這樣每個組件中都可以通過 this.$http
// 把axios加到Vue的原型上
Vue.prototype.$http = axios;
- axios.defaults.baseURL = “”
// 通過defaults給axios設置一個默認的baseURL,可以在所有請求中都能用到這個地址
axios.defaults.baseURL = "http://localhost:8888/api/private/v1/";
// 其實請求頭中的一些數據也可以在這裏設置
axios.defaults.headers.common['Authorization'] = localStorage.getItem("token");
- 通過axios請求攔截器來實現(請求攔截器和響應攔截器)
axios.interceptoers.request.use(function(config){
// config 就是攔截到的請求相關的所有的信息
// 這個信息是可以進行修改的
config.headers.Authorization = localStorage.getItem("token")
// return config不能動,這個函數中必須有這個內容
return config;
})
舉個形象的例子
這個攔截器就好比是海關,你如果要出去就要經過海關的檢查,這裏就相當於攔截器進行檢查,而最後的return 是必不可少的,意思就相當於給予放行。
- 響應攔截器
axios.interceptors.response.use(function (response) {
// Do something with response data
// respone就是響應對象
return response;
});
在服務端返回響應的時候,我們也會對token進行驗證,驗證token是否有效,然後決定頁面的跳轉
Vuex
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式,所謂的狀態管理可以理解爲對數據的管理。
之前我們接觸到的數據傳遞分爲父傳子(屬性傳值)、子傳父(事件傳值)、兄弟組件傳值,這幾種傳值方式如果嵌套結構過於複雜的時候,會導致數據的傳遞變得很複雜,因此Vuex在這裏就體現出了重要的作用, 一個項目只需要一個Vuex來管理我們的數據,集中管理項目中的狀態的,便於組件之間的數據共享。
Vuex是Vue的一個插件,因此要在Vue之後使用,或者Vue.use(vuex)
Vuex使用
在Vuex的實例對象中可以包括以下四個部分
// 通過Vuex.Store構造函數可以創建一個store對象
// store對象中的數據是響應式的
const store = new Vuex.Store({
// 存放狀態(數據)
state: {
},
// mutations裏面放的就是用來修改數據的方法,一般都是寫同步操作的
mutations: {
},
// 一些異步修改數據的方法,最終調用的還是mutation的方法
actions:{
},
// 與vue實例中的計算屬性類似
getters:{
}
});
與vue實例綁定
// 當把store實例和根組件關聯之後
// store就可以在任意組件中通過 this.$store 就可以訪問到這個store實例了
const vm = new Vue({
el:"#app",
store
})
在Vue中使用
<!-- 可以通過$store 來獲取Vuex的對象-->
<div id="app">
{{$store.state.msg}}
</div>
mutation
更改 Vuex 的 store 中的狀態(數據)的唯一方法是提交 mutation
const store = new Vuex.store({
state:{
msg: "hello world"
},
mutations:{
updateMsg(state, value){
// 這個函數就是用來修改state中的數據的
// 可以接收兩個參數
// state 指的就是vuex的存儲數據的對象
// value 當用戶通過 store.commit 方法來觸發這個函數的時候
// commit方法的第二個參數就會被賦值給value
state.msg = value
}
}
})
// vuex不允許直接通過給store.state中的數據賦值 來修改數據
store.state.msg = "123"
// 在vuex中如果需要修改數據
// 那麼我們就需要在vuex中提供修改數據的方法
// 這些修改數據的方法,全都放在一個叫mutations的對象中
// mutations中的方法,如何觸發?
// 只允許傳遞一個參數,如果需要傳遞多個數據,可以傳一個對象
store.commit("updateMsg", 123)
getter
Vuex 允許我們在 store 中定義“getter”(可以認爲是 store 的計算屬性)。就像計算屬性一樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被重新計算。
const store = new Vuex.Store({
state: {
arr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
// getters就像是vue中的計算屬性
// 如果要獲取一個數據,而這個數據是通過對state中的數據進行計算之後的結果
// 我們就可以通過getters來實現了
getters: {
evenNum(state){
return state.arr.filter(v => v % 2 == 0);
},
// 在vue中計算屬性是無法傳參的
// 但是在vuex中,getters是可以傳遞參數的
// $store.getters.zhengchuArr(3)
zhengchuArr(state){
return function(value){
return state.arr.filter(v => v % value == 0);
}
},
// zhengchuArr: state => value => state.arr.filter(v => v % value == 0)
}
})
<div id="app">
<ul>
<!-- 無參數 -->
<li v-for="item in $store.getters.evenNum">{{item}}</li>
<!-- 需要傳參 -->
<li v-for="item in $store.getters.zhengchuArr(4)">{{item}}</li>
</ul>
</div>
action
Action 類似於 mutation,不同在於:
- Action 提交的是 mutation,而不是直接變更狀態。
- Action 可以包含任意異步操作。
Action 函數接受一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以調用 context.commit
提交一個 mutation,或者通過 context.state
和 context.getters
來獲取 state 和 getters
const store = new Vuex.Store({
state: {
msg: "hello world"
},
// mutations裏面放的就是用來修改數據的方法
mutations: {
updateMsg(state, value){
state.msg = value;
}
},
actions: {
updateMsg(context, value){
setTimeout(() => {
// 在修改數據的時候,其實還是提交一個mutation完成數據修改
context.commit("updateMsg", value);
}, 1000)
}
}
});
// store.commit("updateMsg", 123)
store.dispatch("updateMsg", 456);
輔助函數
我們在使用的時候常常會遇到如下的使用
// 在組件的methods中是如下使用的
toggleTodo(id) {
this.$store.commit("toggleTodo", id);
}
// 在vuex的store中的mutation的方法
toggleTodo(state, id) {
state.todoList.forEach(v => {
if (v.id === id) {
v.isCompleted = !v.isCompleted;
}
});
}
可以發現基本上每次我們在組件中創建的方法中只有一條調用mutation中的方法的代碼,因此可以用到Vuex提供的輔助函數mapMutation
我們怎麼在組件中引入呢??
- mapMutation
// 導入mapMutation方法
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 將 `this.increment()` 映射爲 `this.$store.commit('increment')`
// `mapMutations` 也支持載荷:
'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 將 `this.add()` 映射爲 `this.$store.commit('increment')`
})
}
}
這樣做了之後,我們就可以直接在組件中使用這些方法了
- mapGetters
getter中的一些計算屬性也可以這麼映射出來,這樣我們就不用寫很長的一串 $store.getters.xxx,而是可以直接使用xxx
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用對象展開運算符將 getter 混入 computed 對象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
- mapAction
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 將 `this.increment()` 映射爲 `this.$store.dispatch('increment')`
// `mapActions` 也支持載荷:
'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 將 `this.add()` 映射爲 `this.$store.dispatch('increment')`
})
}
}
- mapState
// 在單獨構建的版本中輔助函數爲 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭頭函數可使代碼更簡練
count: state => state.count,
// 傳字符串參數 'count' 等同於 `state => state.count`
countAlias: 'count',
// 爲了能夠使用 `this` 獲取局部狀態,必須使用常規函數
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
注意mutation和action是映射到methods中的,而state和getters是映射到computed計算屬性中去的
module
由於使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常複雜時,store 對象就有可能變得相當臃腫。
爲了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行同樣方式的分割
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態
')`
// `mapActions` 也支持載荷:
'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 將 `this.add()` 映射爲 `this.$store.dispatch('increment')`
})
}
}
4. mapState
```js
// 在單獨構建的版本中輔助函數爲 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭頭函數可使代碼更簡練
count: state => state.count,
// 傳字符串參數 'count' 等同於 `state => state.count`
countAlias: 'count',
// 爲了能夠使用 `this` 獲取局部狀態,必須使用常規函數
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
注意mutation和action是映射到methods中的,而state和getters是映射到computed計算屬性中去的
module
由於使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常複雜時,store 對象就有可能變得相當臃腫。
爲了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行同樣方式的分割
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態