Haoqi's blog-ish!

blah blah blah


  • 首页

  • 归档

vuex

发表于 2019-10-03 | 分类于 vuex vue

vuex和单纯全局对象的不同

  • Vuex 的状态存储是响应式的
  • 限制直接修改, 保留每次修改的记录. 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。
1
2
3
4
5
6
7
8
9
10
11
var prototypeAccessors$1 = { state: { configurable: true } };
prototypeAccessors$1.state.get = function () {
return this._vm._data.$$state
};
prototypeAccessors$1.state.set = function (v) {
if (process.env.NODE_ENV !== 'production') {
assert(false, "use store.replaceState() to explicit replace store state.");
}
};

plugin初始化

  • vuex用法就是install时注册$store, 然后再new Vuex.Store传给vue option, 子组件便可以获取到根组件的state
  • 从$options里获取store, 没有就从parent获取, 设置到$store
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function install(_Vue) {
    if (Vue && _Vue === Vue) return
    Vue = _Vue
    Vue.mixin({ beforeCreate: vuexInit })
    }
    function vuexInit () {
    const options = this.$options
    // store injection
    if (options.store) {
    this.$store = typeof options.store === 'function'
    ? options.store()
    : options.store
    } else if (options.parent && options.parent.$store) {
    this.$store = options.parent.$store
    }
    }
阅读全文 »

vue-transition

发表于 2019-06-22 | 分类于 vue transition

digest

  • transition是抽象组件, 提供动画运行过程的各种回调.

render函数做了什么

  • 获取包裹的vnode child, 给vnode添加key属性用于后续节点操作
  • 获取非keep-alive子节点
  • 获取transition指令上所有的props到child上
  • compile完 render, 创建子组件vnode后, 开始渲染子组件
  • oldRawChild, oldChild状态改变时纪录上个状态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    // props提供以下props
    var transitionProps = {
    name: String,
    appear: Boolean,
    css: Boolean,
    mode: String,
    type: String,
    enterClass: String,
    leaveClass: String,
    enterToClass: String,
    leaveToClass: String,
    enterActiveClass: String,
    leaveActiveClass: String,
    appearClass: String,
    appearActiveClass: String,
    appearToClass: String,
    duration: [Number, String, Object]
    };
    // in case the child is also an abstract component, e.g. <keep-alive>
    // we want to recursively retrieve the real component to be rendered
    function getRealChild (vnode) {
    var compOptions = vnode && vnode.componentOptions;
    if (compOptions && compOptions.Ctor.options.abstract) {
    return getRealChild(getFirstComponentChild(compOptions.children))
    } else {
    return vnode
    }
    }
    function extractTransitionData (comp) {
    var data = {};
    var options = comp.$options;
    // props
    for (var key in options.propsData) {
    data[key] = comp[key];
    }
    // events.
    // extract listeners and pass them directly to the transition methods
    var listeners = options._parentListeners;
    for (var key$1 in listeners) {
    data[camelize(key$1)] = listeners[key$1];
    }
    return data
    }
    function render (h) {
    var this$1 = this;
    var children = this.$slots.default;
    if (!children) {
    return
    }
    // filter out text nodes (possible whitespaces)
    children = children.filter(function (c) { return c.tag || isAsyncPlaceholder(c); });
    /* istanbul ignore if */
    if (!children.length) {
    return
    }
    var mode = this.mode;
    var rawChild = children[0];
    // if this is a component root node and the component's
    // parent container node also has transition, skip.
    if (hasParentTransition(this.$vnode)) {
    return rawChild
    }
    // apply transition data to child
    // use getRealChild() to ignore abstract components e.g. keep-alive
    var child = getRealChild(rawChild);
    /* istanbul ignore if */
    if (!child) {
    return rawChild
    }
    if (this._leaving) {
    return placeholder(h, rawChild)
    }
    // ensure a key that is unique to the vnode type and to this transition
    // component instance. This key will be used to remove pending leaving nodes
    // during entering.
    var id = "__transition-" + (this._uid) + "-";
    child.key = child.key == null
    ? child.isComment
    ? id + 'comment'
    : id + child.tag
    : isPrimitive(child.key)
    ? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)
    : child.key;
    var data = (child.data || (child.data = {})).transition = extractTransitionData(this);
    var oldRawChild = this._vnode;
    var oldChild = getRealChild(oldRawChild);
    // mark v-show
    // so that the transition module can hand over the control to the directive
    if (child.data.directives && child.data.directives.some(function (d) { return d.name === 'show'; })) {
    child.data.show = true;
    }
    if (
    oldChild &&
    oldChild.data &&
    !isSameChild(child, oldChild) &&
    !isAsyncPlaceholder(oldChild) &&
    // #6687 component root is a comment node
    !(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment)
    ) {
    // replace old child transition data with fresh one
    // important for dynamic transitions!
    var oldData = oldChild.data.transition = extend({}, data);
    // handle transition mode
    if (mode === 'out-in') {
    // return placeholder node and queue update when leave finishes
    this._leaving = true;
    mergeVNodeHook(oldData, 'afterLeave', function () {
    this$1._leaving = false;
    this$1.$forceUpdate();
    });
    return placeholder(h, rawChild)
    } else if (mode === 'in-out') {
    if (isAsyncPlaceholder(child)) {
    return oldRawChild
    }
    var delayedLeave;
    var performLeave = function () { delayedLeave(); };
    mergeVNodeHook(data, 'afterEnter', performLeave);
    mergeVNodeHook(data, 'enterCancelled', performLeave);
    mergeVNodeHook(oldData, 'delayLeave', function (leave) { delayedLeave = leave; });
    }
    }
    return rawChild
    }

transition module

  • enter回调
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function _enter (_, vnode) {
    if (vnode.data.show !== true) {
    enter(vnode);
    }
    }
    var transition = inBrowser ? {
    create: _enter,
    activate: _enter,
    remove: function remove$$1 (vnode, rm) {
    /* istanbul ignore else */
    if (vnode.data.show !== true) {
    leave(vnode, rm);
    } else {
    rm();
    }
    }
    } : {};
1
2
```
```js
阅读全文 »

vue-keep-alive

发表于 2019-05-26 | 分类于 vue keep-alive

keep-alive

  • 如果需要让已渲染组件重新创建可以通过删除keep-alive内的cache执行
    1
    2
    this.$vnode.parent.componentInstance.$destroy()
    this.$vnode.parent.componentInstance.cache

keep-alive组件

  • abstract组件不渲染,只处理内部的$slot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var KeepAlive = {
name: 'keep-alive',
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created: function created () {
this.cache = Object.create(null);
this.keys = [];
},
destroyed: function destroyed () {
for (var key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys);
}
},
mounted: function mounted () {
var this$1 = this;
this.$watch('include', function (val) {
pruneCache(this$1, function (name) { return matches(val, name); });
});
this.$watch('exclude', function (val) {
pruneCache(this$1, function (name) { return !matches(val, name); });
});
},
render: function render () {
// ...
}
};

###

  • 建立父子关系. 对keep-alive而言,parent的children没有自己, 但自己的parent还是parent.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function initLifecycle (vm) {
var options = vm.$options;
// locate first non-abstract parent
var parent = options.parent;
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent;
}
parent.$children.push(vm);
}
vm.$parent = parent;
vm.$root = parent ? parent.$root : vm;
}
// keep-alive渲染时的调用栈
at initLifecycle (vue.esm.js?efeb:3916) vm._uid === 1
at VueComponent.Vue._init (vue.esm.js?efeb:5005)
at new VueComponent (vue.esm.js?efeb:5158)
at createComponentInstanceForVnode (vue.esm.js?efeb:3292)
at init (vue.esm.js?efeb:3123)
at createComponent (vue.esm.js?efeb:5984)
at createElm (vue.esm.js?efeb:5931)
at createChildren (vue.esm.js?efeb:6059)
at createElm (vue.esm.js?efeb:5960)
at Vue.patch [as __patch__] (vue.esm.js?efeb:6522)

首次渲染

  • patch根节点到keep-alive组件,init的时候createComponentInstanceForVnode->new VueComponent
  • keep-alive $mount $render接入render函数, 返回包裹的内容的vnode, update该vnode
  • 内容vnode的vm实例的parent是keep-alive的父元素(如上面代码)
  • 根节点patch结束之前调用invokeInsertHook, 会调用activateChildComponent触发activated回调
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    function init (vnode, hydrating) {
    if (
    vnode.componentInstance &&
    !vnode.componentInstance._isDestroyed &&
    vnode.data.keepAlive
    ) {
    // kept-alive components, treat as a patch
    var mountedNode = vnode; // work around flow
    componentVNodeHooks.prepatch(mountedNode, mountedNode);
    } else {
    var child = vnode.componentInstance = createComponentInstanceForVnode(
    vnode,
    activeInstance
    );
    child.$mount(hydrating ? vnode.elm : undefined, hydrating);
    }
    }
    function render(){
    var slot = this.$slots.default;
    var vnode = getFirstComponentChild(slot);
    var componentOptions = vnode && vnode.componentOptions;
    if (componentOptions) {
    var ref = this;
    if (cache[key]) {
    vnode.componentInstance = cache[key].componentInstance;
    // make current key freshest
    remove(keys, key);
    keys.push(key);
    } else {
    cache[key] = vnode;
    keys.push(key);
    }
    vnode.data.keepAlive = true;
    }
    return vnode || (slot && slot[0])
    }
    // call invokeInsertHook
    function insert (vnode) {
    var context = vnode.context;
    var componentInstance = vnode.componentInstance;
    if (!componentInstance._isMounted) {
    componentInstance._isMounted = true;
    callHook(componentInstance, 'mounted');
    }
    if (vnode.data.keepAlive) {
    if (context._isMounted) {
    // vue-router#1212
    // During updates, a kept-alive component's child components may
    // change, so directly walking the tree here may call activated hooks
    // on incorrect children. Instead we push them into a queue which will
    // be processed after the whole patch process ended.
    queueActivatedComponent(componentInstance);
    } else {
    activateChildComponent(componentInstance, true /* direct */);
    }
    }
    }
    function activateChildComponent (vm, direct) {
    if (vm._inactive || vm._inactive === null) {
    vm._inactive = false;
    for (var i = 0; i < vm.$children.length; i++) {
    activateChildComponent(vm.$children[i]);
    }
    callHook(vm, 'activated');
    }
    }

二次渲染

  • patchVnode到keep-alive组件时调用prepatch触发keep-alive组件重新渲染
  • 重新渲染的vnode从缓存获取componentInstance
  • 再次到createComponent 会执行init hook,就会跳过渲染vnode的vm创建
  • 执行initComponent insert后就完成dom插入
  • keep-alive组件的patch最后执行invokeInsertHook, 执行在initComponent添加的包裹vnode的inserthook
  • 与首次渲染不同是activated回调通过先添加到activatedChildren中,再渲染完成后一并执行. 因为子组件在update的时候可能会变.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function patchVnode (){
var i;
var data = vnode.data;
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode);
}
}
function prepatch (oldVnode, vnode) {
var options = vnode.componentOptions;
var child = vnode.componentInstance = oldVnode.componentInstance;
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
);
}
function updateChildComponent (
vm,
propsData,
listeners,
parentVnode,
renderChildren
) {
var needsForceUpdate = !!(
renderChildren || // has new static slots
vm.$options._renderChildren || // has old static slots
hasDynamicScopedSlot
);
// resolve slots + force update if has children
if (needsForceUpdate) {
vm.$slots = resolveSlots(renderChildren, parentVnode.context);
vm.$forceUpdate();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function render(){
var slot = this.$slots.default;
var vnode = getFirstComponentChild(slot);
var componentOptions = vnode && vnode.componentOptions;
if (componentOptions) {
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance;
// make current key freshest
remove(keys, key);
keys.push(key);
}
vnode.data.keepAlive = true;
}
return vnode || (slot && slot[0])
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */);
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
}
function init (vnode, hydrating) {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
var mountedNode = vnode; // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode);
} else {
var child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
);
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
},
function insert (vnode) {
if (vnode.data.keepAlive) {
if (context._isMounted) {
// vue-router#1212
// During updates, a kept-alive component's child components may
// change, so directly walking the tree here may call activated hooks
// on incorrect children. Instead we push them into a queue which will
// be processed after the whole patch process ended.
queueActivatedComponent(componentInstance);
} else {
activateChildComponent(componentInstance, true /* direct */);
}
}
}
function queueActivatedComponent (vm) {
// setting _inactive to false here so that a render function can
// rely on checking whether it's in an inactive tree (e.g. router-view)
vm._inactive = false;
activatedChildren.push(vm);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
function flushSchedulerQueue () {
// watcher执行
// call component updated and activated hooks
callActivatedHooks(activatedQueue);
callUpdatedHooks(updatedQueue);
}
function callActivatedHooks (queue) {
for (var i = 0; i < queue.length; i++) {
queue[i]._inactive = true;
activateChildComponent(queue[i], true /* true */);
}
}

deactivated钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function patch (oldVnode, vnode) {
// destroy old node
if (isDef(parentElm)) {
removeVnodes(parentElm, [oldVnode], 0, 0);
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}
function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
var ch = vnodes[startIdx];
if (isDef(ch)) {
if (isDef(ch.tag)) {
removeAndInvokeRemoveHook(ch);
invokeDestroyHook(ch);
} else { // Text node
removeNode(ch.elm);
}
}
}
}
function invokeDestroyHook (vnode) {
var i, j;
var data = vnode.data;
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.destroy)) { i(vnode); }
for (i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](vnode); }
}
}
function destroy (vnode) {
var componentInstance = vnode.componentInstance;
if (!componentInstance._isDestroyed) {
if (!vnode.data.keepAlive) {
componentInstance.$destroy();
} else {
deactivateChildComponent(componentInstance, true /* direct */);
}
}
}
function deactivateChildComponent (vm, direct) {
if (direct) {
vm._directInactive = true;
if (isInInactiveTree(vm)) {
return
}
}
if (!vm._inactive) {
vm._inactive = true;
for (var i = 0; i < vm.$children.length; i++) {
deactivateChildComponent(vm.$children[i]);
}
callHook(vm, 'deactivated');
}
}

vue-slot

发表于 2019-05-26 | 分类于 vue slot

普通slot

  • 父组件update的时候已经创建了vnode, 子组件render的时候直接从$slot返回对应的vnode

父组件parse 阶段

  • 往ast节点添加属性slot=slottarget, gendata生成对应字符串
  • compile完 render, 创建子组件vnode后, 开始渲染子组件
1
2
3
4
5
6
7
8
9
function processElement (
element,
options
) {
processSlotContent(element);
processSlotOutlet(element);
processAttrs(element);
return element
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// handle content being passed to a component as slot,
// e.g. <template slot="xxx">, <div slot-scope="xxx">
function processSlotContent (el) {
var slotScope;
if (el.tag === 'template') {
slotScope = getAndRemoveAttr(el, 'scope');
el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope');
} else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
el.slotScope = slotScope;
}
// slot="xxx"
var slotTarget = getBindingAttr(el, 'slot');
if (slotTarget) {
el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget;
el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot']);
// preserve slot as an attribute for native shadow DOM compat
// only for non-scoped slots.
if (el.tag !== 'template' && !el.slotScope) {
addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'));
}
}
}
阅读全文 »

vue-model

发表于 2019-05-18 | 分类于 vue model

表单v-model

  • parse的时候添加属性, generate的时候处理属性
  • processAttrs调用addDirective往el.directives添加指令信息

    parse 阶段

  • v-model 被当做普通的指令解析到 el.directives 中,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    function processAttrs (el) {
    var list = el.attrsList;
    var i, l, name, rawName, value, modifiers, syncGen, isDynamic;
    for (i = 0, l = list.length; i < l; i++) {
    name = rawName = list[i].name;
    value = list[i].value;
    if (dirRE.test(name)) {
    name = name.replace(dirRE, '');
    addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]);
    } else {// 文本
    }
    }
    }
    function addDirective (
    // dom, name, 属性值
    el, name, rawName, value, arg, isDynamicArg, modifiers, range
    ) {
    (el.directives || (el.directives = [])).push(rangeSetItem({
    name: name,
    rawName: rawName,
    value: value,
    arg: arg,
    isDynamicArg: isDynamicArg,
    modifiers: modifiers
    }, range));
    el.plain = false;
    }
阅读全文 »

vue-event

发表于 2019-04-15 | 分类于 vue vue-event

编译和生成代码

  • parse的时候添加属性, generate的时候处理属性

编译

  • 在编译标签时handleStartTag->processElement->processAttrs会去解析事件绑定属性
  • addHandler在ast上添加事件属性nativeEvents/events, events[name] = newHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function parseModifiers (name) { // .x.y => {x: true, y: true}
var match = name.match(modifierRE);
if (match) {
var ret = {};
match.forEach(function (m) { ret[m.slice(1)] = true; });
return ret
}
}
function processAttrs (el) {
var list = el.attrsList;
var i, l, name, rawName, value, modifiers, isProp;
for (i = 0, l = list.length; i < l; i++) { // 遍历属性
name = rawName = list[i].name;
value = list[i].value;
if (dirRE.test(name)) { // /^v-|^@|^:/
// mark element as dynamic
el.hasBindings = true;
// modifiers
modifiers = parseModifiers(name);
if (modifiers) {
name = name.replace(modifierRE, '');
}
if (bindRE.test(name)) {
// v-bind
} else if (onRE.test(name)) { // v-on
name = name.replace(onRE, '');
addHandler(el, name, value, modifiers, false, warn$2);
} else {
// normal directives
}
} else {
// literal attribute
addAttr(el, name, JSON.stringify(value));
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function addHandler (el, name, value, modifiers, important, warn) {
// check capture/once/passive modifier
if (modifiers.xx) {
delete modifiers.xx;
name = '!' + name; // mark the event
}
// normalize click.right and click.middle
var events;
if (modifiers.native) {
delete modifiers.native;
events = el.nativeEvents || (el.nativeEvents = {});
} else {
events = el.events || (el.events = {});
}
var newHandler = {
value: value.trim()
};
newHandler.modifiers = modifiers;
if (Array.isArray(handlers)) { // 添加两次以上
important ? handlers.unshift(newHandler) : handlers.push(newHandler);
} else if (handlers) { // 第二次添加
events[name] = important ? [newHandler, handlers] : [handlers, newHandler];
} else {
events[name] = newHandler; // 第一次添加
}
el.plain = false;
}
// el.nativeEvents = {
// click: {
// value: 'clickHandler',
// modifiers: {
// prevent: true
// }
// }
// }

生成代码

  • generate.genElement.genData.genHandlers
    • 对Handler判断是表达式还是方法名, 方法名直接返回, 表达式外面套个functionfunction($event){(handler.value)}, 并传入$event
    • 有modifier通过modifierCode添加对应的代码后和上一步一起返回.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
function genHandlers ( // 遍历事件 拼接成字符串 nativeOn:{ xx: xxx, yy: yyy }
events,
isNative,
warn
) {
var res = isNative ? 'nativeOn:{' : 'on:{';
for (var name in events) {
res += "\"" + name + "\":" + (genHandler(name, events[name])) + ",";
}
return res.slice(0, -1) + '}'
}
var modifierCode = {
stop: '$event.stopPropagation();',
prevent: '$event.preventDefault();',
self: genGuard("$event.target !== $event.currentTarget"),
ctrl: genGuard("!$event.ctrlKey"),
shift: genGuard("!$event.shiftKey"),
alt: genGuard("!$event.altKey"),
meta: genGuard("!$event.metaKey"),
left: genGuard("'button' in $event && $event.button !== 0"),
middle: genGuard("'button' in $event && $event.button !== 1"),
right: genGuard("'button' in $event && $event.button !== 2")
};
function genHandler (
name, // select
handler // {value: "selectHandler"}
) {
if (!handler) {
return 'function(){}'
}
if (Array.isArray(handler)) { // 处理多个事件回调
return ("[" + (handler.map(function (handler) { return genHandler(name, handler); }).join(',')) + "]")
}
var isMethodPath = simplePathRE.test(handler.value); // x.y x[1] x[a] x['a'] x["a"]
var isFunctionExpression = fnExpRE.test(handler.value); / () => or xx => or function(
if (!handler.modifiers) { // 没有修饰器就返回
if (isMethodPath || isFunctionExpression) {
return handler.value
}
/* istanbul ignore if */
return ("function($event){" + (handler.value) + "}") // inline statement
} else { // 处理modifiers
var code = '';
var genModifierCode = '';
var keys = [];
for (var key in handler.modifiers) {
if (modifierCode[key]) {
genModifierCode += modifierCode[key];
// left/right
if (keyCodes[key]) {
keys.push(key);
}
} else if (key === 'exact') {
var modifiers = (handler.modifiers);
genModifierCode += genGuard(
['ctrl', 'shift', 'alt', 'meta']
.filter(function (keyModifier) { return !modifiers[keyModifier]; })
.map(function (keyModifier) { return ("$event." + keyModifier + "Key"); })
.join('||')
);
} else {
keys.push(key);
}
}
if (keys.length) {
code += genKeyFilter(keys);
}
// Make sure modifiers like prevent and stop get executed after key filtering
if (genModifierCode) {
code += genModifierCode;
}
var handlerCode = isMethodPath
? ("return " + (handler.value) + "($event)")
: isFunctionExpression
? ("return (" + (handler.value) + ")($event)") // 返回函数内部调用方法并传入$event
: handler.value;
/* istanbul ignore if */
return ("function($event){" + code + handlerCode + "}") // function call expression
}
}
// {
// on: {"select": selectHandler},
// nativeOn: {"click": function($event) {
// $event.preventDefault();
// return clickHandler($event)
// }
// }
// }
// {
// on: {"click": function($event) {
// clickHandler($event)
// }
// }
// }

dom事件

  • create / update hook事件绑定更新
  • event hooks 安装和调用
    • modules在src/platforms/web/runtime/modules.
    • events的create和update都是调用updateDOMListeners, create时oldVnode是空
    • invokeCreateHooks调用create, patchVnode里调用update hook
      • invokeCreateHooks在createElm, initComponent中调用,vue实例化和初始化组件时
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        var events = {
        create: updateDOMListeners,
        update: updateDOMListeners
        }
        var platformModules = [
        attrs, klass, events, domProps, style, transition
        ]
        var modules = platformModules.concat(baseModules);
        var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
        function createPatchFunction (backend) { // hooks在创建patch方法混入
        var i, j;
        var cbs = {}; // 平台module和basemodule都保存在cbs, 组件update的时候调用
        var modules = backend.modules;
        for (i = 0; i < hooks.length; ++i) {
        cbs[hooks[i]] = [];
        for (j = 0; j < modules.length; ++j) {
        if (isDef(modules[j][hooks[i]])) {
        cbs[hooks[i]].push(modules[j][hooks[i]]);
        }
        }
        }
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function createElm (
vnode, insertedVnodeQueue, parentElm,)
{
createChildren(vnode, children, insertedVnodeQueue);
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue); // new vue时调用
}
insert(parentElm, vnode.elm, refElm);
}
function initComponent (vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert);
vnode.data.pendingInsert = null;
}
vnode.elm = vnode.componentInstance.$el;
if (isPatchable(vnode)) {
invokeCreateHooks(vnode, insertedVnodeQueue); // 创建组件时调用
setScope(vnode);
} else {
// empty component root.
// skip all element-related modules except for ref (#3455)
registerRef(vnode);
// make sure to invoke the insert hook
insertedVnodeQueue.push(vnode);
}
}
  • 调用hook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
cbs.create[i$1](emptyNode, vnode);
}
i = vnode.data.hook; // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) { i.create(emptyNode, vnode); } // create
if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
}
}
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
var i;
var data = vnode.data;
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { // data上vnode的hook怎么挂的 ? prepatch
i(oldVnode, vnode);
}
var oldCh = oldVnode.children;
var ch = vnode.children;
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); } // update
if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); }
}
}

dom事件绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
function updateDOMListeners (oldVnode, vnode) {
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
return
}
var on = vnode.data.on || {};
var oldOn = oldVnode.data.on || {};
target$1 = vnode.elm;
normalizeEvents(on); // normalize v-model event
updateListeners(on, oldOn, add$1, remove$2, vnode.context);
target$1 = undefined;
}
// 遍历data.on, 更新事件监听
function updateListeners (
on,
oldOn,
add,
remove$$1,
vm
) {
var name, def, cur, old, event;
for (name in on) {
def = cur = on[name];
old = oldOn[name];
event = normalizeEvent(name); // once, capture转成事件绑定的option
if (isUndef(old)) {
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur); // 修改回调函数, update不需要重新绑定
}
add(event.name, cur, event.once, event.capture, event.passive, event.params); // add new
} else if (cur !== old) {
old.fns = cur;
on[name] = old;
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name);
remove$$1(event.name, oldOn[name], event.capture); // remove unused
}
}
}
function createFnInvoker (fns) {
function invoker () {
var arguments$1 = arguments;
var fns = invoker.fns;
if (Array.isArray(fns)) {
var cloned = fns.slice();
for (var i = 0; i < cloned.length; i++) {
cloned[i].apply(null, arguments$1);
}
} else {
// return handler return value for single handlers
return fns.apply(null, arguments)
}
}
invoker.fns = fns;
return invoker
}
function add$1 (event, handler, once$$1, capture, passive) {
handler = withMacroTask(handler); // 事件回调引起的state change nextTick通过macro task执行回调
if (once$$1) { handler = createOnceHandler(handler, event, capture); }
target$1.addEventListener(
event,
handler,
supportsPassive
? { capture: capture, passive: passive }
: capture
);
}
function remove$2 (
event,
handler,
capture,
_target
) {
(_target || target$1).removeEventListener(
event,
handler._withTask || handler,
capture
);
}
/**
* Wrap a function so that if any code inside triggers state change,
* the changes are queued using a (macro) task instead of a microtask.
*/
function withMacroTask (fn) {
return fn._withTask || (fn._withTask = function () {
useMacroTask = true;
var res = fn.apply(null, arguments);
useMacroTask = false;
return res
})
}

自定义事件

  • 父组件render的时候发现child是组件, 有非native listener就作为componentOptions传入new Vnode(组件占位符节点)
    • 子组件初始化时调用initInternalComponent initEvents, 把vm.$options._parentListeners里的listener传给updateListeners做dom事件类似的更新监听.
    • componentListener的add和remove方法调用vm.$on $off方法
    • vm._events上有各类型的事件监听, 通过发布订阅模式实现事件回调
    • 子组件update的时候监听native listener.
  • 父组件update的时候监听native listener.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createComponent (Ctor, data, context, children, tag) {
// ...
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
var listeners = data.on;
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn;
// install component management hooks onto the placeholder node
installComponentHooks(data);
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
asyncFactory
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// patch子组件
function createComponentInstanceForVnode (vnode, parent, parentElm, refElm) {
var options = {
_isComponent: true,
parent: parent,
_parentVnode: vnode, // 占位符节点里有`listeners: listeners`
_parentElm: parentElm || null,
_refElm: refElm || null
};
return new vnode.componentOptions.Ctor(options)
}
Vue.prototype._init = function (options) {
var vm = this;
if (options && options._isComponent) {
initInternalComponent(vm, options);
}
// init xxx
initEvents(vm);
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
// options就是上面createComponentInstanceForVnode的options
function initInternalComponent (vm, options) {
vm.$options = Object.create(vm.constructor.options);
// ....
vm.$options._parentVnode = options._parentVnode;
var parentVnode = options._parentVnode;
var vnodeComponentOptions = parentVnode.componentOptions;
opts._parentListeners = vnodeComponentOptions.listeners;
}
function initEvents (vm) {
vm._events = Object.create(null);
vm._hasHookEvent = false;
// init parent attached events
var listeners = vm.$options._parentListeners;// {select: invoker().fns}
if (listeners) {
updateComponentListeners(vm, listeners);
}
}
1
2
3
4
5
6
7
8
9
function updateComponentListeners (
vm,
listeners,
oldListeners
) {
target = vm;
updateListeners(listeners, oldListeners || {}, add, remove$1, vm);
target = undefined;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
function eventsMixin (Vue) {
var hookRE = /^hook:/;
Vue.prototype.$on = function (event, fn) {
var this$1 = this;
var vm = this;
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
this$1.$on(event[i], fn);
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn);
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true;
}
}
return vm
};
Vue.prototype.$once = function (event, fn) {
var vm = this;
function on () {
vm.$off(event, on);
fn.apply(vm, arguments);
}
on.fn = fn;
vm.$on(event, on);
return vm
};
Vue.prototype.$off = function (event, fn) {
var this$1 = this;
var vm = this;
// all
if (!arguments.length) {
vm._events = Object.create(null);
return vm
}
// array of events
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
this$1.$off(event[i], fn);
}
return vm
}
// specific event
var cbs = vm._events[event];
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event] = null;
return vm
}
if (fn) {
// specific handler
var cb;
var i$1 = cbs.length;
while (i$1--) {
cb = cbs[i$1];
if (cb === fn || cb.fn === fn) {
cbs.splice(i$1, 1);
break
}
}
}
return vm
};
Vue.prototype.$emit = function (event) {
var vm = this;
var cbs = vm._events[event]; // cbs是父组件定义但, 子组件实例化的时候作为选项传入了
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
var args = toArray(arguments, 1);
for (var i = 0, l = cbs.length; i < l; i++) {
try {
cbs[i].apply(vm, args);
} catch (e) {
handleError(e, vm, ("event handler for \"" + event + "\""));
}
}
}
return vm
};
}

vue-template-compile

发表于 2019-04-06 | 分类于 vue模板编译

anchors

  • 入口
  • parse
    • parse流程
    • parseHTML
      • startElement
      • endElement
      • text
    • conerCases
  • optimize
  • codegen

入口

  • 通过loader加载的vue文件都是已经编译过得.
  • createCompilerCreator, 缓存了baseCompile(主要编译过程)
  • createCompiler, 缓存了baseOptions(平台差异选项); 内部compile方法在处理options(module,directive)后调用baseCompile
  • createCompileToFunctionFn对传入option做处理, (缓存编译的render function), 调用createCompiler的内部compile方法, 为ast创建function, 处理编译错误信息等.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var ref$1 = createCompiler(baseOptions);
    var compile = ref$1.compile;
    var compileToFunctions = ref$1.compileToFunctions;
    var ref = compileToFunctions(template, {
    outputSourceRange: process.env.NODE_ENV !== 'production',
    shouldDecodeNewlines: shouldDecodeNewlines,
    shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
    delimiters: options.delimiters,
    comments: options.comments
    }, this);
    var render = ref.render;
阅读全文 »

vue components update

发表于 2019-04-05 | 分类于 vue组件更新

anchors

  • 入口
  • 新旧vnode不相同
    • 以当前旧节点为参考节点,创建新的节点
    • 递归更新父占位符节点
    • 删除旧节点
  • 新旧vnode相同比较children
    • 执行 prepatch 钩子函数
    • 执行update钩子函数
    • patch
    • 执行postpatch钩子函数

入口

  • 调用sameVnode判断是不是同一个vnode
1
2
3
4
5
6
7
8
9
10
11
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode;
vm._vnode = vnode;
if (!prevVnode) {
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
vm.$el = vm.__patch__(prevVnode, vnode)
}
}
阅读全文 »
12…6

浩齐

some kind of blog

44 日志
33 分类
61 标签
© 2019 浩齐
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4