关于Vue 3.0

配合李江南的Vue3.0学习效果较好
视频地址:``https://www.bilibili.com/video/BV14k4y117LL?share_source=copy_web

前言:

  • 正式版:Vue团队于2020.09.18日发布3.0正式版
  • 前置条件:Vue虽然保留了大量的2.0版本api,但由于是使用TypeScript重构,所以想要学习3.0起码要掌握TS的基础使用

    Vue3.0中的六大亮点

    | 序号 | 特性 | 解析 | | :—-: | :—-: | :—-: | | 1 | Performance | 性能上比Vue2.0快1.3~2倍 | | 2 | Tree shaking support | 按需编译,体积更加轻量化 | | 3 | Composition API | 组合API,可参考React hooks理解 | | 4 | Better TypeScript support | 对 Ts 提供了更好的支持 | | 5 | Custom Renderer API | 暴露了自定义渲染API | | 6 | Fragment,Teleport(Protal),Suspense | 更先进的组件 |

Vue3.0是基于什么优化,如何做到更轻量,更快的?

  • 一 、diff 算法优化
    • Vue 2中的虚拟Dom是全量比较
    • Vue 3新增静态标记(PatchFlag)
    • 在与数据变化后,与上次虚拟DOM节点比较时,只比较带有PatchFlag标记的节点
    • 并且可以从flag信息中得知具体需要比较的内容。

      静态标记就是非全量比较,只会比较那些被标记的变量,比较的数量大大减少因此提升性能 这让我想到了JS垃圾回收机制里的标记清除,ORZ 感觉熟悉,但回收机是全标记只是清除具有离开环境的标记变量而已) 内存垃圾回收机制在我去年的博文中👉点击

比如下面这个示例

  1. <div>
  2. <a>土豆哇~ </a>
  3. <p>静态文本</p>
  4. <p>{{msg}}</p>
  5. </div>
  1. export function render(_ctx, _cache, $props, $setup, $data, $options) {
  2. return (_openBlock(), _createBlock("div", null, [
  3. _createVNode("a", null, " 土豆哇~ "),
  4. _createVNode("p", null, "静态文本"),
  5. _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* text文本在这里标记为1 */)
  6. ]))
  7. }
  • 由以上可得知:

    • 在vue2.0中对于数据变化后重新渲染的DOM树,会与上次渲染的DOM树逐个比较节点
    • 在vue3.0的diff中,创建虚拟DOM时,会根据该DOM是否会变化而添加静态标记,数据更新需要生成新的虚拟DOM时,只会与上次渲染的且被标记的节点比较。
    • 不同的动态变化类型,为了便于区分,标记的数值也不同
    • 因此在vue3.0中比较次数更少,效率更高,速度更快。

      示例

      1. export function render(_ctx, _cache, $props, $setup, $data, $options) {
      2. return (_openBlock(), _createBlock("div", null, [
      3. _createVNode("a", { id: _ctx.Poo }, " 土豆哇~ ", 8 /* PROPS */, ["id"]),
      4. _createVNode("p", { class: _ctx.style }, " 静态文本", 2 /* CLASS */),
      5. _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
      6. ]))
      7. }

      **

      标记查询列表

      **
      1. TEXT = 1,// --取值是1---表示具有动态textContent的元素
      2. CLASS = 1 << 1, // --取值是2---表示有动态Class的元素
      3. STYLE = 1 << 2, // --取值是4---表示动态样式(静态如style="color: pink",也会提升至动态)
      4. PROPS = 1 << 3, // --取值是8--- 表示具有非类/样式动态道具的元素。
      5. FULL_PROPS = 1 << 4, // --取值是16---表示带有动态键的道具的元素,与上面三种相斥
      6. HYDRATE_EVENTS = 1 << 5, // --取值是32---表示带有事件监听器的元素
      7. STABLE_FRAGMENT = 1 << 6, // --取值是64---表示其子顺序不变,不会改变自顺序的片段。
      8. KEYED_FRAGMENT = 1 << 7, // --取值是128---表示带有键控或部分键控子元素的片段。
      9. UNKEYED_FRAGMENT = 1 << 8, // --取值是256---子节点无key绑定的片段(fragment)
      10. NEED_PATCH = 1 << 9, // --取值是512---表示只需要非属性补丁的元素,例如ref或hooks
      11. DYNAMIC_SLOTS = 1 << 10, // --取值是1024---表示具有动态插槽的元素
  • 二 、hoistStatic 静态提升

    • vue2.0中,在更新时,元素即使没有变化,也会重新创建进行渲染
    • vue3.0中,不参与更新的元素;会静态提升,只创建一次下次渲染直接复用。
    • 因此在vue3.0中复用更多,创建次数更少,速度更快。见下方示例:
  1. <div>
  2. <a>土豆哇~ </a>
  3. <p>静态文本</p>
  4. <p>{{msg}}</p>
  5. <a href='https://vue-next-template-explorer.netlify.app/'>vue3.0编译地址</a>
  6. </div>
  1. /**
  2. * 在下方编译中(在options中勾选hoistStatic)进行静态提升,
  3. * 可以清晰看到不更新元素未参与重新创建
  4. */
  5. const _hoisted_1 = /*#__PURE__*/_createVNode("a", null, "土豆哇~ ", -1 /* HOISTED */)
  6. export function render(_ctx, _cache, $props, $setup, $data, $options) {
  7. return (_openBlock(), _createBlock("div", null, [
  8. _hoisted_1,
  9. _createVNode("p", { style: _ctx.myStyle }, "静态文本", 4 /* STYLE */),
  10. _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
  11. _createVNode("a", {
  12. style: _ctx.myStyle,
  13. href: "https://vue-next-template-explorer.netlify.app/"
  14. }, "vue3.0编译地址", 4 /* STYLE */)
  15. ]))
  16. }
  17. }
  • 三、cachehandlers 事件侦听缓存

    • onClick默认视为动态绑定,因此会追踪它的变化
    • 事件绑定的函数为同一个,因此不追踪它的变化,直接缓存后进行复用
    • 同样的,我在编译中进行演示
      1. <div>
      2. <button @click='Pooo'>按钮</button>
      3. </div>
      1. /**
      2. * 开启事件侦听缓存前:
      3. * 下方为常规编译后,可以看到静态标记为8
      4. * 既然有静态标记,那么它就会进行比较
      5. */
      6. export function render(_ctx, _cache, $props, $setup, $data, $options) {
      7. return (_openBlock(), _createBlock("div", null, [
      8. _createVNode("button", { onClick: _ctx.Pooo }, "按钮", 8 /* PROPS */, ["onClick"])
      9. ]))
      10. }

      **

      然后我在options中打开事件侦听缓存(cachehandlers)

      **
      1. /**
      2. * 可以发现打开侦听缓存后,没有静态标记
      3. * 在diff算法中,没有静态标记的是不会进行比较和进行追踪的
      4. */
      5. export function render(_ctx, _cache, $props, $setup, $data, $options) {
      6. return (_openBlock(), _createBlock("div", null, [
      7. _createVNode("button", {
      8. onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.Pooo(...args)))
      9. }, "按钮")
      10. ]))
      11. }
      12. 复制代码

      **

      使用vue3.0提供的Vite快速创建项目

  • Vite是Vue作者开发的一款意图取代webpack的工具

  • 原理是利用ES6的import发送请求加载文件的特性,进而拦截,然后做预编译,省去webpack冗长的打包
  • 使用步骤

    • 安装Vite命令: npm install -g create-vite-app
    • 创建Vue3项目:create-vite-app PoooName
    • 安装依赖:cd PoooName / npm install / npm run dev
    • Vue3.0中兼容2.0的写法,具体代码在此文件同级的PoooName项目文件中

      **

      vue3.0中的 reactive 用法

  • 在2.0中对于业务实现

  • 需要先在data中变更补充数据,然后在methodswatch中补充业务逻辑
  • 这样数据和逻辑是分模块的,查找不便,不利于业务的管理和维护
  • 为解决这样的问题,Vue3.0中加入了 reactive
  • Vue3.0提供了setup 组合API的入口函数,可以把数据和业务逻辑组合在一起
  1. import { reactive } from "vue"; //在Vue3.0使用中需要引入reactive
  2. export default {
  3. name: "App",
  4. //Vue3.0提供了setup 组合API的入口函数
  5. setup() {
  6. /**
  7. * ref一般用来监听简单类型变化(也可以用来监听复杂类型变化,先不讨论)
  8. * 通常使用reactive用来监听复杂类型变化(比如数组、函数之类)
  9. * 以下为一种常规的写法
  10. */
  11. let stus = reactive({ stusList: [****its data****], });
  12. function removeVeget(index) {
  13. stus.stusList.splice(index, 1);
  14. }
  15. return { stus, removeVeget };// 必须暴露出去,组件中才可以使用
  16. },
  17. methods: {},
  18. };
  • 另一种更加优雅的写法,也是非常非常推荐的写法是 ```javascript import { reactive } from “vue”; export default { name: “App”, setup() { let {stus, removeVeget }=removeItem();// 三、直接声明、获取 return { stus, removeVeget };//四、暴露给外界组件使用 }, methods: {}, }; /**
    • 保证数据和业务不分散利于更新维护
    • 也避免了setup中的大量数据函数填充
    • 也不需要使用this指向Vue实例 / function removeItem() { let stus = reactive({ stusList: [*its data], }); function removeVeget(index) { stus.stusList.splice(index, 1); } return {stus,removeVeget} // 二、暴露给组合API使用 }
  1. - 功能分离:
  2. - 乍一看上方把函数整合到下方,然后在`setup`中引用是很简洁
  3. - 若需要的业务功能多了呢,比如增加个`updateItem`,`addItem`
  4. - 虽然数据和逻辑代码还是在一块,但是各种功能聚集在一块还是显得文件臃肿
  5. - 那么还要继续优化,分离各个功能
  6. 1. 新建一个单独的JS文件,如remove.js
  7. 2. APP文件中引入这个JS文件
  8. 3. 这样就可以在单独的JS文件中对某个功能进行维护了
  9. ```javascript
  10. import { reactive } from "vue"; //引入依赖
  11. function removeItem() {//定义函数,实现功能
  12. let stus = reactive({
  13. stusList: [
  14. { id: 1, Name: "potato", price: "2.5" },
  15. { id: 2, Name: "tomato", price: "3.5" },
  16. { id: 3, Name: "cucumber", price: "4.5" },
  17. ],
  18. });
  19. function removeVeget(index) {
  20. stus.stusList.splice(index, 1);
  21. }
  22. return {stus,removeVeget}
  23. }
  24. export {removeItem};//暴露给外界使用
  1. /*那么主文件就变成了如下形式(单独JS文件中已经引入reactive)*/
  2. import { removeItem } from "./remove"; //导入删除的业务逻辑模块
  3. export default {
  4. name: "App",
  5. setup() {
  6. let { stus, removeVeget } = removeItem();
  7. return { stus, removeVeget };
  8. },
  9. methods: {},
  10. };
  11. 复制代码

**

vue3.0中的 Composition API本质

  • Option API:即在APP中为实现业务逻辑进行的配置

    • 在2.0中比如你要实现一个点击按钮,弹出提示语功能,你需要
      1. 利用 Opaction API
      2. data 中配置数据
      3. methods 中配置相应函数
    • 在3.0中通过上方 reactive 的知识点我们知道,实现这个功能,你需要
      1. 利用 Composition API
      2. setup 中定义数据,编写函数
      3. 通过 return{ 数据,方法}暴露出去
    • 其实 Composition (也叫注入API)本质是在运行时
      1. 把暴露出来的数据注入到 opaction 中的 data
      2. 把暴露出来的函数注入到 opaction 中的 methods

        注: 具体它咋区分数据还是函数用以注入到相应配置中的,我也不知道( 标志位or传参顺序?

  • 小结:

    • Opaction API中对配置项都进行了规定,比如:
      • 在data中配置数据,methods中编写方法,watch中进行监听。
      • 保姆式的分配较为清晰,但也对代码层层分割,维护要扒拉半天跳来跳
    • Composition中更加自由,比如:
      • 不用担心各种this指向
      • 随意进行模块分割导出,维护时查找固定模块文件

        生命周期中的 setup

        | 序列 | 选项式 API | Hook inside setup | | —- | —- | —- | | 1 | beforeCreate | Not needed | | 2 | created | Not needed | | 3 | Composition API | onBeforeMount | | 4 | mounted | onMounted | | 5 | .... | …. |
  • setup的执行时机在beforeCreatecreated之间,还是beforeCreate之前?

    官方文档原话:因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

    • 官网地址:v3.cn.vuejs.org/guide/compo…
    • 依据官网描述及下方2、3点推断setup肯定是在created之前
      • 还有一种是说法是在beforeCreate之前,但我不是很能理解
      • 根据原文beforeCreate中的任何代码可以直接在setup中编写
      • 我更倾向于二者是 ‘平级’ 的关系,也欢迎各位帮忙解答
    • 在Vue生命周期中我们知道:
      1. beforeCreate时,刚初始化一个空 Vue实例对象, datamethods中数据 未初始化
      2. created执行时,data和methods已经初始化完毕
      3. Composition 需要把setup中的数据对应注入到 datamethods中去
    • 4.很显然setup必须要在 created之前执行
    • 也因此,若你在Vue3.0中进行混合开发,不可以在 setup中使用 data中的数据和 methods中的方法
    • 在3.0中setup里的this也被修改为了undefiend
    • 在3.0中setup里也不可以使用异步
    • (下方贴的图我也加入了旁释,可帮助你回想下生命周期)🐵Vue3.0入门 - 图1

      **

      什么是 reactive

  • reactiveVUE3.0中提供的实现响应式数据的方法

  • 在Vue2.0中使用的是defineProperty来实现的(我自己也手动实现过👉 点击
  • VUE3.0中使用的是ES6里的proxy实现的
  • reactive中需要注意的点:

    • 传递给它的类型必须是对象(JSON或者arr数组)
    • 并且它会自动把传递进来条件再赋值给Proxy对象
    • 若传递的为上述以外的对象
      1. 在方法中直接修改它,界面上它也不会自动更新
      2. 若想更新只能通过重新赋值的方式
        1. /*示例如下*/
        2. setup() {
        3. let testJson=reactive({
        4. tip:'its a Json!'
        5. })
        6. let testArray=reactive(['first','second','third'])
        7. let testString=reactive('Just a string')
        8. function showProxyPar(){
        9. testJson.tip='changed';
        10. testArray[2]='selected';
        11. testString='hengxipeng';
        12. console.log(testJson);// Proxy {tip: "changed"}
        13. console.log(testArray);// Proxy {0: "first", 1: "second", 2: "selected"}
        14. console.log(testString);// hengxipeng
        15. }
        16. return { testJson,testArray,testString,showProxyPar };
        17. },
  • 效果正如下图所示,符合传递条件的参数会再赋值给Proxy,并且修改它也会直接影响视图

🐵Vue3.0入门 - 图2

什么是 ref

  • 它也是实现响应式数据的方法
  • reactivce向来都是进行传递对象,实际开发中若只想更改某简单变量则会显得大材小用
  • 所以vue3提供了ref方法,来实现对简单值的监听
  • ref本质也是使用reactive,给ref的值,它底层会自动转化

    1. /**
    2. * 实质是 ref('its a string')==>reactive({value:'its a string'})
    3. * 也因此更改时应该 testRef.value=XX 才能更改
    4. * (视图使用时不必加 value ,Vue会自动进行添加)
    5. */
    6. setup() {
    7. let testRef = ref('its a string');
    8. function showProxyPar() {
    9. testRef.value='ref_string'
    10. console.log(testRef);
    11. }
    12. return { testRef, showProxyPar };
    13. },
  • 如下图

🐵Vue3.0入门 - 图3

**

ref 和 reactive 之间的不同

  • 通过以上得知,使用ref其实相当于使用reactive,只是省略了手动创建对象的步骤
  • ref中底层会添加一个value的键,并且在视图中可省略调用value

    • 经过我自己测试
      1. 使用reactive,创建一个键值为valueJson对象,验证是否可省略value调用(不可以
      2. 得知,只有使用ref传递参数时,视图才允许省略value调用 ```javascript /**
    • Vue在解析时,通过 __v_isRef 来判定当前参数是否由 ref 传递出来的
    • 是的话,则会自动在调用当前参数时添加 value */ __v_isRef: true _rawValue: “its a string” _shallow: false _value: “its a string” value: “its a string” ```
  • 其中Vue3.0中提供了两个方法,isReactiveisRef用来判定数据来源

    1. import {isRef,isReactive } from "vue";
    2. setup() {
    3. let testReactive = reactive({value:'its a string'});
    4. let testRef = ref('its a string');
    5. function showProxyPar() {
    6. console.log('检测是否是Ref',isRef(testReactive));// false
    7. console.log('检测是否是Ref',isRef(testRef));// true
    8. }
    9. return { testRef,testReactive, showProxyPar };
    10. }

    **

    递归监听

  • 通常情况下refreactive都会监听数据变化

    验证如下,点击按钮触发recursion 页面显示都会改变

  1. //验证ref 只需添加value即可,如: parse.value.type='fruit';
  2. setup() {
  3. let parse = reactive({
  4. type: "vegetables",
  5. suchAS: {
  6. name: "tomato",
  7. info: {
  8. price: "0.4元/kg",
  9. size: {
  10. big: "50g",
  11. small: "20g",
  12. },
  13. },
  14. },
  15. });
  16. function recursion() {
  17. parse.type='fruit';
  18. parse.suchAS.name='cucumber';
  19. parse.suchAS.info.price='0.8元/kg';
  20. parse.suchAS.info.size.small='70g';
  21. parse.suchAS.info.size.big='90g';
  22. }
  23. return { parse,recursion };
  24. },
  • 数据量较大时非常消耗性能

    • 在之前< 什么是 reactive >中我们知道:
      • reactiveref通过递归取出参数中所有值,包装为proxy对象
      • 递归的优与劣我总结过,涉及压栈和弹出等,强烈建议回顾下👉点击

        **

        非递归监听

  • 上面知道了递归监听上的种种劣势,而Vue3.0也提供了解决方案

    • 非递归监听,即:只能监听数据的第一层。方案如下:
      1. 引入Vue3.0中提供的shallowReactive
      2. 改用 shallowReactive({})传递参数
      3. 观察发现,控制台只有第一层转为了proxy对象
    • 🐵Vue3.0入门 - 图4
    • 而对于ref对应的shallowRef非递归监听则比较特殊
      1. 首先试引入Vue3.0中官方提供的shallowRef
      2. 原理上与reactive相同,只是它并不会监听JSON第一层数据
      3. 而是要直接修改value的值,这样视图才会同步更新
        1. function recursion() {
        2. /** * shallowRef 对第一层修改不会监听,所以视图不变 */
        3. parse.value.type='fruit';
        4. parse.value.suchAS.name='cucumber';
        5. parse.value.suchAS.info.price='0.8元/kg';
        6. parse.value.suchAS.info.size.small='70g';
        7. parse.value.suchAS.info.size.big='90g';
        8. /** * 正确做法应该是整个修改 value */
        9. parse.value = {
        10. type: "fruit",
        11. suchAS: {
        12. name: "cucumber",
        13. info: {
        14. price: "0.8元/kg",
        15. size: {
        16. big: "70g",
        17. small: "90g",
        18. },},},};}

        注意点:虽然他们只对第一层进行了监听,但若恰巧每次都更改了第一层数据,则也会引起下方数据和视图的同步更新,此时shallowReactive或者shallowRef就和reactive、Ref效果一模一样!

**

数据监听补充

  • 通过以上这些知识点可知:

    • refreactive监听每一层数据,响应好但递归取值性能差。
    • shallowReactiveshallowRef监听第一层(或value),性能好但更新值较麻烦
    • shallowRef中,为了数据和视图一致,更新值要更新整个parse.value太繁琐
    • 场景:若我更新数据的第三层,不整个更新value行不行?
      1. 这就用到了Vue3.0ref准备的triggerRef(不用查啦 就一个)
      2. 作用:根据传入的数据,主动去更新视图
        • 老规矩,import {shallowRef, triggerRef } from "vue"
        • 改完非首层的数据,而你使用的是shallowRef还不想整个更新value
        • 使用triggerRef大法,传入整个对象,就好啦
        • (使用reactive传入的数据,无法触发triggerRef) ```javascript function recursion() { /**
        • 方法一、手动更新 parse.value = { type: “fruit”, suchAS: { name: “cucumber”, info: { price: “0.8元/kg”, size: { big: “70g”, small: “90g”, },},},}; / /** 方法而、使用 triggerRef */ parse.value.suchAS.info.price=’0.8元/kg’; triggerRef(parse) } ```

          数据监听方式选择

  • 正常数据量时,通常使用refreactive(递归监听)即可满足业务需要

  • 当数据量庞大且注重性能时,就需考虑shallowReactiveshallowRef了(非递归监听)


shallowRef底层原理**

  • 在看 ref 时,我们知道它的本质其实是 reactive({value:XX})
  • 那么 shallowRef 其实是 shallowReactive({value:XX})

    • 因为通过shallowRef 创建的数据,它监听的是 .value 的变化
      1. let state1=shallowRef({
      2. a:'a',
      3. b:{
      4. b_1:'b_1',
      5. b_2:'b_2'
      6. }
      7. })
      8. //--其实是如下所示
      9. let state2=shallowReactive({
      10. value:{
      11. a:'a',
      12. b:{
      13. b_1:'b_1',
      14. b_2:'b_2'
      15. }
      16. }
      17. })

      **

      toRaw

  • 在之前的知识体系中我们知道

    • setup 中定义参数对象,在函数中直接修改页面是不会同步更新。
    • 需要利用 Ref 或者 reactive 进行包装,这样修改才生效

      1. let obj={ name:'花花',age:'3'}
      2. let test=reactive(obj);
      3. function myFun() {test.name='乐乐';}
    • objtest是引用关系

    • reactive 会把传进来的参数包装为一个 porxy 对象并返回
    • 例子中 test 本质是一个porxy对象,而这个对象也引用了 obj
      • 那么请注意:
        • 直接修改obj或引用的 test 都会引起内存中数据变化
        • 但是修改 obj因为没有 proxy监听,所以视图不会更新
  • 说了那么多,再绕回来说toRaw

    • 作用:返回由 reactivereadonly 等方法转换成响应式代理的普通对象
    • 特点:toRaw 拿到的数据不会被监听变化,节省性能
    • 场景:数据更改不需更新视图,为提高性能,通过 toRaw 拿到数据修改
    • 提示:因为是原始数据,风险较大,一般不建议使用、
    • 注意:若想拿到的是Ref 创建的对象,记得加 value
      1. let obj={name:'花花',age:'3'}
      2. let testReactive=reactive(obj);
      3. let testRef=ref(obj);
      4. let rawReac=toRaw(testReactive);
      5. let rawRef=toRaw(testRef.value);
      6. console.log(rawReac===obj); //true
      7. console.log(rawRef===obj); //true

      **

      markRaw

  • 在之前的知识体系中我们知道

  • 作用:固定某数据,不追踪它值的变化,同时视图也不会更新
  • 通过控制台查看,使用markRaw的对象参数,被赋予v_skip监听跳过标识符

    1. let obj={
    2. name:'poo',
    3. age:'3'
    4. }
    5. console.log(obj);//{name: "poo", age: "3"}
    6. obj=markRaw(obj)//使其值的改变,不会被监听,视图不会发生变化
    7. let testReactive=reactive(obj);
    8. function myFun() {
    9. testReactive.name='地瓜';
    10. console.log(obj);//{name: "地瓜", age: "3", __v_skip: true}
    11. }

    **

    toRef

  • toRefref一样,同样也是创建响应式数据的

  • 先说结论:
    • ref 将对象中某属性变为响应式,修改时原数据不受影响
    • toRef 会改变原数据
    • 并且 toRef 创建的数据,改变时界面不会自动更新
  • 应用场景:性能优化

    • 想使创建的响应式数据与元数据关联起来
    • 更新响应式数据后,不想更新UI
      1. setup() {
      2. /** * toRef */
      3. let obj={ name:'poo' }
      4. let obj2={name:'boo'}
      5. //-注意:这里是让 toRef 知道是让 obj里的 name变成响应式
      6. let test_toRef=toRef(obj,'name');
      7. let test_ref=ref(obj2.name);
      8. console.log(test_toRef);
      9. function myFun() {
      10. test_toRef.value='土豆';
      11. test_ref.value='地瓜';
      12. console.log(obj,);// {name: "土豆"}
      13. console.log(obj2);// {name: "boo"}
      14. }
      15. return {obj,obj2, myFun };
      16. }

      **

      toRefs

  • toRef只能接受两个参数,当传递某对象多个属性值时会很麻烦

  • 结论:

    • toRefs 是避免 toRef 对多个属性操作繁琐
    • toRefs 底层原理是使用 toRef 方法遍历对象属性值 ```javascript setup() { let obj={ name:’poo’, age:’3’ } let test_toRefs=toRefs(obj); /**
    • 在 toRefs 底层中其实执行了以下遍历方法
    • let par1=toRef(obj,’name’)
    • let par2=toRef(obj,’age’) */ function myFun() { test_toRefs.name.value=’HAHA’; test_toRefs.age.value=’13’; } return {test_toRefs, myFun }; } ```

      **

      在 Vue3.0 中如何通过 ref 获取元素 ?

  • 在 Vue2.0版本内,通常使用 this.$refs.XX 获取元素

  • 在Vue3.0中,废除了类似$的很多符号,如何获取指定元素 ?
  • 根据Vue生命周期图中可知,要操作DOM,最早也要在mounted
  • 结论:

    • 1.setup 是在beforeCreate之前执行
    • 2.在生命周期中 onMounted最先准备好 DOM元素
    • 3.setup中想操纵 DOM 就在函数中引用 onMounted
    • 4.Vue3.0中生命周期函数被抽离,可根据需要引入相应周期函数
      1. setup() {
      2. let btn=ref(null);
      3. console.log(btn.value);
      4. // 回调函数和它在函数中顺序无关,根据 Vue 生命周期顺序执行
      5. onMounted(()=>{
      6. console.log(btn.value);//- <button>clickMe</button>
      7. })
      8. return {btn};
      9. },

      **

      readonly

  • Vue3.0中提供的这个API,使得数据被保护,只读不可修改

  • 默认所有层数据都只读,若只限制第一层只读,可使用shallowReadonly
  • isReadonly用来检测数据创建来源是否是 readonly
  • 若进行修改,浏览器会提示操作失败,目标只读

    1. setup() {
    2. let obj={
    3. name:'poo',
    4. age:'13'
    5. }
    6. let only=readonly(obj)
    7. function myFun() {
    8. only.name='HAHA';// failed: target is readonly
    9. }
    10. return {only, myFun };
    11. }

    **

    Vue3.0响应式数据本质

  • 2.0中使用的 Object.defineProperty 实现响应式数据

  • 3.0中使用的 Proxy 来实现,如下

    1. let obj={
    2. name:'poo',
    3. age:'13'
    4. }
    5. let objProxy=new Proxy(obj,{
    6. //数据读会触发
    7. get(obj,key){
    8. console.log(obj);//{name: "poo", age: "13"}
    9. return obj[key]
    10. },
    11. //监听的数据被修改会触发
    12. set(obj,key,value){
    13. // 操作的对象,操作的属性,赋予的新值
    14. obj[key]=value //把外界赋予的新值更新到该对象
    15. console.log('进行UI之类的操作');
    16. //-补充,有时会多次操作,此时必须return true才不会影响下次操作
    17. return true;
    18. }
    19. })
    20. objProxy.name;

    **

    实现shallowReactive和shallowRef

  • 它们二者也是通过参数传递,包装成 proxy 对象进行监听

  • Proxyset 监听中,同样只监听第一层
  • shallowRef 只是在 shallowReactive 基础上默认添加 value 键名

    1. function shallowReactive(obj){
    2. return new Proxy(obj,{
    3. get(obj,key){
    4. return obj[key]
    5. },
    6. set(obj,key,value){
    7. obj[key]=value
    8. console.log('更新');
    9. return true;
    10. }
    11. })
    12. }
    13. let obj={
    14. A:'A',
    15. B:{
    16. b1:'b1',
    17. b2:'b2',
    18. b3:{
    19. b3_1:'b3-1',
    20. b3_2:'b3-2'
    21. } } }
    22. let test=shallowReactive(obj)
    23. //-这里同样只会监听第一层
    24. test.A='apple';
    25. test.B.b2='banana';
    26. function shallowRef(obj){
    27. return shallowReactive(obj,{value:vl})
    28. }
    29. let state=shallowRef(obj);

    **

    实现 reactive 和 ref

  • 它们与上方区别在于递归监听

  • 上方因为直接传递参数对象,所以只监听第一层
  • 为了递归监听,那么要把数据的每一层都给包装成 Proxy对象
  1. function reactive(obj) {
  2. if (typeof obj === "object") {
  3. if (obj instanceof Array) {
  4. //当前参数为数组类型,则循环取出每一项
  5. obj.forEach((item, index) => {
  6. if (typeof item === "object") {
  7. //分析数组每一项,是对象则递归
  8. obj[index] = reactive(item);
  9. }
  10. });
  11. } else {
  12. // 当前参数是对象且不是数组,则取属性值并进行分析是否是多层对象
  13. for (let key in obj) {
  14. if (typeof obj[key] === "object") {
  15. obj[key] = reactive(item);
  16. }
  17. }
  18. }
  19. } else {
  20. console.log("当前传入为非对象参数");
  21. }
  22. //-正常情况下就进行 Proxy对象包装
  23. return new Proxy(obj, {
  24. get(obj, key) {
  25. return obj[key];
  26. },
  27. set(obj, key, value) {
  28. obj[key] = value;
  29. console.log("更新");
  30. return true;
  31. },
  32. });
  33. }

**

实现 shallowReadonly 和 readonly

  • 二者区别只在于首层监听,只读拒绝修改和数据全层修改
  • 下方实现的是 shallowReadonly
  • readonly 实现是在 shallowReadonly 基础上移除set 中的return true
    1. function shallowReadonly(obj) {
    2. return new Proxy(obj, {
    3. get(obj, key) {
    4. return obj[key];
    5. },
    6. set(obj, key, value) {
    7. // obj[key] = value;
    8. console.error(`${key}为只读,不可修改-`);
    9. return true;//此行移除,则就是 readonly 全层数据只读
    10. },
    11. });
    12. }
    13. let parse = {
    14. type: "fruit",
    15. suchAS: {
    16. name: "cucumber",
    17. },
    18. };
    19. let fakeShowRe=shallowReadonly(parse);
    20. fakeShowRe.type='HAHA';// 此时修改不会生效
    21. fakeShowRe.suchAS.name='HAHA';// 非首层修改会生效