目录

vue3学习笔记第147-149节vue3响应式原理_Reflectreactive对比refsetup的两个注意点

【vue3学习笔记】(第147-149节)vue3响应式原理_Reflect;reactive对比ref;setup的两个注意点

本篇内容对应课程第147-149节

课程 P147节 《vue3响应式原理_Reflect》笔记

想要读取对象上的属性,只需要obj.a 的形式就可以读取到。还有另外一种方式,是使用 window上的Reflect来实现。 读取对象属性时: Reflect.get(obj, ‘a’) https://i-blog.csdnimg.cn/blog_migrate/b89b715bb26617ef67bca5fbce9858d7.png 设置对象属性时: Reflect.set(obj, ‘a’, value) https://i-blog.csdnimg.cn/blog_migrate/a663e01a83080845457f6aab902bb67c.png 删除对象属性时: Reflect.deleteProperty(obj, ‘a’) https://i-blog.csdnimg.cn/blog_migrate/18ab4fa37640ce9648fdf2b444fb79b7.png ECMA正在尝试将Object对象上的许多属性与方法移植到 Reflect 上面: https://i-blog.csdnimg.cn/blog_migrate/8a96b151163fcd090a5274c46399e78e.png 例如,使用 Object.defineProperty 给对象obj 追加一个属性c ,不小心写了两遍,对 c 进行了重复追加定义的情况下,这段代码会直接报错,且由于js单线程报错即会阻塞后续代码正常执行: https://i-blog.csdnimg.cn/blog_migrate/69a796a68b8fd7472babb025b58ab25b.png https://i-blog.csdnimg.cn/blog_migrate/51a6a0d8a54b99c7308a9e52757204b1.png 如果改用 Reflect.defineProperty ,同样给对象obj追加一个属性c,同样不小心写了两遍,进行了重复定义,此时,控制台不仅没有报错,后续代码可以继续运行,且属性c已经被成功定义,只是生效的是第一次定义的代码: https://i-blog.csdnimg.cn/blog_migrate/29a77c0d312408730caf0206fb64a5e8.png https://i-blog.csdnimg.cn/blog_migrate/03f17809271b1d1ac537e36267debc5c.png 此时,给人的感觉像是,用 Object.defineProperty 写的代码产生报错时,会直观地在控制台看到错误,但用 Reflect.defineProperty 写的代码产生错误时,不易发现错误。事实上 Reflect.defineProperty 是有返回值的,如果设置成功返回 true,设置失败则返回 false: https://i-blog.csdnimg.cn/blog_migrate/075e7868f68e8650a590e482d0ef503b.png https://i-blog.csdnimg.cn/blog_migrate/bb97693ac6b66dd0aa9450a1054cabdd.png 用 Object.defineProperty ,如果想让代码报错时不影响后续代码继续运行,只能使用 try catch来捕获错误: https://i-blog.csdnimg.cn/blog_migrate/68f1fbf99a6d76c6af11b98534b5f8ef.png https://i-blog.csdnimg.cn/blog_migrate/d73415291d82c43be09d484b34c11884.png 但使用 Reflect.defineProperty 则不需要 try catch捕获错误,直接判断其返回值就可以知道操作是否成功。 https://i-blog.csdnimg.cn/blog_migrate/5380327208ab29c1a82e0f820c31b2c0.png https://i-blog.csdnimg.cn/blog_migrate/ccf96edcad73bd072e50f7f91708dd7a.png Object.defineProperty 和 Reflect.defineProperty 的以上对比发现,在封装框架时,Reflect.defineProperty 在代码健壮性方面具有很大的优势,假如使用 Object.defineProperty,则可能需要使用大量 try catch来进行错误捕获,避免程序出错导致整个代码挂掉。 了解了 Reflect,回到 vue3响应式实现原理,在Proxy代理中对属性的操作,都是通过 Reflect 完成的: https://i-blog.csdnimg.cn/blog_migrate/095fd5ef6d31897d34a71a1d777f9934.png 总结vue3的响应式: https://i-blog.csdnimg.cn/blog_migrate/26670e6d7b437ca8d63a4ac418d4a517.png

课程 P148节 《reactive对比ref》笔记

https://i-blog.csdnimg.cn/blog_migrate/941c3c3e6c60b4bd3d38b50d10f308f6.png

课程 P149节 《setup的两个注意点》笔记

setup的两个注意点: https://i-blog.csdnimg.cn/blog_migrate/7d89686e5e4e7d5d2421e7c3e8a83f63.png 回顾vue2中的两个知识点:$attrs 与 $slots $attrs: 父组件向子组件传递prop: https://i-blog.csdnimg.cn/blog_migrate/edf66d7c4b2960fe52345a64d37789bb.png 子组件中通过 props 声明接收: https://i-blog.csdnimg.cn/blog_migrate/613ff5af3b3c254fc7d43427c81a4210.png mounted中打印组件实例this,发现父组件通过props传递过来的值被放到了组件实例上面: https://i-blog.csdnimg.cn/blog_migrate/6c247bcfc6efc04c9d31c7575db822a3.png 如果注释掉子组件中的 props 声明接收,也就是父组件通过prop的形式向子组件传递了数据,但子组件中没有通过props声明接收这些数据,再次观察子组件的实例对象this,发现组件实例上没有了父组件传递过来的数据,但组件实例上的 $attrs 上却有了这两个数据: https://i-blog.csdnimg.cn/blog_migrate/75f79d931a4c5389df0352af6c172057.png https://i-blog.csdnimg.cn/blog_migrate/a3ae9329e354490947596f9af0ab11e8.png 打开注释,让子组件中声明接收父组件props传递的数据,会发现子组件实例上有了这两个数据,同时 $attrs 上不再有这两个数据: https://i-blog.csdnimg.cn/blog_migrate/c96a4d92509c6b0e828057c82fcf6ffc.png https://i-blog.csdnimg.cn/blog_migrate/eb4285eee215d80d88f13f5e06eab5da.png 在子组件的props声明接收里,只接收 msg 这一个数据,发现声明接收了的 msg 被放在了子组件实例上面,而未被声明接收的 school 则被放到了 $attrs 上。就好像 $attrs 在捡漏一样,被声明接收的props(父组件通过props向子组件传递的数据)会被放到组件实例上,而没被声明接收的props则会被放到 $attrs 上: https://i-blog.csdnimg.cn/blog_migrate/dfe7fb3c59e93568559993d7d30ad4ce.png https://i-blog.csdnimg.cn/blog_migrate/28ea0504adf158b8f20fcae22da3fabd.png $slots 插槽: vue2中的插槽,简单说就是:子组件中留个“坑位”,等父组件来填充。 子组件中没有留“坑位”,观察子组件实例对象,上面有一个$slots属性: https://i-blog.csdnimg.cn/blog_migrate/f9d5dec9dd4495aaddd2287588ae478e.png https://i-blog.csdnimg.cn/blog_migrate/403115d46ed1335ac606b83b667ad77a.png 父组件中,在引用子组件时向其内部传递一些内容,再次观察子组件实例对象上的 s l o t s ,会发现这个 slots ,会发现这个 slots,会发现这个slots对象上多了 default 属性,里面就是父组件传递的标签内容,被处理成了对应的 vnode,即虚拟dom: https://i-blog.csdnimg.cn/blog_migrate/ced0f3f51c5826bc30721f229ba0ebac.png https://i-blog.csdnimg.cn/blog_migrate/82f2b9df03aa940381400f60a86a5c2c.png 在子组件中放入“坑位”,发现页面上渲染出了父组件传递的插槽内容,同时 $slots 上仍然有这个default : https://i-blog.csdnimg.cn/blog_migrate/3da55ff33aad5972083b957ab44d9fbf.png https://i-blog.csdnimg.cn/blog_migrate/59a43fef391cbf6dd88255e742af7be7.png 父组件中传递两个标签内容时,子组件实例对象上的 $slots 的 default 下也会有两个: https://i-blog.csdnimg.cn/blog_migrate/0b91f4c71111f726bb4b69bd4eb62171.png https://i-blog.csdnimg.cn/blog_migrate/1996b8c0e6ccfc5247023afeb9d14547.png 父组件中传递两个具名插槽内容时,子组件实例对象上也能观察到这两个属性,属性名正是插槽名: https://i-blog.csdnimg.cn/blog_migrate/bb77bbd2dca4a1c401fa20ae41d116bd.png https://i-blog.csdnimg.cn/blog_migrate/8f08c0dc097024f476de2aca4593530a.png vue3 中的setup 补充完vue2中的 $attrs 与 $slots 知识点,回过头来继续看 vue3 中的setup: setup执行的时机:在 beforeCreate 之前执行一次,this是undefined: https://i-blog.csdnimg.cn/blog_migrate/1d2159c0cf8e313703fc9ab388756d99.png https://i-blog.csdnimg.cn/blog_migrate/c41d63ecfe90e4aff010a9fddeabdef7.png vue3的项目中,父组件给子组件传递两个props属性:msg和school https://i-blog.csdnimg.cn/blog_migrate/14d0c405aa41a845267aa901e31f3730.png 打印setup的第一个参数props,发现这个对象上并没有数据,同时控制台出现警告: https://i-blog.csdnimg.cn/blog_migrate/855a0bbaa99a15b4d294e3c14d97caac.png https://i-blog.csdnimg.cn/blog_migrate/1b70d862ac3d90f5cb9815c399555073.png 这是因为在子组件中没有声明接收这两个props参数,增加红框一行代码: https://i-blog.csdnimg.cn/blog_migrate/2c3a45aaa525a1941a0e6995377433c9.png 再次查看控制台,发现打印的setup的第一个参数props对象上有了 msg 与school 两个属性,且它是一个Proxy对象,是响应式的: https://i-blog.csdnimg.cn/blog_migrate/8c7e447aad3d04b36fc17019bfc81a66.png 如果子组件中用 props 声明接收时,多声明一个未传递的数据 a ,会发现 a 是undefined: https://i-blog.csdnimg.cn/blog_migrate/161d90dbf55e53490fab7282dc00aca5.png https://i-blog.csdnimg.cn/blog_migrate/0916e33b6da847b1a501deab2b1f3f79.png 打印setup的第二个参数 context,发现它是一个普通对象,上面主要有 attrs、slots、emit、expose 四个属性: https://i-blog.csdnimg.cn/blog_migrate/98de24b2e8a88c4e62042ca04531d3d3.png https://i-blog.csdnimg.cn/blog_migrate/afd401f6d320ef6ab32e1e3772fc7153.png 打印 context.attrs,发现这个Proxy对象上没有有用属性: https://i-blog.csdnimg.cn/blog_migrate/694c171030b7aa96664e79a54bbb2fe0.png https://i-blog.csdnimg.cn/blog_migrate/d6d92e741a5da409150610474e020b69.png 这是因为父组件通过props传递给子组件的数据,都通过 props 声明接收了。将声明接收的代码注释掉,再次观察 context.attrs,发现此时这里面有了 msg 与 school 这两个数据,同时由于没有声明接收,控制台出现了警告: https://i-blog.csdnimg.cn/blog_migrate/a5b97e24417b1d117ce0fc3e8fa565bd.png https://i-blog.csdnimg.cn/blog_migrate/aa2733d0a32f8b1d922ad980bd2b245b.png 如果只声明接收一个 msg ,会发现:声明接收了的这个msg被放在了 setup 的第一个参数 props 中,未被声明接收的 school 被放在了 setup 的第二个参数 context.attrs 上。由此可见:context.attrs 就相当于vue2中的 $attrs : https://i-blog.csdnimg.cn/blog_migrate/ea7745a8ed7c0a3848c3ef4bc8cdb75e.png https://i-blog.csdnimg.cn/blog_migrate/393093a94a47c1b9ce916dbd446fe984.png 测试自定义事件:父组件中给子组件绑定一个自定义的 hello 事件(如果给子组件绑定原生事件,如click,则需要 .native修饰符。即 @click.native=“someEvent” ): https://i-blog.csdnimg.cn/blog_migrate/576652f5098ab68c6ceb30e7aa47461f.png 子组件中通过context.emit触发这个事件: https://i-blog.csdnimg.cn/blog_migrate/10ea9a586332eeda3fc4f9a2efaf69ac.png 会发现控制台出现警告,提示需要使用 emits 选项: https://i-blog.csdnimg.cn/blog_migrate/f9b1ab0591a200ce49720bb6ece7c0c3.png 但此时事件已经奏效了: https://i-blog.csdnimg.cn/blog_migrate/5375a4be0a5184377c6133a555c5d020.png 增加 emits 选项,用一个数组的形式注册这个 hello 事件: https://i-blog.csdnimg.cn/blog_migrate/9ecff2b6663c55f4179d656a83a46688.png 父组件中向子组件内部传递一些内容: https://i-blog.csdnimg.cn/blog_migrate/e15a696b4cddb1f90d787eb9d1929ba5.png 打印 setup 的第二个参数context中的slots : https://i-blog.csdnimg.cn/blog_migrate/65130557ef20bfa3b99a4b2d9e1cbaa8.png 发现其是一个Proxy对象,且里面有一个default属性: https://i-blog.csdnimg.cn/blog_migrate/36f0e5af2b80d841c9b41b6bd30ada38.png 父组件中用 slot=“slotName” 的形式给子组件传递具名插槽内容: https://i-blog.csdnimg.cn/blog_migrate/5d9c1150d93cf7f8861ab7fc8926ec7e.png 观察 context.slots ,仍然是传递匿名插槽时的default : https://i-blog.csdnimg.cn/blog_migrate/4725482d54c99e30d7650b5c7bc83f0b.png 父组件中改用 v-slot:slotName 的形式给子组件传递具名插槽内容: https://i-blog.csdnimg.cn/blog_migrate/c506ee23841a9f3f4adf37fa14a36b9c.png 再次观察 context.slots ,这次就变成了传递的插槽的名字 : https://i-blog.csdnimg.cn/blog_migrate/36f15a9920d149bce3240af417549186.png 父组件中用 v-slot:slotName 的形式给子组件传递两个具名插槽内容:qwe 和 asd https://i-blog.csdnimg.cn/blog_migrate/177d49dbfe49ac8800c075895b7c52bb.png context.slots 中就可以打印出 qwe 和 asd : https://i-blog.csdnimg.cn/blog_migrate/e2dde07fe19736d7bb41a354ae681838.png 在vue3里,使用具名插槽时尽量使用 v-slot:slotName 的形式。