A directory frame

  • Vue principle
    • The MVVM framework
    • The vue MVVM
  • Vue – principle of the router
  • Vuex principle
  • vue-ssr

Vue principle

The MVC and MVVM
  • M:modelIt’s the one that handles the data, and it doesn’t know what it’s going to do with it
  • V:viewBasically, what we see in the interface.
  • C: Controller: a ControllerIt’s the part of the application that handles user interaction where the Controller assigns the Model data to the View, and the Controller doesn’t care how the Model gets the data, it just calls it, okay
  • VM:-Leonard: The viewModel

MVC: update from M to V is via C, M’s data is displayed by C assigned to V, C accepts user behavior, and passes the data to M by sending action to C

MVVM: it is the realization principle of vUE two-way data binding. In MVVM mode, the appearance of VM completely reduces the sense of existence of Controller. C directly holds VM, and VM acts as the intermediate bridge between V and M.

Implement an MVVM manually

General flow chart

Create a Vue base class
// Base class Vue {constructor(options) {// bind in the current instance$el.$data, various processing of options this.$el = options.el 
    this.$data= options.data ... }}Copy the code
Data binding
  • Basically, parsing instructions, parsing interpolation, replacing data
  • Process:

In order to reduce backflow and redraw of the browser during parsing, we put the processing DOM in memory, and after parsing, it is inserted into the container.

The dom is traversed in memory and each node is judged. If it is text, variable replacement is performed. If it is tag, attribute parsing is performed.

// nodeType class Compiler {/* @el element @vm */ constructor(el, vm) { If not, get this.el = this.iselementNode (el) === 1? El: document.querySelector(el) this.vm = vm // Put all the node content in memory and appendChild() removes the element to another element. Const domFragment = this.createnodefragment (this.el) // To reduce browser backflow and redraw // compile memory node this.compile(domFragment) // This.el.appendchild (domFragment)} compile(node) {compile(node) {if(! node) {return} /** Determine the type * text, {{}} * tag, V - */ / Get the child of the memory node (only one layer) childNodes const childNodes = node.childNodes // Array array. from(childNodes).ifThis.iselementnode (child) {this.iselementNode (child)} this.compile(child) {this.iselementNode (child)}elseThis.piletext (child)}}); }/ / compileText(text) {const content = text.textContent const metchResult = /\{\{(.+)\}\}/.exec(content)if (/\{\{(.+)\}\}/.test(content)) {
      CompilerUtil['text'](text, content, this.vm)}} Fetch element (node) {v-, fetch element attributes, const attributes = node.attributes, Array.from(attributes). ForEach (attr => {const {name, value} = attr // name is an instructionif//v-mdel const [,directive]= name.split(this.isdirective (name)) {//v-mdel const [,directive]= name.split(this.isdirective (name))The '-') //v-on: CompilerUtil[directive](node, value, this.vm)}} isDirective(name) {return name.startsWith('v-'} // Check the node type isElementNode(node) {returnNode.nodetype === 1} // Move the contents of the node to the memory createNodeFragment(el) {let firstChild;
    let fragment = document.createDocumentFragment()
    while(firstChild = el.firstChild) {
      fragment.appendChild(firstChild)
    }
    returnFragment}} // Base class Vue {constructor(options) {... new Compiler(this.$el, this)
    
  }
}
Copy the code

For ease of management, the process of parsing is put together to prepare for the next step of data hijacking

// Parsing tools... CompilerUtil = {getValue(name, vm) {return name.split('. ').reduce((acc, current) => {
      return acc[current]
    }, vm.$data)},setValue(expr, value) {
    return expr.split('. ').reduce((acc, current, index, arr) => {console.log(acc, current, index, arr) // Assign the last itemif (index === arr.length - 1) {
        return acc[current] = value
      }
      return acc[current]
    }, vm.$data)}, model(node, name, vm) {// node.value = value node.adDeventListener ('input', (e) => { const newValue = e.target.value this.setValue(name, newValue) }) const value = this.getValue(name, // Generate watcher, New Watcher(vm, name, (newValue) => {fn(node, newValue)}) const fn = this.updater.modelUpdate fn(node, newValue) const fn = this.updater.modelUpdate fn(node, newValue) value) }, on(node, name, vm, eventname) { node.addEventListener(eventname, (e) => { vm[name].call(vm, e) }) }, HTML (node, name, vm) {const value = this.getValue(name, vm) // generate watcher, New Watcher(vm, name, (newValue) => {fn(node, newValue)}) const fn = this.updater.htmlUpdate fn(node, newValue) const fn = this.updater.htmlupdate fn(node, newValue) value) }, getContentValue(vm, content) {returncontent.replace(/\{\{(.+?) \}\}/g, (... arg) => {returnthis.getValue(arg[1], vm) }) }, text(node, content, vm){ const value = content.replace(/\{\{(.+?) \}\}/g, (... Arg) => {new Watcher(vm,arg[1], (newValue) => {{a}}, {{b}} fn(node, this.getContentValue(vm, content))})returnthis.getValue(arg[1], vm) }) const fn = this.updater.textUpdate fn(node, value) }, updater: ModelUpdate (node, value) {// Node. value = value}, htmlUpdate(node, value) {node.innerhtml = value}, textUpdate(node, value){ node.textContent = value }, } }Copy the code
The data was hijacked
  • The publish-subscribe model (the observer model) is essentially watcher by Watcher
  • Observe data with Observe. this process is simply to change the incoming data into object. definedProperty and then perform Object processing on its GET and set
  • Watcher: Stores the old value, retrieves the new value, and performs a callback when the old value is inconsistent with the new value
  • Collect the watcher via Dep and execute watcher.update () in sequence with distribution

DefinedProperty will eventually go to the model or text method of CompilerUtild, so we can use new Watcher() here to see if the data has changed. At the same time, we will hang the instance watcher on the target attribute of the Dep class. At the same time, we will get an old value, so we will start another get method. At this time, we instantiate Dep, and dynamically collect the watcher according to whether the Dep has the target attribute. When the user changes the data, we fire the set() method, which triggers notify, and let the collected Watcher in turn execute their update methods

. class Dep { constructor(vm, watcher){ this.subs = [] } add(watcher) { this.subs.push(watcher) }notify() {this.subs.foreach (watcher => {watcher. Update ()})}} Watcher class constructor(vm, expr, cb) {// Look for a certain attribute, This. Vm = vm this.expr = expr this.cb = cb dep. target = this This.oldvalue = this.get() dep.target = null}get() {
    return CompilerUtil.getValue(this.expr, this.vm)
  }
  update() {const newValue = compilerutil.getvalue (this.expr, this.vm) // when the newValue is different from the old value, the callback is executedif(newValue ! {this.oldValue) {this.cb(newValue)}} // Data jacking class Observe {// Change data to object.definedProperty type constructor(data) { this.observe(data) } isObject(data) {return typeof data === 'object'&& data ! == null } defineReactive(data, key, value) { this.observe(value) const dep = new Dep() const _this = this Object.defineProperty(data, key, {get() {
        Dep.target && dep.add(Dep.target)
        return value
      },
      set(newValue) {
        if(newValue ! == value) { _this.observe(newValue) value = newValue dep.notify() } } }) } observe(data) {ifDefinedProperty (this.isObject(data)) {// Is an Object, turn the Object into Object.definedPropertyfor(let key inData) {this.definereactive (data, key, data[key])}}}} // Base class Vue {constructor(options) {// bind in the current instance$el.$data
    this.$el = options.el
    this.$data = options.data
    letComputed = options.computed // Has dependencieslet methods = options.methods
    if (this.$el) {
      new Observe(this.$data)... this.proxyVm(this.$data) / / VMS.$dataPut the new Compiler(this) on the VM.$el, this)
    }
  }
  proxyVm(data) {
    for (let key in data) {
      Object.defineProperty(this, key, {
        get() {
          returnData [key] // perform proxy operation},set(newValue) {
          data[key] = newValue
        }
      })
    }
  }
}

Copy the code
Computed and methods are implemented
Class Vue {constructor(options) {// Bind in the current instance$el.$data
    this.$el = options.el
    this.$data = options.data
    letComputed = options.computed // Has dependencieslet methods = options.methods
    if (this.$el) {
      new Observe(this.$data)
      for(let key in computed) {
        Object.defineProperty(this.$data, key, {
          get: () => {
            return computed[key].call(this)
          }
        })
      }
      for(let eventName in methods) {
        Object.defineProperty(this, eventName, {
          get() {
            return methods[eventName]
          }
        })
      }
      this.proxyVm(this.$data) / / VMS.$dataPut the new Compiler(this) on the VM.$el, this)
    }
  }
  proxyVm(data) {
    for (let key in data) {
      Object.defineProperty(this, key, {
        get() {
          returnData [key] // perform proxy operation},set(newValue) { data[key] = newValue } }) } } } ... omitCopy the code

Vue – principle of the router

Vue-router is used as a plug-in for vue, using the plugins provided by vue.js. Use (plugin) to install VueRouter, which calls the install method of the plugin object

Manually implement the hash mode of vue-router
Install phase
+ In the install method, we use'Vue.mixin', define the _router attribute in the root component or, if it is a child component, pass$parentThe _router that makes each child component accessible to the root component. + for vue prototype injection$routerand$routeProperty to allow each component to pass this.$routeAnd this.$router+ point the _route attribute to current and make it responsive, and later if we update the _route attribute of our app we will re-render the view + register router-View as a global component + define the init method for the global routerCopy the code
// install
export function install (Vue) {
  if (install.installed) return
  install.installed = true// Assign a private Vue reference _Vue = Vue // inject$router $route
  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this.$root._router }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this.$root._route }
  })
  // beforeCreate mixin
  Vue.mixin({
    beforeCreate() {// Check whether there is a router, check the root instance, through vue.mixin so that each component can get the root is the exampleif (this.$options.router) {this._rooter // assign _router this._router = this.$options.router // Init this._router.init(this) // Define a responsive _route object vue.util.definereActive (this,'_route', this._router.history.current)
      } else {
          this._router = this.$parent && this.$parent.$options.router}}}) // Vue.component('router-view', View)
  Vue.component('router-link', Link)
// ...
}
Copy the code
The init phase
+ Init phase, based on the flattening user passed in parameters + based on the parameters to create a router instance + based on the current path, render and set up listeningCopy the code
import install from './install'
import createMatcher from './createMatcher'
import HashHistory from './history/HashHistory'
exportDefault class VueRouter {constructor(options) {// Render different components according to different paths // Flat incoming data to create a mapping table, Dynamically add routes enclosing the matcher = createMatcher (options. The routes | | []). This mode = options. The mode | |'hash'/* // Both classes inherit from the History base classhash: HashHistoryhistory: HTML5History */ this.history = new HashHistory(this)} match(path) {HTML5History */ this.history = new HashHistory(this)} match(path) {returnThis.matcher. Match (path)} init(app) {// app is the root instance // according to the route, display to the specified component, and set to listen for constsetUpListener = () = > {/ / jump to the success of the callback. This history. SetUpListener ()} this. History. TransitionTo ( this.history.getCurrentLocation(),setUpListener) this.history.listen((route) => {app._route = route // Responsive_mechanism}) // transitionTo public, getCurrentLocation belongs tohashforhistoryPath.name}} VueRouter. Install = installCopy the code
createMatcher
This method mainly has two methods: match and addRoutes + flatters the incoming data to generate a routing mapping table + addRoutes When doing permission management, we often add a temporary route configuration + match to find a match in the routing mapping table based on the current pathCopy the code
import createRouterMap from './createRouterMap'
import {createRoute} from './history/BaseHistory'
export default functionCreateMatcher (routes) {/* Flatters the user's incoming data, the set of paths [/... Creating a routing mapping table {/: []... } */ const {oldPathMap: pathMap} = createRouterMap(routes) // Initializes datafunctionConst record = pathMap[path] const record = pathMap[path] const record = pathMap[path]if (record) {
      returnCreateRoute (record, {path})} // Get path get pathMap find /, find the corresponding record, and generate a matching array according to the recordreturnCreateRoute (null, {path})} // Dynamically added methodfunction addRoutesConst {pathList, pathMap} = createRouterMap(routes, pathList, pathMap) // Add new data}return {
    match,
    addRoutes
  }
}

// createRouterMap.js
export default functioncreateRouterMap(routes = [], oldPathList = [], OldPathMap = object.create (null)) {// Flat array routes. ForEach (route => {addRouteRecord(route, oldPathList, oldPathMap) });return {
    oldPathList,
    oldPathMap
  }
}

function addRouteRecord(route, oldPathList, oldPathMap, parent) {
  // path, name, component
  let path = parent ? `${parent.path}/${route.path}` : route.path
  let record = {
    path,
    component: route.component,
    parent
  }
  if(! oldPathMap[path]) { oldPathList.push(path) oldPathMap[path] = record }if (route.children) {
    route.children.forEach(child => {
      addRouteRecord(child, oldPathList, oldPathMap, route)
    })
  }
}
Copy the code
The History class
Routing is divided intohashandhistoryModel +hashThe principle of patterns is to listenhashThe Change event, because anchor changes also add new records to the history stack, history.length also changes after the anchor changes. + Histroy mode: + in HTML5,historyObject presents the pushState() and replaceState() methods, which can be used to add data to the history stack and then listen for a popState event, + the popState event will be triggered when the active history entry changes. If the active history entry was created by a call to history.pushState (), or is affected by a call to history.replacEstate (), the state property of the popState event contains a copy of the state object for the history entry. + Note that a call to history.pushState() or history.replacEstate () does not trigger a popState event. This event is triggered only when a browser action is taken, such as when the user clicks the browser's back button (or calls history.back() in Javascript code)Copy the code
// historyThe base classexport function createRoute(record, location) {
  let res = []
  if (record) {
    while(record) {
      res.unshift(record)
      record = record.parent
    }
  }
  return {
    ...location,
    matched: res
  }
}
export default class Base {
  constructor(router) { // Vue-router
    this.router = router
    this.curret = createRoute(null, {
      path: '/'})} transitionTo(path, callback) {const route = this.router. Match (path) const route = this.router.if(this.curret.path ! == path && route.matched.length ! == this.curret.matched.length) { this.updateRoute(route) } callback && callback() } updateRoute(route) { this.curret = route this.cb && this.cb(this.curret) } listen(cb) { this.cb = cb } } //hashRouting class import BaseHistory from'./BaseHistory'
function getHash() {
  return window.location.hash.slice(1)
}
function ensureSlash() {
  if (window.location.hash) {
    return
  }
  window.location.hash = '/'} // let HashHistory inherit from BaseHistoryexportDefault Class HashHistory extends BaseHistory {// Inherits the Base class constructor(router) {super(router) ensureSlash() // Make sure the path has / }getCurrentLocation() {
    return getHash()
  }
  setUpListener() {// listenhashChange the window. The addEventListener ('hashchange', () => {ensureSlash() this.transitionto (this.getCurrentLocation()) // After the path change, to override the new route attribute current})}}Copy the code
Functional component
// view.js // functional componentsexport default {
  functional: trueRender (h, {parent, data}) {render(h, {parent, data}) {render(h, {parent, data}) {render(h, {parent, data});let matched = parent.$route.matched
    let dep = 0
    data.routerView = true
    while(parent) {
      if (parent.$vnode && parent.$vnode.data.routerView) {
        dep++
      }
      parent = parent.$parent
    }
    let record = matched[dep]
    if(! record) {return h()
    }
    let component = record.component
    return h(component, data)
  }
}
Copy the code

Vuex principle

  • What is the difference between action and mutation in VUE
  • How to solve vuex persistence problem
  • Mutation is a synchronous operation and actions is an asynchronous operation

Vuex is the state manager of VUE. It adopts the idea of Flux and the main idea is one-way data flow. In the classic scenario, the user dispatches actions through the view layer, and actions updates state on commit mutations, facilitating the update of the view

Source code analysis

  • Data: state –> data Get data: getters –> computed change data: mutations –> methods
  • As with vue-Router, you need to provide an install method as a plug-in for VUE
install
Vue.mixin({
    beforeCreate() {
        if (this.$options && this.$options.store) {// Find the root component main to hang one on$store
            this.$store = this.$options.store

        } else{// the non-root component points to its parent$store
            this.$store = this.$parent && this.$parent.$store}}})Copy the code
state
  • It can be seen that the state of Vuex is responsive, and vUE’s data is responsive. The state is stored in the data of vUE instance components.
Class Store{constructor() {this._s = new Vue({data: {// only data is responsive state: options.state}})} getstate() {
        return this._s.state
    }
}
...

Copy the code
getters
  • Vuex’s getters realize real-time data monitoring through computed property of VUE.
.letgetters = options.getters || {} this.getters = {}; Object.defineproperty (this.defineProperty, this.defineproperty, this.defineProperty, this.defineProperty, GetterName, {// When you want to get getterName (myAge) it automatically calls the get method // Arrow function does not have this get: () => {return getters[getterName](this.state)
        }
    })
})
...
Copy the code
Mutations and actions
.letmutations = options.mutations || {} this.mutations = {}; Object.keys(mutations).forEach(mutationName=>{ this.mutations[mutationName] = (payload) =>{ this.mutations[mutationName](this.state,payload) } }) ... Const actions = options.actions this.actions = {} object.keys (actions). ForEach (actionName => { Action.actions [actionName] = payload => {this.actions[actionName](this, payload)}}Copy the code
Commit Dispatch implementation
// commit
commit = (type// Ensure that this points to this.mutations[type](payload)
}
// dispatch
dispatch = (type,payload)=>{
    this.actions[type](payload)
}
Copy the code
Modules (modules)
  • Combine the incoming parameters into a tree structure and iterate over + publish subscribe as shown in the figure below:

Class ModuleCollections {constructor(options) {this.regiester([], options)} regiester(path, rootModule) {let module = {
      _rowModule: rootModule,
      _children:  {},
      state: rootModule.state
    }
    if(path.length === = 0) {this.root = module // root}else {
      // reduce [a, b, c] = { a: {b: {c{}}}}
     path.reduce((root, item, index, arr) => {
       if (arr.length -1 === index) {
         return root._children[item] = module
       } else {
         return root = root._children[item]
       }

      }, this.root)
    }

    if(rootModule.modules) { Object.keys(rootModule.modules).forEach((moduleName) => { this.regiester(path.concat(moduleName),  rootModule.modules[moduleName]) }) } } }Copy the code
Store the class
  • All getters are defined in this.getters, even in modules, actions and mutations
class Store {
  constructor(options) {
    this.s = new Vue({
      data() {
        return  {
          state: options.state
        }
      }
    })
    this.getters = {}
    this.mutations = {}
    this.actions = {}
    this._modules= new ModuleCollections()

    installModule(this, this.state, [], this._modules.root)
  }
  commit = (name, payload) => {
    this.mutations[name].forEach(fn => fn(preload))
  }
  dispatch = (dispatchName, payload) => {
    this.actions[dispatchName].forEach(fn => fn(preload))
  }
  get state() {// The class's property accessor // calling this.state takes you therereturn this._s.state
  }
}

Copy the code
// installModule const installModule = (store, rootState, path = [], rootModule) => {if (path.length > 0) {
    let parent = path.slice(0, -1).reduce((root, item) => {
        returnRoot = root[item]}, rootState) // Implement Vue$set(parent, path[path.length -1], rootModule.state)
  }


  let  getters = rootModule._rowModule.getters
  if (getters) {
    Object.keys(getters).forEach(getterName => {
      Object.defineProperty(store.getters, getterName, {
        get() {
          returnGetters [getterName](RootModule.state) // Update your status}})})} // integrate mutaionslet mutations = rootModule._rowModule.mutations
  if(mutations) {object.keys (mutations). ForEach (mutationName => {// There may be more than one event with the same nameletmutationArr = store.mutations[mutationName] || [] mutationArr.push((preload) => { Mutations [mutationName](rootModule. State, preload)}) store. Mutations [mutationName] = mutationArr})let actions = rootModule._rowModule.actions
  if(Actions) {object.keys (actions). ForEach (actionName => {// There may be multiple events with the same namelet actionsArr = store.actions[actionName] || []
      actionsArr.push((preload) => {
        actions[actionName](rootModule.state, preload)
      })
      store.actions[actionName] = actionsArr 
    })
  }
  Object.keys(rootModule._children).forEach((childName) => {
    installModule(store, rootState, path.concat(childName), rootModule._children[childName])
  })
}

Copy the code
Data persistence
  • Add plug-ins to store
const persits = (store) => {
  store.subscribe(mutations, state) {
    localStorage.setItem('vuex-store', JSON.stringify(state))
  }
}
new Vuex.Store({
    plugin: [
        persits
    ]
    ...
})
Copy the code

reference

  • www.jianshu.com/p/b0aab1ffa…
  • Blog.csdn.net/caoxinhui52…
  • Caibaojian.com/react/flux….

~ ~ not finished, continue to update