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')// 导入 expressconst express = require('express')const app = express()// 注册解决 history 模式的中间件,不应用这个则会导致网站404app.use(history())// 解决动态资源的中间件,网站根目录 ../webapp.use(express.static(path.join(__dirname, '../web')))// 开启服务器,端口是 3000app.listen(3000, () => { console.log('服务器开启,端口:3000')})
通过history.pushState()办法扭转地址栏
监听postate
依据以后路由寻找对应组件
5、VUE Router外围原理实现
console.dir(Vue)let _Vue = nullclass 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// 返回新的 VNodelet 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函数
函数重载:参数个数或类型不同的函数