// button.jsimport ButtonComponent from './button.vue';const Button={ install:function (Vue) { Vue.component('Button',ButtonComponent) }}export default Button;// main.jsimport Button from './component/button.js';Vue.use(Button);
完成上面的步驟就可以在全局使用button組件了,其實最重要的 Vue.component('Button',ButtonComponent) , Vue.use(Button) 會執行install方法,也可以直接在 main.js 使用 Vue.component() 註冊全局組件。
props
子組件是不能直接修改props的。
Vue組件之間的通信問題可以看這裡…
Vue 組件 extend
使用 Vue.extend 就是構造了一個Vue構造函數的“子類”。它的參數是一個 包含組件選項的對象 ,其中 data 選項必須是函數。
import Vue from 'vue'// 一個包含組件選項的對象const compoent = { props: { active: Boolean, propOne: String }, template: `see me if active`, data () { return { text: 0 } }, mounted () { // 這個mounted先打印 console.log('comp mounted'); }}// 創建一個“子類”const CompVue = Vue.extend(compoent);// 實例化一個“子類”new CompVue({ el: '#root', propsData: { // 這裡如果用props,組件內是拿不到值的 propOne: 'xxx' }, data: { text: '123' }, mounted () { console.log('instance mounted'); }})const component2 = { extends: component, // 繼承於 component data(){ return { text: 1 } }, mounted () { this.$parent.text = '111111111'; // 可以改變父組件的值 console.log('comp2 mounted') }}new Vue({ name: 'Root', el: '#root', mounted () { console.log(this.$parent.$options.name) }, components: { Comp: componet2 }, data: { text: 23333 }, template: `{{text}}`})
Vue 組件高級屬性
Vue 組件插槽
通常我們會向一個組件中傳入一些自定義的內容,這個時候就可以用到插槽。插槽內可以包含任何模板代碼,包括HTML或者是一個組件。
// 定義一個帶插槽的組件const component = { name: 'comp', template: ``}new CompVue({ el: '#root', components:{ Comp }, template: ` `}這裡的內容顯示在插槽內
具名插槽
官網鏈接: https://cn.vuejs.org/v2/guide/components-slots.html
具名插槽的使用:
第一種:在一個父組件的 元素上使用 slot 特性
Here might be a page title
A paragraph for the main content.
And another one.
Here's some contact info
第二種:直接在普通元素上使用
Here might be a page title
A paragraph for the main content.
And another one.
Here's some contact info
插槽的默認內容
在插槽中可以設置一個默認內容,如果用戶沒有設置新的內容,則會顯示默認內容
作用域插槽
2.1.0+ 新增 在 2.5.0+, slot-scope 不再限制在 元素上使用,而可以用在插槽內的任何元素或組件上。
const component = { name: 'comp', template: ``}new CompVue({ el: '#root', components:{ Comp }, template: ` `}{{props.value}} {{props.name}}
// 456 finget
provide/inject 跨級組件交互
2.2.0 新增
這對選項需要一起使用,以允許一個祖先組件向其所有子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裡始終生效。
// 父級組件提供 'foo'var Provider = { provide: { foo: 'bar' }, // ...}// 子組件注入 'foo'var Child = { inject: ['foo'], created () { console.log(this.foo) // => "bar" } // ...}
如果是注入一個父級組件內部的值,provide需要作為一個函數,類似於data
const component = { name: 'comp', inject: ["value"] template: `子組件 {{value}}`}new CompVue({ el: '#root', data() { return { value: '123' } } components:{ Comp }, provide() { // 這裡如果只是一個對象的話是無法拿到this.value的 return { value: this.value } }, template: ``}
如果要監聽父級組件的屬性值的變化,從而自動更新子組件的值,需要手動實現監聽
const component = { name: 'comp', inject: ["data"] template: `子組件 {{data.value}}`}...provide() { const data = {} // 這是vue雙向綁定的基礎 Object.defineProperty(data,"value",{ get: () => this.value, enumerable: true }) return { data }},...
Vue 的render
Vue模板的解析: https://finget.github.io/2018/05/31/mvvm-vue/
Vue-router
router構建選項
重定向:
{ path: '/', redirect: '/app'}
History 模式:
const router = new VueRouter({ mode: 'history', routes: [...]})
vue-router 默認 hash 模式 —— 使用 URL 的 hash 來模擬一個完整的 URL,於是當 URL 改變時,頁面不會重新加載。
不過這種模式要玩好,還需要後臺配置支持。因為我們的應用是個單頁客戶端應用,如果後臺沒有正確的配置,當用戶在瀏覽器直接訪問 http://oursite.com/user/id 就會返回 404,這就不好看了。
給個警告頁:
const router = new VueRouter({ mode: 'history', routes: [ { path: '*', component: NotFoundComponent } ]})
base
const router = new VueRouter({ mode: 'history', base: '/base/', routes: [ { path: '/hello', component: hello } ]})
當訪問 localhost:8080/hello 會變成 localhost:8080/base/hello ,所有的路由路徑都會加上 /base ,當然手動刪除 /base 還是可以打開頁面
linkActiveClass 和 linkExactActiveClass
app login
router-link 在頁面中會渲染成 a 標籤,點擊之後會添加兩個類名: router-link-exact-active 和 router-link-active
const router = new VueRouter({ linkActiveClass: 'active-link', linkExactActiveClass: 'exact-active-link'})
這相當於是重新命名了兩個類名。
兩者的不同點:
login login exact
上面這兩個路由有一部分 /login 是相同的,在點擊了 login exact 路由調轉到 /login/exact 後:
/login 上還保留了 router-link-active 類名
scrollBehavior
使用前端路由,當切換到新路由時,想要頁面滾到頂部,或者是保持原先的滾動位置,就像重新加載頁面那樣。
注意: 這個功能只在支持 history.pushState 的瀏覽器中可用。
const router = new VueRouter({ scrollBehavior(to, form, savedPosition){ if (savedPosition) { return savedPosition } else { return { x: 0, y: 0 } } }, routes: [...]})
scrollBehavior 方法接收 to 和 from 路由對象。第三個參數 savedPosition 當且僅當 popstate 導航 (通過瀏覽器的 前進/後退 按鈕觸發) 時才可用。
parseQuery 和 stringifyQuery
提供自定義查詢字符串的解析/反解析函數。覆蓋默認行為。
const router = new VueRouter({ parseQuery (query) { console.log(query) }, stringifyQuery (obj) { console.log(obj) }})
fallback
當瀏覽器不支持 history.pushState 控制路由是否應該回退到 hash 模式。默認值為 true。
在 IE9 中,設置為 false 會使得每個 router-link 導航都觸發整頁刷新。它可用於工作在 IE9 下的服務端渲染應用,因為一個 hash 模式的 URL 並不支持服務端渲染。
const router = new VueRouter({ fallback: true})
路由元信息
官網例子:
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, children: [ { path: 'bar', component: Bar, // a meta field meta: { requiresAuth: true } } ] } ]})
那麼如何訪問這個 meta 字段呢?
首先,我們稱呼 routes 配置中的每個路由對象為 路由記錄。路由記錄可以是嵌套的,因此,當一個路由匹配成功後,他可能匹配多個路由記錄
例如,根據上面的路由配置, /foo/bar 這個 URL 將會匹配父路由記錄以及子路由記錄。
一個路由匹配到的所有路由記錄會暴露為 $route 對象 (還有在導航守衛中的路由對象) 的 $route.matched 數組。因此,我們需要遍歷 $route.matched 來檢查路由記錄中的 meta 字段。
下面例子展示在全局導航守衛中檢查元字段:
router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.requiresAuth)) { // this route requires auth, check if logged in // if not, redirect to login page. if (!auth.loggedIn()) { next({ path: '/login', query: { redirect: to.fullPath } }) } else { next() } } else { next() // 確保一定要調用 next() }})
命名視圖
在一個路由下展示多個視圖組件,用的並不多
// 在這個頁面中要分別展示三個視圖// 默認的 // 視圖a // 視圖bconst router = new VueRouter({ routes: [ { path: '/', components: { // 加s default: Foo, // 對應默認router-view a: Bar, // name = "a" b: Baz // name = "b" } } ]})
導航守衛
路由改變時,按順序觸發的鉤子函數
全局守衛
const router = new VueRouter({ ... })router.beforeEach((to, from, next) => { console.log('before each invoked'); next();})router.beforeResolve((to, from, next) => { console.log('before resolve invoked'); next();})
每個守衛方法接收三個參數:
- 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 的 to prop 或 router.push 中的選項。
- next(error) : (2.4.0+) 如果傳入 next 的參數是一個 Error 實例,則導航會被終止且該錯誤會被傳遞給 router.onError() 註冊過的回調。
確保要調用 next 方法,否則鉤子就不會被 resolved 。
路由對象
一個路由對象 (route object) 表示當前激活的路由的狀態信息,包含了當前 URL 解析得到的信息,還有 URL 匹配到的路由記錄 (route records)。
路由對象是不可變 (immutable) 的,每次成功的導航後都會產生一個新的對象。
路由對象屬性:
- $route.path
- 類型: string
- 字符串,對應當前路由的路徑,總是解析為絕對路徑,如 “/foo/bar”。
- $route.params
- 類型: Object
- 一個 key/value對象,包含了動態片段和全匹配片段,如果沒有路由參數,就是一個空對象。
- $route.query
- 類型: Object
- 一個 key/value 對象,表示 URL 查詢參數。例如,對於路徑 /foo?user=1,則有 $route.query.user == 1,如果沒有查詢參數,則是個空對象。
- $route.hash
- 類型: string
- 當前路由的 hash 值 (帶 #) ,如果沒有 hash 值,則為空字符串。
- $route.fullPath
- 類型: string
- 完成解析後的 URL,包含查詢參數和 hash 的完整路徑。
- $route.matched
- 類型: Array 一個數組,包含當前路由的所有嵌套路徑片段的路由記錄 。路由記錄就是 routes 配置數組中的對象副本 (還有在 children 數組)。
const router = new VueRouter({ routes: [ // 下面的對象就是路由記錄 { path: '/foo', component: Foo, children: [ // 這也是個路由記錄 { path: 'bar', component: Bar } ] } ]})
當 URL 為 /foo/bar, $route.matched 將會是一個包含從上到下的所有對象 (副本)。
- $route.name
- 當前路由的名稱,如果有的話。(查看命名路由)
- $route.redirectedFrom
- 如果存在重定向,即為重定向來源的路由的名字
全局後置鉤子
router.afterEach((to, from) => { console.log('after each invoked');})
路由獨享的守衛
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // ... } } ]})
組件內的守衛
const Foo = { template: `...`, beforeRouteEnter (to, from, next) { // 在渲染該組件的對應路由被 confirm 前調用 // 不!能!獲取組件實例 `this` // 因為當守衛執行前,組件實例還沒被創建 }, beforeRouteUpdate (to, from, next) { // 在當前路由改變,但是該組件被複用時調用 // 舉例來說,對於一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候, // 由於會渲染同樣的 Foo 組件,因此組件實例會被複用。而這個鉤子就會在這個情況下被調用。 // 可以訪問組件實例 `this` }, beforeRouteLeave (to, from, next) { // 導航離開該組件的對應路由時調用 // 可以訪問組件實例 `this` }}
beforeRouteEnter 守衛 不能 訪問 this,因為守衛在導航確認前被調用,因此即將登場的新組件還沒被創建。
不過,你可以通過傳一個回調給 next來訪問組件實例。在導航被確認的時候執行回調,並且把組件實例作為回調方法的參數。
beforeRouteEnter (to, from, next) { next(vm => { // 通過 `vm` 訪問組件實例 })}
完整的導航解析流程
- 導航被觸發。
- 在失活的組件裡調用離開守衛。
- 調用全局的 beforeEach 守衛。
- 在重用的組件裡調用 beforeRouteUpdate 守衛 (2.2+)。
- 在路由配置裡調用 beforeEnter 。
- 解析異步路由組件。
- 在被激活的組件裡調用 beforeRouteEnter 。
- 調用全局的 beforeResolve 守衛 (2.5+)。
- 導航被確認。
- 調用全局的 afterEach 鉤子。
- 觸發 DOM 更新。
- 用創建好的實例調用 beforeRouteEnter 守衛中傳給 next 的回調函數。
異步路由
在路由文件中,直接import所有組件勢必造成頁面首次渲染時間變長,異步路由,當進入對應的路由才加載對應的頁面。
const router = new VueRouter({ routes: [ { path: '/foo', component: () => import('../view/...'), } ]})
這種寫法需要安裝 syntax-dynamic-import ,並在 .babelrc 進行配置
// .babelrc{ "plugins": ["syntax-dynamic-import"]}
Vux
以下內容來自 官網:https://vuex.vuejs.org/zh/
簡單使用vuex
// store.jsimport Vuex from 'vuex'import Vue from 'vue'Vue.use(Vuex)const store = new Vuex.Store({ state: { count: 0 }, mutations: { updateCount(state, num) { state.count = num } }})export default store// main.jsimport Vue from 'vue'import App from './App'import store from './store/store.js'Vue.config.productionTip = false/* eslint-disable no-new */new Vue({ el: '#app', store, // 掛載 components: { App }, template: ''})// 任意組件mounted(){ console.log(this.$store) let i = 1 setInterval(() => { this.$store.commit('updateCount', i++) })},computed: { count() { return this.$store.state.count }}
核心概念
State
Vuex 使用單一狀態樹——是的,用一個對象就包含了全部的應用層級狀態。至此它便作為一個“唯一數據源 (SSOT)”而存在。這也意味著,每個應用將僅僅包含一個 store 實例。單一狀態樹讓我們能夠直接地定位任一特定的狀態片段,在調試的過程中也能輕易地取得整個當前應用狀態的快照。
大白話: state就相當於是個全局對象,通過 Vue.use(Vuex) 全局註冊了vuex之後,在任意組件中可以用 this.$store.state 拿到該對象
Vuex的狀態存儲是響應式的,從store實例中讀取狀態最簡單的方法就是在計算屬性中返回某個狀態。
computed: { count() { return this.$store.state.count }}
當 state 中的 count 變化時,自動會更新 computed ,從而改變相關 DOM
mapState 輔助函數
當一個組件需要獲取多個狀態時候,將這些狀態都聲明為計算屬性會有些重複和冗餘。為了解決這個問題,我們可以使用 mapState 輔助函數幫助我們生成計算屬性,讓你少按幾次鍵:
// 在單獨構建的版本中輔助函數為 Vuex.mapStateimport { 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 } })}
當映射的計算屬性的名稱與 state 的子節點名稱相同時,我們也可以給 mapState 傳一個字符串數組。
computed: mapState([ // 映射 this.count 為 store.state.count 'count'])// 常用操作computed: { ...mapState(['count'])}// 換一個變量名computed: { ...mapState({ count1 : 'count', count2 : state => state.count })}
Getter
Getter就是vuex種state的computed,通過state派生出新的state,而且它會被緩存起來,只有依賴的state發生變化才會重新計算
export default { fullName(state) { // 默認接收state作為第一個參數 return `${state.firstName}${state.lastName}` }}
mapGetters 輔助函數
getter的使用和state類似,可以把它看成state來用。
import { mapGetters } from 'vuex'export default { // ... computed: { // 使用對象展開運算符將 getter 混入 computed 對象中 ...mapGetters([ 'doneTodosCount', 'anotherGetter', // ... ]) }}
如果想給getter換個名字,方法和state一樣,不重複
Mutation
Mutation必須是同步的
更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似於事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是我們實際進行狀態更改的地方,並且它會接受 state 作為第一個參數:
const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { // 變更狀態 state.count++ } }})
你不能直接調用一個 mutation handler。這個選項更像是事件註冊:“當觸發一個類型為 increment 的 mutation 時,調用此函數。”要喚醒一個 mutation handler,你需要以相應的 type 調用 store.commit 方法:
store.commit('increment')
提交載荷(傳參)
你可以向 store.commit 傳入額外的參數,即 mutation 的 載荷(payload):
// ...mutations: { increment (state, n) { state.count += n }}store.commit('increment', 10)
在大多數情況下,載荷應該是一個 對象 ,這樣可以包含多個字段並且記錄的 mutation 會更易讀:
// ...mutations: { increment (state, payload) { state.count += payload.amount }}store.commit('increment', { amount: 10})
對象風格的提交方式
提交 mutation 的另一種方式是直接使用包含 type 屬性的對象:
store.commit({ type: 'increment', amount: 10})
當使用對象風格的提交方式,整個對象都作為載荷傳給 mutation 函數,因此 handler 保持不變:
mutations: { increment (state, payload) { state.count += payload.amount }}
使用常量替代 Mutation 事件類型
使用常量替代 mutation 事件類型在各種 Flux 實現中是很常見的模式。這樣可以使 linter之類的工具發揮作用,同時把這些常量放在單獨的文件中可以讓你的代碼合作者對整個 app 包含的 mutation 一目瞭然:
// mutation-types.jsexport const SOME_MUTATION = 'SOME_MUTATION'// store.jsimport Vuex from 'vuex'import { SOME_MUTATION } from './mutation-types'const store = new Vuex.Store({ state: { ... }, mutations: { // 我們可以使用 ES2015 風格的計算屬性命名功能來使用一個常量作為函數名 [SOME_MUTATION] (state) { // mutate state } }})
在組件中提交 Mutation
你可以在組件中使用 this.$store.commit('xxx') 提交 mutation ,或者使用 mapMutations 輔助函數將組件中的 methods 映射為 store.commit 調用(需要在根節點注入 store)。
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')` }) }}
Action
Action 可以包含異步操作
Action跟Mutation類似,Action是調用 commit 方法,提交 mutation 的。
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } }})
Action 函數接受一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以調用 context.commit 提交一個 mutation ,或者通過 context.state 和 context.getters 來獲取 state 和 getters 。
實踐中,我們會經常用到 ES2015 的 參數解構 來簡化代碼(特別是我們需要調用 commit 很多次的時候):
actions: {// {commit} = context 解構出來 increment ({ commit }) { commit('increment') }}
實際代碼:
在組件中分發 Action
你在組件中使用 this.$store.dispatch('xxx') 分發 action ,或者使用 mapActions 輔助函數將組件的 methods 映射為 store.dispatch 調用(需要先在根節點注入 store):
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')` }) }}
嚴格模式
開啟嚴格模式,僅需在創建 store 的時候傳入 strict: true:
const store = new Vuex.Store({ // ... strict: true})
在嚴格模式下,無論何時發生了狀態變更且不是由 mutation 函數引起的,將會拋出錯誤。這能保證所有的狀態變更都能被調試工具跟蹤到。
開發環境與發佈環境
不要在發佈環境下啟用嚴格模式!嚴格模式會深度監測狀態樹來檢測不合規的狀態變更——請確保在發佈環境下關閉嚴格模式,以避免性能損失。
類似於插件,我們可以讓構建工具來處理這種情況:
const store = new Vuex.Store({ // ... strict: process.env.NODE_ENV !== 'production'})
原文鏈接:https://finget.github.io/2018/06/28/vue-family/?utm_source=tuicool&utm_medium=referral 如有侵權請聯繫刪除,謝謝
感覺文章不錯的同學可以通過原文鏈接查看作者其他JS系列文章
閱讀更多 全棧取經之路 的文章
關鍵字: 路由器 JavaScript HTML