目录
  • 前言
  • 介绍
  • 原理
    • 前提
    • 创建组件实例对象
    • 初始化Props操作
    • 创建proxy对象去获取Props
    • props作为参数传入setup
    • 将proxy挂载到render上
  • 总结

    前言

    props指父组件往子组件中传入参数,我们来介绍下如何理解vue3的props的原理

    介绍

    了解其原理之前我们要清楚vue的虚拟节点是什么,有什么表现。

    • 虚拟节点主要分成两种,分别是组件类型及元素类型,当然还有文本类型等特殊的虚拟节点。
    • 虚拟节点都会接收三个基本参数,分别是type, props, children

    对于组件类型而言:

    type是一个对象里面包括基本的render函数及setup函数等

    {
      render() {
        return h() // render函数返回一个虚拟节点
      }
      setup() {}
    }

    props是父组件往子组件传入的参数

    children是父组件往子组件传入的插槽

    对于元素类型而言:

    • type是当前节点的元素类型,如div
    • props是当前节点的元素属性,如class
    • children是当前节点的子元素是个数组,数组的内容有可能是组件也有可能是元素

    原理

    前提

    基于此我们可以创建两个组件互为父子的组件分别是APP组件(父),FOO组件(子)

    import { h } from '../h.js';
    import { Foo } from './foo.js';
    export const App = {
        // render 页面元素内容即template
        render() {
            // 接收一个Foo,并通过h函数创建一个子组件node2
            let node2 = h(
                Foo,
                { 
                  count: 1 
                },
                {}
            )
            return h(
                'div',
                {
                    id: 'root',
                },
                [
                    node2 // App接收Foo组件作为其子组件
                ] 
            );
        },
        setup() {
        }
    };
    import { h } from '../h.js';
    // 定义一个Foo组件用于验证Props功能
    export const Foo = {
        // render 页面元素内容即template
        render() {
            // ui 页面内容
            const foo = h(
                'div',
                {},
                'Foo' + this.count
            );
            return h('div', {}, [foo]);
        },
        // 第一个参数props,用于父子组件传值
        setup(props) { 
            console.log(props.count); // 打印传入的props值
        }
    };

    通过上面的代码我们可以看到,这里创建了两个文件分别代表父组件和子组件。

    vue3的编译过程中我们会先去解析组件,就将组件传入patch函数中,判断当前的虚拟节点类型是组件还是元素,再走下面的编译。

    上面的父组件代码中我可以看到对于Foo,我们创建了一个组件叫node2,并在props的位置中传入了一个count: 1的props。

    let node2 = h(
        Foo,
        { 
          count: 1 
        },
        {}
    )

    因为vue会递归的去解析每一个虚拟节点所以这个node2最后也会被解析。!!!

    下面介绍解析这个node2的时候做了什么,如何实现props的功能的

    创建组件实例对象

    如果是组件类型的话,将我们组件的虚拟节点作为参数传入createComponentInstance, 去创建一个组件实例对象,如下:

    export function createComponentInstance(vnode) {
        const component = {
            vnode,
            type: vnode.type,
            // 先创建一个空的setupState,去暂存组件类型虚拟节点的setup返回值
            setupState: {},
          	// 创建一个props,用来存储组件虚拟节点接收的props,注意:props不允许改变 (props)
            props: {}, 
        }
        return component
    }

    因为我们创建vnode的时候,实际上会接收接收三个基本参数,分别是type, props, children

    所以这里传入的vnode会带有props字段,而这个props字段是count:1(细品)

    假设我们在这一步创建了一个组件实例对象,叫instance

    初始化Props操作

    因为instance就接收了vnode,而组件的vnode实际上包含了props

    所以接着就会执行一个initProps的操作,如果vnode中props存在,那么就将props挂载到instance下的props字段中

    // 将传入的props挂载到组件实例对象上
    export function initProps(instance, props) {
        instance.props = props
    }

    这时候组件实例对象就可以正常的拿到props的值了

    创建proxy对象去获取Props

    因为我们知道代码中我们可以通过this. 的方式去获取props的值,而且props已经被挂载到了组件实例对象中。

    因此创建一个proxy对象(后续通过bind的方式将这个对象挂载到render函数等位置,this.的时候由props去映射到对应的props中

    instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
    const PublicInstanceProxyHandlers = {
        get({ _: instance }, key) {
            const { setupState, props } = instance
            // 如果在传入的props中,则返回的对应的值 (props)
            if (hasOwn(props, key)) {
                return props[key]
            }
        }
    }

    props作为参数传入setup

    因为我们知道vue3中在setup中没有this,但可以接收一个props,通过这个props去获取到父组件传入的值。

    那我们已经将props的值挂载到组件实例对象上,所以我们可以将props作为参数传入到setup中。

    const {setup} = instance.type.setup // 获取setup函数
    // 在执行setup的时候将props传入即可
    setup(shallowReadonly(instance.props))

    因此我们在使用的时候就可以通过接收props在setup中读值。

    // 第一个参数props,用于父子组件传值
    setup(props) { 
        console.log(props.count); // 打印传入的props值
    }

    将proxy挂载到render上

    在解析一个虚拟节点的时候,其实会先执行setup函数,然后再执行render,因为我们可以通过this. 的方式去获取props的值。

    所以我们通过bind的方式将我们之前创建proxy对象挂载到render函数中,保证其this可以正确取到props的值。

    instance.render.call(instance.proxy)

    总结

    到这里props的原理就讲完了。

    props实际上是 父组件往子组件的虚拟节点的props处插入的参数。

    因此我们在创建子组件的组件实例对象的时候可以拿到这个props的值并将其挂载到子组件的组件实例对象中。

    如果组件中需要使用props,通常是两个位置setup或render

    对于setup,我们可以将组件实例对象的props作为参数传入,这样就可以使用了,但是注意props是一个不能改的值,所以我们要用readonly包裹起来。

    在组件的页面中我们可以通过 this. 的方式去读取props的值,所以在渲染页面即调用render函数的时候,我们可以通过创一个proxy对象,将这个对象挂载到render函数中,通过proxy去读取到对应的props值

    声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。