共计 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 函数
函数重载:参数个数或类型不同的函数
正文完