Vue 第2章 简述 Vue3

一、Vue3

1、vite 构建工具

  • 优势如下:

    • 开发环境中,无需打包操作,可快速的冷启动。
    • 更加轻量快速的热重载(HMR)
    • 真正的按需编译,不再等待整个应用编译完成。
    • **没有打包操作,需要什么动态加载什么。
  • 项目构建过程

    npm init vite-app <project-name>
        
    cd <project-name>
        
    npm i 
    
    npm run dev
    

2、响应式原理

1)vue2

  • 实现原理

    • 对象类型:通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)。
    • 数组类型:对数组的变更方法进行了包裹。
  • 模拟实现

    // 无法实现新增属性、删除属性
    let person = {
        name:'张三'age:18
    }
    
    let p = {}
    
    Object.defineProperty( p,'name',{
        get(){
            return person.name
        },
        set(value){
            person.name = value
        }
    })
    
    Object.defineProperty( p,'age',{
        get(){
            return person.age
        },
        set(value){
            person.age = value
        }
    })
    
  • 存在问题

    • 新增属性、删除属性,界面不会更新;

      • 补充:新增属性

        this.$set(this.person,'sex','女')
        
      • 补充:删除属性

        this.$delete(this.person,'sex')
        
    • 直接通过下标修改数组,界面不会自动更新。

      • 补充:修改属性

        hobby = ['吃饭''学习','学习']
        
        this.$set(this.hobby,0,'学习')
        
        console.log(this.hobby)	// 学习,学习,学习
        

2)vue3

  • 实现原理

    • 通过 Proxy 代理:拦截对象中任意属性的变化,包括:属性值的读写、添加、删除等。
    • 通过 Reflect 反射:对被代理对象的属性进行操作
  • 模拟实现

    let person = {
        name:'张三'age:18
    }
    
    // 建立映射关系
    const p = new Proxy( person ,{
        //读取
        get(target,propName){
            return Reflect.get(target,propName)
        },
        //修改 新增
        set(target,propName,value){
            Reflect.get(target,propName,value)
        },
        //删除
        deleteProperty(target,propName){
            return Reflect.deleteProperty(target,propName)
        }
    })
    

3、生命周期

  • beforeMount 读不到 Dom 元素,返回值是 undefined
  • beforeUpdate 获取的是更新之前的 Dom 元素,updated 获取的是 更新之后的 Dom 元素。

1)Vue2

  1. 此阶段初始化 生命周期。
  2. beforeCreate
    1. 此时无法访问 data 中的数据和 method 中的方法。
  3. 此阶段进行数据监测与数据代理。
  4. created
    1. 可以访问到 data 中的数据和 method 中的方法。
  5. 此阶段解析模板,生成虚拟DOM,但是页面还不能显示解析好的内容。
  6. beforeMount
    1. 页面呈现的是未经 Vue 编译的DOM结构。
    2. 所有对DOM的操作,最终都不奏效。
  7. 此阶段将虚拟DOM转为真实DOM插入页面。
  8. mounted
    1. 页面呈现的是经过 Vue 编译的DOM结构。
    2. 所有对DOM的操作,均奏效。
    3. 一般在此进行初始化操作:开启定时器、发送网络请求、订阅消息、绑定自定义事件等。
  9. 此阶段数据发生改变。
  10. beforeUpdate
    1. 数据是新的,页面是旧的。
  11. 此阶段根据新数据生成新的虚拟DOM,随后与旧的虚拟DOM比较,最终完成页面更新。
  12. updated
    1. 数据是新的,页面也是新的。
  13. beforeDestroy
    1. 此时的 data、method 都处于可用状态,可以修改但页面不再更新。
    2. 一般在此进行收尾工作:关闭定时器、取消订阅消息、解绑自定义事件等。
  14. destroy
    1. 销毁后自定义事件会失效,但是原生DOM事件依旧有效。

2)Vue3

  • Vue3 使用 unmount 代替 destroy。
  • Vue3 可以在 setup 中使用组合式生命周期钩子,命名在钩子前面添加 “ on ”。
  • 组合式生命周期钩子的执行时机优先于配置项的钩子。

4、setup 函数-旧式写法

1)概述

  • Vue3的一个新的配置项,与 data、method 等同级,值是一个函数。

  • setup 执行时机:在 beforeCreate 之前执行一次,this 是 undefined。

  • 尽量不要与Vue2的配置混用

    • Vue2的配置,如 data、method、computed 可以访问 setup 的属性与方法;
    • 但是 setup 不能访问到 Vue2 的配置。
    • 如果重名,以 setup 优先。

2)参数

  • setup 的参数

    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收的属性。

      // 父组件
      <template>
         <Demo msg='hello' school='daan'> 
      </template>    
      
      // 子组件
      name:'Demo',
      props:['msg','school'],
      setup(props){
          
      }
      
    • context:上下文对象

      • attrs:值为对象,包含:组件外部传递过来,但没有在 Props 配置中声明的属性,相当于 this.$attrs

      • slots:收到的插槽内容,相当于 this.$slots

      • emit:分发自定义事件的函数,相当于 this.$emit

        // 父组件
        <template>
           <Demo @hello='sayHello' msg='hello' school='daan'> 
        </template>
        
        setup(){
            function sayHello(value){
                alert("你好啊",value)
            }
        }
        
        // 子组件
        name:'Demo',
        props:['msg','school'],
        eimts:['hello']
        setup(props,context){
            function test(){
                context.emit('hello',666)
            }
        }
        

5、Ref 全家桶

1)ref 函数

  • 支持所有类型;取值赋值都需要添加 .value。
(1)定义响应式对象
  • setup 本身不能实现响应式,需要借助 ref 函数,将变量封装成一个引用对象,并使用 引用对象.value 的方式修改变量的值。

  • 该函数的参数可以是基本类型,也可以是对象类型。

    • 基本类型:响应式依旧借助 object.defineProperty( ) 的 get 与 set 完成。

      <h1> {{ name }} </h1>
      
      <script setup lang=ts>
      // 需要导入 ref
      import {ref} from 'vue'
      
      let name = ref("zhangsan")
      
      const changeInfo() =>{
          name.value = 'lisi'
      }
      
    • 对象类型:内部 “求助” 了 Vue3 中的一个新函数,reactive 函数。

      let job = ref({
          type:'前端工程师',
          salary:30
      })
      
      function changeInfo(){
          job.value.type = "UI设计",
          job.value.salary = 60
      }
      
(2)获取DOM元素
  • ref 可以用于获取节点

    <div ref='h1'> </div>
    
    const change = () =>{
        let h1 = ref<HTMLDivElement>();
        log(h1.value?.innerText);
    }
    

2)isRef 函数

  • 判断参数是否为 ref实例对象

3)shallowRef 函数

  • 只处理基本数据类型的响应式,不进行对象的响应式处理。

  • 如果需要处理对象,则需要用新的对象来替换。

    <h1> {{ name }} </h1>
    
    <script setup lang=ts>
    // 需要导入 ref
    import { shallowRef } from 'vue'
    
    let obj = shallowRef({
        name:'zhanshan'
    })
    
    const changeInfo() =>{
        obj.value.name = 'lisi'		// 无法实现响应式
    }
    
    const changeInfo2() =>{
        obj.value = {
            name:'lisi'				// 可以实现响应式
        }
    }
    
  • shallowRef 函数不能与 ref 函数同时使用,否则 shallowRef 等效于 ref

    let obj1 = ref({
        name:'zhanshan'
    })
    
    let obj2 = shallowRef({
        name:'lisi'
    })
    
    // 此时,obj2 也会变成 深层次的响应式对象
    const changeInfo = () =>{
        obj1.value.name = 'obj1';
        obj2.value.name = 'obj2';
    }
    
  • 应用场景:如果有一个对象数据,后续功能不会修改该对象中的属性,而是新的对象来替换。

4)triggerRef 函数

  • ref = shallowRef + triggerRef

5)customRef 函数

  • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。

    <template>
    	<input type="text" v-model="keyword">
    	<h3>{{keyword}}</h3>
    </template>
    
    <script setup lang='ts'>
    import {customRef} from 'vue'
    
    //自定义一个myRef
    function myRef(value,delay){
    	let timer
    	//通过customRef去实现自定义
    	return customRef((track,trigger)=>{
    		return{
    			get(){
    				track() //告诉Vue这个value值是需要被“追踪”的
    				return value
    			},
    			set(newValue){
    				clearTimeout(timer)
    				timer = setTimeout(()=>{
    					value = newValue
    					trigger() //告诉Vue去更新界面
    				},delay)
    			}
    		}
    	})
    }
    let keyword = myRef('hello',500) //使用程序员自定义的ref
    
    </script>
    

6、reactive 全家桶

1)reactive 函数

  • reactive 函数的参数只能是引用类型,因为在源码中参数使用了泛型约束。

    reactive<T extends object>(target:T)
    
  • reactive 函数的参数可以是多层次的对象。

    import { reactive } from 'vue'
    
    let job = reactive({
        type:'前端工程师',
        salary:30,
        a:{
            b:{
                c:666
            }
        }
    })
    
    function changeInfo(){
        job.type = "UI设计",
        job.salary = 60,
        job.a.b.c = 999
    }
    
  • reactive 函数的参数也可以是数组,可以通过数组下标修改数据。

    import { reactive } from 'vue'
    
    let hobby = reactive(['抽烟','喝酒','烫头'])
    
    function changeInfo(){
        hobby[0] = '学习'
    }
    
  • reactive 实例对象不能直接赋值,否则破坏响应式对象。

    let list = reactive([])
    let res = ['A','B']
    list = res;				// 破坏响应式对象。
    list.push(...res);		// 有效赋值。
    

2)shallowReactive 函数

  • 只处理对象最外层属性的响应式(浅响应式)
  • 应用场景:如果有一个对象数据,结构比较深,但变化时只是外层属性变化。
  • shallowReactive 函数不能与 reactive 函数同时使用,否则 shallowReactive 等效于 reactive。
    • 原因:收集本次宏任务中的所有更改,最后主线程空闲,只会重新渲染一次。

3)readonly 函数

  • 让一个响应式数据变为只读的(深只读)

    let person = reactive({
        name:'zhan',
        age:18,
        job:{
            j1:'ptn'
        }
    })
    
    person = readonly( person )
    

4)shallowReadonly 函数

  • 让一个响应式数据变为只读的(浅只读)

    let person = reactive({
        name:'zhan',
        age:18,
        job:{
            j1:'ptn'
        }
    })
    
    person = shallowReadonly( person )
    

7、to 全家桶

1)toRef 函数

  • 创建一个 ref 对象,将响应式对象中的某个属性单独提供给外部使用。

    const man = reactive({ name:'zhan',age:23 })
    
    cont name = toRef(man,'name')
    
    const change = () =>{
        name.value = 'lisi'
    }
    
  • 非响应式对象用了 toRef 函数,视图毫无变化。

2)toRefs 函数

  • 将响应式对象的每个属性都单独提供给外部使用,实质是内部为每个属性调用了 toRef 函数。

    const man = reactive({ name:'zhan',age:23 })
    
    cont {name,age} = toRefs(man)
    
    const change = () =>{
        name.value = 'lisi';
        age.value = 30
    }
    

3)toRaw 函数

  • 作用:将一个由 reactive 生成的响应式对象转为普通对象。
  • 应用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。

4)markRaw 函数

  • 标记一个对象,使其永远不会再成为响应式对象。
  • 应用场景
    1. 有些值不应被设置为响应式,如复杂的第三方类库。
    2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

8、computed 计算属性

  • 依赖项的某一项发生改变时,就会触发重新计算。

    import { reactive,computed } from 'vue'
    
    let person = reactive({
        firstname:'zhan',
        lastname:'shan'
    })
    
    let fullname = computed(() => {
        return person.firstname + person.lastname
    })
    

9、watch 侦听器

1)参数

  • 参数1是监视的变量
  • 参数2是回调函数
  • 参数3是配置

2)ref 类型

  • 监视 ref 所定义的一个响应式数据

    let sum = ref(0)
    
    watch( sum,(newValue,oldValue)=>{
        ...
    },{
        immediate:true
    })
    
  • 监视 ref 所定义的多个响应式数据,此时 newValue 和 oldValue 也变成数组。

    watch( [ sum,msg ],(newValue,oldValue)=>{
        ...
    })
    
  • 监听 ref 类型的数据只是浅层次的,可以开启深度监听,但是深度监听后返回的新旧值是一样的。

    let sum = ref({
        foo:{
            bar:'zhanshan'
        }
    })
    
    // 开启了深度监听,newval和oldVal 一致。
    watch( sum,(newValue,oldValue)=>{
        ...
    },{
        deep:true
    })
    

3)reactive 类型

  • 监视 reactive 所定义的一个响应式数据的全部属性。

    • 底层强制开启了深度监视,即 deep 配置无效,且无法正确的获取 OldVal。
    let person = reactive({
        name:'zhan',
        age:18
    })
    
    watch( person,(newValue,oldValue)=>{
        ...
    },{
        immediate:true
    })
    
  • 监视 reactive 所定义的一个响应式数据的单个属性,可以使用回调函数返回一个值的形式。

    • 可以正确获取 OldVal
    watch( ()=>person.age ,(newValue,oldValue)=>{
        ...
    },{
        immediate:true
    })
    
  • 监视 reactive 所定义的一个响应式数据的多个属性。

    watch( [()=>person.age,()=>person.name ],(newValue,oldValue)=>{
        ...
    })
    
  • 监视 reactive 所定义的一个响应式数据的对象属性,配置有效。

    let person = reactive({
        name:'zhan',
        job:{
            j1:'tx'
        }
    })
    
    // 监视 j1的变化
    watch( ()=>person.job ,(newValue,oldValue)=>{
        ...
    },{
        immediate:true,
        deep:true
    })
    

4)watchEffect 函数

(1)定义
  • watch:指明监视的属性,也要指明监视的回调。

  • watchEffect:不用指明监视哪个属性;监视的回调中用到哪个属性,就监视哪个属性。

    watchEffect(()=>{
        const x1 = sum.value
        const x2 = person.job.j1.salary
    })
    
  • watchEffect 对比 computed

    • computed 注重的是计算出来的值,所以必须写返回值
    • watchEffect 注重的是过程,不用写返回值。
    • ==watchEffect 默认是立即执行的。
(2)高级配置
  • 回调函数的参数为 oninvalidate 函数,在回调执行之前会触发这个函数,可以做防抖之类的操作。

  • watchEffect 的第二个参数是配置项

    • flush 是执行时机,有 pre,async,post;
  • watchEffect 函数的返回值可以用于停止监听。

    const stop = watchEffect((oninvalidate) =>{
        log('this is stop');
        
        oninvalidate(() =>{
            log('before')
        })
    },{
        flush:'post',	// 先执行 DOM视图 再执行 监听器
        onTrigger(e){
            debugger;
        }
    })
    
    // 停止监听
    const stopWatch = () => stop()
    

10、父子组件传参

1)父传子 - defineProps

  • 通过 defineProps API

    // 父组件
    <Child :num='num' />
        
    // 子组件
    <p> {{num}} </p>
    import { defineProps } from 'vue'
    
    const props = defineProps({
        num:{
            type:Number,
            default:30
        }
    })
    
    log(pros.num)
    

2)子传父 - defineEmits

  • 通过 defineEmits API

    // 父组件
    <Child @fn='count' />
        
    let num = ref(20)
    const count = ()=>{
        num.value++
    }
        
    // 子组件
    <p @click='handleCount'> {{num}} </p>
    import { defineEmits } from 'vue'
    
    const emit = defineEmits(['fn'])
    
    const handleCount=()=>{
        emit('fn')
    }
    

3)依赖注入

  • 作用:实现组件的跨任意级别的自顶向下的通信,同时后代组件也可以修改值,祖先组件也可以响应。

  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

  • 具体写法:

    1. 祖先组件中:

         let car = reactive({name:'奔驰',price:'40万'})
         provide('car',car);
      
    2. 后代组件中:

       const car = inject<Ref<string>>('car')
       
        <style>
         .box {
             color: V-bind(color);
         }
         </style>
      
  • 防止后代组件修改数据,可以在祖先组件中使用 readonly 限制。

    provide('color',readonly( colorVal ));
    

4)Mitt

  • 使用第三方库 Mitt,代替事件总线。

5)v-model

  • 可以在自定义组件中双向通信。

  • v-model 其实是一个语法糖,通过 props 和 emit 组合而成。

  • Vue3 新增支持多个 v-model,支持自定义 Modifiers 修饰符。

  • 父组件

    <vModelVue v-model:textVal.isBt='text' v-modelValue='isShow'></vModelVue>
    
    const isShow = ref(true)
    const text = ref('zhanshan')
    
  • 子组件

    const props = defineProps<{
        modelValue:boolean,
        textVal:string,
        texyValModifiers?:{
        	isBt:boolean
    	}
    }>
        
    const emit = defineEmits(['update:modelValue','update:textVal'])
    
    const change = ()=>{
        emit('update:modelValue',false);
        emit('update:textVal',props?.texyValModifiers?.isBt ? a++ : a-- )
    }
    

11、自定义指令

  • 属于破坏性更新,指令的生命周期被完全改变,指令的生命周期与Vue的生命周期一致。

  • 格式

    // 自定义指令名:参数.修饰符="{}"
    <A v-move:aa.man="{ background: 'red' }"></A>
    
  • 指令的生命周期有两个参数,参数1是元素本身,参数2可以找到指令的参数、修饰符等。

    const vMove: Directive = {
        mounted(el: HTMLElement, dir: DirectiveBinding) {
            el.style.background = dir.value.background
        },
        updated() {
    
        },
    }
    

12、自定义 hooks

  • 用于处理复用代码逻辑的一些封装,类似于Vue2的 mixins。

  • mixins 的弊端是存在覆盖,组件的 data、methods、filter等会覆盖 mixins 同名的data、methods。

  • 本质是一个函数,把 setup 函数中使用的 Composition API 进行了封装。

    // 组件
    import usePoint from '../hooks/userPoint'
    export default {
        setup(){
          let point = usePoint();
    
          return {point}
        }
      }
    
    // hooks 函数
    import {reactive,onMounted,onBeforeMount } from 'vue'
    export default function(){
        let point = reactive({
            x:0,
            y:0
        })
    
        function savePoint(event){
            point.x = event.pageX
            point.y = event.pageY
        }
    
        onMounted(() => {
            window.addEventListener('click',savePoint)
        })
    
        onBeforeMount(() => {
            window.removeEventListener('click',savePoint)
        })
    
        return point
    }
    

13、全局函数与变量

  • Vue3 移除了 filter,但是可以在 globalProperties 追加属性,添加全局函数和变量,达到相同效果。

    // main.js
    app.config.globalProperties.$filters = {
        format<T>(str:T){
            return '123--'+str
        }
    }
    
    app.config.globalProperties.$env = 'dev'
    
    // App.vue
    <div>{{$filters.format('456')}}</div>
    <div> {{ $env }} </div>
    
    const app = getCurrentInstance()
    app?.proxy.$filters.format(('ts'))
    
  • 这种形式需要声明文件

    type Filter = {
        format<T>(str:T):string
    }
    
    declare module 'vue'{
        export interface ComponentCustomProperties{
            $filters:Filter,
            $env:string
        }
    }
    

14、CSS 新特性

  • 样式穿透

    .demo{
      :deep(.el-input__inner){
        color:red
      }
    }
    
  • 插槽选择器

     :slotted(div){
        color:red
     }
    
  • 动态 class

    const color = ref('red');
    
    <style>
        div{
            color:v-bind('color')
        }
    </style>
    

二、对比 Vue2

  1. Vue3 自带 treeShaking,因为组合式API不引入就不会被打包。而 Vue2 属于 options API,无法做到。组合式API将同一功能的代码同一管理,不至于像之前那样分散。

  2. 生命周期的 beforeCreate 和 created 被 setup 代替。

  3. 生命周期的 destroyed 被重命名为 unmounted;beforeDestroy 被重命名为 beforeUnmount。

  4. 新增了3个组件:Fragment 支持多个根结点、Suspense 可以在组件渲染之前的等待时间显示指定内容、Teleport 可以让子组件在布局上跳出父组件。

  5. Proxy 代替 Object.defineProperty 重构了响应式系统,可以监听到数组下标变化和对象新增属性,因为监听的不是对象属性,而是对象本身。

  6. 优化了Diff 算法,虚拟DOM生成速度提升了 200%

  7. 支持在 style 标签中使用 v-bind,给 CSS 绑定 JS 变量。

  8. $set 和 $delete 移除。

  9. 事件总线Bus被移除,但是可以使用第三方库 Mitt。

  10. 父组件使用子组件,通过 import 引入后不再需要 component 配置项声明。

  11. 移除了过滤器(filter),但是可以在 app.config.globalProperties追加。

    app.config.globalProperties.$filters = {}
    
  12. 自定义指令破坏性更新,Vue3中指令的生命周期与Vue实例的生命周期一致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值