Vue3 新特性、组合式API、新的组件(笔记)

1. Vue3带来了什么

1.1 性能的提升
  • 打包大小减少41%
  • 初次渲染快55%, 更新渲染快133%
  • 内存减少54%
1.2 源码的升级
  • 使用 Proxy 代替 defineProperty 实现响应式
  • 重写虚拟 DOM、diff 算法的实现
  • 引入 Tree-Shaking 的技术
    • 如果没有引入 KeepAlive、Transition 等组件,打包时则不会去引入
1.3 支持TypeScript
  • Vue3 可以更好的支持 TypeScript
1.4 新的特性
  1. Composition API(组合API)

    • setup 配置
    • ref 与 reactive
    • watch 与 watchEffect
    • provide 与 inject
  2. 新的内置组件

    • Fragment
    • Teleport
    • Suspense
  3. 其他改变

    • 新的生命周期钩子
    • data 选项应始终被声明为一个函数
    • 移除keyCode支持作为 v-on 的修饰符

2. 常用的 Composition(组合式) API

2.1 setup函数
  • 在 Vue3 中组件用到的数据、方法等,都要配置在 setup 函数中

  • setup 的两种返回值

    • 如果返回的是一个对象,则对象中的属性和方法在模板中才可以使用
    • 如果返回的是一个渲染函数,则可以自定义渲染内容
  • setup 的参数

    • props:父组件传过来的值
    • context:上下文对象
  • setup 在 beforeCreate 前执行一次,this 是 undefined,所以 Vue2 中的配置项可以访问到 setup 中的数据,而 setup 不能通过 this 访问 Vue2 中的配置项,但是不建议混合使用

    // 当返回值为一个渲染函数时,视图由渲染函数中的内容承包
    import { h } from "vue";
    
    export default {
      setup() {
        return () => h("h1", "哈哈");
      }
    }
    
    // 定义的数据和方法,需要以对象的形式去返回
    export default {
      setup() {
        let num = 123;
        let str = "123";
          
        function fn() {
          console.log("我是一个方法");
        }
        return {
          num,
          str,
          fn,
        };
      }
    }
    
  • 当 setup 为 async 函数时,需要配合 Suspense 和异步组件使用

2.2 ref函数
  • 作用:
    • 定义一个响应式数据
  • 创建响应式数据:
    • 当定义一个基本数据类型的响应式时,使用的是 Object.defineProperty
    • 当定义一个引用数据类型的响应式时,调用了 reactive 函数
  • 获取
    • 在 js 中需要以 .value 的形式去获取
    • 在模板中直接获取
  • 用法:
    import { ref } from "vue";
    
    export default {
      setup() {
        let num = ref(123);
    
        // 触发 fn 函数,改变 num 的值
        function fn() {
          num.value = 234;
        }
        return {
          num,
          fn,
        };
      }
    }
    
2.2.1 在模板上使用
<input ref="input">

<script setup>
import { ref } from 'vue'

// 必须和模板里的 ref 同名,就可以拿到 input DOM 对象
const input = ref()
</script>
2.2.2 在组件上使用
<myComponent ref="myComponent">

<script setup>
import { ref } from 'vue'

// 组件实例对象,不过在子组件中需要使用 expose 保留对应的属性,父组件才可以访问
const myComponent = ref()
</script>
2.2.3 函数模板引用
<input :ref="(el) => { el:当前元素 }">
2.3 reactive函数
  • 作用:

    • 定义一个响应式数据
  • 创建响应式数据:

    • 不能定义基本数据类型
    • 当定义一个引用数据类型的响应式时,使用的是 Proxy 和 Reflect
  • 获取:

    • 在 js 中直接获取
    • 在模板中直接获取
  • 用法:

    import { reactive } from "vue";
    
    export default {
      setup() {
        // p 的所有属性都是响应式的
        let p = reactive({
          name: "小红",
          age: 18,
        });
    
        function fn() {
          p.name = "小白";
          p.age = 20;
        }
        return {
          p,
          fn,
        };
      }
    }
    
  • ref 与 reactive 的区别:

    • ref 定义数据后,如果在 js 中获取需要 .value,基本引用数据类型都可以定义
    • reactive 定义数据后,可以直接获取,只能定义引用数据类型
    • 最好使用 ref 定义基本数据类型,使用 reactive 定义引用数据类型
2.4 计算属性与侦听器
2.4.1 computed函数

与 Vue2 中的计算属性使用方法一致

import { computed } from 'vue'

export default {
  setup(){
   	// 简写
    let MyName = computed(() => {
      return;
    });
    
    // 完整写法
    let MyName1 = computed({
      set(value) {},
      get() {
        return;
      },
    });
    return {
      MyName,
      MyName1,
    };
  }
}
2.4.2 watch函数

与 Vue2 中的侦听器一样,但是采用的响应式不同,会分很多种情况

// 基本使用,watch 可以监视 getter/effect 函数、ref、reactive、或这些类型的数组。
watch(观察的数据,数据发生变化的回调,{配置项,可选});
  1. 情况一:监视 ref 定义的响应式数据

    // 当观察 ref 响应式数据时,不需要 .value
    import { ref, watch } from "vue";
    
    let num = ref(123);
    
    watch(num, (newValue, oldValue) => {
      console.log("值发生了改变", newValue, oldValue);
    });
    
  2. 情况二:监视 reactive 定义的响应式数据

    // 引用数据类型无法获取正确的 oldValue,Vue2 也会有这个问题
    // Proxy 默认开启了深度监视,关不掉
    import { reactive, watch } from "vue";
    
    let obj = reactive({
      a: {
        b: {
          c: {
            d: 1,
          },
        },
      },
    });
    
    watch(obj, (newValue, oldValue) => {
        // 最里面的 d 不论怎么改变,watch 都可以检测到
        console.log("值发生了改变", newValue, oldValue);
      }
    );
    
  3. 情况三:监视 getter 函数

    // 需要记住一点,getter 函数是监视响应式对象中的某一个值,不是响应式的监视不到
    // 如果返回值为引用数据类型,深度监视需要手动开启
    import { ref, reactive, watch } from "vue";
    
    // 情况一
    let num = ref(1)
    // 当 num 的值变化时可以监视到到
    watch(() => num.value, (v) => {
      console.log(v)
    })
    
    // 情况二
    let obj = reactive({
      a: {
        b: 1,
      },
    });
    
    // 1. 修改 obj 中的某一个值时可以监视到
    // 2. 需要手动开启 deep
    // 3. 不能修改 obj 的地址
    watch(() => obj, (newValue, oldValue) => {
      console.log("值发生了改变", newValue, oldValue);
      },
      { deep: true }
    );
    
    // 情况三
    let obj = {}
    
    // 这种情况就废了,监视不到
    watch(() => obj, (newValue, oldValue) => {
        console.log("值发生了改变", newValue, oldValue);
      },
      { deep: true }
    );
    
  4. 情况四:几种方式配合使用

    import { ref, watch, reactive } from "vue";
    
    let num = ref(123);
    let str = ref("123");
    let obj = reactive({
      a: {
        b: 1,
      },
    });
    
    watch([num, () => str.value, obj],
      (newValue, oldValue) => {
        // 其中一个值发生了改变,就会被检测到,会返回一个数组
        console.log("值发生了改变", newValue, oldValue);
      },
      { deep: true }
    );
    

总结:

  • 一般使用 ref 定义基本数据类型的响应式,可以直接监视,并不会存在任何问题
  • 使用 reactive 定义引用数据类型的响应式,监视默认会开启深度监视( 无法通过配置去关闭 )。此时 newValue 和 oldValue 的值一样,Vue2 也会有一个问题,可能是因为浏览器引用地址相同,展示数据也会相同
  • 使用 getter 函数,具体看上面情况三的案例
  • 数组可以监视多个数据,当其中一个数据发生改变时,都会被监视到并返回一个数组,并且数组中的每一项,必须为可监视数据
2.4.3 watchEffect函数
// watchEffect 中用到哪个响应式属性,那么就会监视那个响应式属性,并且也是深度监视,默认会执行一次
import { ref, watchEffect } from "vue"

let num = ref(10)
let str = ref("10")

watchEffect((beforeFn) => {
  let newNum = num.value
  let newStr = str.value
  console.log("发生了改变")

  beforeFn(() => {
	console.log("我会先执行")
  })
})
2.4.4 回调触发时机
// 当响应式状态值发生改变时,是先触发了侦听器,后触发的组件更新
// 下面这两种方式,可以获取到最新的 DOM,也就是在组件更新后触发
// flush:默认 pre 组件更新之前调用、sync 同步执行,post 组件更新之后执行
watch(source, callback, {
  flush: 'post'
})

watchEffect(callback, {
  flush: 'post'
})

// 也可以使用这种方式
import { watchPostEffect } from 'vue'

watchPostEffect(() => {
  /* 在 Vue 更新后执行 */
})
2.4.5 取消侦听器
// 和 Vue2 中全局使用侦听器是一样的
let unwatch = watch(source, callback)
2.5 自定义hook
  • hook 本质是一个函数,把 setup 函数中使用的 Composition API 进行了封装。

  • 类似于 vue2 中的mixin

    // 说白了就是函数的封装、复用
    // hooks/useXxx.js 文件
    export default function (){
      // 一些公共的逻辑
      return xxx
    }
    
    
    // 组件文件
    import useXxx from "@/hooks/useXxx.js"
    
    setup(){
      let xxx = useXxx()
    }
    
2.6 toRef与toRefs
  • toRef 为一个响应式对象的某个属性新建一个 ref,使这两个数据相连接(如果不是响应式数据,则失效)

    // toRef 用法
    const state = reactive({
      foo: 1,
      bar: 2
    })
    
    // 此时 fooRef 和 state.foo 相连接,并且都是响应式的
    const fooRef = toRef(state, 'foo')
    
    fooRef.value++
    console.log(state.foo) // 2
    
    state.foo++
    console.log(fooRef.value) // 3
    
  • toRefs 为一个响应式对象的每一条属性新建一个 ref

    // toRefs 用法
    const state = reactive({
      foo: 1,
      bar: 2
    })
    
    // 此时 stateAsRefs 与 state 相连接
    const stateAsRefs = toRefs(state)
    
2.7 Provide与Inject
  • 作用:实现祖孙组件间通信

  • 用法:

    // 祖先组件
    import { provide } from "vue"
    
    setup(){
      let data = "我是要传入的数据"
      provide('msg', data)
    }
    
    // 后代组件
    import { inject } from "vue"
    
    setup(){
      inject('msg')
    }
    

3. 其他组合式API

3.1 shallowRef与shallowReactive
  • shallowRef:只考虑基本数据类型的响应式,引用数据类型只有当地址发生改变时才会响应
  • shallowReactive:对于引用数据类型,只对数据的第一层进行响应式
  • 用法:和 ref/reactive 函数用法一样

注意:当数据不是响应式时,数据会发生改变,但是视图不会被更新,如果使用其他的方法去更新视图,那么视图也可以拿到 shallowReactive/shallowRef 中最新的数据

3.2 readonly与shallowReadonly
  • readonly:让一个响应式数据变为只读的,所有的数据都为只读
  • shallowReadonly:让一个响应式数据变为只读的,只是第一层的数据只读
  • 用法:调用 readonly/shallowReadonly 函数,返回一个新的只读属性
  • 应用场景:不希望数据被修改时
3.3 toRaw与markRaw
  • toRaw:
    • 作用:将一个由 reactive 定义的响应式对象,变为普通对象
    • 用法:调用 toRaw 函数,返回一个新的普通对象
  • markRaw:
    • 作用:标记一个对象,使其永远不会再成为响应式对象
    • 用法:调用 markRaw 函数,为原对象添加一个 __v_skip 为 true 的属性
3.4 customRef
  • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制

  • 实现一个防抖效果:

    <template>
      <input type="text" v-model="keyword" />
      <h3>{{ keyword }}</h3>
    </template>
    
    <script>
    import { customRef } from "vue";
    export default {
      name: "Demo",
      setup() {
        //自定义一个 myRef
        function myRef(value, delay) {
          let timer;
          return customRef((track, trigger) => {
            return {
              get() {
                track(); // 允许添加依赖
                return value;
              },
              set(newValue) {
                clearTimeout(timer);
                timer = setTimeout(() => {
                  value = newValue;
                  trigger(); // 触发依赖更新视图
                }, delay);
              },
            };
          });
        }
        let keyword = myRef("hello", 500);
        return {
          keyword,
        };
      },
    };
    </script>
    
3.5 triggerRef
  • 强制通过数据更新视图
3.6 响应式数据的判断
  • isRef:检查一个值是否为一个 ref 对象
  • isReactive:检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly:检查一个对象是否是由 readonly 创建的只读代理
  • isProxy:检查一个对象是否是由 reactive 或者 readonly 方法创建的代理,readonly 也是经过 Proxy 进行的代理,这不过它是为只读的代理
  • 返回值:都为布尔值

4. 新的组件

4.1 Fragment
  • 在 Vue2 中:组件必须有一个根标签
  • 在 Vue3 中:如果组件没有根标签,那么默认会包裹在一个 Fragment 虚拟元素里
  • 好处:减少标签层级,减小内存占用
4.2 teleport
  • 作用:能够将组件的html结构移动到指定位置

  • 用法:

    <!-- 
      to:把 teleport 标签中的内容,移动到 to 中指定的内容(追加)
      disabled:是否禁用移动
    -->
    <teleport to="标签名/class类名/id名">
      <div>
        <h1>我是要被移动的内容</h1>
      </div>
    </teleport>
    
4.3 Suspense(实验性)
  • 当组件异步引入时,可以和 Suspense 配合 loading 展示

  • 用法:生产环境请勿使用

    • 异步引入组件

      // 创建一个只有在需要时才会加载的异步组件(性能优化:在打包时不会打包到主文件,会单独打包到一个js文件)
      import { defineAsyncComponent } from 'vue'
      const Child = defineAsyncComponent(() => import('./components/Child.vue'))
      
    • 使用 Suspense 包裹组件,并配置好 default 与 fallback

      <template>
        <div class="app">
      	<h3>我是App组件</h3>
      	<Suspense>
      	  <template #default>
              <!-- 当 Child 组件渲染好时展示 -->
      		<Child/>
      	  </template>
      	  <template #fallback>
      		<!-- 当 Child 组件未渲染好时展示 -->
      		<h3>加载中.....</h3>
      	  </template>
      	</Suspense>
        </div>
      </template>
      

5. 自定义指令的修改

// 全局自定义指令
const app = createApp({})
// 使 v-focus 在所有组件中都可用
app.directive('focus', {
  /* ... */
})

// vue3 的自定义指令,在 setup 中默认使用 vXxx 命名的变量默认可以当做自定义指令使用
const vDirective = { // 指令生命周期的更改,全局的方式一样
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
}

6. 优化 Vdom

  • 在 vue2 中,每次更新 diff 都是全量对比,vue3 则只对比有标记的,大大减少了非动态内容的对比消耗
  • patch flag 在生成虚拟 dom 时,对使用变量的节点打上标记,更新时只需要对比这些打标记的节点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值