尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通
本篇内容对应课程第147-149节
课程 P147节 《vue3响应式原理_Reflect》笔记
想要读取对象上的属性,只需要obj.a 的形式就可以读取到。还有另外一种方式,是使用 window上的Reflect来实现。
读取对象属性时:
Reflect.get(obj, 'a')
设置对象属性时:
Reflect.set(obj, 'a', value)
删除对象属性时:
Reflect.deleteProperty(obj, 'a')
ECMA正在尝试将Object对象上的许多属性与方法移植到 Reflect 上面:
例如,使用 Object.defineProperty 给对象obj 追加一个属性c ,不小心写了两遍,对 c 进行了重复追加定义的情况下,这段代码会直接报错,且由于js单线程报错即会阻塞后续代码正常执行:
如果改用 Reflect.defineProperty ,同样给对象obj追加一个属性c,同样不小心写了两遍,进行了重复定义,此时,控制台不仅没有报错,后续代码可以继续运行,且属性c已经被成功定义,只是生效的是第一次定义的代码:
此时,给人的感觉像是,用 Object.defineProperty 写的代码产生报错时,会直观地在控制台看到错误,但用 Reflect.defineProperty 写的代码产生错误时,不易发现错误。事实上 Reflect.defineProperty 是有返回值的,如果设置成功返回 true,设置失败则返回 false:
用 Object.defineProperty ,如果想让代码报错时不影响后续代码继续运行,只能使用 try catch来捕获错误:
但使用 Reflect.defineProperty 则不需要 try catch捕获错误,直接判断其返回值就可以知道操作是否成功。
Object.defineProperty 和 Reflect.defineProperty 的以上对比发现,在封装框架时,Reflect.defineProperty 在代码健壮性方面具有很大的优势,假如使用 Object.defineProperty,则可能需要使用大量 try catch来进行错误捕获,避免程序出错导致整个代码挂掉。
了解了 Reflect,回到 vue3响应式实现原理,在Proxy代理中对属性的操作,都是通过 Reflect 完成的:
总结vue3的响应式:
课程 P148节 《reactive对比ref》笔记
课程 P149节 《setup的两个注意点》笔记
setup的两个注意点:
回顾vue2中的两个知识点:$attrs 与 $slots
$attrs:
父组件向子组件传递prop:
子组件中通过 props 声明接收:
mounted中打印组件实例this,发现父组件通过props传递过来的值被放到了组件实例上面:
如果注释掉子组件中的 props 声明接收,也就是父组件通过prop的形式向子组件传递了数据,但子组件中没有通过props声明接收这些数据,再次观察子组件的实例对象this,发现组件实例上没有了父组件传递过来的数据,但组件实例上的 $attrs 上却有了这两个数据:
打开注释,让子组件中声明接收父组件props传递的数据,会发现子组件实例上有了这两个数据,同时 $attrs 上不再有这两个数据:
在子组件的props声明接收里,只接收 msg 这一个数据,发现声明接收了的 msg 被放在了子组件实例上面,而未被声明接收的 school 则被放到了 $attrs 上。就好像 $attrs 在捡漏一样,被声明接收的props(父组件通过props向子组件传递的数据)会被放到组件实例上,而没被声明接收的props则会被放到 $attrs 上:
$slots 插槽:
vue2中的插槽,简单说就是:子组件中留个“坑位”,等父组件来填充。
子组件中没有留“坑位”,观察子组件实例对象,上面有一个$slots属性:
父组件中,在引用子组件时向其内部传递一些内容,再次观察子组件实例对象上的 s l o t s ,会发现这个 slots ,会发现这个 slots,会发现这个slots对象上多了 default 属性,里面就是父组件传递的标签内容,被处理成了对应的 vnode,即虚拟dom:
在子组件中放入“坑位”,发现页面上渲染出了父组件传递的插槽内容,同时 $slots 上仍然有这个default :
父组件中传递两个标签内容时,子组件实例对象上的 $slots 的 default 下也会有两个:
父组件中传递两个具名插槽内容时,子组件实例对象上也能观察到这两个属性,属性名正是插槽名:
vue3 中的setup
补充完vue2中的 $attrs 与 $slots 知识点,回过头来继续看 vue3 中的setup:
setup执行的时机:在 beforeCreate 之前执行一次,this是undefined:
vue3的项目中,父组件给子组件传递两个props属性:msg和school
打印setup的第一个参数props,发现这个对象上并没有数据,同时控制台出现警告:
这是因为在子组件中没有声明接收这两个props参数,增加红框一行代码:
再次查看控制台,发现打印的setup的第一个参数props对象上有了 msg 与school 两个属性,且它是一个Proxy对象,是响应式的:
如果子组件中用 props 声明接收时,多声明一个未传递的数据 a ,会发现 a 是undefined:
打印setup的第二个参数 context,发现它是一个普通对象,上面主要有 attrs、slots、emit、expose 四个属性:
打印 context.attrs,发现这个Proxy对象上没有有用属性:
这是因为父组件通过props传递给子组件的数据,都通过 props 声明接收了。将声明接收的代码注释掉,再次观察 context.attrs,发现此时这里面有了 msg 与 school 这两个数据,同时由于没有声明接收,控制台出现了警告:
如果只声明接收一个 msg ,会发现:声明接收了的这个msg被放在了 setup 的第一个参数 props 中,未被声明接收的 school 被放在了 setup 的第二个参数 context.attrs 上。由此可见:context.attrs 就相当于vue2中的 $attrs :
测试自定义事件:父组件中给子组件绑定一个自定义的 hello 事件(如果给子组件绑定原生事件,如click,则需要 .native修饰符。即 @click.native=“someEvent” ):
子组件中通过context.emit触发这个事件:
会发现控制台出现警告,提示需要使用 emits 选项:
但此时事件已经奏效了:
增加 emits 选项,用一个数组的形式注册这个 hello 事件:
父组件中向子组件内部传递一些内容:
打印 setup 的第二个参数context中的slots :
发现其是一个Proxy对象,且里面有一个default属性:
父组件中用 slot=“slotName” 的形式给子组件传递具名插槽内容:
观察 context.slots ,仍然是传递匿名插槽时的default :
父组件中改用 v-slot:slotName 的形式给子组件传递具名插槽内容:
再次观察 context.slots ,这次就变成了传递的插槽的名字 :
父组件中用 v-slot:slotName 的形式给子组件传递两个具名插槽内容:qwe 和 asd
context.slots 中就可以打印出 qwe 和 asd :
在vue3里,使用具名插槽时尽量使用 v-slot:slotName 的形式。