萬字總結Vue(包含全家桶),但願這一篇能夠幫到您

基礎使用

如下代碼均通過本身測試,能夠複製直接看效果。注意引入Vue文件javascript

所有代碼等全部都更新完成以後會上傳GitHub或者碼雲.我會抓緊時間更新的css

Vue基礎部分和手寫Vue部分(解釋還沒更新出來)已經上傳.須要請點擊這裏html

VueX和VueRouter 部分更新也上傳了。須要點擊這裏vue

VueX差了輔助函數之類。VueRouter基本完成。後續可能會有單元測試和SSRjava

渲染優先級

  1. render>template>data的插值表達式
  2. {{}} 放的是表達式的時候會 輸出結果,內部轉爲函數
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>基本概念</title>
    <script src="vue.js"></script>
</head>
<body>
<h1>顯示優先級</h1>
<ul>
    <li>第一個是render 有render方法就輸出render的渲染結果</li>
    <li>第二個是template 有template方法就輸出template的內容</li>
    <li>最後一個是data,若是二者不存在 則輸出data裏面的插值表達式</li>
    {{ }} 當這裏面放的是一個表達式的時候,會輸出表達式的結果 緣由 會轉化成一個函數 render
</ul>
<p>指令修飾符,有好多 本身官網看</p>

<div id="app">

    {{ msg }}
</div>
<script> let vm = new Vue({ el: '#app', data() { return { msg: '我是data', } }, template: '<div>我是template</div>', render(h) { return h('div', ['我是render']) }, method: { fn(e) { // 不添加括號自動添加事件源, 添加括號 手動傳入事件源 console.log('內部已經使用bind 綁定了this ,再次綁定也沒什麼用') console.log(this) }, }, }) </script>
</body>
</html>

複製代碼

v-model

v-model 其實是一個 語法糖node

<input type="text" :value = 'msg' @input="handleInput">
<!-- 其實是上述的語法糖-->
<input type="text" v-model="msg">
複製代碼

v-model 的應用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>v-model</title>
    <script src="vue.js"></script>
</head>
<body>
<div id="app">

    {{ msg }}
    <p>@input</p>
    <input type="text" :value = 'msg' @input="handleInput">
    <p>這個是@chage</p>
    <input type="text" :value = 'msg' @change="handleInput">
    <p>v-model  是上面@input的語法糖</p>
    <input type="text" v-model="msg">
    <p>@input 和@change 區別  一個是 聚焦的時候 一個是 失去焦點的時候</p>
    <p>下拉列表</p>
    {{ selected }}<br>
    <select v-model="selected">
        <option value="" disabled>請選擇</option>
        <option value="1">a</option>
        <option value="2">b</option>
        <option value="3">c</option>
    </select>

    <p>下拉列表多選 這樣綁定的值必須是一個列表</p>
    {{ selectedMore }}<br>
    <select v-model="selectedMore" multiple>
        <option value="" disabled>請選擇</option>
        <option value="1">a</option>
        <option value="2">b</option>
        <option value="3">c</option>
    </select>

    <p>複選框</p>
    {{ checked }}<br>
    游泳 <input v-model="checked" type="checkbox" value="游泳">
    洗澡 <input v-model="checked" type="checkbox" value="洗澡">
    睡覺 <input v-model="checked" type="checkbox" value="睡覺">

    <p>單選框</p>
    {{ radioed }}<br><input type="radio" value="男" v-model="radioed"><input type="radio" value="女" v-model="radioed">
    <p>v-model 修飾符</p>
    <p>{{ attr }}</p>
    <input type="number" v-model.number="attr">
    <p>{{ attrText }}做用相似@chage</p>
    <input type="text" v-model.lazy="attrText">
    <p>{{ attrText }} 去除空格</p>
    <input type="text" v-model.trim="attrText">
</div>
<script> let vm = new Vue({ el: '#app', data() { return { msg: '我是data', selected:'', selectedMore:[], checked:[], radioed:'', attr:0, attrText:'' } }, methods: { handleInput(e){ this.msg = e.target.value } }, }) </script>
</body>
</html>
複製代碼

watch

觀測值的變化 執行對應函數react

三種寫法:git

  1. 添加deep屬性,代表要深度遍歷
  2. 添加immediate屬性,代表 當即執行
  3. 添加 name屬性,執行methods的這個方法
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<div id="app">
    {{ msg }}
    {{ name }}
</div>
<script src="vue.js"></script>
<script> let vm = new Vue({ el: '#app', data() { return { msg: { a: '123' }, name:'456' } }, methods: { fn() { console.log('這是methods') }, }, // 第一種 // watch:{ // msg:{ // handler(oldValue,newValue){ // console.log(oldValue,newValue) // 若是是對象的不到老值 // }, // deep: true // 若是是對象繼續深度遍歷 // } // } watch: { msg: [ // { // handler(oldValue, newValue) { // console.log(oldValue, newValue) // 若是是對象的不到老值 // }, // immediate: true, // 當即執行 // }, // 'fn', // 不知道爲何不行 ], name:'fn' }, }) setTimeout(() => { vm.msg.a = '456' vm.name = '789' }, 1000) </script>
</body>
</html>
複製代碼

computed

常用get,可是還有一個setes6

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<div id="app">
    全選: <input type="checkbox" v-model="checkAll">
    <hr>
    <input type="checkbox" v-for="check in checks" v-model="check.check">
</div>
<script src="vue.js"></script>
<script> let vm = new Vue({ el: '#app', data() { return { checks: [{ check: true }, { check: true }, { check: true }], } }, computed: { checkAll: { get() { // 有一個不知足 返回false 而且不往下進行 return this.checks.every((item) => item.check) }, set(newValue) { this.checks.forEach(item => item.check = newValue) }, }, }, }) </script>
</body>
</html>
複製代碼

watch 和computed區別

  1. computed不會立馬取值,用到的時候纔會取值. 而且有緩存,依賴數據不改變不更新結果
  2. watch 當即執行,會先算出來一個老值.數據變化就執行函數

filter

過濾器,將屬性進行格式化後在進行展現github

分爲 全局局部兩種

會接受兩個參數,一個是要格式化的數據,一個是格式化的規則

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js"></script>
    <script src="vue.js"></script>
</head>
<body>
<div id="app">
    <p>局部</p>
    {{ timer | format1('YYYY:MM:DD') }}
    <p>全局</p>
    {{ timer | format('YYYY:MM:DD') }}
</div>
<script> Vue.filter('format', function (timer, format) { return dayjs(timer).format(format) }) let vm = new Vue({ el:'#app', data() { return { timer: 123456789, } }, filters:{ format1(timer, format){ return dayjs(timer).format(format) } } }) </script>
</body>
</html>
複製代碼

指令

一樣分爲 局部全局

使用的時候 在想要使用的標籤上添加 v-xxx xxx爲指令名字就能夠

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>指令</title>
    <script src="vue.js"></script>
</head>
<body>
<div id="app">
    <p>自動獲取焦點</p>
    <input type="text" v-focus>
    <p>點擊顯示 日曆效果</p>
    <div v-click-outside="hide">
        <input type="text" @focus="show">
        <div v-if="isShow">
            日曆顯示 時間
        </div>
    </div>
    <h1>指令有生命週期.有鉤子</h1>
    <ul>
        <li>bind 綁定上的時候會執行一次</li>
        <li>inserted 插入的時候</li>
        <li>update 當引用數據發生變化的時候</li>
        <li>componentUpdate 模板更新</li>
        <li>unbind 解除綁定</li>
        <li>默認寫成一個函數 bind+update</li>
    </ul>
    <h1>指令傳入三個參數的含義</h1>
    <ul>
        <li>el 當前元素 </li>
        <li>bindings 有關指令的各個屬性</li>
        <li>vNode 虛擬節點</li>
        <li>vNode.context Vue實例</li>
    </ul>
</div>

<script> // 全局註冊指令 Vue.directive('focus', { // 當被綁定的元素插入到 DOM 中時…… inserted: function (el) { // 聚焦元素 el.focus() }, }) let vm = new Vue({ el: '#app', // 局部指令 directives: { clickOutside: { bind(el, bindings, vNode) { el.handler = function (e) { // console.log(e.target) console.log(vNode.context) if (!el.contains(e.target)) { vNode.context[bindings.expression]() } } document.addEventListener('click', el.handler) }, unbind(el) { document.removeEventListener('click', el.handler) }, }, }, data() { return { isShow: false, } }, methods: { show() { this.isShow = true }, hide() { this.isShow = false }, }, }) </script>
</body>
</html>
複製代碼

實例屬性

介紹一些經常使用的 實例屬性

  1. $mount() 掛載,參數寫要掛載的節點。若是不寫,則掛載的$el屬性上,能夠手動掛載(好比寫Message彈框)
  2. $options 獲取用戶寫的配置
  3. $watch 跟watch 用法同樣
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>實例屬性</title>
    <script src="vue.js"></script>
</head>
<body>
<div id="app">
    {{ msg }}
</div>
<script> let vm = new Vue({ // el:'#app', data() { return { msg: '我是data', } }, template: '<div>我是template</div>', render(h) { return h('div', ['我是render']) }, }) vm.$mount() // 掛載 提供值了就掛載到對應的 節點上 // 不提供就掛載到$el 屬性上 表明要手動掛載 console.log(vm.$el) // 獲取真實節點 document.body.appendChild(vm.$el) console.log(vm.$options) // 用戶參數 console.log(vm.$watch('msg', function (oldValue, newValue) { console.log(oldValue, newValue) })) // 就是 watch 另外一種寫法 批量更新 只更新一次 內部有隊列 </script>
</body>
</html>
複製代碼

進階

動畫

動畫分爲兩種,一種是CSS動畫,一種是js動畫。各位按照需求選擇

由於我的推薦使用CSS做動畫,因此JS版本就再也不寫出來了。有興趣的朋友能夠點擊這裏

css版本

就是把 要作動畫的DOM元素用transition包裹一下

而後記住一下6個名字,分別對應動畫不一樣的週期

  1. .v-enter 進入動畫時候
  2. .v-enter-active 進入動畫過程當中
  3. .v-enter-to 進入動畫進行到最後
  4. .v-leave 這個沒有實際意義,爲了美感
  5. .v-leave-active 離開動畫過程當中
  6. .v-leave-to 離開動畫結束
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>動畫</title>
    <script src="../vue.js"></script>
</head>
<body>
<div id="app">
    <p>transiton 能夠有name 屬性 給更名字,這樣一些 v-leave 則變成的  name-leave</p>
    <transition>
        <div v-show="isShow" class="box" style=" width: 100px;height: 100px;">
        </div>
    </transition>
    <button @click="handleShow">點我</button>
    <p>transition Vue動畫標籤 transition-group 動畫組</p>
</div>
<script> let vm = new Vue({ el: '#app', data() { return { isShow: false, } }, methods: { handleShow() { this.isShow = !this.isShow }, }, }) </script>
<style> .box { background-color: red } /*進入動畫時候的顏色*/ .v-enter { background-color: blue; } /*動畫過程當中*/ .v-enter-active { transition: all 2s linear; } /*動畫進行到最後*/ .v-enter-to { background-color: yellow; } /* 進行完以後會變成紅色*/ /*這個沒有實際意義,爲了美感*/ .v-leave { background-color: purple; } .v-leave-active{ transition: all 2s linear; } .v-leave-to{ background-color: blue; } </style>
</body>
</html>
複製代碼

動畫組

與上一個不同的是,這個數多組動畫。

區別 使用了 transition-group

動畫名稱

  • enter-class
  • enter-active-class
  • enter-to-class (2.1.8+)
  • leave-class
  • leave-active-class
  • leave-to-class (2.1.8+)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>動畫</title>
    <script src="../vue.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
</head>
<body>
<div id="app">
    <p>vue中動畫組</p>
    <input type="text" v-model="content">
    <transition-group enter-active-class="animated bounceInLeft" leave-active-class="animated bounceOutRight" >
        <li v-for="arr in computedArr" :key="arr">{{ arr }}</li>
    </transition-group>
</div>
<script> let vm = new Vue({ el: '#app', data() { return { content:'', arrs:['abc','basd','zxcw','awqec','kjea'] } }, methods: { handleShow() { this.isShow = !this.isShow }, }, computed:{ computedArr(){ return this.arrs.filter(item => item.includes(this.content)) } } }) </script>
<style> li{ width: 200px; background-color: blue; line-height: 35px; } </style>
</body>
</html>
複製代碼

組件

組件通信(重點)

我總結了 一下,大概如下幾種

  1. props+emit
  2. provide+inject 單項 數據流
  3. $parent+$children 直接觸發父/子類的事件
  4. $broadcast + $dispatch 本身在原型上寫的
  5. $attrs+$listeners 經過全部屬性和方法的集合獲取
  6. $bus 相似Vuex
  7. Vuex Vue插件

props+emit

// parents
<template>
    <div>
        <h1>Parent</h1>
        <h2>第一種</h2>
        <Son :money="money" :changMoney="changMoney"></Son>
        <p>第二中方法 click2是本身定義的名字,不是原生事件</p>
        <Son :money="money" @click2="changMoney"></Son>
    </div>
</template>
<script>
  import Son from './Son'
  export default {
    name: 'Parent',
    data() {
      return {
        money: 200,
      }
    },
    components: {
      Son
    },
    methods: {
      changMoney(value) {
        this.money = value
      },
      changMoney2(val) {
        this.money += val
      },
    },
  }
</script>

// son
<template>
    <div>
        <h1>Son</h1>
        <p>子組件接收到以後,利用props 屬性接受,而後能夠直接使用</p>
        <p>子組件可使用父組件傳遞的屬性和函數</p>
        我是爸爸給個人錢{{ money }}
        <h2>第一種</h2>
		<button @click="changMoney(500)">改變父親錢數</button>
        <h2>第二種方法</h2>
        <button @click="change">改變父親錢數2</button>
    </div>
</template>
<script>
  export default {
    props:{
      money: {
        type:Number,
        default:100
      },
      changMoney:{
        type:Function,
        default: ()=>{}
      }
    },
    methods:{
      change(){
        this.$emit('click2',300)
      }
    }
  }
</script>
複製代碼

第一種是 傳遞一個屬性還有一個函數,子代接收到以後,能夠在使用

第二種是 利用$emit, 直接觸發 在父級定義的函數

特別注意,這個click2不是原生的,你把它叫作 a , b 之類等均可以

provide+inject

官方建議

provideinject 主要在開發高階插件/組件庫時使用。並不推薦用於普通應用程序代碼中。

這個就比較簡單。相似於reactredux

// parent
<template>
    <div>
        <h1>Parent</h1>
        <h1> 關於son2  跨代通信</h1>
        <Son2 @eat="eat"></Son2>
    </div>
</template>
<script>
  import Son2 from './Son2'

  export default {
    provide(){
      return {parent:this}
    },
    name: 'Parent',
    data() {
      return {
        money: 200,
      }
    },
    components: {
      Son2,
    },
    methods: {
      eat(){
        console.log('patent中的eat方法')
      }
    },
  }
</script>

// son2
<template>
    <div>
        Son2
        <GrandSon></GrandSon>
    </div>
</template>
<script>
  import GrandSon from './GrandSon'

  export default {
    name:'Son2',
    components: {
      GrandSon,
    },
  }
</script>

// grandSon
<template>
<div>
    GrandSon
    <p>跨代通信的值</p>
    {{ parent.money }}
    <button @click="$parent.$emit('eat')">觸發父組件的eat方法</button>
</div>
</template>
<script>
export default {
  inject:['parent']
}
</script>
複製代碼

寫一個Son2的做用,就是讓你們明白,隔代也是能夠的。一個提供,一個接收以後就可使用

$parent+$children

這個我就直接用上面的代碼了。這個比較簡單。就是經過$parent/$children 找到它的父/子級。而後 使用或者觸發他們的屬性或者方法

$broadcast + $dispatch

再次引用官方的話

$dispatch$broadcast 已經被棄用。請使用更多簡明清晰的組件間通訊和更好的狀態管理方案,如:Vuex

固然,咱們仍是介紹一些這兩個方法,各位看須要使用(小聲bb一下,我以爲Vuex真香)

// 在main.js上
import Vue from 'vue'
import App from './App';

/** * 找父節點觸發事件 * @param eventName * @param ComName * @param Value */
Vue.prototype.$dispatch = function (eventName, ComName = '', Value = '') {
  let parent = this.$parent;
  while (parent) {
    if (ComName && parent.$options.name === ComName) {
      parent.$emit(eventName, Value)
      return
    } else {
      parent.$emit(eventName, Value)
      parent = parent.$parent
    }
  }
}
/** * 找子節點觸發事件 * @param eventName * @param ComName * @param value */
Vue.prototype.$broadcast = function (eventName, ComName = '', value = '') {
  let children = this.$children // 獲取得是數組
  function broadcast(children) {
    for (let i = 0; i < children.length; i++) {
      let child = children[i]
      if (ComName === child.$options.name) {
        child.$emit(eventName, value)
        return
      } else {
        if (child.$children) {
          broadcast(child)
        }
      }
    }
  }
  broadcast(children)
}
複製代碼

這兩個方法利用了$parent$children。不斷獲取父/子節點,觸發相對應的事件。

我這個$dispatchelse寫的是,若是不是這個組件的事件,我也觸發了。其實應該把這句刪除。只 繼續往上找就能夠

使用

// 直接這樣使用就好
<button @click="$parent.$emit('eat')">觸發父組件的eat方法</button> 
複製代碼

$attrs+$listeners

官方定義

// APP.vue
<template>
    <div>
        <Test :a="1" :b="2" :c="3" :d="4" @click="click"></Test>
    </div>
</template>
<script>
  import Test from './test'
  export default {
    data() {
      return {
        msg: 'hello',
      }
    },
    components: {
      Test,
    },
    methods:{
      click(){
        console.log('我是APP中的click')
      }
    }
  }
</script>

// test.vue
<template>
<div>
    我是test
    <h1>使用$attrs能夠得到,可是會綁定在DOM元素上</h1>
    <ul>
        <li>設置 <strong>inheritAttrs:false </strong>就不會綁定了</li>
        <li>當props接收後,arrts將不會顯示已經被接收的</li>
        {{ $attrs }}
        <li>這樣子代傳遞</li>
        <button @click="$listeners.click">觸發APP中的click</button>
        <test2 v-bind="$attrs" v-on="$listeners"></test2>
    </ul>
</div>
</template>
<script>
    import test2 from './test2';
    export default {
      props:['a'],
      name:'Test',
      inheritAttrs:false,
      components:{
        test2
      }
    }
</script>

//test2.vue
<template>
    <div>
        <h1>我是test2</h1>
        {{ $attrs }}
        <button @click="$listeners.click">觸發APP中的click</button>
    </div>
</template>
<script>
  export default {
    name: 'test2',
  }
</script>
複製代碼

注意

  1. 父級這樣傳遞屬性的過程當中,會把這個屬性綁定在DOM元素上,(被props接收的不會被綁定),能夠在子類中使用inheritAttrs:false,來設置取消綁定
  2. 使用得時候,直接使用$attrs.x/$listeners.x使用
  3. 往下一代傳遞的時候,直接使用v-bind="$attrs" v-on="$listeners",就能夠把沒有被props接收過的都傳給下一代使用

$bus

就是掛載了一個Vue實例

// APP.vue
<template>
    <div>
        <h1>子組件如何監聽父組件的mounted</h1>
        <p>組件掛載, 先掛載父組件 -》渲染子組件,子mounted -》 父mounted</p>
        <p>能夠實現任意組件之間的通信,但只適合小規模的</p>
        <bus></bus>
    </div>
</template>
<script>
  import bus from './$bus使用';
  export default {
    data() {
      return {
        msg: 'hello',
      }
    },
    mounted(){
      this.$bus.$emit('監聽事件','hello')
    },
    components: {
      bus
    },
  }
</script>
// $bus使用
<template>
<div>
    bus
    {{ $bus.a }}
</div>
</template>
<script>
  export default {
    name: 'bus',
    mounted() {
      // 發佈訂閱模式  能夠屢次訂閱
      this.$bus.$on('監聽事件', function (value) {
        console.log(value)
      })
    },
    beforeDestroy(){
      // 解綁組件
      this.$bus.$off('監聽組件')
    }
  }
</script>
複製代碼

Vuex

請日後面看

插槽

<template>
    <div>
        <h1>插槽</h1>
        <test1>
            我是標籤裏面的內容
        </test1>
        <h1>具名插槽</h1>
        <p>新版寫法只能夠用 template</p>
        <p>這裏插值表達式的數據用的是父類的</p>
        <test1>
<!--            老版本寫法-->
<!--            <div slot="header">asd</div>-->
<!--            <div slot="footer">qwe</div>-->
            <template v-slot:header>header {{ msg }}<br></template>
            <template v-slot:footer>footer</template>
        </test1>

        <h1>做用域插槽</h1>
        <p>這樣用的是子類的數據</p>
        <test1>
<!--            老版本寫法-->
<!--            <div slot="header" slot-scope="{a,b}">{{ a }}{{ b }}</div>-->
            <template v-slot:header="{a,b}" >{{ a }},{{ b }}</template>
        </test1>
    </div>
</template>
<script>
import test1 from './test1';
  export default {
    data() {
      return {
        msg: 'hello',
      }
    },
    components:{
      test1
    }
  }
</script>

// test1
<template>
    <div>
        <h1>我是test1</h1>
        <slot></slot>
        <slot name="header" a="1" b="2"></slot>
        <slot name="footer"></slot>
    </div>
</template>
<script>
  export default {
    name: 'test1',
  }
</script>
複製代碼

這個比較簡單,就再也不多多敘述。強調一點,新老版本區別

  1. 新版本只能夠用template進心包裹
  2. 老版本能夠用div

總結

看完上面的內容能夠嘗試模仿寫一下 element-ui的表單組件。他們使用了async-validator做爲校驗。

github地址

Vue

一樣有一個簡單版本Vue數據響應式和編譯原理分析 和 模擬實戰.這個版本沒有用到虛擬Dom等。

虛擬dom。我的也總結了一篇幫你深刻了解虛擬DOM和DOM-diff,但願能幫到各位

僅僅是一個簡單的實現。可是實現了 部分指令

完整部分(即此次總結的,帶上虛擬dom等等),這個內容因爲太多(標題細分太多。很差去尋找)。我另寫了一篇文章,還在整理中,1號大概能夠放出來。

貼一個圖證實一下。實在是考慮的太多,因此寫出來比較慢

UTOOLS1593399637478.png

vueX

​ 推薦一下本身的另外一篇文章Vuex的簡單實現,感受這一篇寫的相對簡單一點

Vuex 用法

這個就很少作解釋了。不太熟練的朋友能夠先去看官方文檔

給出一下個人數據定義

// store/index.js
import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from './../vuex2'

Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    age: 10
  },
  strict: true,
  getters: {
    myAge(state) {
      return state.age + 30
    }
  },
  mutations: {
    // 同步更改state 在嚴格模式下不可使用異步
    change(state, payload) {
      state.age += payload
    }
  },
  actions: {
    // 異步更改state
    asyncChange({ commit }, payload) {
      setTimeout(()=>{
        commit('change', payload)
      }, 1000)
    }
  }
})
export default store
複製代碼

基本Vuex的實現

install方法

Vuex做爲一個 插件,首先執行的是install方法,咱們但願的是,任何組件均可以訪問到這裏面的數據。組件的渲染是由父到子的,因此咱們既能夠先進行判斷。若是它是根節點,就把這個屬性掛載到根節點上,若是不是,就找他父級的這個屬性,而後掛載到這個Vue實例上

// 官方Api 會把Vue做爲參數傳入
const install = (_vue)=>{
    Vue = _vue
  Vue.mixin({ // 先父後子
    beforeCreate() {
      if (this.$options.store) { // 跟節點
        this.$store = this.$options.store
      } else { // 往子節點傳遞
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}
複製代碼

訪問state的實現

咱們平時使用的過程是是這樣的

const store = new Vuex.Store({
    state:{
        
    }
})
複製代碼

因此咱們發現,咱們其實是new了一個VueX 的 Store 類。接下來咱們開始寫這個類。

let Vue
class Store{
    constructor(options) {
        this.state = options.state
        this.mutations = {}
        this.actions = {}
        this.getters = {}
        this.actions = {}
    }
}
// 下面是install 方法
複製代碼

再其餘組件中使用

// VuexUse.vue
<template>
  <div>
    VueX中的屬性{{$store.state.age}}
    <button @click="change2">=100</button>
  </div>
</template>
<script>
  export default {
    name: 'VuexUse',
    mounted() {
      console.log(this.$store)
    },
    methods: {
      change2() {
        this.$store.state.age = 100
        console.log(this.$store)
      }
    }
  }
</script>
複製代碼

你們會看到 輸出10.當點擊按鈕的時候。再次打印,會發現數據已經發生變化,可是視圖並無刷新。咱們應該讓數據更新以後,視圖也跟着刷新。這時候咱們就應該想到用Vue的特性。咱們改造一下剛纔的代碼

let Vue
class Store{
    constructor(options) {
        this.state = new Vue({ data: { state: options.state } }).state
        this.mutations = options.mutations || {}
        this.actions = options.actions || {}
        this.getters = {}
    }
}
// 下面是install 方法
複製代碼

這樣咱們就實現了數據改變,就刷新視圖

commit 和dispatch

VueX,中,更改狀態通常須要這兩個方法,一個是同步,一個是異步,咱們來實現一下這兩個方法

// 使用的時候
change() {
    this.$store.commit('xxx', 10)
},
複製代碼

因此,這兩個方法是寫在Store類裏面的

let Vue
class Store{
    constructor(options) {
        this.state = new Vue({ data: { state: options.state } }).state
        this.mutations = options.mutations || {}
        this.actions = options.actions || {}
        this.getters = {}
    }
    commit = (mutationName, payload)=>{
     this.mutations[mutationName](this.state, payload)
  	}	
    dispatch = (actionName, payload)=>{
    	this.actions[actionName](this, payload)
  	}
}
複製代碼

commit,我以爲你們均可以看懂,就是找到用戶定義的mutations,把參數傳入,就能夠執行了。

dispatch,爲何要傳入this緣由,在定義的時候,使用的是ES6的解構賦值,因此這裏要把this傳入

注意,這兩種方法還可使用 柯里化來實現,這樣傳值的時候只用傳入 payload,更方便一點

getter實現

首先咱們要明白,getter是做什麼用的。我 我的理解,須要對訪問數據進行必定處理。也就是咱們訪問這個屬性的時候,獲得這個函數的返回結果。

let Vue
class Store{
    constructor(options) {
        this.state = new Vue({ data: { state: options.state } }).state
        this.mutations = options.mutations || {}
        this.actions = options.actions || {}
       
        // 這下面是修改的部分
        options.getters && this.handleGetters(options.getters)
    }
    
   handleGetters(getters) {
    this.getters = {}
    Object.keys(getters).forEach(key=>{
      Object.defineProperty(this.getters, key, {
        get: ()=>{
          return getters[key](this.state)
        }
      })
    })
  }
}
複製代碼

解釋一下handleGetters這一段代碼

  1. 獲取每一個函數函數名稱
  2. 根據每一個函數的名稱 設置對應的返回值

這段代碼相對比較簡單,這樣就實現了getters

模塊(module)功能的實現

store/index

import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from './../vuex'

Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    age: 10
  },
  strict: true,
  getters: {
    myAge(state) {
      return state.age + 30
    }
  },
  mutations: {
    change(state, payload) {
      state.age += payload
    }
  },
  actions: {
    // 異步更改state
    asyncChange({ commit }, payload) {
      setTimeout(()=>{
        commit('change', payload)
      }, 1000)
    }
  },
  modules: {
    a: {
      namespaced: true,
      state: {
        num: 'a1'
      },
      mutations: {
        // 同步更改state 在嚴格模式下不可使用異步
        change(state, payload) {
          console.log(state, payload) // 是本身這個模塊內的state
          console.log('a')
        }
      }
    },
    b: {
      state: {
        num: 'b1'
      },
      mutations: {
        // 同步更改state 在嚴格模式下不可使用異步
        change(state, payload) {
          console.log('b')
        }
      },
      modules: {
        c: {
          namespaced: true,
          state: {
            num: 'c1'
          },
          mutations: {
            // 同步更改state 在嚴格模式下不可使用異步
            change(state, payload) {
              console.log('c')
            }
          }
        }
      }
    }
  }
})
export default store

複製代碼

接下來這一部分可能會難理解一點。我盡力把我學習到給你們清楚的講出來。這部分會對以前的代碼進行大量修改

先改造一下咱們的Store,變會最開始的樣子

class ModuleCollection {
    
}

let Vue
class Store{
    constructor(options) {
        this.state = options.state
        this.mutations = {}
        this.actions = {}
        this.getters = {}
        this.modules = new ModuleCollection(options)
        console.log('收集完成的模塊')
        console.log(this.modules)
    }
}
// 下面是install 方法
複製代碼

如今,咱們須要模塊化,因此咱們要寫一個方法來 格式化數據,變成咱們想要的樣子

思路,咱們要把這邊模塊進行遍歷 註冊,若是模塊下面還有子類,則繼續遍歷。 核心方法, reduce

ModuleCollection

/**
 * 循環對象的值
 * @param obj
 * @param cb
 */
function forEach(obj, cb) {
  Object.keys(obj).forEach(key=>{
    cb(key, obj[key])
  })
}


class ModuleCollection {
  constructor(options) {
    this.register([], options)
  }
  register(path, rootModule) {
      
    // 格式化模塊
    const rawModule = { 
      _raw: rootModule, //原來的modules
      _children: {},  // 孩子
      state: rootModule.state // 原來的數據
    }
    
    // 雙向記錄 把格式化以後的數據記錄下來
    rootModule.rawModule = rawModule 
      
    // 判斷是否是根的存在
    if (!this.root) {
      // 第一次確定不存在
      this.root = rawModule
    } else {
      // 核心  返回的是各個module 對應的格式化後的模塊
      const parentModel = path.slice(0, -1).reduce((root, current)=>{
        console.log(current)
        return root._children[current]
      }, this.root)
    /----------------------------------------------------/
      parentModel._children[path[path.length - 1]] = rawModule
    }

    // 遞歸,遍歷子代。核心邏輯
    if (rootModule.modules) {
      forEach(rootModule.modules, (moduleName, module)=>{
        this.register(path.concat(moduleName), module)
      })
    }
  }
}
複製代碼

主要解釋一下 /-------------/上下的代碼。上面的parentModel,指的是模塊

  1. 第一次 parentModelthis.rootrawModulea模塊的定義
  2. 第二次 parentModelthis.rootrawModuleb模塊的定義
  3. 第三次 parentModelb模塊,rawModulec模塊的定義

打印一下 this.modules

UTOOLS1592985514369.png

如今咱們就把全部的模塊進行了 格式化。接下來。咱們就要對 咱們格式化後的數據進行安裝。使他們能夠訪問獲得

總結一下,這個函數的做用就是把 咱們傳入的modules進行一個格式化,而且將模塊進行分類。

installModule

這個函數的做用 循環遍歷子節點,安裝 state action mutation getters

/** * 安裝 state action mutation getters 並 * @param store Vuex 中的store * @param rootState 根state * @param path 路徑 * @param rawModule 原模塊 */
function installModule(store, rootState, path, rawModule) {

  // 安裝state
  if (path.length > 0) { // 證實是子節點 
    const parentState = path.slice(0, -1).reduce((root, current)=>{
      return rootState[current] 
    }, rootState)
    
    // 官方API。
    // 向響應式對象中添加一個 property,並確保這個新 property 一樣是響應式的,且觸發視圖更新
    Vue.set(parentState, path[path.length - 1], rawModule.state)
  }

  // rawModule 裏面有 
  // _raw 原來的模塊 
  // _children 孩子 
  // state 原來的state
    
    
  // 安裝getters 
  // 注意狀態的使用,要使用本模塊的狀態
  const getters = rawModule._raw.getters || {}
  if (getters) {
    forEach(getters, (getterName, getterFun)=>{
      Object.defineProperty(store.getters, getterName, {
        get: ()=>getterFun(rawModule.state) 
      })
    })
  }

  // mutations跟actions 差很少。都是把 全部的模塊的函數存在 root根模塊中 使用的時候直接遍歷
  const mutations = rawModule._raw.mutations || {}
  if (mutations) {
    forEach(mutations, (mutationName, mutationFun)=>{
      // 寫一個發佈訂閱模式
      const arr = store.mutations[mutationName] || (store.mutations[mutationName] = [])
      arr.push((payload)=>{
        mutationFun(rawModule.state, payload)
      })
    })
  }

  const actions = rawModule._raw.actions || {}
  if (actions) {
    forEach(actions, (actionName, actionsFun)=>{
      const arr = store.actions[actionName] || (store.actions[actionName] = [])
      arr.push((payload)=>{
        actionsFun(store, payload)
      })
    })
  }

  // 遍歷子節點
  forEach(rawModule._children, (moduleName, rawModule)=>{
    // console.log(rawModule) // 一個個子節點
    installModule(store, rootState, path.concat(moduleName), rawModule)
  })
}
複製代碼

storerootState始終是。Vuex中的store 和根上面的state

  1. 第一次 patch[]rawModule模塊的定義
  2. 第二次 patch['a']rawModulea模塊的定義
  3. 第三次 patch['b']rawModuleb模塊的定義
    1. 走進來發現 b下面還有modules,因此patch['b',‘c’]rawModulec模塊的定義

命名空間的實現

命名空間這個就簡單了。只須要在每一個方法前面加上x/就能夠了

function installModule(store, rootState, path, rawModule) {
  
  // 命名空間的實現 獲取命名
  let root = store.modules.root // 拿到的是格式化以後的結果
  const nameSpace = path.reduce((str, current)=>{
    root = root._children[current]
    str = str + (root._raw.namespaced ? current + '/' : '')
    return str
  }, '')

  // 安裝state 這裏沒有發生變化
  if (path.length > 0) {
    // 證實是子節點
    const parentState = path.slice(0, -1).reduce((root, current)=>{
      return rootState[current]
    }, rootState)
    Vue.set(parentState, path[path.length - 1], rawModule.state)
  }

 
  // rawModule 裏面有 
  // _raw 原來的模塊 
  // _children 孩子 
  // state 原來的state
    
  // 安裝getters 把方法前面加上 命名
  const getters = rawModule._raw.getters || {}
  if (getters) {
    forEach(getters, (getterName, getterFun)=>{
      Object.defineProperty(store.getters, nameSpace + getterName, {
        get: ()=>getterFun(rawModule.state) // 使用模塊中的狀態
      })
    })
  }

  const mutations = rawModule._raw.mutations || {}
  if (mutations) {
    forEach(mutations, (mutationName, mutationFun)=>{
      // 寫一個發佈訂閱模式
      const arr = store.mutations[nameSpace + mutationName] || (store.mutations[nameSpace + mutationName] = [])
      arr.push((payload)=>{
        mutationFun(rawModule.state, payload)
      })
    })
  }

  const actions = rawModule._raw.actions || {}
  if (actions) {
    forEach(actions, (actionName, actionsFun)=>{
      const arr = store.actions[nameSpace + actionName] || (store.actions[nameSpace + actionName] = [])
      arr.push((payload)=>{
        actionsFun(store, payload)
      })
    })
  }

  // 遍歷子節點
  forEach(rawModule._children, (moduleName, rawModule)=>{
    // console.log(rawModule) // 一個個子節點
    installModule(store, rootState, path.concat(moduleName), rawModule)
  })
}
複製代碼

''(空)字符串開始。根節點不須要命名空間

registerModule API的實現

class Store {
  constructor(options) {
    this.state = new Vue({ data: { state: options.state } }).state
    this.mutations = {}
    this.actions = {}
    this.getters = {}
    // 模塊收集 並格式化
    this.modules = new ModuleCollection(options)
    console.log('收集完成的模塊')
    console.log(this.modules)
    // 模塊的安裝並訪問 store rootState path 根模塊 安裝所有模塊
    installModule(this, this.state, [], this.modules.root)
  }
  // 模塊開發完以後的寫法
  commit = (mutationName, payload)=>{
    this.mutations[mutationName].forEach(fn=>fn(payload))
  }
  dispatch = (actionName, payload)=>{
    this.actions[actionName].forEach(fn=>fn(payload))
  }
  /** * 自定義註冊 module * @param moduleName * @param module */
  registerModule(moduleName, module) {
    if (!Array.isArray(moduleName)) {
      moduleName = [moduleName]
    }
    this.modules.register(moduleName, module)
    console.log(this.modules.root)
    // 安裝當前模塊
    installModule(this, this.state, moduleName, module.rawModule)
  }
}
複製代碼

思路很簡單,就是把 註冊的module,進行格式化以後。再進行安裝就能夠

注意,要安裝位置要肯定好哦

輔助函數

VueRouter

Vuex同樣,也寫過一篇比較簡單的實現 VueRouter的簡單實現,感受這一篇寫的相對簡單一點

這部分我我的以爲本身掌握的不是特別好。因此 講述的不太清楚。僅提供一個思路。

最開始install方法

在咱們的日常使用過程當中,除了router-linkrouter-view。最經常使用的可能就是this.$router.push(xx)。因此咱們仍是跟VueX的作法差很少。在每個實例上掛在一個屬性

const install = (Vue)=>{
  Vue.mixin({
    beforeCreate() {
      if (this.$options.router) {
        // console.log(this) // 指的是一個new Vue
        this._routerRoot = this // 把vue實例掛載到這個屬性上
        this._router = this.$options.router // 用戶傳入得 router
        // 路由的初始化
        this._router.init(this)
      } else {
        this._routerRoot = this.$parent && this.$parent._routerRoot
      }
    }
  })
}
export default install
複製代碼

以上代碼只作了兩件事。

  1. 掛載屬性
  2. 調用路由的初始化方法。對路由進行初始化

index文件

首先咱們應該分析。咱們這個主文件應該有什麼。在咱們平常使用的過程當中,通常是import VueRouter from 'vue-router'

因此

  1. 咱們應該友一個VueRoter類。
  2. 上面得有初始化方法install.。
  3. VueRoter類的constructor中,咱們應該對用戶傳入的數據進行處理。還有就是分析它路由模式
  4. init方法,要能夠監聽到路由變換,而後跳轉到對應的 路由。渲染對應的組件

分析完以後。咱們就開始着手寫

我先把大致框架給你們展現一下

import install from './install'
import createMatcher from './createMatcher'
import HashHistory from './history/hash'

class VueRouter {
  constructor(options) {
    // matcher 匹配器 處理樹形結構 將他扁平化
    // 返回兩個方法 addStore match 匹配對應結果
    this.matcher = createMatcher(options.routes || [])

    // 內部須要用 hash history 進行路由的初始化工做
    // base 表示基類,實現全部路由的公告方法都放在基本類上 保證不一樣路由API 有相同的使用方法
    this.history = new HashHistory(this)
  }

  push(location) {
    
  }

  init(app) {
    // app 是頂層Vue 實例
    // 獲取到路徑 並進行跳轉 並渲染對應組件
    // 匹配一次完成後,監聽路有變化,完成以後的更新操做
    
  }
}

VueRouter.install = install
export default VueRouter

複製代碼

createMatcher方法

裏面出現的方法在下面都會有所解釋

import createRouteMap from './createRouteMap'
import { createRoute } from './history/base'

export default function createMatcher(routes) {
  // 開始扁平化數據
  const { pathList, pathMap } = createRouteMap(routes)

  // 重載s
  function addRoute(routes) {
    createRouteMap(routes, pathList, pathMap)
  }

  function match(location) {
    console.log('create裏面的match' + location)
    // 從pathMap獲取的location
    const record = pathMap[location]
    // console.log(record)
    return createRoute(record, {
      path: location
    })
  }
  return {
    addRoute, match
  }
}
複製代碼

咱們先經過createRouteMap方法,把傳入的routes(即用戶傳入的配置)進行一個格式化處理,獲得一個pathList(地址的列表)pathMap(地址映射,裏面有地址,組件等等屬性)

官方API中,有一個交addRotes,也就是再添加進一組路由。

咱們仍是利用createRouteMap方法。這個方法具體是什麼樣的看下面

match方法的做用是匹配器,匹配傳入的location(地址)。返回相對應的 記錄

createRouteMap方法

export default function createRouteMap(routes, oldPathList, oldPathMap) {
  const pathList = oldPathList || []
  const pathMap = oldPathMap || Object.create(null)
  // Object.create(null) 和 {} 區別 前者沒有原型鏈
  // 數組扁平化
  routes.forEach(route=>{
      addRouteRecord(route, pathList, pathMap)
    }
  )
  return {
    pathList, pathMap
  }
}

function addRouteRecord(route, pathList, pathMap, parent) {
  const path = parent ? parent.path + '/' + route.path : route.path
  const record = {
    path,
    component: route.component,
    parent
    // todo
  }
  if (!pathList[route]) {
    pathList.push(path)
    pathMap[path] = record
  }
  if (route.children) {
    route.children.forEach(route=>{
      addRouteRecord(route, pathList, pathMap, record)
    })
  }
}

複製代碼

Object.create(null) 和 {} 區別 前者沒有原型鏈

{} 會存在一堆的屬性

UTOOLS1593155710472.png

UTOOLS1593155769799.png
**Object.create(null)**不存在這些

addRouteRecord 是這個的核心方法。它的工做是

  1. 先差找父級元素。若是有。則加上父級 例如 about/a。沒有就是自己
  2. 而後生成一條記錄record
  3. 判斷你傳入的route(即每一項路由)是否在pathList裏面。在了就跳過。不在就添加進去。 **這個方法就實現了addRoutes**的做用
  4. 遞歸遍歷。若是有孩子繼續添加

createRoute方法

這個方法及其關鍵!!!!

緣由:好比咱們渲染about/a這個路徑的組件。咱們是否是必須得渲染about,這樣才能夠渲染a

因此這個方法的主要做用就是。把路徑的父級也都存下來

export function createRoute(record, location) {
  const res = [] // 若是匹配到路徑 就放進來
  if (record) {
    while (record) {
      res.unshift(record)
      record = record.parent
    }
  } // 把父級路徑也存放下來
  console.log(res, location)
  return {
    ...location,
    matched: res
  }
}
複製代碼

history方法

這個即解釋this.history = new HashHistory(this)

爲何要單獨列出來?由於有不一樣的 路由模式,可是有公共的處理方法。固然還須要有不一樣的方法來處理不一樣的路由。

咱們這裏只考慮hash

base.js

export function createRoute(record, location) {
  const res = [] // 若是匹配到路徑 就放進來
  if (record) {
    while (record) {
      res.unshift(record)
      record = record.parent
    }
  } // 把父級路徑也存放下來
  console.log(res, location)
  return {
    ...location,
    matched: res
  }
}

class History {
  constructor(router) {
    this.router = router
    this.current = createRoute(null, {
      path: '/'// 默認路徑
    })
  }

  transitionTo(location, cb) { // 最好屏蔽一下,以防止屢次調用
    console.log(location, cb)
    // 獲得路徑 開始匹配對應的模板
    const r = this.router.match(location)
    this.current = r // 對當前路徑進行更新
    // eslint-disable-next-line eqeqeq
    if (location === this.current.path && r.matched.length === this.current.matched) {
      return
    }
    this.cb && this.cb(r)
    cb && cb()
  }

  setupListeners() {
    window.addEventListener('hashchange', ()=>{
      this.transitionTo(window.location.hash.slice(1))
    })
  }

  listen(cb) {
    this.cb = cb
  }
}

export default History
複製代碼

能夠看出 這個base.js作了幾件事

  1. 初始化了一個默認路由
  2. 提供了跳轉方法
  3. 監聽了路由變化
  4. listen這個等會再說

transitionTo中間的if判斷。是爲了防止屢次調用的。

hash.js

import History from './base'

function ensureSlash() {
  if (window.location.hash) {
    return
  }
  window.location.hash = '/'
}
class HashHistory extends History {
  constructor(router) {
    super(router) // super === parent.call(this) 向父級傳遞router
    this.router = router
    ensureSlash() // 確保有hash值
  }

  getCurrentLocation() {
    return window.location.hash.slice(1) // 除了# 號後面的路徑
  }
}

export default HashHistory
複製代碼

這個就比較簡單了。就再也不解釋了

從新回到index.js

import install from './install'
import createMatcher from './createMatcher'
import HashHistory from './history/hash'

class VueRouter {
  constructor(options) {
    // matcher 匹配器 處理樹形結構 將他扁平化
    // 返回兩個方法 addStore match 匹配對應結果
    this.matcher = createMatcher(options.routes || [])

    // 內部須要用 hash history 進行路由的初始化工做
    // base 表示基類,實現全部路由的公告方法都放在基本類上 保證不一樣路由API 有相同的使用方法
    this.history = new HashHistory(this)
  }

  match(location) { // 做了一層封裝 返回匹配結果
    return this.matcher.match(location)
  }

  push(location) {
    this.history.transitionTo(location, ()=>{
      window.location.hash = location// 這樣的話 要渲染兩遍 一邊transitionTo 一邊是hash的監聽
    }) // hash沒有改變 要改變hash
  }

  init(app) {
    // app 是頂層Vue 實例
    // console.log(app)
    // 獲取到路徑 並進行跳轉 並渲染對應組件
    // 匹配一次完成後,監聽路有變化,完成以後的更新操做
    const history = this.history
    const setupHashListener = ()=>{ // 監聽以後回調
      history.setupListeners() // 監聽路由變化 父類
    }
    history.transitionTo( // 跳轉方法 父類
      history.getCurrentLocation(), // 獲取當前路徑 分路由 因此是子類
      setupHashListener
    )
    // 訂閱好 而後路由 屬性變化 更新此方法
    history.listen((route)=>{
      app._route = route
    })
  }
}

VueRouter.install = install
export default VueRouter
複製代碼

改造完以後的index.js作的事,

  1. 監聽路由。
  2. 跳轉路由。
  3. 設置改變_route的函數(這時候 _route還不是動態的)

回到install

import RouterView from './components/router-view'
import RouterLink from './components/router-link'

const install = (Vue)=>{
  Vue.mixin({
    beforeCreate() {
      if (this.$options.router) {
        // console.log(this) // 指的是一個new Vue
        this._routerRoot = this
        this._router = this.$options.router // 用戶傳入得 router
        // 路由的初始化
        this._router.init(this)
        // 將current 定義成 響應式的。數據改變則刷新視圖
        console.log(this._router)
        // 給當前實例建立了 _route 屬性, 取自this._router.history.current
        Vue.util.defineReactive(this, '_route', this._router.history.current)
        // 定義以後 更新_route
      } else {
        this._routerRoot = this.$parent && this.$parent._routerRoot
      }
    }
  })
  Object.defineProperty(Vue.prototype, '$route', {
    get() {
      console.log(this._routerRoot._route)
      return this._routerRoot._route
    }
  })

  Object.defineProperty(Vue.prototype, '$router', {
    get() {
      return this._routerRoot._router
    }
  })
  Vue.component('RouterView', RouterView)
  Vue.component('RouterLink', RouterLink)
}
export default install
複製代碼

回到install方法,初始化以後。把_route設置成動態(有getset)。

以後數據發生改變,視圖就會刷新。

組件

RouterView

export default {
  functional: true, // 函數式組件 沒有狀態 沒有this
  render(h, { parent, data }) { // 裏面有不少options 這是經過解構賦值出來的
    // console.log(options)
    const route = parent.$route // 被放到了vue 原型上
    console.log(route)
    let depth = 0
    // $vnode表示佔位符Vnode
    while (parent) {
      if (parent.$vnode && parent.$vnode.data.routerView) {
        depth++
      }
      parent = parent.$parent
    }
    data.routerView = true
    const record = route.matched[depth]
    console.log(record)
    if (!record) {
      return h()
    }
    return h(record.component, data)
  }
}
複製代碼

這段代碼中最難理解的就是 depth

route是屬性。 這段代碼在history/base.jscreateRoute 返回的結果中有一個match。裏面存放了全部的 父級路徑

routerView的解釋。自定義屬性。看他是不是根節點。 第一次進來的時候 ,渲染App組件(裏面放有RouterView)。若是存在,證實要渲染的是下一個節點了

router是方法

RouterLink

export default {
  props: {
    to: {
      type: String,
      require: true
    },
    tag: {
      type: String,
      default: 'a'
    }
  },
  methods: {
    handle() {
      this.$router.push(this.to)
    }
  },
  render(h) {
    const tag = this.tag
    return <tag onclick = { this.handle } > { this.$slots.default } < /tag>
  }
}
複製代碼

我這裏用的是jsx語法。以爲看不懂的能夠直接用RouterLink.vue正常寫來代替

鉤子函數(路由守衛)

路由index

....

router.beforeEach((to, from, next)=>{
  console.log(1)
  setTimeout(()=>{
    next()
  }, 1000)
})
router.beforeEach((to, from, next)=>{
  console.log(2)
  setTimeout(()=>{
    next()
  }, 1000)
})
export default router
複製代碼

簡單思路

這個就比較像expresskoa裏面的了。

簡單寫個思路就是這樣。

// 儲存
let deps = []

// 放置
beforeXxx(cb){
    this.deps.push(cb)
}

// 使用
// 在視圖更新或者跳轉前
this.deps.forEach(dep=>{
    dep()
})
複製代碼

beforeEach實現

  1. 首先如今index。js文件夾下(VueRouter/index) 作初始化工做

寫過的我就直接用省略號代替了

class VueRouter {
  constructor(options) {
 ....
    this.beforeEachs = []
  }

  match(location) { // 做了一層封裝 返回匹配結果
   ....
  }

  push(location) {
....
  }

  init(app) {
....
  }

  beforeEach(cb) {
    this.beforeEachs.push(cb)
  }
}
複製代碼

先把全部的beforeEach都存起來。在跳轉以前執行它

再道history/base文件夾下,找到transitionTo方法.裏面的這三句代碼是跳轉路由的

this.current = r // 對當前路徑進行更新
    this.cb && this.cb(r)
    cb && cb()
複製代碼

下面的代碼是 如何實現beforeEach的運行的

// 1. 取到咱們 收集到的隊列
const queue = this.router.beforeEachs

// 輔助函數
const iterator = (hook, next)=>{
    hook(r, this.current, next)
}

// 運行隊列
runQueue(queue, iterator, ()=>{
    this.upDateRoute(r, cb)
})

// 這個方法是對上面三句更新方法的封裝
upDateRoute(r, cb) {
    this.current = r
    this.cb && this.cb(r)
    cb && cb()
  }
// runQueue 函數定義 其實就是一個遞歸調用
function runQueue(queue, iterator, callback) {
  function step(index) {
    if (index === queue.length) {
      return callback()
    }
    const hook = queue[index]
    iterator(hook, ()=>step(index + 1))
  }
  step(0)
}
複製代碼

思路:

  1. 獲取到收集的beforeEach隊列
  2. 讓這個隊列 依次執行
  3. 爲何不用forJS單線程,用for的話異步會 出現問題

完整代碼

// history/bsae。js
export function createRoute(record, location) {
 ...
}

function runQueue(queue, iterator, callback) {
  function step(index) {
    if (index === queue.length) {
      return callback()
    }
    const hook = queue[index]
    iterator(hook, ()=>step(index + 1))
  }
  step(0)
}

class History {
  constructor(router) {
    ...
  }

  transitionTo(location, cb) { 
    console.log(location, cb)
    const r = this.router.match(location)
    if (location === this.current.path && r.matched.length === this.current.matched) {
      return
    }
    const queue = this.router.beforeEachs
    const iterator = (hook, next)=>{
      hook(r, this.current, next)
    }
    runQueue(queue, iterator, ()=>{
      this.upDateRoute(r, cb)
    })
  }

  upDateRoute(r, cb) {
    this.current = r // 對當前路徑進行更新
    this.cb && this.cb(r)
    cb && cb()
  }

  setupListeners() {
      ...
  }

  listen(cb) {
      ...
  }
}

export default History
複製代碼

總結

這個會實現了以後。其餘幾個鉤子函數也就能夠進行實現了。方式大致都差很少。即經過回調函數來執行

路由權限

推薦一下花褲衩大佬寫的權限登錄的文章。 手摸手,帶你用vue擼後臺 系列二(登陸權限篇)

是我目前看到的關於路由權限 講解最好的一篇文章

本身的總結正在努力

Vue3

proxy

阮一峯老師這一本書關於這部分已經寫的很好了。我就再也不多作敘述了。詳情點擊這裏地址

let obj = {
  name: {
    achen: {
      name: '阿琛',
      age: 22,
    },
  },
  sex:'男',
  arr: ['吃', '喝', '玩'],
}

let handler = {
  // target就是原對象,key是鍵
  get(target, key) {
    // 懶代理 若是取到了這個對象纔會觸發,沒有取到就不會代理
    if (typeof target[key]=== 'object'){
      // 遞歸調用
      return new Proxy(target[key],handler)
    }
    console.log('收集')
    // return target[key] 老方法
    return Reflect.get(target,key)
  },
  set(target, key, value) {
    console.log('觸發更新')
    let oldValue = target[key]
    console.log(oldValue,value,key)
    if (!oldValue){
      console.log('設置屬性')
    }else if (oldValue!==value){
      console.log('修改屬性')
    }
    // target[key] = value
    // 有返回值
    return Reflect.set(target,key,value)
  },
}

// 兼容性差,可是能夠代理13中方法
// defineProperty 他只能對特定屬性進行攔截

// 攔截的是整個對象
let proxy = new Proxy(obj,handler)
// proxy.sex = 'nv'
// console.log(proxy.sex)

// 數組
// proxy.arr.push(132) // 先走一次obj 再收集 push length 在改值
// proxy.arr[0] = 100 // 直接觸發
proxy.xxx = 100

複製代碼

關於Reflect

developer.mozilla.org/zh-CN/docs/…

相關文章
相關標籤/搜索