最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • [Vue源码系列-6]vue3渲染流程实现原理

    正文概述 掘金(我的幸运7)   2021-03-11   608

    一. vue3的类型处理

    1.1 ShapeFlag.ts

    export const enum ShapeFlags {	// JS位运算,不是TS语法
        ELEMENT = 1,    				// 00000001 -> 1
        FUNCTIONAL_COMPONENT = 1 << 1,  // 00000010 -> 2
        STATEFUL_COMPONENT = 1 << 2,  	// 00000100 -> 4
        TEXT_CHILDREN = 1 << 3,  		// 00001000 -> 8
        ARRAY_CHILDREN = 1 << 4, 		// 00010000 -> 16
        SLOTS_CHILDREN = 1 << 5, 		// 00100000 -> 32
        TELEPORT = 1 << 6,				// ...
        SUSPENSE = 1 << 7,
        COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
        COMPONENT_KEPT_ALIVE = 1 << 9,
        COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT     // 00000110
    }
    
    // 00000001 & 00000001 => 00000001
    // 未知组件 & ShapeFlags.ELEMENT => 00000001  只要 & 出来的结果不是0,就说明他是元素组件
    if(xxx & ShapeFlags.ELEMENT){
        // 处理 element
    }
    
    // COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
    // 00000100 | 00000010 => 00000110 
    if(xxx & ShapeFlags.COMPONENT){
        // 只要 & 出来结果不为 0 ,就既可能是 状态组件 ,又可能是 函数组件
    }
    

    二. 初始化渲染逻辑

    [Vue源码系列-6]vue3渲染流程实现原理

    2.1 patch

    const processElement = (n1, n2, container) => {
    
    }
    const mountComponent = (initialVNode, container) => {
        // 组件初始化
    }
    const processComponent = (n1, n2, container) => {
        if (n1 == null) {
            mountComponent(n2, container)
        }
    }
    const patch = (n1, n2, container) => {
        const { shapeFlag } = n2;
        if (shapeFlag & ShapeFlags.ELEMENT) {
            processElement(n1, n2, container); // 处理元素类型
        } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
            processComponent(n1, n2, container); // 处理组件类型
        }
    }
    
    const render = (vnode, container) => {
        patch(null, vnode, container); // 初始化逻辑老的虚拟节点为null
    }
    

    三. 组件渲染流程

    3.1 组件创建实例

    1. 根据虚拟节点创建组件实例 - createComponentInstance()
    2. 将数据解析到实例上 - setupComponent()
    3. 创建effect,让render执行 - setupRenderEffect()
    const mountComponent = (initialVNode, container) => {
            // 1. 先有实例
            const instance = initialVNode.component = createComponentInstance(initialVNode)
            // 2. 需要的数据解析到实例上
            setupComponent(instance);
            // 3. 创建一个effect 让render执行
            setupRenderEffect(instance, container);
        }
    

    3.2 创建组件实例

    // runtime-core/src/component.ts
    export function createComponentInstance(vnode) {
        const type = vnode.type;
        const instance = { // 组件实例
             __v_isVNode: true,
            vnode, 			// 组件对应的虚拟节点
            subTree: null, 	// 组件要渲染的子元素
            type,			// 组件对象
            ctx: {}, 		// 组件的上下文
            props: {}, 		// 组件的属性
            attrs: {}, 		// 元素本身的属性
            slots: {}, 		// 组件的插槽
            setupState: {}, // 组件setup的返回值
            isMounted: false // 组件是否被挂载?
        }
        instance.ctx = { _: instance };
        return instance
    }
    

    3.3 拓展instance

    export function setupComponent(instance){
        const {props,children} = instance.vnode;
        // 根据props解析出 attrs 和 props ,将其放在 instan上
        instance.props = props; 		// 1.初始化属性 initProps()
        instance.children = children;   // 2.初始化插槽 initSlot()
    
        // 需要先看一下当前组件是不是有状态的组件,函数组件
        let stateful = instance.vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
        if(stateful){   // 表示现在是一个带状态的组件
            // 调用 当前实例的setup 方法,用setup的返回值填充 setupState 和对应的 render 方法
            setupStatefulComponent(instance)
        }
    }
    

    3.4 实例属性代理

    // runtime-core/src/componentPublicInstance.ts
    import { hasOwn } from "@vue/shared/src"
    
    export const PublicInstanceProxyHandlers = {
        get({ _: instance }, key) {
            // 取值时 要访问 setupState props data
            const { setupState, props, data } = instance
            if(key[0] == '$'){
                return; // 不能取 $ 开头的属性
            }
            if (hasOwn(setupState, key)) {
                return setupState[key];
            } else if (hasOwn(props, key)) {
                return props[key];
            } else if (hasOwn(data, key)) {
                return data[key];
            } else {
                return undefined;
            }
        },
        set({ _: instance }, key, value) {
            const { setupState, props, data } = instance;
            if (hasOwn(setupState, key)) {
                setupState[key] = value
            } else if (hasOwn(props, key)) {
                props[key] = value
            } else if (hasOwn(data, key)) {
                data[key] = value
            }
            return true;
        }
    }
    
    // runtime-core/src/component.ts
    function setupStatefulComponent(instance){
        // 1. 代理  传递给 render 函数的参数
        instance.proxy = new Proxy(instance.ctx,PublicInstanceProxyHandlers as any)
        // 2. 获取组件的类型,拿到组件的 setup 方法
        let Component = instance.type
        let { setup } = Component;
        
        if(setup){  // 有 setup 再创建执行上下文的实例
            let setupContext = createSetupContext(instance);
            const setupResult = setup(instance.props,setupContext);
            // instance 中 props attrs slots emit expose 会被提取出来,因为开发过程中会使用这些属性
            handlerSetupResult(instance,setupResult)
        }else{
            finishComponentSetup(instance); // 完成组件的启动
        }
    }
    
    function handlerSetupResult(instance,setupResult){
        if(isFunction(setupResult)){
            instance.render = setupResult
        }else if(isObject(setupResult)){
            instance.setupState = setupResult
        }
        finishComponentSetup(instance);
    }
    
    function createSetupContext(instance){
        return {
            attrs: instance.attrs,
            slots: instance.slots,
            emit: ()=>{},
            expose: ()=>{}
        }
    }
    
    function finishComponentSetup(instance){
        let Component = instance.type
    
        if(!instance.render){
            // 对template 模板编译产生render函数
            if(!Component.render && Component.template){
                // 编译 将结果 赋予给 Component.template
            }
            instance.render = Component.render;
        }
        // console.log(instance);
        
        // 对Vue2.0 的API 做兼容
        // applyOptions  循环
        // applyOptions(instance,Component);
    }
    

    3.5 初始化渲染effect

    // runtime-core/src/renderer.ts
    const setupRenderEffect = (instance, initialVNode, container) => {
        instance.update = effect(function componentEffect(){
            if(!instance.isMounted){
                const proxyToUse = instance.proxy; // 实例中的代理属性
                // vue2 _vnode $vnode
                // vue3 vnode subTree
                const subTree = (instance.subTree = instance.render.call(proxyToUse,proxyToUse));
                patch(null,subTree,container); // 渲染子树
                initialVNode.el = subTree.el; // 组件的el和子树的el是同一个
                instance.isMounted = true; // 组件已经挂载完毕
            }else{
                console.log('更新逻辑')
            }
        })
    }
    

    四. 元素创建流程

    4.1 h 方法实现原理

    • 只有两个参数 类型 + 孩子 / 类型 + 属性
    • 三个参数 最后一个不是数组
    • 超过三个 多个参数
    // runtime-core/src/h.ts
    import { isArray, isObject } from "@vue/shared/src";
    import { createVNode, isVnode } from "./vnode";
    
    export function h(type, propsOrChildren, children) {
        const l = arguments.length; // 儿子节点要呢是字符串,要么是数组,针对的是 createVnode 
        if (l == 2) {    // 类型 + 属性 /  类型 + 孩子
            // 如果 propsOrChildren 是数组,直接作为第三个参数
            if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
                if (isVnode(propsOrChildren)) {
                    return createVNode(type, null, [propsOrChildren]);
                }
                return createVNode(type, propsOrChildren);
            } else {  // 如果第二个属性是不是对象,一定是孩子
                return createVNode(type, null, propsOrChildren);
            }
        } else {
            if (l > 3) {
                children = Array.prototype.slice.call(arguments, 2)
            } else if (l === 3 && isVnode(children)) {
                children = [children];
            }
            return createVNode(type,propsOrChildren,children);
        }
    
    }
    

    4.2 创建真实节点

    const mountElement = (vnode, container) => {
        // 创建节点保存到vnode中 递归渲染
        const { props, shapeFlag, type, children } = vnode
        let el = vnode.el = hostCreateElement(type);
    
        if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // 文本直接插入即可
            hostSetElementText(el, children);
        } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
            mountChildren(children, el)
        }
        if (props) { // 处理属性
            for (const key in props) {
                hostPatchProp(el, key, null, props[key])
            }
        }
        hostInsert(el, container)
    }
    

    4.3 子节点的处理

    // runtime-core/src/renderer.ts
    const mountChildren = (children, container) => {
        for (let i = 0; i < children.length; i++) {
            const child = normalizeVNode(children[i]);
            patch(null, child, container)
        }
    }
    const processText = (n1,n2,container) =>{
        if(n1 == null){ // 创建文本插入到容器中
            hostInsert(n2.el = hostCreateText(n2.children),container)
        }
    }
    
    // runtime-core/src/vnode.ts
    export const Text = Symbol('Text')
    export function normalizeVNode(child) { 
        if(isObject(child)){
            return child
        }
        return createVNode(Text,null,String(child));
    }
    

    更多vue源码系列

    • vue源码系列

    起源地下载网 » [Vue源码系列-6]vue3渲染流程实现原理

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元