Based on using

The following code has been tested, you can copy directly to see the effect. Note the introduction of Vue files

All codes will be uploaded to GitHub or the code cloud after all updates are completed. I will update it as soon as possible

The Vue base section and the handwritten Vue section (the explanation has not been updated yet) have been uploaded. Click here to do so

Partial updates for VueX and VueRouter have also been uploaded. You need to click here

VueX has an auxiliary function and so on. The VueRouter is almost complete. Unit testing and SSR may follow

Aachen’s official account

Render priority

  1. render>template>dataInterpolation expression of
  2. When {{}} is an expression, it outputs the result and internally converts it to a function

      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>The basic concept</title>
    <script src="vue.js"></script>
</head>
<body>
<h1>Display priority</h1>
<ul>
    <li>The first one is render. The render method outputs the render result</li>
    <li>The second is the template method</li>
    <li>The last one is data, and if neither exists, the interpolation in data is printed</li>{{}}} when an expression is placed inside, the result of the output expression is converted to a function called render</ul>
<p>Directive modifiers, there are a lot of their own official website to see</p>

<div id="app">

    {{ msg }}
</div>
<script>
  let vm = new Vue({
    el: '#app',
    data() {
      return {
        msg: 'I am data',}},template: '< div > I am a template < / div >',
    render(h) {
      return h('div'['I am a render'])},method: {
      fn(e) { // The event source is automatically added without parentheses, and the event source is passed in manually with parentheses
        console.log('We already bound this internally with bind, there's no point in binding it again')
        console.log(this)}}})</script>
</body>
</html>

Copy the code

v-model

V-model is actually a syntactic sugar

<input type="text" :value = 'msg' @input="handleInput">
<! -- actually the grammar sugar above -->
<input type="text" v-model="msg">
Copy the code

V – the application of the model


      
<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>This is @ chage</p>
    <input type="text" :value = 'msg' @change="handleInput">
    <p>V-model is the syntactic sugar of @input above</p>
    <input type="text" v-model="msg">
    <p>The difference between @input and @change is when you focus and when you lose focus</p>
    <p>The drop-down list</p>
    {{ selected }}<br>
    <select v-model="selected">
        <option value="" disabled>Please select a</option>
        <option value="1">a</option>
        <option value="2">b</option>
        <option value="3">c</option>
    </select>

    <p>The value bound in this way must be a list</p>
    {{ selectedMore }}<br>
    <select v-model="selectedMore" multiple>
        <option value="" disabled>Please select a</option>
        <option value="1">a</option>
        <option value="2">b</option>
        <option value="3">c</option>
    </select>

    <p>Check box</p>
    {{ checked }}<br>swimming<input v-model="checked" type="checkbox" value="Swimming">Take a shower<input v-model="checked" type="checkbox" value="Shower">Go to bed<input v-model="checked" type="checkbox" value="Sleep">

    <p>Radio buttons</p>
    {{ radioed }}<br><input type="radio" value="Male" v-model="radioed"><input type="radio" value="Female" v-model="radioed">
    <p>V - model modifier</p>
    <p>{{ attr }}</p>
    <input type="number" v-model.number="attr">
    <p>{{attrText}} works like @chage</p>
    <input type="text" v-model.lazy="attrText">
    <p>{{attrText}} removes whitespace</p>
    <input type="text" v-model.trim="attrText">
</div>
<script>
  let vm = new Vue({
    el: '#app',
    data() {
      return {
        msg: 'I am data'.selected:' '.selectedMore: [].checked: [].radioed:' '.attr:0.attrText:' '}},methods: {
     handleInput(e){
       this.msg = e.target.value
     }
    },
  })
</script>
</body>
</html>
Copy the code

watch

The corresponding function is performed for changes in observed values

Three ways to write:

  1. adddeepProperty indicating deep traversal
  2. addimmediateProperty indicating immediate execution
  3. addnameProperty, executemethodsThis method of

      
<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('this is the methods')}},/ / the first
    // watch:{
    // msg:{
    // handler(oldValue,newValue){
    // console.log(oldValue,newValue) // if the object is less than the oldValue
    / /},
    // deep: true // If the object continues deep traversal
    / /}
    // }
    watch: {
      msg: [
        / / {
        // handler(oldValue, newValue) {
        // console.log(oldValue, newValue) // if the object is less than the oldValue
        / /},
        // immediate: true, // Executes immediately
        // },
        // 'fn', // why not].name:'fn'
    },

  })
  setTimeout((a)= > {
    vm.msg.a = '456'
    vm.name = '789'
  }, 1000)
</script>
</body>
</html>
Copy the code

computed

Get is often used, but there is also a set


      
<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">Selection:<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() {
          // Return false if one does not satisfy and do not proceed
          return this.checks.every((item) = > item.check)
        },
        set(newValue) {
          this.checks.forEach(item= > item.check = newValue)
        },
      },
    },
  })
</script>
</body>
</html>
Copy the code

Watch and computed

  1. Computed doesn’t take value right away, it takes value when it’s needed. And there is a cache, depending on the data does not change does not update the results
  2. Watch executes immediately, calculating an old value first. The function is executed when the data changes

filter

Filter, which formats the attributes before displaying them

It is divided into global and local

It takes two arguments, one for the data to be formatted and one for the formatting rule


      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://unpkg.com/[email protected]/dayjs.min.js"></script>
    <script src="vue.js"></script>
</head>
<body>
<div id="app">
    <p>local</p>
    {{ timer | format1('YYYY:MM:DD') }}
    <p>global</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>
Copy the code

instruction

Also divided into local and global

To use it, add v-xxx XXX as the instruction name on the label you want to use


      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>instruction</title>
    <script src="vue.js"></script>
</head>
<body>
<div id="app">
    <p>Automatic focus capture</p>
    <input type="text" v-focus>
    <p>Click to show calendar effect</p>
    <div v-click-outside="hide">
        <input type="text" @focus="show">
        <div v-if="isShow">Calendar display time</div>
    </div>
    <h1>Instructions have a life cycle. Have a hook</h1>
    <ul>
        <li>Bind is executed once on the binding</li>
        <li>[INSERT</li>
        <li>Update When reference data changes</li>
        <li>ComponentUpdate Updates templates</li>
        <li>Unbind Unbind</li>
        <li>The default is written as a function bind+update</li>
    </ul>
    <h1>The directive passes in the meaning of the three arguments</h1>
    <ul>
        <li>El Current element</li>
        <li>Each attribute of Bindings relevant instruction</li>
        <li>VNode Virtual node</li>
        <li>VNode. Context Vue instance</li>
    </ul>
</div>

<script>
  // Global register directive
  Vue.directive('focus', {
    // When the bound element is inserted into the DOM...
    inserted: function (el) {
      // Focus elements
      el.focus()
    },
  })
  let vm = new Vue({
    el: '#app'.// Local directives
    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>
Copy the code

Instance attributes

This section describes some common instance properties

  1. $mount()Mount, parameter write node to be mounted. If not written, it is mounted$elProperty, you can manually mount it (for example, write Message pop-up)
  2. $optionsGets the user write configuration
  3. $watchIt’s the same as watch

      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Instance attributes</title>
    <script src="vue.js"></script>
</head>
<body>
<div id="app">
    {{ msg }}
</div>
<script>
  let vm = new Vue({
    // el:'#app',
    data() {
      return {
        msg: 'I am data',}},template: '< div > I am a template < / div >',
    render(h) {
      return h('div'['I am a render'])
    },
  })
  vm.$mount() // Mount the value to the corresponding node
  // Mount to $el without providing it means manually mount it
  console.log(vm.$el) // Get the real node
  document.body.appendChild(vm.$el)

  console.log(vm.$options) // User parameters
  console.log(vm.$watch('msg'.function (oldValue, newValue) {
    console.log(oldValue, newValue)
  })) // This is just another way of saying watch
</script>
</body>
</html>
Copy the code

The advanced

animation

Animation is divided into two kinds, one is CSS animation, one is JS animation. You choose according to your needs

Since I personally recommend using CSS for animation, THE JS version will not be written. Those who are interested can click here

CSS version

You wrap the DOM element that you want to animate with transition

Then memorize the six names that correspond to different periods of the animation

  1. .v-enterWhen I go into the animation
  2. .v-enter-activeEnter the animation process
  3. .v-enter-toEnter the end of the animation
  4. .v-leaveIt doesn’t make any sense. It’s aesthetic
  5. .v-leave-activeLeave the animation process
  6. .v-leave-toEnd of animation

      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>animation</title>
    <script src=".. /vue.js"></script>
</head>
<body>
<div id="app">
    <p>Transiton can have a name attribute to change the name, so some V-leave becomes name-leave</p>
    <transition>
        <div v-show="isShow" class="box" style=" width: 100px; height: 100px;">
        </div>
    </transition>
    <button @click="handleShow">Am I</button>
    <p>Transition Vue Animation label transition-group Animation 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
    }

    /* Enter the animation color */
    .v-enter {
        background-color: blue;
    }

    /* During animation */
    .v-enter-active {
        transition: all 2s linear;
    }

    /* End of the animation */
    .v-enter-to {
        background-color: yellow;
    }

    /* will change to red */

    /* This has no practical meaning, for aesthetics */
    .v-leave {
        background-color: purple;
    }
    .v-leave-active{
        transition: all 2s linear;
    }
    .v-leave-to{
        background-color: blue;
    }
</style>
</body>
</html>
Copy the code

Animation group

Unlike the previous one, this number is multiple groups of animation.

Transition-group is used differently

The name of the animation

  • enter-class
  • enter-active-class
  • enter-to-class(2.1.8 +)
  • leave-class
  • leave-active-class
  • leave-to-class(2.1.8 +)

      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>animation</title>
    <script src=".. /vue.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]" rel="stylesheet" type="text/css">
</head>
<body>
<div id="app">
    <p>Vue animation group</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>
Copy the code

component

Component Communication (emphasis)

I have summarized the following

  1. props+emit
  2. provide+injectSingle data flow
  3. $parent+$childrenTriggers an event for a parent or child directly
  4. $broadcast + $dispatchI wrote it on a prototype
  5. $attrs+$listenersFrom a collection of all properties and methods
  6. $bussimilarVuex
  7. Vuex VueThe plug-in

props+emit

// parents <template> <div> <h1>Parent</h1> <h2> first kind </h2> <Son :money="money" :changMoney="changMoney"></Son> <p> second method Click2 is a self-defined name, not a native event </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> After receiving this, the son </h1> <p> child component can use the properties and functions passed by the parent component </p> {{money}} <h2> first kind </h2> <button @click="changMoney(500)"> change father money </button> </h2> second kind of method </h2> </button </button> </div> </template> <script> export default {props:{money: { type:Number, default:100 }, changMoney:{ type:Function, default: ()=>{} } }, methods:{ change(){ this.$emit('click2',300) } } } </script>Copy the code

The first is to pass in a property and a function that the child can use again after receiving it

The second way is to use $emit to directly fire functions defined at the parent level

Note that click2 is not native, you can call it a, B, etc

provide+inject

Official advice:

Provide and Inject are mainly used when developing high-level plug-in/component libraries. Not recommended for use in normal application code.

This one is easier. It’s like Redux for React

// parent <template> <div> <h1> parent </h1> <h1> About son2 cross generation communication </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(' eat method in patent ')}}, } </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')" </button> </div> </template> <script> export default {inject:['parent']} </script>Copy the code

So the whole point of writing a Son2 is just to show you that you can do this. One is provided and one is received and then used

$parent+$children

I’m just going to use the code above. This is the easy one. $parent/$children = $parent/$children Then use or trigger their properties or methods

$broadcast + $dispatch

To quote the official word again

$dispatch and $broadcast have been deprecated. Use more concise communication between components and better state management solutions such as Vuex.

Of course, we still introduce some of these two methods, you need to use (whisper bb, I think Vuex really smells good)

/ / in the main. Js
import Vue from 'vue'
import App from './App';

/** * Find the parent node to trigger the event * @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
    }
  }
}
/** * Find child node to trigger event * @param eventName * @param ComName * @param value */
Vue.prototype.$broadcast = function (eventName, ComName = ' ', value = ' ') {
  let children = this.$children // Get is an array
  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)
}
Copy the code

These two methods make use of $parent and $children. Constantly fetching the parent/child node triggers the corresponding event.

My else for $dispatch says, if not for this component’s event, I also fired. Actually, that should have been deleted. Just keep looking up

use

<button @click="$parent.$emit('eat')"> </button>Copy the code

$attrs+$listeners

The official definition of

// 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, }, </script> // test.vue <template> <div> <h1> </h1> <ul> <li> set <strong>inheritAttrs:false </li> </li> </li> Arrts will not show the </li> {{$attrs}} <li> that has been received so that the child </li> < button@click ="$listeners. Click "> trigger the click</button> <test2 in the APP v-bind="$attrs" v-on="$listeners"></test2> </ul> </div> </template> <script> import test2 from './test2'; export default { props:['a'], name:'Test', inheritAttrs:false, </script> //test2.vue <template> <div> <h1> I am test2</h1> {{$attrs}} <button $listeners. Click </button> </div> </template> <script> export default {name: 'test2',} </script>Copy the code

Pay attention to

  1. When the parent passes the property in this way, it binds the property to the DOM elementpropsReceived will not be bound), can be used in subclassesinheritAttrs:false,To set unbinding
  2. Use directly when necessary$attrs.x/$listeners.xuse
  3. When passing it on to the next generation, use it directlyv-bind="$attrs" v-on="$listeners", can be put without beingpropsWhatever is received is passed on to the next generation

$bus

A Vue instance is mounted

// app. vue <template> <div> <h1> <div> <h1> </p> <p> <p> </p> <bus></bus> </div> </template> <script> import bus from '. Export default {data() {return {MSG: 'hello',}}, mounted(){this.$bus.$emit(' listener ','hello')}, components: {bus},} </script> // $bus use <template> <div> bus {{$bus.a}} </div> </template> <script> export default {name: $bus.$on(function (value) {console.log(value)})}, // if ($bus.$on(function (value) {console.log(value)})}, BeforeDestroy (){// Unbind component this.$bus.$off(' Listening component ')}} </script>Copy the code

Vuex

Please look back

slot

<template> <div> <h1> <test1> I am the contents of the tag </test1> <h1> </h1> <p> The new version can only use template</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> Scope slot </h1> <p> This uses subclass data </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', } }, </script> // test1< template> <div> <h1> I am test1</h1> <slot></slot> <slot name="header" a="1" b="2"></slot> <slot name="footer"></slot> </div> </template> <script> export default { name: 'test1', } </script>Copy the code

This is a simple one, so I don’t want to talk about it too much. There’s a difference between the old version and the new one

  1. The new versionOnly can usetemplateInto the heart package
  2. The old versionYou can usedivEtc.

conclusion

After reading the above, try writing a form component like element-UI. They use async-Validator for validation.

Making the address

Vue

There is also a simple version of Vue data responsive and compilation principle analysis and simulation of actual combat. This version does not use the virtual Dom, etc.

Virtual dom. I have also summarized an article to help you understand the virtual DOM and DOM-diff

Just a simple implementation. But some of the instructions are implemented

The full section (this time summarized, with the virtual DOM, etc.) is due to too much (too much subdivision of the title). Hard to find). I have written another article, which is still being sorted out. Probably No.1 can be released.

Post a picture to prove it. I think about it too much, so I write it slowly

vueX

I would like to recommend the simple implementation of Vuex, another article of my own. I feel that this article is relatively simple

Vuex usage

I don’t have to explain that much. Not quite skilled friends can go to see the official documents first

Give me my data definition

// 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: {
    Asynchrony cannot be used in strict mode
    change(state, payload) {
      state.age += payload
    }
  },
  actions: {
    // Asynchronously change state
    asyncChange({ commit }, payload) {
      setTimeout((a)= >{
        commit('change', payload)
      }, 1000)}}})export default store
Copy the code

Implementation of basic Vuex

The install method

As a plug-in, Vuex first executes the install method, and we want any component to have access to this data. Components are rendered from parent to child, so we can judge first. If it is the root node, mount the property to the root node; if not, find the parent property and mount it to the Vue instance

// The official Api passes in Vue as a parameter
const install = (_vue) = >{
    Vue = _vue
  Vue.mixin({ // Father before son
    beforeCreate() {
      if (this.$options.store) { / / with a node
        this.$store = this.$options.store
      } else { // pass to the child node
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}
Copy the code

Access the implementation of state

The process that we normally use is this

const store = new Vuex.Store({
    state: {}})Copy the code

So we found that we actually new a VueX Store class. So let’s start writing this class.

let Vue
class Store{
    constructor(options) {
        this.state = options.state
        this.mutations = {}
        this.actions = {}
        this.getters = {}
        this.actions = {}
    }
}
// Here is the install method
Copy the code

Use it in other components

{{$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>Copy the code

You’ll see the output 10. When the button is clicked. Print again, and you’ll see that the data has changed, but the view has not been refreshed. We should let the view refresh as the data is updated. This is where we should think about using Vue’s features. So let’s modify that code

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 = {}
    }
}
// Here is the install method
Copy the code

So now that we’ve done the data change, we’re refreshing the view

Commit and dispatch

In VueX, changing state generally requires two methods, one synchronous and one asynchronous. Let’s implement these two methods

// When used
change() {
    this.$store.commit('xxx'.10)},Copy the code

So, these two methods are written inside the Store class

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)
  	}
}
Copy the code

Commit, I think everyone can understand, is to find user-defined mutations, pass in the parameters, and you can execute.

Dispatch, why pass this? The reason is that we use ES6 destruct assignment when we define it, so we pass this here

Note that you can also use both methodsCurrie,So when you pass a value, you just pass it inpayloadIt’s more convenient

Getter implementation

So the first thing we need to understand is what a getter does. Personally, I understand that there needs to be some processing for accessing data. That is, when we access this property, we get the return of this function.

let Vue
class Store{
    constructor(options) {
        this.state = new Vue({ data: { state: options.state } }).state
        this.mutations = options.mutations || {}
        this.actions = options.actions || {}
       
        // The following is the modified part
        options.getters && this.handleGetters(options.getters)
    }
    
   handleGetters(getters) {
    this.getters = {}
    Object.keys(getters).forEach(key= >{
      Object.defineProperty(this.getters, key, {
        get: (a)= >{
          return getters[key](this.state)
        }
      })
    })
  }
}
Copy the code

Explain the handleGetters code

  1. Gets the function name for each function
  2. Set the corresponding return value based on the name of each function

This relatively simple code implements getters

Module function implementation

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: {
    // Asynchronously change state
    asyncChange({ commit }, payload) {
      setTimeout((a)= >{
        commit('change', payload)
      }, 1000)}},modules: {
    a: {
      namespaced: true.state: {
        num: 'a1'
      },
      mutations: {
        Asynchrony cannot be used in strict mode
        change(state, payload) {
          console.log(state, payload) // is the state of the module
          console.log('a')}}},b: {
      state: {
        num: 'b1'
      },
      mutations: {
        Asynchrony cannot be used in strict mode
        change(state, payload) {
          console.log('b')}},modules: {
        c: {
          namespaced: true.state: {
            num: 'c1'
          },
          mutations: {
            Asynchrony cannot be used in strict mode
            change(state, payload) {
              console.log('c')}}}}}}})export default store

Copy the code

Now, this is going to be a little bit harder to understand. I will try my best to tell you clearly what I have learned. This section makes a lot of changes to the previous code

Let’s start with oursStoreIt’s going to be the way it started

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('Collected completed modules')
        console.log(this.modules)
    }
}
// Here is the install method
Copy the code

Now, we need modularity, so we’ll write a method to format the data the way we want it to be

We need to register this module and continue to register it if there are subclasses below it. The core method, Reduce

ModuleCollection

@param obj @param cb */ function forEach(obj, cb) {object.keys (obj). ForEach (key=>{cb(key, cb)) obj[key]) }) } class ModuleCollection { constructor(options) { this.register([], options) } register(path, RootModule) {// Format module const rawModule = {_raw: rootModule, // original modules _children: {}, // child state: Rootmodule. state // Original data} // Two-way record The formatted data rootModule.rawModule = rawModule // Check whether the root exists if (! This. root) {this.root = rawModule} else {// The core returns a formatted module corresponding to each 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} // recursively iterates through children. Modules) {forEach(rootModule.modules, (moduleName, module)=>{ this.register(path.concat(moduleName), module) }) } } }Copy the code

Mainly explain the code above and below /————-/. The parentModel above refers to the module

  1. For the first time,parentModelisthis.root.rawModuleisaModule definition
  2. The second timeparentModelisthis.root.rawModuleisbModule definition
  3. The third timeparentModelisbModule,rawModuleiscModule definition

Print this.modules

Now we have formatted all the modules. The next. We need to install our formatted data. Make it accessible to them

To summarize, this function formats the modules we pass in and categorizes them.

installModule

This function iterates through the child nodes, installing the State Action Mutation getters

/** * install state action mutation getters and * @param store store in Vuex * @param rootState rootState * @param path path * @param RawModule rawModule */
function installModule(store, rootState, path, rawModule) {

  / / installation state
  if (path.length > 0) { // Prove to be a child node
    const parentState = path.slice(0.- 1).reduce((root, current) = >{
      return rootState[current] 
    }, rootState)
    
    // Official API.
    // Add a property to the reactive object and make sure the new property is also reactive and triggers view updates
    Vue.set(parentState, path[path.length - 1], rawModule.state)
  }

  // In the rawModule
  // _raw Specifies the original module
  / / _children children
  // state Specifies the original state
    
    
  / / install getters
  // Pay attention to the use of state, use the state of this module
  const getters = rawModule._raw.getters || {}
  if (getters) {
    forEach(getters, (getterName, getterFun)=>{
      Object.defineProperty(store.getters, getterName, {
        get: (a)= >getterFun(rawModule.state) 
      })
    })
  }

  // mutations are similar to actions. All module functions are directly traversed when stored in the root module
  const mutations = rawModule._raw.mutations || {}
  if (mutations) {
    forEach(mutations, (mutationName, mutationFun)=>{
      // Write a publish subscribe pattern
      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)
      })
    })
  }

  // Iterate over the child nodes
  forEach(rawModule._children, (moduleName, rawModule)=>{
    Log (rawModule) // a child node
    installModule(store, rootState, path.concat(moduleName), rawModule)
  })
}
Copy the code

Store and rootState are always. Store in Vuex and state above the root

  1. For the first time,patchis[].rawModuleisThe rootModule definition
  2. The second timepatchis['a'].rawModuleisaModule definition
  3. The third timepatchis['b'].rawModuleisbModule definition
    1. Walk in and findbThere aremodules, sopatchis[' b ', 'c'].rawModuleiscModule definition

Implementation of a namespace

Namespaces are easy. Just prefix each method with x/

function installModule(store, rootState, path, rawModule) {
  
  // The implementation of the namespace gets the name
  let root = store.modules.root // Get the result of formatting
  const nameSpace = path.reduce((str, current) = >{
    root = root._children[current]
    str = str + (root._raw.namespaced ? current + '/' : ' ')
    return str
  }, ' ')

  // Install state there is no change
  if (path.length > 0) {
    // Prove to be a child node
    const parentState = path.slice(0.- 1).reduce((root, current) = >{
      return rootState[current]
    }, rootState)
    Vue.set(parentState, path[path.length - 1], rawModule.state)
  }

 
  // In the rawModule
  // _raw Specifies the original module
  / / _children children
  // state Specifies the original state
    
  Install getters to prefix the method with a name
  const getters = rawModule._raw.getters || {}
  if (getters) {
    forEach(getters, (getterName, getterFun)=>{
      Object.defineProperty(store.getters, nameSpace + getterName, {
        get: (a)= >getterFun(rawModule.state) // Use the state in the module})})}const mutations = rawModule._raw.mutations || {}
  if (mutations) {
    forEach(mutations, (mutationName, mutationFun)=>{
      // Write a publish subscribe pattern
      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)
      })
    })
  }

  // Iterate over the child nodes
  forEach(rawModule._children, (moduleName, rawModule)=>{
    Log (rawModule) // a child node
    installModule(store, rootState, path.concat(moduleName), rawModule)
  })
}
Copy the code

Start with the ‘(empty) string. The root node does not require a namespace

Implementation of the registerModule API

class Store {
  constructor(options) {
    this.state = new Vue({ data: { state: options.state } }).state
    this.mutations = {}
    this.actions = {}
    this.getters = {}
    // Modules are collected and formatted
    this.modules = new ModuleCollection(options)
    console.log('Collected completed modules')
    console.log(this.modules)
    // Module installation and access Store rootState Path Root module Installs all modules
    installModule(this.this.state, [], this.modules.root)
  }
  // How to write the module after it is developed
  commit = (mutationName, payload) = >{
    this.mutations[mutationName].forEach(fn= >fn(payload))
  }
  dispatch = (actionName, payload) = >{
    this.actions[actionName].forEach(fn= >fn(payload))
  }
  /** * Custom register module * @param moduleName * @param module */
  registerModule(moduleName, module) {
    if (!Array.isArray(moduleName)) {
      moduleName = [moduleName]
    }
    this.modules.register(moduleName, module)
    console.log(this.modules.root)
    // Install the current module
    installModule(this.this.state, moduleName, module.rawModule)
  }
}
Copy the code

The idea is very simple, is to register the module, after formatting. Just install it again

Note that the installation position must be determined

Auxiliary function

VueRouter

Like Vuex, I also wrote a relatively simple implementation of VueRouter. I feel that this one is relatively simple

I personally don’t think I have a good grasp of this part. So it’s not very clear. Only one idea is provided.

In the beginninginstallmethods

In our normal use, except router-link and router-View. The most commonly used is probably this.$router.push(xx). So we still do the same thing as VueX. Hang a property on each instance

const install = (Vue) = >{
  Vue.mixin({
    beforeCreate() {
      if (this.$options.router) {
        // console.log(this) // refers to a new Vue
        this._routerRoot = this // Mount the vue instance to this property
        this._router = this.$options.router // The user passes in the router
        // Route initialization
        this._router.init(this)}else {
        this._routerRoot = this.$parent && this.$parent._routerRoot
      }
    }
  })
}
export default install
Copy the code

The above code does only two things.

  1. Mount attributes
  2. Call the initialization method of the route. Initialize the route

The mainindexfile

First we should analyze. What should we have in this master file. Import VueRouter from ‘vue-router’

so

  1. We should be friendsVueRoterClass.
  2. It has to have an initialization method on itinstall.
  3. inVueRoterOf the classconstructorIn, we should process the data passed in by the user. And analyze itRouting patterns
  4. writeinitMethod that can listen for route transformation and then jump to the corresponding route. Render the corresponding component

After the analysis. So we started writing

Let me just show you the big picture

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

class VueRouter {
  constructor(options) {
    // Matcher matcher handles the tree structure and flattens it
    // Returns the results of the two methods addStore match
    this.matcher = createMatcher(options.routes || [])

    // Hash history is used internally to initialize the route
    // Base indicates the base class. The notification methods for all routes are placed on the base class to ensure that different routing apis have the same usage methods
    this.history = new HashHistory(this)
  }

  push(location) {
    
  }

  init(app) {
    // App is the top-level Vue instance
    // Get the path, jump and render the component
    // After the match is completed, the listening path changes, and the update operation is completed
    
  }
}

VueRouter.install = install
export default VueRouter

Copy the code

CreateMatcher method

The methods that appear in it are explained below

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

export default function createMatcher(routes) {
  // Start flattening the data
  const { pathList, pathMap } = createRouteMap(routes)

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

  function match(location) {
    console.log('Create match inside' + location)
    // Location obtained from pathMap
    const record = pathMap[location]
    // console.log(record)
    return createRoute(record, {
      path: location
    })
  }
  return {
    addRoute, match
  }
}
Copy the code

We use the createRouteMap method to format the routes passed in (i.e. the configuration passed in by the user). We get a pathList(address list) and pathMap(address map, which contains addresses, components, etc.).

In the official API, there’s an addRotes, which is adding another set of routes.

Let’s use the createRouteMap method again. What does this method look like

The match method matches the incoming location(address). Returns the corresponding record

CreateRouteMap method

export default function createRouteMap(routes, oldPathList, oldPathMap) {
  const pathList = oldPathList || []
  const pathMap = oldPathMap || Object.create(null)
  // Object.create(null) differs from {} the former has no prototype chain
  // Array flattening
  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)
    })
  }
}

Copy the code

Object.create(null) differs from {} the former has no prototype chain

{} will have a bunch of attributes

AddRouteRecord is the core method for this. What it does is

  1. Let’s find the parent element first. If you have. Plus the parent for exampleabout/a. Nothing is itself
  2. A record is then generatedrecord
  3. Judge what you pass inRoute (i.e., each route)Whether inpathListThe inside. If you do, skip. If it’s not there, add it. ** this method is implementedaddRoutesThe role of * *
  4. Recursive traversal. If any children continue to add

CreateRoute method

This method and its key !!!!

Reason: For example, we render the component of the about/ A path. Do we have to render about so that we can render A?

So the main function of this method is. Save the path’s parents as well

export function createRoute(record, location) {
  const res = [] // If it matches a path, put it in
  if (record) {
    while (record) {
      res.unshift(record)
      record = record.parent
    }
  } // Save the parent path
  console.log(res, location)
  return {
    ...location,
    matched: res
  }
}
Copy the code

The history method

This.history = new HashHistory(this)

Why list them separately? Because there are different routing modes, but there is a common approach. Of course, there are also different ways to handle different routes.

We’re just thinking about hash here

base.js

export function createRoute(record, location) {
  const res = [] // If it matches a path, put it in
  if (record) {
    while (record) {
      res.unshift(record)
      record = record.parent
    }
  } // Save the parent path
  console.log(res, location)
  return {
    ...location,
    matched: res
  }
}

class History {
  constructor(router) {
    this.router = router
    this.current = createRoute(null, {
      path: '/'// The default path
    })
  }

  transitionTo(location, cb) { // It is better to mask it to prevent multiple calls
    console.log(location, cb)
    // The path starts matching the corresponding template
    const r = this.router.match(location)
    this.current = r // Update the current path
    // 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
Copy the code

You can see that this base.js does several things

  1. A default route was initialized
  2. Provides a jump method
  3. Route changes were monitored
  4. listenWe’ll talk about that later

If judgment in transitionTo. This is to prevent multiple calls.

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) passes router to parent
    this.router = router
    ensureSlash() // Make sure there is a hash value
  }

  getCurrentLocation() {
    return window.location.hash.slice(1) // Except for the path after #}}export default HashHistory
Copy the code

This is a little bit easier. I won’t explain it

Go back to index.js

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

class VueRouter {
  constructor(options) {
    // Matcher matcher handles the tree structure and flattens it
    // Returns the results of the two methods addStore match
    this.matcher = createMatcher(options.routes || [])

    // Hash history is used internally to initialize the route
    // Base indicates the base class. The notification methods for all routes are placed on the base class to ensure that different routing apis have the same usage methods
    this.history = new HashHistory(this)
  }

  match(location) { // Returns a matching result
    return this.matcher.match(location)
  }

  push(location) {
    this.history.transitionTo(location, ()=>{
      window.location.hash = location// Render twice with transitionTo and hash listener
    }) // Hash does not change to change hash
  }

  init(app) {
    // App is the top-level Vue instance
    // console.log(app)
    // Get the path, jump and render the component
    // After the match is completed, the listening path changes, and the update operation is completed
    const history = this.history
    const setupHashListener = (a)= >{ // Callback after listening
      history.setupListeners() // Listen for route changes parent class
    }
    history.transitionTo( // Jump method parent class
      history.getCurrentLocation(), // Get the current path shunt so it is a subclass
      setupHashListener
    )
    // Subscribe and update this method with route property changes
    history.listen((route) = >{
      app._route = route
    })
  }
}

VueRouter.install = install
export default VueRouter
Copy the code

What index.js does after it’s been modified,

  1. Listen for routes.
  2. Forward route.
  3. Set the change_routeIs a function of_routeNot yet dynamic)

Return to the 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) // refers to a new Vue
        this._routerRoot = this
        this._router = this.$options.router // The user passes in the router
        // Route initialization
        this._router.init(this)
        // define current as responsive. Data changes refresh the view
        console.log(this._router)
        // Create a _route attribute for the current instance, taken from this._router.history.current
        Vue.util.defineReactive(this.'_route'.this._router.history.current)
        // Update _route after definition
      } 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
Copy the code

Go back to the install method, after initialization. Set _route to dynamic (get and set).

Then the data changes and the view is refreshed.

component

RouterView

export default {
  functional: true.// Functional components have no state
  render(h, { parent, data }) { // There are a lot of options in it
    // console.log(options)
    const route = parent.$route // Is placed on the vue prototype
    console.log(route)
    let depth = 0
    // $vnode represents the placeholder 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)
  }
}
Copy the code

The hardest thing to understand in this code is depth.

Route is an attribute. This code is in history/base.js. CreateRoute returns a match. This is where all the parent paths are stored

Explanation of the routerView. Custom properties. See if it’s the root node. The first time you come in, render the App component (with the RouterView in it). If so, it is the next node to render

The router is a way

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>}}Copy the code

I’m using JSX syntax here. RouterLink. Vue (RouterLink. Vue

Hook function (route guard)

Routing index

. router.beforeEach((to, from, next) = >{
  console.log(1)
  setTimeout((a)= >{
    next()
  }, 1000)
})
router.beforeEach((to, from, next) = >{
  console.log(2)
  setTimeout((a)= >{
    next()
  }, 1000)})export default router
Copy the code

Simple ideas

This is more like express and KOA.

So that’s the idea.

/ / store
let deps = []

/ / place
beforeXxx(cb){
    this.deps.push(cb)
}

/ / use
// Before the view is updated or jumped
this.deps.forEach(dep= >{
    dep()
})
Copy the code

BeforeEach implementation

  1. In the first place nowThe index. jsUnder folder (VueRouter/index) do the initialization

I’m just going to use the ellipsis

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

  match(location) { // Returns a matching result. } push(location) { .... } init(app) { .... } beforeEach(cb) {this.beforeEachs.push(cb)
  }
}
Copy the code

Store all beforeEach first. Perform it before the jump

Go to the history/ Base folder and find the transitionTo method. These three lines of code in it are jump routes

	this.current = r // Update the current path
    this.cb && this.cb(r)
    cb && cb()
Copy the code

How does the following code implement running beforeEach

// 1. Fetch the queue we collected
const queue = this.router.beforeEachs

// Auxiliary functions
const iterator = (hook, next) = >{
    hook(r, this.current, next)
}

// Run queue
runQueue(queue, iterator, ()=>{
    this.upDateRoute(r, cb)
})

// This method encapsulates the above three update methods
upDateRoute(r, cb) {
    this.current = r
    this.cb && this.cb(r)
    cb && cb()
  }
The runQueue function definition is actually a recursive call
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)}Copy the code

Ideas:

  1. Get the collectedbeforeEachThe queue
  2. Let the queue execute in sequence
  3. Why not?for.JS single-threadedwithforAsynchrony is a problem

The complete code

/ / 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 // Update the current path
    this.cb && this.cb(r) cb && cb() } setupListeners() { ... } listen(cb) { ... }}export default History
Copy the code

conclusion

And when that happens. Several other hook functions can also be implemented. In much the same way. That is, through the callback function

Routing permissions

Recommend the article that spends underpants big guy to write authority to land. Hand to hand, take you to use vUE masturbation background series two (login permission)

This is the best article I have read about routing permissions

I am working on my own summary

Vue3

proxy

Ruan Yifeng teacher this book about this part has been written very well. I’m not going to talk about it any more. Click here for details

let obj = {
  name: {
    achen: {
      name: 'he had o'.age: 22,}},sex:'male'.arr: ['eat'.'喝'.'play'],}let handler = {
  // Target is the original object and key is the key
  get(target, key) {
    // The lazy proxy will only fire if it gets the object, and will not fire if it does not
    if (typeof target[key]=== 'object') {// recursive call
      return new Proxy(target[key],handler)
    }
    console.log('collect')
    // return target[key] old method
    return Reflect.get(target,key)
  },
  set(target, key, value) {
    console.log('Trigger update')
    let oldValue = target[key]
    console.log(oldValue,value,key)
    if(! oldValue){console.log('Set properties')}else if(oldValue! ==value){console.log('Modify Properties')}// target[key] = value
    // Returns a value
    return Reflect.set(target,key,value)
  },
}

// Poor compatibility, but can proxy 13 methods
// defineProperty He can only intercept specific attributes

// Intercepts the entire object
let proxy = new Proxy(obj,handler)
// proxy.sex = 'nv'
// console.log(proxy.sex)

/ / array
// proxy.arr. Push (132) // go to obj and collect push length
// proxy.arr[0] = 100
proxy.xxx = 100

Copy the code

About Reflect

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