Open cisen opened 4 years ago
如何解析html的指令?
如何拦截html的解释?
如何将html转化为AST的?
es\packages\compiler-core\src\index.js
es\packages\compiler-core\src\parse.js
使用深度优先后续遍历模板树,然后根据正则匹配处理各个节点createApp函数在哪里?
es\packages\runtime-core\src\createRenderer.js
里,通过传入render调用createAppAPI
函数创建返回的const app = {
get config() {
},
set config(v) {
},
use(plugin) {
},
mixin(mixin) {
},
component(name, component) {
},
directive(name, directive) {
},
mount(rootComponent, rootContainer, rootProps) {
},
provide(key, value) {
}
};
computed是如何实现的 ? mount
es\packages\reactivity\src\computed.js
,对computed的getter封装的函数也是在这里封装进去的。mount的时候
computed (vue.global.js:4146) // 生成computed effect
computed$1 (vue.global.js:7619) // 将computed生成的effect插入effect数组
applyOptions (vue.global.js:7225) // 遍历检查所有的配置比如:computedOptions,watch, methods。是这些配置的初始化入口
finishComponentSetup (vue.global.js:7577)
setupStatefulComponent (vue.global.js:7522)
mountComponent (vue.global.js:6037)
processComponent (vue.global.js:5982)
patch (vue.global.js:5713)
render (vue.global.js:6544)
mount (vue.global.js:5622)
app.mount (vue.global.js:8310)
(anonymous) (todomvc-classic.html:199)
computed(getterOrOptios)
函数生成effect,再在computed$1(getterOrOptions)
使用recordEffect(c.effect);
将effect存入vnoderender的时候
watch是如何实现的? mount
applyOptions(instance, options, asMixin = false)
函数开始处理watchcreateWatcher(watchOptions[key], renderContext, ctx, key);
作为watch的入口es\packages\runtime-core\src\scheduler.js
任务队列queueJob。这个任务队列是nextTick的,也就是通过promise实现任务先后运行udpate
从哪里开始循环遍历vnode的?
remaining (todomvc-classic.html:115)
run (vue.global.js:3975)
reactiveEffect (vue.global.js:3958)
get value (vue.global.js:4161)
get (vue.global.js:3567)
get (vue.global.js:7039)
get (todomvc-classic.html:120)
run (vue.global.js:3975)
reactiveEffect (vue.global.js:3958)
get value (vue.global.js:4161)
get (vue.global.js:3567)
get (vue.global.js:7039)
render (VM68:51)
renderComponentRoot (vue.global.js:4501)
componentEffect (vue.global.js:6062)
run (vue.global.js:3975)
reactiveEffect (vue.global.js:3958)
effect (vue.global.js:3943)
setupRenderEffect (vue.global.js:6060)(这里如果有子则从新调用patch,开始遍历vnode树,没有则执行下去)
mountComponent (vue.global.js:6052)
processComponent (vue.global.js:5981)
patch (vue.global.js:5712)(循环的入口)
render (vue.global.js:6543)
mount (vue.global.js:5621)
app.mount (vue.global.js:8309)
(anonymous) (todomvc-classic.html:199)
effect是如何存储到Vnode的?
es\packages\reactivity\src\effect.js
的effect函数的闭包里面,currentInstance.effects
,也就是instance对象下面vnode和instance的关系是什么?
effect是什么?
effect是用来做什么的?
会有全局的effect-list嘛?
effect跟computed/watcher有什么关系?
PublicInstanceProxyHandlers做什么用的?
vm.$key
值的操作为什么要把computed/watch/methods/inject的effect都存入renderContext?
vm.$key
取值/赋值操作,减少代码因为有上一条,说以computed/watch/methods/inject的名称key不能相同?
target/dep/effect有什么关系?
Proxy是如何到effect的?
trackChildRun(childRunner) # 将effect添加到dep,track的时候会将target-key-dep-effect连在一起
effect(fn, options = EMPTY_OBJ) # fn可能是computed对应key的getter函数, 给getter添加createReactiveEffect的属性
computed(getterOrOptions) # 执行effect()函数,用effect属性包裹computed的getter
computed$1(getterOrOptions) # 生成effect并插入instance.effects数组
applyOptions(instance, options, asMixin = false) # 应用vue应用配置
trigger(target, type, key, extraInfo) # 从data/computed/methon里面根据key从**全局targetMap**获取所有订阅key对应数据改变的effect,并全部执行
set/get/deleteProperty/has # 这些函数都会调用trigger
mutableHandlers/readonlyHandlers # Proxy 拦截的操作,有全局的set/get/deleteProperty/has
createReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers) # 创建Proxy对象
reactive(target) # 设置数据绑定入口,target是data/computed/methon
为什么effect要存一份到instance.effects?
proxy的创建和effect的创建有何不同?
instance.renderContext(Proxy).computedkey.effect
instance.renderContext(Proxy).methonkey.effect
instance.renderContext(Proxy).injectkey.effect
# data是比较特殊的
data.dataKey(Proxy)
computed/methon是什么时候被存入targetMap?
computed的effect如何跟dep关联的?
track(target, type, key)
函数会将effectStack的最后一个effect存到targetMap[target(computed)][key]
里面dep是一个Set?dep从哪里添加effect的?
effectStack是什么?跟targetMap有什么关系
track(target, type, key)
函数的作用
父子组件共用targetMap和effectStack
为什么要使用effectStack栈来存储effect?
vue3的schedule是怎样的?
vue3如何将vnode变更应用到真实dom?
const nodeOps = {
下面的方法vue3 diff 算法在哪里?
ssrTransformIf做了什么?
packages\compiler-ssr\src\transforms\ssrVIf.ts1
es\packages\runtime-core\src\vnode.js
V3:
const vnode = {
_isVNode: true,
type,
props,
key: (props !== null && props.key) || null,
ref: (props !== null && props.ref) || null,
children: null,
component: null,
suspense: null,
dirs: null,
el: null,
anchor: null,
target: null,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
};
V2:
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
functionalContext: Component | void; // only for functional component root nodes
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions
) {
/*当前节点的标签名*/
this.tag = tag
/*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
this.data = data
/*当前节点的子节点,是一个数组*/
this.children = children
/*当前节点的文本*/
this.text = text
/*当前虚拟节点对应的真实dom节点*/
this.elm = elm
/*当前节点的名字空间*/
this.ns = undefined
/*编译作用域*/
this.context = context
/*函数化组件作用域*/
this.functionalContext = undefined
/*节点的key属性,被当作节点的标志,用以优化*/
this.key = data && data.key
/*组件的option选项*/
this.componentOptions = componentOptions
/*当前节点对应的组件的实例*/
this.componentInstance = undefined
/*当前节点的父节点*/
this.parent = undefined
/*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
this.raw = false
/*静态节点标志*/
this.isStatic = false
/*是否作为根节点插入*/
this.isRootInsert = true
/*是否为注释节点*/
this.isComment = false
/*是否为克隆节点*/
this.isCloned = false
/*是否有v-once指令*/
this.isOnce = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}
const instance = {
vnode,
parent,
appContext,
type: vnode.type,
root: null,
next: null,
subTree: null,
update: null,
render: null,
renderProxy: null,
propsProxy: null,
setupContext: null,
// 脏操作数组
effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null,
renderCache: null,
// setup context properties
renderContext: EMPTY_OBJ,
data: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ,
// per-instance asset storage (mutable during options resolution)
components: Object.create(appContext.components),
directives: Object.create(appContext.directives),
// async dependency management
asyncDep: null,
asyncResult: null,
asyncResolved: false,
// user namespace for storing whatever the user assigns to `this`
// can also be used as a wildcard storage for ad-hoc injections internally
sink: {},
// lifecycle hooks
// not using enums here because it results in computed properties
isUnmounted: false,
isDeactivated: false,
bc: null,
c: null,
bm: null,
m: null,
bu: null,
u: null,
um: null,
bum: null,
da: null,
a: null,
rtg: null,
rtc: null,
ec: null,
emit: (event, ...args) => {
const props = instance.vnode.props || EMPTY_OBJ;
const handler = props[`on${event}`] || props[`on${capitalize(event)}`];
if (handler) {
callWithAsyncErrorHandling(handler, instance, 6 /* COMPONENT_EVENT_HANDLER */, args);
}
}
};
effect的数据结构
// effect就是添加这些属性的computed的getter函数。fn就是getter
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect(...args) {
return run(effect, fn, args);
};
effect._isEffect = true;
effect.active = true;
effect.raw = fn;
effect.deps = [];
effect.options = options;
return effect;
}
浅谈vue3中effect与computed的亲密关系 https://www.jb51.net/article/171534.htm
在我刚看完vue3响应式的时候,心中就有一个不可磨灭的谜团,让我茶不思饭不想,总想生病。那么这个谜团是什么呢?就是在响应式中一直穿行在tranger跟track之间的effect。如果单纯的响应式原理根本就用不上effect,那么effect到底是干什么的呢?
船到桥头自然直,柳岸花明又一村。苦心人天不负,偶然间我看到了effect测试代码用例!
it('should observe basic properties', () => {
let dummy
const counter = reactive({ num: 0 })
effect(() => (dummy = counter.num))
expect(dummy).toBe(0)
counter.num = 7
expect(dummy).toBe(7)
})
解释一下,这段代码
首先声明dummy变量,然后在effect的回调中把已响应的对象counter的num属性赋值给dummy 然后做断言判断 dummy是否等于 0 将 counter.num 赋值 7 ,然后 dummy 也变成了 7 ! 这,,,让我想到了什么??
这就是computed的吗? 赶紧看下 computed 的测试用例!
const value = reactive<{ foo?: number }>({})
const cValue = computed(() => value.foo)
expect(cValue.value).toBe(undefined)
value.foo = 1
expect(cValue.value).toBe(1)
哈哈哈
阿哈哈哈哈
hhhhhhhhhhhhhhhhhhhh
忍不住想仰天长啸!!
果然跟我猜想的一样!!!我终于直到effect是个什么鬼了,顾名思义effect是副作用的意思,也就是说它是响应式副产品,每次触发了 get 时收集effect,每次set时在触发这些effects。这样就可以做一些响应式数据之外的一些事情了,比如计算属性computed。
让我们用effect实现一个computed 可能会更清晰一点 我就不写一些乱七八糟的判断了,让大家能够看的更加清楚
function computed (fn) {
let value = undefined
const runner = effect(fn, {
// 如果lazy不置为true的话,每次创建effect的时候都会立即执行一次
// 而我们要实现computed显然是不需要的
lazy: true
})
// 为什么要使用对象的形式,是因为我们最后需要得到computed的值
// 如果不用对象的 get 方法的话我们就需要手动再调用一次 computed()
return {
get value() {
return runner()
}
}
}
// 使用起来是这样的
const value = reactive({})
const cValue = computed(() => value.foo)
value.foo = 1
console.log(cValue.value) // 1
这也太简单了吧,那么重点来了,effect怎么实现的呢? 别着急,我们先捋一下逻辑
首先 如果 effect 回调内有已响应的对象被触发了 get 时,effect就应该被储存起来 然后,我们需要一个储存effect的地方,在effect函数调用的时候就应该把effect放进这个储存空间,在vue中使用的是一个数组activeReactiveEffectStack = [] 再后,每个target被触发的时候,都可能有多个effect,所以每个target需要有一个对应的依赖收集器 deps,等到 set 时遍历 deps 执行 effect() 然而,这个依赖收集器 deps 不能放在 target 本身上,这样会使数据看起来不是很简洁,还会存在多余无用的数据,所以我们需要一个 map 集合来储存 target 跟 deps 的关系, 在vue中这个储存集合叫 targetMap 。
几个概念 track 追踪器,在 get 时调用该函数,将所有 get 的 target 跟 key 以及 effect 建立起对应关系
// 比如 const react = reactive({a: { b: 2 })
// react.a 时 target -> {a: { b: 2 } key -> a
// targetMap 储存了 target --> Map --> key --> Set --> dep --> effect
// 当调用 react.a.b.c.d.e 时 depsMap
// {"a" => Set(1)} --> Set --> effect
// {"b" => Set(1)}
// {"c" => Set(1)}
// {"d" => Set(1)}
// {"e" => Set(1)}
export function track(target: any, key: string) {
const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1];
if (effect) {
let depsMap = targetMap.get(target);
if (depsMap === void 0) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key!);
if (!dep) {
depsMap.set(key!, (dep = new Set()));
}
if (!dep.has(effect)) {
dep.add(effect);
effect.deps.push(dep);
}
}
}
trigger 触发器,这个就比较好理解了,拿到target key下的对应的所有 effect,然后遍历执行 effect()
export function trigger(target: any, key?: string | symbol) {
const depsMap: any = targetMap.get(target);
const effects: any = new Set()
if (depsMap && depsMap.get(key)) {
depsMap.get(key).forEach((dep: any) => {
effects.add(dep)
});
effects.forEach((e: any) => e())
}
}
effect 函数实现
// 暴露的 effect 函数
export function effect(
fn: Function,
options: any = EMPTY_OBJ
): any {
if ((fn as any).isEffect) {
fn = (fn as any).raw
}
const effect = createReactiveEffect(fn, options)
// 如果不是 lazy,则会立即执行一次
if (!options.lazy) {
effect()
}
return effect
}
// 创建 effect
function createReactiveEffect(
fn: Function,
options: any
): any {
const effect = function effect(...args: any): any {
return run(effect as any, fn, args)
} as any
effect.isEffect = true
effect.active = true
effect.raw = fn
effect.scheduler = options.scheduler
effect.onTrack = options.onTrack
effect.onTrigger = options.onTrigger
effect.onStop = options.onStop
effect.computed = options.computed
effect.deps = []
return effect
}
// 执行函数,执行完之后会将储存的 effect 删除
// 这是函数 effect 的所有执行,所经历的完整的声明周期
function run(effect: any, fn: Function, args: any[]): any {
if (!effect.active) {
return fn(...args)
}
if (activeReactiveEffectStack.indexOf(effect) === -1) {
try {
activeReactiveEffectStack.push(effect)
return fn(...args)
} finally {
activeReactiveEffectStack.pop()
}
}
}
一口气写了这么多,最后总结一下。在大家看源码的时候,如果发现有哪个地方无从下手的话,可以先从测试用例开始看。因为测试用例可以很清楚的知道这个函数想要达到什么效果,然后从效果上想,为什么这么做,如果我自己写的话应该怎么写,这样一点点就能揣摩出作者的意图了。再根据源码结合自己的想法你就能够学到很多。
hydration是什么?
为什么要用Function('require', code)(require))
travels的实现在哪里?
packages\compiler-core\dist\compiler-core.cjs.js
transform是在哪里分发
packages\compiler-core\dist\compiler-core.cjs.js
的traverseNode函数html的ast结构是怎样的?
export interface Position {
// 文件开始的便宜
offset: number // from start of file
// 第几行
line: number
// 第几列
column: number
}
export interface SourceLocation {
// 代码开始的位置
start: Position
end: Position
source: string
}
export interface BaseElementNode extends Node {
// 代码位置对象
loc: SourceLocation
type: NodeTypes.ELEMENT
ns: Namespace
// html标签名
tag: string
tagType: ElementTypes
isSelfClosing: boolean
props: Array<AttributeNode | DirectiveNode>
children: TemplateChildNode[]
}
指令是在哪里?如何解析执行的?
packages\compiler-core\src\transforms\transformExpression.ts
@babel/parser
来parse表达式,traverseNode做了什么?
const onExit = nodeTransforms[i](node, context);
为什么会有codegen?
codegen之后是做什么?
<div v-if="1 === true"><span>user: {{ user }}</span></div>
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require(\"@vue/server-renderer\")\n\nreturn function ssrRender(_ctx, _push, _parent, _attrs) {\n if (1 === true) {\n _push(`<div${\n _ssrRenderAttrs(_attrs)\n }><span>user: ${\n _ssrInterpolate(_ctx.user)\n }</span></div>`)\n } else {\n _push(`<!---->`)\n }\n}"
const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("@vue/server-renderer")
return function ssrRender(_ctx, _push, _parent, _attrs) {
if (1 === true) {
_push(`<div${
_ssrRenderAttrs(_attrs)
}><span>user: ${
_ssrInterpolate(_ctx.user)
}</span></div>`)
} else {
_push(`<!---->`)
}
}
return (compileCache[template] = Function('require', code)(reqire));
生成最后的HTML字符串并存到数组里面再组装SSR生命周期在哪里执行的?
created (c:\Users\cisen\Desktop\develop\sourcecode\sourcecode-vue\ssr.js:14)
callSyncHook (c:\Users\cisen\Desktop\develop\sourcecode\sourcecode-vue\packages\runtime-core\dist\runtime-core.cjs.js:5888)
applyOptions (c:\Users\cisen\Desktop\develop\sourcecode\sourcecode-vue\packages\runtime-core\dist\runtime-core.cjs.js:5835)
finishComponentSetup (c:\Users\cisen\Desktop\develop\sourcecode\sourcecode-vue\packages\runtime-core\dist\runtime-core.cjs.js:6485)
setupStatefulComponent (c:\Users\cisen\Desktop\develop\sourcecode\sourcecode-vue\packages\runtime-core\dist\runtime-core.cjs.js:6416)
setupComponent (c:\Users\cisen\Desktop\develop\sourcecode\sourcecode-vue\packages\runtime-core\dist\runtime-core.cjs.js:6356)
renderComponentVNode (c:\Users\cisen\Desktop\develop\sourcecode\sourcecode-vue\packages\server-renderer\dist\server-renderer.cjs.js:159)
renderToString (c:\Users\cisen\Desktop\develop\sourcecode\sourcecode-vue\packages\server-renderer\dist\server-renderer.cjs.js:395)
buildAll (c:\Users\cisen\Desktop\develop\sourcecode\sourcecode-vue\ssr.js:22)
总结
Vue.component is not a function
所以component暂时未实现,实现原理可以参考vue2的总体流程
targetMap[target][depMaps][key]
里面找到所有依赖该dep的effect,并执行资源收集
总体流程
mount
属性
data下面的数据可以直接被模板使用
computed 封装html变量,data下面的数据改变才会重新运算computed(依赖data)
watch 监控data
computed VS watch
methods render时执行
指令
指令缩写
v-bind
v-on
v-model
component
directive 自定义指令
v-{name}
就是自定义的指令名称