第1章 课程导读
第2章 框架设计前瞻 - 框架设计中的一些基本概念
2-1前言
2-2编程范式之命令式编程
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<div>
<p></p>
</div>
</div>
<script>
const divEl = document.querySelector("#app");
// divEl.innerHTML='hello world'
const subDivEl = divEl.querySelector('div');
const pEl = subDivEl.querySelector('p')
const msg = 'hello world'
pEl.innerHTML = msg
</script>
</body>
</html>
2-3编程范式之声明式编程
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<template>
<div>{{msg}}</div>
</template>
</body>
</html>
2-4命令式VS声明式
2-5企业应用的开发与设计原则
2-6为什么说框架的设计过程其实是一个不断取舍的过程
2-7vue中的html是真实的html吗
2-8什么是运行时
2-9什么是编译时
2-10运行时编译时
2-11什么是副作用
2-12Vue3框架设计概述
2-13扩展所谓良好的TypeScript
支持是如何提供的
2-14总结
第3章 Vue 3源码结构 - 搭建框架雏形
3-1前言
3-2探索源码设计Vue3源码设计大解析
3-3创建测试实例在Vue源码中运行测试实例
npm i -g pnpm
pnpm i --ignore-scripts
# 使用git clone下来,否则build会报错
npm run build
<!-- package/vue/examples/imooc/reactive.html起个服务来运行 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../../../dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const {reactive, effect} = Vue
const obj = reactive({
name: '张三'
})
effect(() => {
document.querySelector('#app').innerText = obj.name
})
setTimeout(() => {
obj.name = '李四'
}, 2000)
</script>
</body>
</html>
3-4跟踪解析运行行为为vue开启SourceMap
3-5授人以鱼如何针对源码进行debugger
3-6授人以渔如何阅读源码
3-7开始搭建自己的框架创建vue-next-mini
npm init -y
3-8为框架进行配置导入ts
tsc -init
tsconfig.json
{
// 编译器配置
"compilerOptions": {
// 根目录
"rootDir": ".",
// 严格模式标志
"strict": true,
// 指定类型的脚本如何从给定的模块说明符查找文件
"moduleResolution": "node",
"esModuleInterop": true,
// js语言版本
"target": "ES5",
// 允许未读取局部变量
"noUnusedLocals": false,
// 允许未读取参数
"noUnusedParameters": false,
// 允许解析json
"resolveJsonModule": true,
// 支持语法迭代
"downlevelIteration": true,
// 允许使用隐式的any类型(有助于简化ts复杂度)
"noImplicitAny": false,
// 模块化
"module": "ESNext",
// 转换为js时,从ts中删除所有注释
"removeComments": false,
// 禁用sourceMap
"sourceMap": false,
"lib": [
"ESNext",
"DOM"
]
},
// 入口
"include": [
"packages/*/src"
]
}
3-9引入代码格式化工具prettier让你的代码结构更加规范
// .prettierrc
{
// 去掉末尾分号
"semi": false,
// 双引号变单引号
"singleQuote": true,
// 多少个字符才换行
"printWidth": 80,
// 对象最后一个属性不加逗号
"trailingComma": "none",
// 省略箭头函数小括号
"arrowParens": "avoid"
}
3-10模块打包器rollup
{
"devDependencies": {
"@rollup/plugin-node-resolve": "13.3.0",
"@rollup/plugin-commonjs": "22.0.1",
"@rollup/plugin-typescript": "8.3.4"
}
}
// rollup.config.js
import typescript from '@rollup/plugin-typescript'
import nodeResolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
/**
* 默认导出一个数组, 数组的每个对象都是一个单独的导出文件配置
*/
export default [
{
// 入口文件
input: 'packages/vue/src/index.ts',
// 打包出口
output: [
// 导出 iife 模式的包
{
// 开启sourcemap
sourcemap: true,
// 导出文件地址
file: './packages/vue/dist/vue.js',
// 生成包的格式
format: 'iife',
// 变量名
name: 'Vue'
}
],
// 插件
plugins: [
// ts
typescript({
sourceMap: true
}),
// 模块导入的路径补全
nodeResolve(),
// 转 commonjs 为 esm
commonjs()
]
}
]
npm i --save-dev tslib@2.4.0 typescript@4.7.4
3-11初见框架雏形配置路径映射
{
// 编译器配置
"compilerOptions": {
// ...
// 设置快捷导入
"baseUrl": ".",
"paths": {
"@vue/*": [
"packages/*/src"
]
}
}
}
3-12总结
第4章 响应系统 - 响应系统的核心设计原则
4-1前言
4-2JS的程序性
4-3如何让你的程序变得更加聪明
4-4vue2的响应性核心APIObjectdefineProperty
4-5ObjectdefineProperty在设计层的缺陷
4-6vue3的响应性核心APIproxy
4-7proxy的最佳拍档Reflect拦截js对象操作
4-8总结
第5章 响应系统 - 初见 reactivity 模块
5-1前言
5-2源码阅读reactive的响应性跟踪Vue3源码实现逻辑
5-3源码阅读reactive的响应性跟踪Vue3源码实现逻辑
5-4框架实现构建reactive函数获取proxy实例
// packages/vue/src/index.ts
// 入口文件进行导出
export {reactive} from '@vue/reactivity'
// packages/reactivity/src/reactive.ts
export {reactive} from './reactive'
// packages/reactivity/src/reactive.ts
import {mutableHandlers} from './baseHandlers'
// k:原始对象 -- v:响应式对象
export const reactiveMap = new WeakMap<object, any>()
function createReactiveObject(
target: object,
baseHandlers: ProxyHandler<any>,
proxyMap: WeakMap<object, any>) {
// 从缓存中 根据对象获取对应的代理对象
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 创建代理对象
const proxy = new Proxy(target, baseHandlers)
// 缓存代理对象
proxyMap.set(target, proxy)
return proxy
}
export function reactive(target: object) {
// 创建响应式对象
return createReactiveObject(target, mutableHandlers, reactiveMap)
}
// packages/reactivity/src/baseHandlers.ts
/**
* interface ProxyHandler<T extends object> {
* apply?(target: T, thisArg: any, argArray: any[]): any;
* construct?(target: T, argArray: any[], newTarget: Function): object;
* defineProperty?(target: T, p: string | symbol, attributes: PropertyDescriptor): boolean;
* deleteProperty?(target: T, p: string | symbol): boolean;
* get?(target: T, p: string | symbol, receiver: any): any;
* getOwnPropertyDescriptor?(target: T, p: string | symbol): PropertyDescriptor | undefined;
* getPrototypeOf?(target: T): object | null;
* has?(target: T, p: string | symbol): boolean;
* isExtensible?(target: T): boolean;
* ownKeys?(target: T): ArrayLike<string | symbol>;
* preventExtensions?(target: T): boolean;
* set?(target: T, p: string | symbol, value: any, receiver: any): boolean;
* setPrototypeOf?(target: T, v: object | null): boolean;
* }
*/
export const mutableHandlers: ProxyHandler<object> = {}
<!--packages/vue/examples/reactivity/reactivity.html-->
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>Title</title>
<script src='../../dist/vue.js'></script>
</head>
<body>
<script>
const {reactive} = Vue
console.log(reactive)
const obj = reactive({
name: '张三'
})
console.log(obj)
</script>
</body>
</html>
5-5框架实现什么是WeakMap它和Map有什么区别
5-6框架实现createGettercreateSetter
// packages/reactivity/src/baseHandlers.ts
import {track, trigger} from './effect'
function createGetter() {
return function get(target: object, key: string | symbol, receiver: object) {
const res = Reflect.get(target, key, receiver)
// 依赖收集 收集触发更新的fn,到时set的时候调用
track(target, key)
return res
}
}
const get = createGetter()
function createSetter() {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
) {
const res = Reflect.set(target, key, value, receiver)
// 触发依赖
trigger(target, key, value)
return res
}
}
const set = createSetter()
export const mutableHandlers: ProxyHandler<object> = {
get,
set
}
// packages/reactivity/src/effect.ts
/**
* 收集依赖
* @param target
* @param key
*/
export function track(target: object, key: unknown) {
console.log('track: 收集依赖')
}
/**
* 依赖触发
* @param target
* @param key
* @param newVal
*/
export function trigger(target: object, key: unknown, newVal: unknown) {
console.log('trigger: 触发依赖')
}
<!--packages/vue/examples/reactivity/reactivity.html-->
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>Title</title>
<script src='../../dist/vue.js'></script>
</head>
<body>
<script>
const {reactive} = Vue
console.log(reactive)
const obj = reactive({
name: '张三'
})
console.log(obj)
// 触发依赖收集
console.log(obj.name)
// 依赖触发
obj.name = '李四'
</script>
</body>
</html>
5-7热更新的开发时提升开发体验
5-8框架实现构建effect函数生成ReactiveEffect实例
// packages/reactivity/src/effect.ts
export class ReactiveEffect<T = any> {
constructor(public fn: () => T) {
}
run() {
activeEffect = this
// 触发effect回调
return this.fn()
}
}
/**
* 让传入的函数发生作用
* @param fn
*/
export function effect<T = any>(fn: () => T) {
const _effect = new ReactiveEffect(fn)
// 第一次fn执行
_effect.run()
}
// 当前触发的effect
export let activeEffect: ReactiveEffect | undefined
// ...
// packages/vue/src/index.ts
// 入口文件进行导出
export {reactive, effect} from '@vue/reactivity'
// packages/reactivity/src/reactive.ts
export {reactive} from './reactive'
export {effect} from './effect'
<!--packages/vue/examples/reactivity/reactivity.html-->
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>Title</title>
<script src='../../dist/vue.js'></script>
</head>
<body>
<div id='app'></div>
<script>
const {reactive, effect} = Vue
// console.log(reactive)
const obj = reactive({
name: '张三'
})
effect(() => {
document.querySelector('#app').innerText = obj.name
})
</script>
</body>
</html>
5-9框架实现tracktrigger
5-10框架实现构建track依赖收集函数
// packages/reactivity/src/effect.ts
/**
* 收集依赖
* @param target
* @param key
*/
export function track(target: object, key: unknown) {
// 当前不存在执行函数, return
if (!activeEffect) return
// 从targetMap中读取depsMap
let depsMap = targetMap.get(target)
// 如果targetMap中没有
if (!depsMap) {
// 缓存到targetMap
targetMap.set(target, (depsMap = new Map()))
}
// 绑定 proxy.attribute -- effect_fn
depsMap.set(key, activeEffect)
console.log(depsMap)
}
5-11框架实现构建trigger触发依赖
// packages/reactivity/src/effect.ts
// ...
/**
* 依赖触发
* @param target
* @param key
* @param newVal
*/
export function trigger(target: object, key: unknown, newVal: unknown) {
// 拿到对象对应的depsMap
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
// 拿到对象属性对应的effect
const effect = depsMap.get(key) as ReactiveEffect
if (!effect) {
return
}
// 触发依赖
effect.fn()
}
<!--packages/vue/examples/reactivity/reactivity.html-->
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>Title</title>
<script src='../../dist/vue.js'></script>
</head>
<body>
<div id='app'></div>
<script>
const {reactive, effect} = Vue
const obj = reactive({
name: '张三'
})
effect(() => {
document.querySelector('#app').innerText = obj.name
})
setTimeout(() => {
// 因为effect传入fn是更新dom的,所以触发依赖后视图更新了
obj.name = '李四'
}, 2000)
</script>
</body>
</html>
5-12总结单一依赖的reactive
5-13功能升级响应数据对应多个effect
5-14框架实现构建Dep模块处理一对多的依赖关系
// packages/reactivity/src/dep.ts
import {ReactiveEffect} from './effect'
export type Dep = Set<ReactiveEffect>
export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects)
return dep
}
// packages/reactivity/src/effect.ts
import {createDep, Dep} from './dep'
import {isArray} from '@vue/shared'
// ...
/**
* 收集依赖
* @param target
* @param key
*/
export function track(target: object, key: unknown) {
// 当前不存在执行函数, return
if (!activeEffect) return
// 从targetMap中读取depsMap
let depsMap = targetMap.get(target)
// 如果targetMap中没有
if (!depsMap) {
// 缓存到targetMap
targetMap.set(target, (depsMap = new Map()))
}
// 尝试获取dep
let dep = depsMap.get(key)
if (!dep) {
// k: obj.attribute -- v: dep<effect>
depsMap.set(key, (dep = createDep()))
}
trackEffects(dep)
}
/**
* 利用dep依次跟踪指定key的所有effect
*/
export function trackEffects(dep: Dep) {
dep.add(activeEffect!)
}
/**
* 依赖触发
* @param target
* @param key
* @param newVal
*/
export function trigger(target: object, key: unknown, newVal: unknown) {
// 拿到对象对应的depsMap
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
// 拿到对象属性对应的effect
const dep: Dep | undefined = depsMap.get(key)
if (!dep) {
return
}
// 触发依赖
triggerEffects(dep)
}
/**
* 依次触发dep中保存的依赖
*/
export function triggerEffects(dep: Dep) {
const effects = isArray(dep) ? dep : [...dep]
// 依次触发
for (let effect of effects) {
triggerEffect(effect)
}
}
/**
* 触发指定依赖
* @param effect
*/
export function triggerEffect(effect: ReactiveEffect) {
effect.run()
}
5-15reactive函数的局限性
5-16总结
第6章 响应系统 - ref 的响应性
6-1前言
6-2源码阅读ref复杂数据类型的响应性
6-3源码阅读ref复杂数据类型的响应性
6-4框架实现ref函数-构建复杂数据类型的响应性
// packages/reactivity/src/ref.ts
import {createDep, Dep} from './dep'
import {toReactive} from './reactive'
import {activeEffect, track, trackEffects} from './effect'
export interface Ref<T = any> {
value: T
}
/**
* 是否为ref
* @param r
*/
function isRef(r: any): r is Ref {
// !! 强行转为布尔值
return !!(r && r.__v_isRef === true)
}
export function trackRefValue(ref) {
if (activeEffect) {
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
class RefImpl<T> {
private _value: T
public dep?: Dep = undefined
public readonly __v__isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
// 如果是基本类型就不做处理, 不是则转为reactive
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
// 依赖收集
trackRefValue(this)
return this._value
}
set value(newVal) {
}
}
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
export function ref(value?: unknown) {
return createRef(value, false)
}
// packages/reactivity/src/reactive.ts
import {isObject} from '@vue/shared'
// ...
/**
* 如果是对象,就转为reactive
* @param value
*/
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value as object) : value
// packages/reactivity/src/reactive.ts
export {reactive} from './reactive'
export {effect} from './effect'
export {ref} from './ref'
// packages/shared/src/index.ts
// ...
/**
* 判断是不是对象
* @param val
*/
export const isObject = (val: unknown) => val !== null && typeof val === 'object'
// packages/vue/src/index.ts
// 入口文件进行导出
export {reactive, effect, ref} from '@vue/reactivity'
<!--packages/vue/examples/reactivity/ref.html-->
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>Title</title>
<script src='../../dist/vue.js'></script>
</head>
<body>
<div id='app'>
</div>
<script>
const {ref, effect} = Vue
const obj = ref({
name: '张三'
})
effect(() => {
document.querySelector('#app').innerText = obj.value.name
})
setTimeout(() => {
// 因为effect传入fn是更新dom的,所以触发依赖后视图更新了
obj.value.name = '李四'
}, 2000)
</script>
</body>
</html>
6-5总结ref复杂数据类型的响应性
6-6源码阅读ref简单数据类型的响应性
ref是主动触发get和set, 自身不具备响应性
6-7框架实现ref函数-构建简单数据类型的响应性
// packages/shared/src/index.ts
// ...
/**
* 是否有改变
*/
export const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue)
// packages/reactivity/src/ref.ts
// ...
/**
* 依赖触发
*/
export function triggerRefValue(ref) {
if (ref.dep) {
triggerEffects(ref.dep)
}
}
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v__isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
// 保存当前的value作为初始值
this._rawValue = value
// 如果是基本类型就不做处理, 不是则转为reactive
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
// 依赖收集
trackRefValue(this)
return this._value
}
set value(newVal) {
// 如果值有变化
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = toReactive(newVal)
triggerRefValue(this)
}
}
}
6-8总结ref简单数据类型响应性
6-9总结
第7章 响应系统 - watch && computed
7-1开篇
7-2源码阅读computed的响应性跟踪Vue3源码实现逻辑
7-3源码阅读computed的响应性跟踪Vue3源码实现逻辑
7-4框架实现构建ComputedRefImpl读取计算属性的值
// packages/reactivity/src/computed.ts
import {isFunction} from '@vue/shared'
import {Dep} from './dep'
import {ReactiveEffect} from './effect'
import {trackRefValue} from './ref'
export class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
constructor(getter) {
this.effect = new ReactiveEffect(getter)
this.effect.computed = this
}
get value() {
trackRefValue(this)
// 执行getter
this._value = this.effect.run()
return this._value
}
}
export function computed(getterOrOptions) {
let getter
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
getter = getterOrOptions
}
const cRef = new ComputedRefImpl(getter)
return cRef
}
// packages/reactivity/src/effect.ts
export class ReactiveEffect<T = any> {
computed?: ComputedRefImpl<T>
constructor(public fn: () => T) {
}
run() {
activeEffect = this
// 触发effect回调
return this.fn()
}
}
// ...
// packages/shared/src/index.ts
// ...
/**
* 是不是函数
*/
export const isFunction = (val: unknown): val is Function => typeof val === 'function'
// packages/reactivity/src/reactive.ts
export {reactive} from './reactive'
export {effect} from './effect'
export {ref} from './ref'
export {computed} from './computed'
// packages/vue/src/index.ts
// 入口文件进行导出
export {reactive, effect, ref, computed} from '@vue/reactivity'
<!--packages/vue/examples/reactivity/computed.html-->
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>Title</title>
<script src='../../dist/vue.js'></script>
</head>
<body>
<div id='app'></div>
<script>
const {reactive, effect, computed} = Vue
const obj = reactive({
name: '张三'
})
const computedObj = computed(() => {
return '姓名: ' + obj.name
})
effect(() => {
document.querySelector('#app').innerText = computedObj.value
})
setTimeout(() => {
obj.name = '李四'
}, 2000)
</script>
</body>
</html>
7-5框架实现computed的响应性初见调度器处理脏的状态
// packages/reactivity/src/computed.ts
import {isFunction} from '@vue/shared'
import {Dep} from './dep'
import {ReactiveEffect} from './effect'
import {trackRefValue, triggerRefValue} from './ref'
export class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
// 脏状态
// 为true需要重新执行effect.run
public _dirty = true
constructor(getter) {
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
this.effect.computed = this
}
get value() {
trackRefValue(this)
if (this._dirty) {
// 执行getter
this._dirty = false
this._value = this.effect.run()
}
return this._value
}
}
// ...
// packages/reactivity/src/effect.ts
// ...
export class ReactiveEffect<T = any> {
computed?: ComputedRefImpl<T>
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null) {
}
}
export function triggerEffect(effect: ReactiveEffect) {
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
7-6框架实现computed的缓存性
// packages/reactivity/src/effect.ts
// ...
export function triggerEffects(dep: Dep) {
const effects = isArray(dep) ? dep : [...dep]
// 依次触发
for (let effect of effects) {
if (effect.computed) {
triggerEffect(effect)
}
}
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect)
}
}
}
第一次执行getter,将computed设置为false,后面再getter不会触发依赖