Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue(v3.0.11)源码简析之组件子节点自动继承属性(非props和emits)的相关实现 #15

Open
unproductive-wanyicheng opened this issue May 11, 2021 · 0 comments

Comments

@unproductive-wanyicheng
Copy link
Owner

unproductive-wanyicheng commented May 11, 2021

vue文档描述了这种场景:我们写在组件上的props,如果不在子组件的声明props和emits中,则会被放到attrs中,如果子组件渲染出来是单个vnode,则自动继承所有的attrs到自己身上,如果是个片段且没有子节点显示声明我要使用$attrs,则输出警告信息。
上述功能实现的时机是在渲染组件调用patch,patch要得到render函数对应的vnode子树的时候介入的,得到vnode子树之后,我们就可以根据子树的结构来实施自动继承逻辑了:

/**
 * dev only flag to track whether $attrs was used during render.
 * If $attrs was used during render then the warning for failed attrs
 * fallthrough can be suppressed.
 */
// 用来标记 某个render过程中有vnode使用到了 $attrs 这个内置prop,它是用来承接所有非prop和emit声明的组件属性,都会被解析放到$attrs对象中去
let accessedAttrs = false;
function markAttrsAccessed() {
    accessedAttrs = true;
}
// 根据组件实例对象 调用render得到渲染的vnode树 返回值以这颗新的vnode树的根节点给调用者
function renderComponentRoot(instance) {
    /**
     * 取出组件实例上要用到的属性
     */
    const { type: Component, vnode, proxy, withProxy, props, propsOptions: [propsOptions], slots, attrs, emit, render, renderCache, data, setupState, ctx } = instance;
    let result;
    // 设置当前准备渲染的组件实例
    const prev = setCurrentRenderingInstance(instance);
    {
        // 先置为false 在这个组件渲染过程中如果有谁用到了 $attrs 则会重新置为true
        accessedAttrs = false;
    }
    try {
        // 准备接受非prop和emit的组件属性
        let fallthroughAttrs;
        // 原始vnode信息中的类型是组件 也就是我们平常自己写的组件对象
        if (vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */) {
            // withProxy is a proxy with a different `has` trap only for
            // runtime-compiled render functions using `with` block.
            // 如注释所言 其实就多一个has的一层代理 基本同 proxy
            const proxyToUse = withProxy || proxy;
            // 执行render 绑定作用域 proxyToUse 注意后面 5个参数 后面在分析生成render的时候再将他们的使用
            // render返回的是一个新的vnode树 上面的字段都是确定好的了 可以用于渲染得到真实的dom树
            // normalizeVNode 保证返回的格式总是一个vnode根节点
            result = normalizeVNode(render.call(proxyToUse, proxyToUse, renderCache, props, setupState, data, ctx));
            // 继承 无人接受的 用户设置的属性
            fallthroughAttrs = attrs;
        }
        else {
            /**
             *  const DynamicHeading = (props, context) => {
                    return Vue.h(`h${props.level}`, context.attrs, context.slots)
                }

                DynamicHeading.props = ['level'];

                app.component('dynamic-heading', DynamicHeading)

                以这种方式创建的函数组件的情况
             */
            // functional
            const render = Component;
            // in dev, mark attrs accessed if optional props (attrs === props)
            // 后文会有分析 这种情况 如果没设置函数组件的prop那么2者就会相等
            if (true && attrs === props) {
                // 开启标记
                markAttrsAccessed();
            }
            // 运行用户设置的render得到vnode内容
            result = normalizeVNode(render.length > 1
                // 函数组件对应文档中的调用的时候看声明了几个参数 1.第一个是 props 第二个则是 context 包含 3个属性
                ? render(props, true
                    ? {
                        get attrs() {
                            // 开启标记
                            markAttrsAccessed();
                            return attrs;
                        },
                        slots,
                        emit
                    }
                    : { attrs, slots, emit })
                : render(props, null /* we know it doesn't need it */));
            // 取得要继承的回落attrs 对函数组件设了props那attrs就是已经解析好的了 否则调用 getFunctionalFallthrough 提取
            fallthroughAttrs = Component.props
                ? attrs
                : getFunctionalFallthrough(attrs);
        }
        // attr merging
        // in dev mode, comments are preserved, and it's possible for a template
        // to have comments along side the root element which makes it a fragment
        // 根节点 之后有可能会被修改
        let root = result;
        let setRoot = undefined;
        if (true &&
            result.patchFlag > 0 &&
            // DEV_ROOT_FRAGMENT 表示 一个片段中只有一个元素是有效节点其他都是注释的情况 也默认继承attrs到这个有效节点上
            result.patchFlag & 2048 /* DEV_ROOT_FRAGMENT */) {
            ;
            // 得到新节点和重设方法
            [root, setRoot] = getChildRoot(result);
        }
        if (Component.inheritAttrs !== false && fallthroughAttrs) {
            const keys = Object.keys(fallthroughAttrs);
            const { shapeFlag } = root;
            // 存在需要继承的回落属性
            if (keys.length) {
                // 可以继承的2类vnode节点
                if (shapeFlag & 1 /* ELEMENT */ ||
                    shapeFlag & 6 /* COMPONENT */) {
                    if (propsOptions && keys.some(isModelListener)) {
                        // If a v-model listener (onUpdate:xxx) has a corresponding declared
                        // prop, it indicates this component expects to handle v-model and
                        // it should not fallthrough.
                        // related: #1543, #1643, #1989
                        // 移除onUpdate:xxxx类型的事件 不需要被回落继承
                        fallthroughAttrs = filterModelListeners(fallthroughAttrs, propsOptions);
                    }
                    // 返回合并回落属性后重新克隆出来的一个的新的root节点 
                    root = cloneVNode(root, fallthroughAttrs);
                }
                // 无vnode可以继承存在的回落属性
                else if (true && !accessedAttrs && root.type !== Comment) {
                    const allAttrs = Object.keys(attrs);
                    // 无人继承的属性们
                    const eventAttrs = [];
                    const extraAttrs = [];
                    for (let i = 0, l = allAttrs.length; i < l; i++) {
                        const key = allAttrs[i];
                        if (isOn(key)) {
                            // ignore v-model handlers when they fail to fallthrough
                            if (!isModelListener(key)) {
                                // remove `on`, lowercase first letter to reflect event casing
                                // accurately
                                // 只收集非onUpdate:xxx的其他事件
                                eventAttrs.push(key[2].toLowerCase() + key.slice(3));
                            }
                        }
                        else {
                            extraAttrs.push(key);
                        }
                    }
                    // 对应文档中的输出警告信息 存在无法被自动继承的回落属性
                    if (extraAttrs.length) {
                        warn(`Extraneous non-props attributes (` +
                            `${extraAttrs.join(', ')}) ` +
                            `were passed to component but could not be automatically inherited ` +
                            `because component renders fragment or text root nodes.`);
                    }
                    if (eventAttrs.length) {
                        warn(`Extraneous non-emits event listeners (` +
                            `${eventAttrs.join(', ')}) ` +
                            `were passed to component but could not be automatically inherited ` +
                            `because component renders fragment or text root nodes. ` +
                            `If the listener is intended to be a component custom event listener only, ` +
                            `declare it using the "emits" option.`);
                    }
                }
            }
        }
        // inherit directives
        if (vnode.dirs) {
            // 元素 组件
            if (true && !isElementRoot(root)) {
                warn(`Runtime directive used on component with non-element root node. ` +
                    `The directives will not function as intended.`);
            }
            // 继承指令部分
            root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs;
        }
        // inherit transition data
        // transition 的场景之后再分析 todo
        if (vnode.transition) {
            if (true && !isElementRoot(root)) {
                // 元素 组件
                warn(`Component inside <Transition> renders non-element root node ` +
                    `that cannot be animated.`);
            }
            // 高级抽象组件 <Transition> 才有 transition 属性
            root.transition = vnode.transition;
        }
        // 修改root在原子数组中的引用
        if (true && setRoot) {
            setRoot(root);
        }
        else {
            result = root;
        }
    }
    catch (err) {
        // 生成子树如果失败了 就整体作为一个注释返回
        blockStack.length = 0;
        handleError(err, instance, 1 /* RENDER_FUNCTION */);
        result = createVNode(Comment);
    }
    // 恢复实例
    setCurrentRenderingInstance(prev);
    return result;
}
/**
 * dev only
 * In dev mode, template root level comments are rendered, which turns the
 * template into a fragment root, but we need to locate the single element
 * root for attrs and scope id processing.
 */
// 尝试从vnode的子节点中选出一个唯一的有效节点 作为新的根节点替换当前vnode
const getChildRoot = (vnode) => {
    const rawChildren = vnode.children;
    const dynamicChildren = vnode.dynamicChildren;
    // 尝试从子节点中提取出新的根节点
    const childRoot = filterSingleRoot(rawChildren);
    // 不满足条件 返回原始值即可
    if (!childRoot) {
        return [vnode, undefined];
    }
    // 如果找到了 找到这个节点在原子数组节点以及动态节点数组中的位置
    const index = rawChildren.indexOf(childRoot);
    const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1;
    // 重设root节点的方法实现
    const setRoot = (updatedRoot) => {
        // updatedRoot 就是上面找到的目标节点被 normalizeVNode 处理之后的
        // 更新引用
        rawChildren[index] = updatedRoot;
        if (dynamicChildren) {
            if (dynamicIndex > -1) {
                // 直接更新
                dynamicChildren[dynamicIndex] = updatedRoot;
            }
            // 变成了一个动态节点 那就添加即可
            else if (updatedRoot.patchFlag > 0) {
                vnode.dynamicChildren = [...dynamicChildren, updatedRoot];
            }
        }
    };
    // 注意返回的时候按照 normalizeVNode 格式化了
    return [normalizeVNode(childRoot), setRoot];
};
// 从一个vnode片段中筛选出可能出现的单个有效vnode节点
function filterSingleRoot(children) {
    let singleRoot;
    for (let i = 0; i < children.length; i++) {
        const child = children[i];
        // 非vnode情况直接不处理
        if (isVNode(child)) {
            // ignore user comment
            // 非注释节点才处理 或者 v-if最终的回退情况 一个内容是v-if的注释节点占位符
            if (child.type !== Comment || child.children === 'v-if') {
                if (singleRoot) {
                    // has more than 1 non-comment child, return now
                    // 返回undefined告诉调用者 寻找失败
                    return;
                }
                else {
                    singleRoot = child;
                }
            }
        }
        else {
            return;
        }
    }
    // 找到了 确实存在这样的子节点 返回它
    return singleRoot;
}
// 从函数式组件上 继承的attrs只有3种
const getFunctionalFallthrough = (attrs) => {
    let res;
    for (const key in attrs) {
        if (key === 'class' || key === 'style' || isOn(key)) {
            (res || (res = {}))[key] = attrs[key];
        }
    }
    return res;
};
// 移除 onUpdate:xxxx 这样的prop 不需要作为回落继承
const filterModelListeners = (attrs, props) => {
    const res = {};
    for (const key in attrs) {
        if (!isModelListener(key) || !(key.slice(9) in props)) {
            res[key] = attrs[key];
        }
    }
    return res;
};
// 可以继承attrs的几类vnode类型
const isElementRoot = (vnode) => {
    return (vnode.shapeFlag & 6 /* COMPONENT */ ||
        vnode.shapeFlag & 1 /* ELEMENT */ ||
        vnode.type === Comment // potential v-if branch switch
    );
};
// patch更新之前 需要对比2个相同类型的vnode是否确实需要更新
function shouldUpdateComponent(prevVNode, nextVNode, optimized) {
    const { props: prevProps, children: prevChildren, component } = prevVNode;
    const { props: nextProps, children: nextChildren, patchFlag } = nextVNode;
    // ['event1', 'event2'] 这样的
    const emits = component.emitsOptions;
    // Parent component's render function was hot-updated. Since this may have
    // caused the child component's slots content to have changed, we need to
    // force the child to update as well.
    // 这个先忽略
    if ((prevChildren || nextChildren) && isHmrUpdating) {
        return true;
    }
    // 指令和transitin包裹的vnode总是要更新
    // force child update for runtime directive or transition on component vnode.
    if (nextVNode.dirs || nextVNode.transition) {
        return true;
    }
    // 开启优化的情况下 回忆一下 patchFlag 为 -1 和 -2 2个值的时候 -1 代表是静态节点 不会改变 -2 则是关闭优化策略
    if (optimized && patchFlag >= 0) {
        // 动态插槽
        if (patchFlag & 1024 /* DYNAMIC_SLOTS */) {
            // slot content that references values that might have changed,
            // e.g. in a v-for
            return true;
        }
        // 动态key prop
        if (patchFlag & 16 /* FULL_PROPS */) {
            // 之前不存在
            if (!prevProps) {
                return !!nextProps;
            }
            // 之前存在 对比新值即可
            // presence of this flag indicates props are always non-null
            return hasPropsChanged(prevProps, nextProps, emits);
        }
        else if (patchFlag & 8 /* PROPS */) {
            const dynamicProps = nextVNode.dynamicProps;
            for (let i = 0; i < dynamicProps.length; i++) {
                const key = dynamicProps[i];
                // 对比2个prop值是否变化即可 注意不需要对比emits中的自定义事件 因为他们的值不是null就是用户设置的事件配置对象
                if (nextProps[key] !== prevProps[key] &&
                    !isEmitListener(emits, key)) {
                    return true;
                }
            }
        }
    }
    else {
        // this path is only taken by manually written render functions
        // so presence of any children leads to a forced update
        // 用户手写的render函数
        if (prevChildren || nextChildren) {
            // $stable 代表稳定
            if (!nextChildren || !nextChildren.$stable) {
                return true;
            }
        }
        if (prevProps === nextProps) {
            return false;
        }
        if (!prevProps) {
            return !!nextProps;
        }
        if (!nextProps) {
            return true;
        }
        return hasPropsChanged(prevProps, nextProps, emits);
    }
    return false;
}
// 2个prop对象的对比 没啥好说的 遍历就是了
function hasPropsChanged(prevProps, nextProps, emitsOptions) {
    const nextKeys = Object.keys(nextProps);
    if (nextKeys.length !== Object.keys(prevProps).length) {
        return true;
    }
    for (let i = 0; i < nextKeys.length; i++) {
        const key = nextKeys[i];
        if (nextProps[key] !== prevProps[key] &&
            !isEmitListener(emitsOptions, key)) {
            return true;
        }
    }
    return false;
}
// 更新高阶组件的托管元素 也就是指的 这个组件实际渲染出来的是那个dom元素节点 多个高阶组件可以嵌套以最底下的那个组件的真实dom元素作为hostEle
// 这也指示出来 高阶组件只是一种虚拟的逻辑组件 并不真实反映在dom结构中 它们都对应真实占位的那个元素
// HOC指的 是  某个组件render的结果返回的是 另一个组件的vnode
// 详情见 http://devui.huawei.com/components/zh-cn/overview
function updateHOCHostEl({ vnode, parent }, el // HostNode
) {
    // 一直往上找
    while (parent && parent.subTree === vnode) {
        (vnode = parent.vnode).el = el;
        parent = parent.parent;
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant