关于vue.js:vue路由响应式和虚拟dom

11次阅读

共计 9101 个字符,预计需要花费 23 分钟才能阅读完成。

1、动静路由


import Vue from 'vue'
import VueRouter from 'vue-router'
import Index from '../views/Index.vue'


Vue.use(VueRouter)


const routes = [
  {
    path: '/',
    name: 'Index',
    component: Index
  },
  {
    path: '/detail/:id',
    name: 'Detail',
    // 开启 props,会把 URL 中的参数传递给组件
    // 在组件中通过 props 来接管 URL 参数
    props: true,
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "detail" */ '../views/Detail.vue')
  }
]


const router = new VueRouter({routes})


export default router



<template>
  <div>
    <!-- 形式 1:通过以后路由规定,获取数据 -->
    通过以后路由规定获取:{{$route.params.id}}


    

    <!-- 形式 2:路由规定中开启 props 传参 -->
    通过开启 props 获取:{{id}}
  </div>
</template>

2、嵌套路由


// 加载组件
import Layout from '@/components/Layout.vue'
import Index from '@/views/Index.vue'
import Login from '@/views/Login.vue'


Vue.use(VueRouter)


const routes = [
  {
    name: 'login',
    path: '/login',
    component: Login
  },
  // 嵌套路由
  {
    path: '/',
    component: Layout,
    children: [
      {
        name: 'index',
        path: '',
        component: Index
      },
      {
        name: 'detail',
        path: 'detail/:id',
        props: true,
        component: () => import('@/views/Detail.vue')
      }
    ]
  }
]

3、编程式导航

4、history


const path = require('path')
// 导入解决 history 模式的模块
const history = require('connect-history-api-fallback')
// 导入 express
const express = require('express')


const app = express()
// 注册解决 history 模式的中间件,不应用这个则会导致网站 404
app.use(history())
// 解决动态资源的中间件,网站根目录 ../web
app.use(express.static(path.join(__dirname, '../web')))


// 开启服务器,端口是 3000
app.listen(3000, () => {console.log('服务器开启,端口:3000')
})

通过 history.pushState()办法扭转地址栏

监听 postate

依据以后路由寻找对应组件

5、VUE Router 外围原理实现


console.dir(Vue)
let _Vue = null
class VueRouter {static install(Vue){
        //1 判断以后插件是否被装置
        if(VueRouter.install.installed){return;}
        VueRouter.install.installed = true
        //2 把 Vue 的构造函数记录在全局
        _Vue = Vue
        //3 把创立 Vue 的实例传入的 router 对象注入到 Vue 实例
        // _Vue.prototype.$router = this.$options.router
        _Vue.mixin({beforeCreate(){if(this.$options.router){_Vue.prototype.$router = this.$options.router}
               
            }
        })
    }
    constructor(options){
        this.options = options
        this.routeMap = {}
        // observable
        this.data = _Vue.observable({current:"/"})
        this.init()}
    init(){this.createRouteMap()
        this.initComponent(_Vue)
        this.initEvent()}
    createRouteMap(){
        // 遍历所有的路由规定 吧路由规定解析成键值对的模式存储到 routeMap 中
        this.options.routes.forEach(route => {this.routeMap[route.path] = route.component
        });
    }
    initComponent(Vue){
        Vue.component("router-link",{
            props:{to:String},
            render(h){
                return h("a",{
                    attrs:{href:this.to},
                    on:{click:this.clickhander}
                },[this.$slots.default])
            },
            methods:{clickhander(e){history.pushState({},"",this.to)
                    this.$router.data.current=this.to
                    e.preventDefault()}
            }
            // template:"<a :href='to'><slot></slot><>"
        })
        const self = this
        Vue.component("router-view",{render(h){
                // self.data.current
                const cm=self.routeMap[self.data.current]
                return h(cm)
            }
        })
        
    }
    initEvent(){
        //
        window.addEventListener("popstate",()=>{this.data.current = window.location.pathname})
    }
}

6、数据驱动

数据模型仅仅是 JS 对象,当咱们批改数据时,视图灰烬更新

双向绑定

数据与视图双向扭转
v-model

defineProperty:vue2.0 的响应式根底


    // 数据劫持:当拜访或者设置 vm 中的成员的时候,做一些干涉操作
    Object.defineProperty(vm, 'msg', {
      // 可枚举(可遍历)enumerable: true,
      // 可配置(能够应用 delete 删除,能够通过 defineProperty 从新定义)configurable: true,
      // 当获取值的时候执行
      get () {console.log('get:', data.msg)
        return data.msg
      },
      // 当设置值的时候执行
      set (newValue) {console.log('set:', newValue)
        if (newValue === data.msg) {return}
        data.msg = newValue
        // 数据更改,更新 DOM 的值
        document.querySelector('#app').textContent = data.msg
      }
    })

function proxyData(data) {
      // 遍历 data 对象的所有属性
      Object.keys(data).forEach(key => {
        // 把 data 中的属性,转换成 vm 的 setter/setter
        Object.defineProperty(vm, key, {
          enumerable: true,
          configurable: true,
          get () {console.log('get:', key, data[key])
            return data[key]
          },
          set (newValue) {console.log('set:', key, newValue)
            if (newValue === data[key]) {return}
            data[key] = newValue
            // 数据更改,更新 DOM 的值
            document.querySelector('#app').textContent = data[key]
          }
        })
      })
    }

Proxy 对象:vue3.0 响应式的根底


    // 模仿 Vue 实例
    let vm = new Proxy(data, {
      // 执行代理行为的函数
      // 当拜访 vm 的成员会执行
      get (target, key) {console.log('get, key:', key, target[key])
        return target[key]
      },
      // 当设置 vm 的成员会执行
      set (target, key, newValue) {console.log('set, key:', key, newValue)
        if (target[key] === newValue) {return}
        target[key] = newValue
        document.querySelector('#app').textContent = target[key]
      }
    })

7、公布订阅模式和观察者模式

公布订阅模式

// 事件触发器

class EventEmitter {constructor () {// { 'click': [fn1, fn2], 'change': [fn] }
    this.subs = Object.create(null)
  }


  // 注册事件
  $on (eventType, handler) {this.subs[eventType] = this.subs[eventType] || []
    this.subs[eventType].push(handler)
  }


  // 触发事件
  $emit (eventType) {if (this.subs[eventType]) {this.subs[eventType].forEach(handler => {handler()
      })
    }
  }
}

观察者模式


    // 发布者 - 指标
    class Dep {constructor () {
        // 记录所有的订阅者
        this.subs = []}
      // 增加订阅者
      addSub (sub) {if (sub && sub.update) {this.subs.push(sub)
        }
      }
      // 公布告诉
      notify () {
        this.subs.forEach(sub => {sub.update()
        })
      }
    }
    // 订阅者 - 观察者
    class Watcher {update () {console.log('update')
      }
    }

8、VUE 响应式原理


class Vue {constructor (options) {
    // 1. 通过属性保留选项的数据
    this.$options = options || {}
    this.$data = options.data || {}
    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
    // 2. 把 data 中的成员转换成 getter 和 setter,注入到 vue 实例中
    this._proxyData(this.$data)
    // 3. 调用 observer 对象,监听数据的变动
    new Observer(this.$data)
    // 4. 调用 compiler 对象,解析指令和差值表达式
    new Compiler(this)
  }
  _proxyData (data) {
    // 遍历 data 中的所有属性
    Object.keys(data).forEach(key => {
      // 把 data 的属性注入到 vue 实例中
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get () {return data[key]
        },
        set (newValue) {if (newValue === data[key]) {return}
          data[key] = newValue
        }
      })
    })
  }
}

Observer

负责把 data 的属性变成响应式数据
如果属性是对象,则把对象也转换成响应式
数据变动发送告诉



class Observer {constructor (data) {this.walk(data)
  }
  walk (data) {
    // 1. 判断 data 是否是对象
    if (!data || typeof data !== 'object') {return}
    // 2. 遍历 data 对象的所有属性
    Object.keys(data).forEach(key => {this.defineReactive(data, key, data[key])
    })
  }
  defineReactive (obj, key, val) {
    let that = this
    // 负责收集依赖,并发送告诉
    let dep = new Dep()
    // 如果 val 是对象,把 val 外部的属性转换成响应式数据
    this.walk(val)
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get () {
        // 收集依赖
        Dep.target && dep.addSub(Dep.target)
        return val
        // 不能应用 obj[KEY],因为会产生子递归,反复援用
      },
      set (newValue) {if (newValue === val) {return}
        val = newValue
        // 如果 newValue 是对象,把 newValue 外部的属性转换成响应式数据,而且因为 set 是一个 function,调用时 this 指向会扭转
        that.walk(newValue)
        // 发送告诉
        dep.notify()}
    })
  }
}

Compiler

负责编译模板,解析指令和差值表达式
负责首次渲染
负责数据扭转时驱动视图扭转


class Compiler {constructor (vm) {
    this.el = vm.$el
    this.vm = vm
    this.compile(this.el)
  }
  // 编译模板,解决文本节点和元素节点
  compile (el) {
    let childNodes = el.childNodes
    Array.from(childNodes).forEach(node => {
      // 解决文本节点
      if (this.isTextNode(node)) {this.compileText(node)
      } else if (this.isElementNode(node)) {
        // 解决元素节点
        this.compileElement(node)
      }


      // 判断 node 节点,是否有子节点,如果有子节点,要递归调用 compile
      if (node.childNodes && node.childNodes.length) {this.compile(node)
      }
    })
  }
  // 编译元素节点,解决指令
  compileElement (node) {// console.log(node.attributes)
    // 遍历所有的属性节点
    Array.from(node.attributes).forEach(attr => {
      // 判断是否是指令
      let attrName = attr.name
      if (this.isDirective(attrName)) {
        // v-text --> text
        attrName = attrName.substr(2)
        let key = attr.value
        this.update(node, key, attrName)
      }
    })
  }


  update (node, key, attrName) {let updateFn = this[attrName + 'Updater']
    updateFn && updateFn.call(this, node, this.vm[key], key)
  }


  // 解决 v-text 指令
  textUpdater (node, value, key) {
    node.textContent = value
    new Watcher(this.vm, key, (newValue) => {node.textContent = newValue})
  }
  // v-model
  modelUpdater (node, value, key) {
    node.value = value
    new Watcher(this.vm, key, (newValue) => {node.value = newValue})
    // 双向绑定
    node.addEventListener('input', () => {this.vm[key] = node.value
    })
  }


  // 编译文本节点,解决差值表达式
  compileText (node) {// console.dir(node)
    // {{msg}}
    let reg = /\{\{(.+?)\}\}/
    let value = node.textContent
    if (reg.test(value)) {let key = RegExp.$1.trim()
      node.textContent = value.replace(reg, this.vm[key])


      // 创立 watcher 对象,当数据扭转更新视图
      new Watcher(this.vm, key, (newValue) => {node.textContent = newValue})
    }
  }
  // 判断元素属性是否是指令
  isDirective (attrName) {return attrName.startsWith('v-')
  }
  // 判断节点是否是文本节点
  isTextNode (node) {return node.nodeType === 3}
  // 判断节点是否是元素节点
  isElementNode (node) {return node.nodeType === 1}
}

Watcher


class Watcher {constructor (vm, key, cb) {
    this.vm = vm
    // data 中的属性名称
    this.key = key
    // 回调函数负责更新视图
    this.cb = cb


    // 把 watcher 对象记录到 Dep 类的动态属性 target
    Dep.target = this
    // 触发 get 办法,在 get 办法中会调用 addSub
    this.oldValue = vm[key]
    Dep.target = null
  }
  // 当数据发生变化的时候更新视图
  update () {let newValue = this.vm[this.key]
    if (this.oldValue === newValue) {return}
    this.cb(newValue)
  }
}

9、调试

10、虚构 DOM

保护视图和状态的关系
简单视图状况下晋升渲染性能
跨平台
浏览器
ssr
原生利用
小程序

11、snabbdom

根本应用

繁多元素,只蕴含文本


import {init} from 'snabbdom/build/package/init'

import {h} from 'snabbdom/build/package/h'



const patch = init([])



// 第一个参数:标签 + 选择器
// 第二个参数:如果是字符串就是标签中的文本内容
let vnode = h('div#container.cls',{

hook: {init (vnode) {console.log(vnode.elm)
},
create (emptyNode, vnode) {console.log(vnode.elm)
}
}
}, 'Hello World')

let app = document.querySelector('#app')

// 第一个参数:旧的 VNode,能够是 DOM 元素
// 第二个参数:新的 VNode
// 返回新的 VNode
let oldVnode = patch(app, vnode)



vnode = h('div#container.xxx', 'Hello Snabbdom')

patch(oldVnode, vnode)


父子元素

import {init} from 'snabbdom/build/package/init'

import {h} from 'snabbdom/build/package/h'



const patch = init([])



let vnode = h('div#container', [h('h1', 'Hello Snabbdom'),

h('p', '这是一个 p')

])


let app = document.querySelector('#app')

let oldVnode = patch(app, vnode)



setTimeout(() => {

// vnode = h('div#container', [// h('h1', 'Hello World'),
// h('p', 'Hello P')
// ])
// patch(oldVnode, vnode)


// 革除 div 中的内容
patch(oldVnode, h('!'))

}, 2000);

模块

模块的作用是搞定 DOM 的款式、属性
能够扩大
是应用钩子的机制实现的


import {init} from 'snabbdom/build/package/init'

import {h} from 'snabbdom/build/package/h'



// 1. 导入模块
import {styleModule} from 'snabbdom/build/package/modules/style'

import {eventListenersModule} from 'snabbdom/build/package/modules/eventlisteners'



// 2. 注册模块
const patch = init([

styleModule,
eventListenersModule
])


// 3. 应用 h() 函数的第二个参数传入模块中应用的数据(对象)let vnode = h('div', [h('h1', { style: { backgroundColor: 'red'} }, 'Hello World'),

h('p', { on: { click: eventHandler} }, 'Hello P')

])


function eventHandler () {console.log('别点我,疼')
}


let app = document.querySelector('#app')

patch(app, vnode)

12、源码剖析

h 函数

函数重载:参数个数或类型不同的函数

正文完
 0