Vue3新的特性与知识点详解

vue3.0

一、Vue3带来了什么

     ### 1.性能的提升

     - 打包大小减少41%

     - 初次渲染快55%, 更新渲染快133%

     - 内存减少54%
 
       ......

     ### 2.源码的升级

     - 使用Proxy代替defineProperty实现响应式

     - 重写虚拟DOM的实现(对比更快)和Tree-Shaking

       ......

     ### 3.拥抱TypeScript(重点)

     - Vue3可以更好的支持TypeScript

     ### 4.新的特性(重点)

     1. Composition API(组合API)

        - setup配置
        - ref与reactive
        - watch与watchEffect
        - provide与inject
        - ......
 
     2. 新的内置组件
        - Fragment 
        - Teleport
        - Suspense

     3. 其他改变

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

二、创建Vue3.0工程

     ## 1.使用 vue-cli 创建

     ## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上(注)
     vue --version / vue -V  (安装或者升级你的@vue/cli: npm install -g @vue/cli  )
     ## 创建,再选择vue3版本即可
     vue create vue_test
   

     ## 2.使用 vite 创建

     - 什么是vite?—— 新一代前端构建工具(还未大规模应用)。(旧:webpack)
     - 优势如下:
       - 开发环境中,无需打包操作,可快速的冷启动。
       - 轻量快速的热重载(HMR)。
       - 真正的按需编译,不再等待整个应用编译完成。(启动速度比脚手架明显快)
     - 传统构建 与 vite构建对比图

     ## 创建工程
     npm init vite-app <project-name>
     ## 进入工程目录
     cd <project-name>
     ## 安装依赖
     npm install
     ## 运行
     npm run dev


三、工程化结构分析

 以vue3-cli为例:结构和vue2-cli一样,只是里面写法不同:主要分析src文件夹

 1.src/main.js:

    //引入的不再是Vue构造函数,引入的是一个名为createApp的工厂函数
    import { createApp } from 'vue'
    import App from './App.vue'

    //创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”,打印app对象可以看到属性比vm少)
    const app = createApp(App)
    app.mount('#app')

   ---
    (相当于:实例-> App.vue ->各种组件)

    写法与vue2对比:
    import Vue from 'vue'    //vue3工程里面引入不到Vue构造函数
    import App from './App.vue'

    const vm = new Vue({
       render:h => h(App)   //相当于(h) =>{return h(App) }  ,h是渲染函数
    })
    vm.$mount('#app') 

  2.App.vue:

     <template>
      <!-- Vue3组件中的模板结构可以没有根标签 -->
      <img alt="Vue logo" src="./assets/logo.png">
     </template>

     <script>

      export default {
       name: 'App',
      }
     </script>


  3.vue开发者工具,需要也用能同时兼容vue3的,可以打开谷歌商城在线下载。
    edge浏览器上面下载的可以同时兼容vue2和3

四、常用 Composition API  (组合)


## 1.拉开序幕的setup

  1. 理解:Vue3.0中一个新的配置项,值为一个函数,简写:setup(){...}。 
  2. 组件中所用到的:数据、方法等等,均要配置在setup中。

  3. setup函数的两种返回值:
     1.若返回一个对象,则对象中的属性、方法, 在模板(页面)中均可以直接使用。(重点)
     2.若返回一个渲染函数:则可以自定义渲染内容,以自定义的内容代替模板页面(了解)
  4. 注意点:
     1. 尽量不要与Vue2.x配置混用(了解)
        - Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。
        - 但在setup中不能访问到Vue2.x配置(data、methos、computed...)。
        - 如果有重名, setup优先。
     2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(注)


  代码:
    (了解:vue2是直接写data、method,vue3是将其写在setup函数里面,数据不需要data,方法不需要method,但是vue3里面也可以写vue2的方式,向下兼容)
  
  app.vue:

     <template>
     <h1>一个人的信息</h1>
     <h2>姓名:{{name}}</h2>
     <h2>年龄:{{age}}</h2>
     <button @click="sayHello">说话(Vue3所配置的——sayHello)</button>
    </template>

    <script>
     // import {h} from 'vue'   引入渲染函数
     export default {
      name: 'App',
      setup(){

       //数据,/此处只是测试一下setup,暂时不考虑响应式的问题。
       let name = '张三'
       let age = 18
       let a = 200

       //方法
       function sayHello(){
        alert(`我叫${name},我${age}岁了,你好啊!`)
       }

       //返回一个对象(常用)
       return {
        name,
        age,
        sayHello,
       }

       //返回一个函数(渲染函数),内容会代替页面模板template里的内容
       //return ()=>{return h('h1','111') }  //h1标签,内容为'111'
      }
     }
    </script>


##  2.ref函数

(vue2的ref是来标识一个组件/标签元素的,vue3里面ref是一个函数)
- 作用: 定义一个响应式的数据
- 语法: `const xxx = ref(initValue)`   (拓展:const定义的对象,可以修改属性)
  - 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
  - JS中操作数据: `xxx.value`  (注)
  - 模板中读取数据: 不需要.value,直接:`<div>{{xxx}}</div>` 
  - 特殊:Ref定义了数组\对象,除了对象在页面中不用.value,其余都要.value。
- 备注:
  - 接收的数据可以是:基本类型、也可以是对象类型。
  - 基本类型的数据:响应式依然是靠`Object.defineProperty()`的`get`与`set`完成的。(注)
  - 对象类型的数据:内部求助了Vue3.0中的一个新函数—— `reactive`函数。(该函数封装了proxy)


 代码:

    <template>
    <h1>一个人的信息</h1>
    <h2>姓名:{{name}}</h2>
    <h3>工作种类:{{job.type}}</h3>
    <h3>工作薪水:{{job.salary}}</h3>
    <button @click="changeInfo">修改人的信息</button>
   </template>

   <script>
    import {ref} from 'vue'
    export default {
     name: 'App',
     setup(){
      //数据
      // let name = '张三'
      let name = ref('张三') //将字符串修饰成一个引用对象refImpl,变成响应式,结构见图(类似vue2的响应式)
      let job = ref({                            (与实践冲突)//对象修饰后也是一个引用对象refImpl,但是value是一个proxy对象,proxy里面可以直接调属性
       type:'前端工程师',
       salary:'30K'
      })

      //方法
      function changeInfo(){
       //name= '李四'   ; 不使用ref时,直接修改数据,页面不会响应。
       name.value = '李四'   //这样修改才能同时响应页面;而在页面修改数据,template中不需要name.value
     
       console.log(job.value)
       job.value.type = 'UI设计师'            //无论对象是否嵌套,属性都会被监视
       job.value.salary = '60K'
      }

      //返回一个对象(常用)
      return {
       name,
       age,
       job,
       changeInfo
      }
     }
    }
   </script>

## 3.reactive函数

  - 作用: 定义一个对象类型的响应式数据(基本类型不要用它,用不了,要用ref函数)
  - 语法:`const 代理对象= reactive(源对象),接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
  - reactive定义的响应式数据是“深层次的”。
  - 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。


  代码:

    <template>
    <h1>一个人的信息</h1>
    <h2>姓名:{{person.name}}</h2>
    ...
    <button @click="changeInfo">修改人的信息</button>
   </template>

   <script>
    import {reactive} from 'vue'
    export default {
     name: 'App',
     setup(){
      //数据
      let person = reactive({
       name:'张三',
       age:18,
       job:{
        type:'前端工程师',
        salary:'30K',
        a:{
         b:{
          c:666
         }
        }
       },
       hobby:['抽烟','喝酒','烫头']
      })

      //不再需要对象.vaule.属性,reactive传入对象比ref传入对象调用属性方便,也有响应式。(注)
      function changeInfo(){
       person.name = '李四'        
       person.age = 48
       ...
       person.job.a.b.c = 999
       person.hobby[0] = '学习'   //vue2里面直接索引修改数组无法响应式,reactive可以。
      }

      return {
       person,
       changeInfo
      }
     }
    }
   </script>

## 4.Vue3.0中的响应式原理

  ### vue2.x的响应式

    - 实现原理:
      - 对象类型:通过`Object.defineProperty()`对属性的读取、修改进行拦截(数据劫持)。
      
      - 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
      
        Object.defineProperty(data, 'count', {
            get () {}, 
            set () {}
        })
        

    - 存在问题:
      - 新增属性、删除属性, 界面不会更新。 (注意:get/set,只能检测属性值读取、设置)
      - 直接通过下标修改数组, 界面不会自动更新。
      (但是也有解决方法,如this.$set等)


   代码:

   <template>
       <div>
       <h2 v-show="person.name">姓名:{{person.name}}</h2>
       <h2>年龄:{{person.age}}</h2>
       <h2 v-show="person.sex">性别:{{person.sex}}</h2>
       <h2>性别{{person.sex}}</h2>
       <button@click="addSex">添加一个sex属性</button>
       <button @click="deleteName">删除name属性</button>
       </div>
   </template>

    <script>
    import Vue from 'vue'
      export default {
       name: 'App',
       data(){
        return {
          person:{
            name:'张三',
            age:18,
            hobby:['学习',吃饭']
          }
       }
     }

   methods:{
     addSex(){
        this.person.sex ='女'   //追加属性,无响应式效果

        this.$set(this.person,'sex','女')
        Vue.set(this.person,'sex','女')
     },
     deleteName(){
        delete this.person.name   //无响应式效果,数据属性没了,页面不会更新

        this.$delete(this.person,'name')  (记)
        Vue.delete(this.person,'name','女')
      },
     updateHobby(){
        this.person.hobby[0]='逛街'   //无响应式效果

        this.$set(this.person.hobby,0,'诞街')
        Vue.set(this.person.hobby,0,'诞街')
        this.person.hobby.splice(0,1,'逛街')
     }
   }
   }

  ### Vue3.0的响应式


    - 实现原理: 
    - 通过Proxy(代理):  拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。(reactive(对象)即如此)
    
        new Proxy(data, {
            // 拦截读取属性值
            get (target, prop) {
             return Reflect.get(target, prop)
            },
            // 拦截设置属性值或添加新属性
            set (target, prop, value) {
             return Reflect.set(target, prop, value)
            },
            // 拦截删除属性
            deleteProperty (target, prop) {
             return Reflect.deleteProperty(target, prop)
            }
        })
        proxy.name = 'tom'  


  代码:

      //源数据
          let person = {
            name:'张三',
            age:18
          }

            // 1.模拟Vue2中实现响应式,此时删除、增加属性不能检测到
            let p = {}
            Object.defineProperty(p,'name',{
                configurable:true,(可配置的)
                get(){ //有人读取name时调用
                    return person.name
                },
                set(value){ //有人修改name时调用
                    console.log('有人修改了name属性,我发现了,我要去更新界面!')
                    person.name = value    (此处若p.name = value 会陷入死循环)
                }
            })
            
            //2.    模拟Vue3中实现响应式(Proxy意思为代理,无论跨域代理 还是数据代理都是此含义)

                const p = new Proxy(person,{})  //此最简单的方式不能捕获读、改、增、删,作出响应,
                但是p已经完全代理person,即修改p与修改person对象一致

                const p=new Proxy( person ,{        //配置这些为了重写功能,变成响应式(即同时响应到页面)
                //有人读取p的某个属性时调用
                get(target,propName){    //target指的就是person
                        console.log(`有人读取了p身上的${propName}属性`)
                        // return target[propName];
                        return Reflect.get(target,propName)
                }
                //有人修改p的某个属性、或给p追加某个属性时调用(新,defineProperty没有的)
                set(target,propName,value){
                        console.log(`有人修改了p身上的$(propName)属性,我要去更新界面了!`)
                     // target[propName] = value  ,与下面的一样,都是直接操作person,不操作p,避免了死循环
                     Reflect.set(target,propName,value)
                },
                //有人删除p的某个属性时调用(新)
                deleteProperty(target,propName){
                        console.log(`有人删除了p身上的$(propName)属性,我要去更新界面了!)
                        // return delete target[propName]
                     return Reflect.deleteProperty(target,propName)
                    }
      })


            -window.Reflect(反射) :es6新增,反射对象,Object身上很多api在慢慢复制到其身上。

                    Reflect.get(obj,'a')
                    Reflect.set(obj,'a',100)
                    Reflect.deleteProperty(obj,'a')
                    以上也能修改全局的某个对象


                下面举例子体现Reflect的优势:(了解)
                        let obj = {a:1,b:2}

                        //通过Object.defineProperty去操作,框架代码中报错会停止执行,需要try catch
                        //#region 
                        /* try {
                            Object.defineProperty(obj,'c',{
                                get(){
                                    return 3
                                }
                            })
                            Object.defineProperty(obj,'c',{ //不处理异常,重复代理相同属性到对象上会报错。
                                get(){
                                    return 4
                                }
                            })
                        } catch (error) {
                            console.log(error)
                        } */
                        //#endregion

                        //通过Reflect.defineProperty去操作,框架代码中报错不会停止执行,并且少了try catch
                        //#region 
                        /* const x1 = Reflect.defineProperty(obj,'c',{
                            get(){
                                return 3
                            }
                        })
                        console.log(x1)
                        
                        const x2 = Reflect.defineProperty(obj,'c',{
                            get(){
                                return 4
                            }
                        }) 
                        if(x2){
                            console.log('某某某操作成功了!')
                        }else{
                            console.log('某某某操作失败了!')
                        } */
                        //#endregion

## 5.reactive对比ref

-  从定义数据角度对比:
   -  ref用来定义:基本类型数据。
   -  reactive用来定义:对象(或数组)类型数据。
   -  备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过`reactive`转为代理对象。(但是第一层仍然是ref引用对象形式,而.value里面是proxy)
-  从原理角度对比:
   -  ref通过`Object.defineProperty()`的`get`与`set`来实现响应式(数据劫持)。
   -  reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
-  从使用角度对比:
   -  ref定义的数据:js中操作数据需要`.value`,读取数据时模板中直接读取不需要`.value`。
   -  reactive定义的数据:操作数据与读取数据:均不需要`.value`。

## 6.setup的两个注意点

  #vue2补充知识点$attrs、$slot: (了解)

    1.vue2的父传子prop值,传个子组件的某个属性没有在prop声明,vc的$attrs会获取该属性,但是$attrs不能做类型限制,并且给页面直接调用写法麻烦。

    2.插槽:当父组件在子组件标签之间传入内容时,vc的$slot可以看到传入的标签。

    -父组件:
      <template>
      <div class="app">
      <h1>我是Vue2写的效果</h1>
      <Demo msg="你好啊" school="11">
        <span>11</span>
        <span>11!!!</span>

        <template slot="test1">//具体名插槽
        <span>222</span>
        </template>
        <template slot="test2>
        <span>222</span>
        </template>

      </Demo>

      </div>
      </template>
      <script>
      import Demo from './components/Demo'
      export default {
      name: 'App',
      components:{Demo}
      }
      </script>

   -子组件Demo:

      <template>
      <div class="demo"
      <h2>我是Demo组件</h2>
      <slot></slot>  //插入位置

      <slot name="test1">我是一些默认值1</slot>
      <slot name="test2">我是一些默认值2</slot>

      </div>
      </template>
      <script>
      export default {
      name:'Demo',
      props:['msg'],
      mounted(){
      console.log(this)
      },
      }
      </script>


# setup执行的时机
  - 在beforeCreate之前执行一次(即更早),this是undefined。 (特别注意, setup没有this,由于执行太早)
  
# setup的参数
  - props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
  - context:上下文对象
    - attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 `this.$attrs`。
    - slots: 收到的插槽内容, 相当于 `this.$slots`。
    - emit: 分发自定义事件的函数, 相当于 `this.$emit`的函数。


  代码:

  -子组件:

    <template>
      <h1>一个人的信息</h1>
      <h2>姓名:{{person.name}}</h2>
      <h2>年龄:{{person.age}}</h2>
      <button @click="test">测试触发一下Demo组件的Hello事件</button>
    </template>


        (其实就是setup里面没有this,围绕这个开展,完成vue2原有的操作)
    <script>
      import {reactive} from 'vue'
      export default {
        name: 'Demo',
        beforeCreate(){
          console.log("beforeCreate不是最早执行,而是setup最早");
        },

        props:['msg','school'],  //父组件传了没接收会有警告,声明了没传没有警告而是undefined。(vue3是如此,vue2情况待确定)
        emits:['hello'], //自定义事件需接收 (vue3特有,因为vue2中父给子传事件,是直接绑到子组件标签上,子组件再this.$emit调用;
                                              vue3绑定是一样的,但是调用时setup没有this,所以需在此声明接收,后面再setup的参数触发)
      
        setup(props,context){
          console.log("setup最早执行");
          console.log('---setup---',props)         //具有proxy对象的props属性
          console.log('---setup---',context)   //普通对象,有attrs、slot、emit
          console.log('---setup---',context.attrs) //相当与Vue2中的$attrs,只有props没有接收的属性才会到这里
          console.log('---setup---',context.emit('xx')) //触发自定义事件的。
          console.log('---setup---',context.slots) //插槽$slot

          //数据
          let person = reactive({
            name:'张三',
            age:18
          }),
          
          //方法
          function test(){
            context.emit('hello',666)
          }

          //返回一个对象(常用)
          return {
            person,
            test
          }
        }
      }
    </script>


  -父组件:

  <template>
    <Demo @hello="showHelloMsg" msg="你好啊" school="333">
      <template v-slot:qwe> (vue2里具体名插槽:slot='xx',v-slot:xxx都可以;但是vue3兼容最好使用v-slot:xx) (注意:vue3弃用slot='插槽名称')
        <span>333</span>
      </template>
    </Demo>
  </template>

  <script>
    import Demo from './components/Demo'
    export default {
      name: 'App',
      components:{Demo},
      setup(){
      ...
      }
    }
  </script>


    - 使用setup语法糖的时候获取contenxt的值

    useAttrs 方法 获取 attrs 属性
    useSlots 方法获取 slots 插槽
    defineEmits 方法获取 emit 自定义事件

    另外,如父组件传了值,子组件未接收,会报警告,即使用了useAttrs()。 (开发中注意)
    此时只需在页面中使用{{$attrs}}或者useAttrs()返回的变量即可。
        
    获取默认插槽:
    const slots = useSlots()
    const defaultSlot = slots.default()  //数组

## 7.计算属性与监视  (都是组合式api,需引入)


### 1.computed函数(与vue2的区别,不写成属性形式computed:{},而是函数调用的形式)

    <template>
      ...
      </template>

      <script>
        import {reactive,computed} from 'vue'
        export default {
          name: 'Demo',
          setup(){
            //数据
            let person = reactive({
              firstName:'张',
              lastName:'三'
            })

            let fullName = computed(()=>{
              return person.firstName + '-' + person.lastName
            }) 

            //为了语义化更好,直接追加到person对象,有响应式;计算属性——简写(没有考虑计算属性被修改的情况)
            person.fullName = computed(()=>{   (注意,简写只需考虑读,这里是个箭头函数)
              return person.firstName + '-' + person.lastName
            }) 

            //计算属性——完整写法(考虑读取与修改,vue2也有)
            person.fullName = computed({  (注意,完整写法考虑读和写,传的是个配置对象)
              get(){
                return person.firstName + '-' + person.lastName
              },
              set(value){
                const nameArr = value.split('-')
                person.firstName = nameArr[0]
                person.lastName = nameArr[1]
              }
            })

            return {
              person
            }
          }
        }
      </script>

    注意:
        - computed属性的调用(WritableComputedRef类型):页面可以直接获取需要.value,否则计算属性return的是一个对象/数组时,会有类型问题;js中要获取也需.value。
          (特别注意)

        - 配置对象写法时,若计算属性return的是一个对象/数组,后续再修改,则触发不了set函数。若是追加属性,会直接设置,并且有响应式;
            若是整个对象替换,会丢失响应式。且此时若需要对象的某个属性X++,只能完整写法X=X+1(特殊)

            解释:
              因为在Vue 3中,计算属性的实现方式已经发生了变化。在Vue 2中,计算属性是通过Object.defineProperty()方法实现的,
              而在Vue 3中,计算属性是通过Proxy实现的。当计算属性返回的是一个对象时,Proxy会将这个对象转换为响应式对象,
              从而使得这个对象的属性变化可以被Vue追踪到并触发更新,而不需要再通过Set回调来实现,同时也解释了追加属性有响应式、替换整个对象没有。

### 2.watch函数  (与vue2的区别,不写成属性显示watch:{},而是函数调用的形式 ;更好的一对多,多对一变化;并且reactive数据检测会出现的情况较多)

  <template>
    ...
  </template>

  <script>
    import { ref, reactive , watch } from 'vue' //组合api的体现
    export default {
      name: 'Demo',
      setup(){
        let sum = ref(0)
        let msg = ref('你好啊')
        let person = reactive({
          name:'张三',
          age:18,
          job:{
            j1:{
              salary:20
            }
          }
        })

        * 监测ref属性:

        -情况一:监视ref所定义的一个响应式数据
            watch(sum,(newValue,oldValue)=>{    //不用.value,sum.value相当于值0
              console.log('sum变了',newValue,oldValue)    //直接拿到值,不需要再newValue.value
            },{immediate:true}) 

        -情况二:监视ref所定义的多个响应式数据(即多个属性变化产生一个效应)
            watch([sum,msg],(newValue,oldValue)=>{        //此时newValue是数组[],两个元素为监测的两个变量
              console.log('sum或msg变了',newValue,oldValue)
            },{immediate:true}) 

            监测ref属性注意点:

                setup(){
                  let sum = ref(0)
                  let msg = ref('你好啊')
                  let person = ref({
                    name:'张三',
                    age:18,
                    job:{
                      j1:{
                        salary:20
                      }
                    }
                  })
           
                  下面是ref加工对象数据,了解即可:
                  watch(person,(newValue,oldValue)=>{        //不能检测到person变化,特殊
                    console.log('person的值变化了',newValue,oldValue)
                  },)
                  watch(person.value,(newValue,oldValue)=>{            //方法一,检测refImpi对象的value(proxy对象)
                    console.log('person的值变化了',newValue,oldValue)
                  },)
                  watch(person,(newValue,oldValue)=>{                //方法二,深度监听,因为refImpi对象的第一层属性值都没有变化(value地址值不变)
                    console.log('person的值变化了',newValue,oldValue)
                  },{deep:true})
                  上面两个的oldValue都是无效的,与newValue一致。


        * 监测reactive属性:

        -情况三:监视reactive所定义的一个响应式数据的全部属性(相当于监测proxy对象,会带来下面两个问题)
            1.注意:此处无法正确的获取oldValue;用了ref也一样,因为ref对象的value属性里面也是一个用reactive处理的proxy对象。
            若一定需要用到oldValue:用ref单独拿出该属性,再追加给person对象
            2.注意:默认强制开启了深度监视(deep配置无效)

            watch(person,(newValue,oldValue)=>{
              console.log('person变化了',newValue,oldValue)
            },{deep:false}) //此处的deep配置无效 

        -情况四:监视reactive所定义的一个响应式数据中的某个属性 (监测指定对象某个属性写法,此处相当于监测普通类型,oldValue有效)
           //watch(person.name,(newValue,oldValue)=>{        //直接person.name会报警告,需箭头函数返回,特殊
             watch(()=>person.name,(newValue,oldValue)=>{  
              console.log('person的name变化了',newValue,oldValue)
            })  

        -情况五:监视reactive所定义的一个响应式数据中的多个属性        (监测指定对象几个特定属性写法)
            watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
              console.log('person的name或age变化了',newValue,oldValue)
            })  

        -特殊情况
            watch(()=>person.job,(newValue,oldValue)=>{
              console.log('person的job变化了',newValue,oldValue)    //此时不开启deep配置,多层属性检测不到
            },{deep:true}) //此处由于监视的是reactive定义的proxy对象中的某个属性,且属性是对象(特殊),
            所以deep配置有效;oldValue也拿不到(因为也是对象) */


      监测对象总结:如果检测的具体内容是一个对象,那么oldValue会获取不到。如果检测的是reactive定义所产生的proxy对象,会强制深度监测。

      }

        return {
          sum,
          msg,
          person
        }
      }
    }
  </script>

### 3.watchEffect函数

  - watch的套路是:既要指明监视的属性,也要指明监视的回调。

  - watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  - watchEffect有点像computed:

    - 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    - 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。


      <script>
        import {ref,reactive,watchEffect} from 'vue'
        export default {
          name: 'Demo',
          setup(){
            let sum = ref(0)
            let msg = ref('你好啊')
            let person = reactive({
              name:'张三',
              age:18,
              job:{
                j1:{
                  salary:20
                }
              }
            })

      //watchEffect初始化调用一次,所指定的回调中用到的数据只要发生变化,则也会重新执行回调。
      (像没有返回值的计算属性。而计算属性实现这种两个变量有变化,就执行回调的功能,需要有返回值,且被页面引用)
           
            watchEffect(()=>{
              const x1 = sum.value
              const x2 = person.job.j1.salary
              console.log('watchEffect所指定的回调执行了')
            })

            return {
              sum,
              msg,
              person
            }
          }
        }
      </script>

## 8.生命周期

  不同点:
  
  1.vue3销毁是unmounted
  2.vue3的app(vm)较轻量,且当el或者mount指定容器后,才会执行beforeCreaed()和created();vue2会执行这两个之后才判断有没有指定容器。(注)
  3.生命周期普通写法(即按vue2写法)和组合式API写法(写在setup里),都可以用。

  - Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有两个被更名:
    - ```beforeDestroy```改名为 ```beforeUnmount```
    - ```destroyed```改名为 ```unmounted```
  - Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
    - `beforeCreate`===>`setup()`
    - `created`=======>`setup()`
    - `beforeMount` ===>`onBeforeMount`
    - `mounted`=======>`onMounted`
    - `beforeUpdate`===>`onBeforeUpdate`
    - `updated` =======>`onUpdated`
    - `beforeUnmount` ==>`onBeforeUnmount`
    - `unmounted` =====>`onUnmounted`
    (其实就是将create的两个合到setup,然后其它生命周期写在setup中,并且名字加on;然后vue3最后两个函数更名) (注)

    注意vue3是写在setup里面,组合式API用法,并且是函数调用形式,传箭头函数。如onMounted(()=>{})

## 9.自定义hook函数

    - 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
    - 类似于vue2.x中的mixin。
    - 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

  例子:(详细见例子)
  hooks文件夹的usePoint.js:

      import {reactive,onMounted,onBeforeUnmount} from 'vue'
      export default function (){
        //实现鼠标“打点”相关的数据
        let point = reactive({
          x:0,
          y:0
        })

        //实现鼠标“打点”相关的方法
        function savePoint(event){
          point.x = event.pageX
          point.y = event.pageY
          console.log(event.pageX,event.pageY)
        }

        //实现鼠标“打点”相关的生命周期钩子
        onMounted(()=>{
          window.addEventListener('click',savePoint)
        })

        onBeforeUnmount(()=>{
          window.removeEventListener('click',savePoint)
        })

        return point
      }

  页面:

    <template>
     ...
    </template>

    <script>
      import {ref} from 'vue'
      import usePoint from '../hooks/usePoint'
      export default {
        name: 'Demo',
        setup(){
          let point = usePoint()   
          return {point}
        }
      }
    </script>


  hook函数的作用:其实就是将一个功能的数据、方法、生命周期函数,都封装出去到自定义的js文件中,
              该文件暴露一个函数,函数返回值为该功能需要的数据;要复用时,只需引入该功能函数赋值数据即可。
              hook函数调用后返回的取决于函数,一般是普通对象包裹响应式数据。

## 10.toRef(为了页面有对象数据层次较深时,调用方便些)

ref:定义响应式数据,基本、对象数据类型。(toRef与ref没有直接关系)

- 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
- 语法:```const name = toRef(person,'name')```
- 应用:   要将响应式对象中的某个属性单独提供给外部使用时,减少调用麻烦。
- 扩展:```toRefs``` 与```toRef```功能一致,但可以批量创建多个 ref 对象,语法:```toRefs(person)```


<template>
    <h4>{{person}}</h4>
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <h2>薪资:{{job.j1.salary}}K</h2>
    <button @click="name+='~'">修改姓名</button>
    <button @click="age++">增长年龄</button>
    <button @click="job.j1.salary++">涨薪</button>
</template>

<script>
    import {ref,reactive,toRef,toRefs} from 'vue'
    export default {
        name: 'Demo',
        setup(){
            //数据
            let person = reactive({
                name:'张三',
                age:18,
                job:{
                    j1:{
                        salary:20
                    }
                }
            })

            //返回一个对象(常用)
            return {
                person,

             //下面三个写法原意是为模版中简写变量,但是这样只能一开始显示,会丢失响应式,因为没改到对象的堆内容的东西,相当于返回死的数据给页面。
             name:person.name,
             age:person.age,
             salary:person.job.j1.salary,

                //于是出现了toRef,将变量改为ref对象,可以使页面方便的读取到对象的某个属性值,并且能和原对象建立响应式的关联,即回显和修改都相当于操作person。
                // name:toRef(person,'name'),
                // salary:toRef(person.job.j1,'salary'),  //多层时,该方式的简写优势

                //toRefs将整个对象直接改造,只能改造对象的第一层,返回一个新对象,将其解构后页面能直接使用第一层,对象里的属性调用与修改都相当于操作person
        但是需要第一层.value
                ...toRefs(person)
            }
        }
    }
</script>

# 三、其它 Composition API (相对少用)

1.shallowReactive 与 shallowRef
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
- shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

- 什么时候使用?
  -  如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
  -  如果有一个对象数据,后续功能不会修改该对象中的属性,而是只渲染或者直接用新的对象替换 ===> shallowRef。


  <template>
    <h4>当前的x.y值是:{{x.y}}</h4>
    <button @click="x={y:888}">点我替换x</button>  (有效)
    <button @click="x.y++">点我x.y++</button>   (无效)
    <hr>
    <h4>{{person}}</h4>
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <h2>薪资:{{job.j1.salary}}K</h2>                
    <button @click="name+='~'">修改姓名</button>
    <button @click="age++">增长年龄</button>
    <button @click="job.j1.salary++">涨薪</button>        (无效)
  </template>

  <script>
    import {ref,reactive,toRef,toRefs,shallowReactive,shallowRef} from 'vue'
    export default {
      name: 'Demo',
      setup(){
        let person = shallowReactive({ //只考虑第一层数据的响应式
          name:'张三',
          age:18,
          job:{
            j1:{
              salary:20
            }
          }
        })

        shallowRef内容是基本数据类型时,与ref效果一样,但是对象类型时不一样,传入对象时区别:
        1.ref和shallowRef都会转为refImpl对象,但是里面有属性时,ref会求助reactive将该属性转为proxy,shallowRef不会。
        2.shallowRef传入对象没有响应式,但是可以直接新对象赋值替换。

        let x = shallowRef({
          y:0
        })

        return {
          x,
          person
        }
      }
    }
  </script>


  2.readonly 与 shallowReadonly

  - readonly: 让一个响应式数据变为只读的。
  - shallowReadonly:让一个响应式数据第一层变为只读的(浅只读)。
  - 应用场景: 不希望数据被修改时。

        import {ref,reactive,readonly,shallowReadonly} from 'vue'

      let sum = ref(0)
            let person = reactive({
                name:'张三',
                age:18,
                job:{
                    j1:{
                        salary:20
                    }
                }
            })
      
      //加工数据后,覆盖要暴露的变量。
            person = readonly(person)
            person = shallowReadonly(person)  //只是第一层属性只读,深层次的依然可以修改
            sum = readonly(sum)
            sum = shallowReadonly(sum)   //不是对象,此时与readonly没区别

      补充:
      如果直接去掉ref/reactive,将数据写死,此时仍然可以修改数据,只是页面上一样不会有变化,若希望数据源不被修改,最好用此api。

   3.toRaw 与 markRaw(只处理reartive数据)

    背景:
    普通数据->响应式数据(ref/reactive)
    普通数据<-响应式数据

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

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


    let person = reactive({
            name:'张三',
            age:18,
            job:{ 
              j1:{
                salary:20
              }
            }
          })

          function showRawPerson(){
            const p = toRaw(person)     //将该数据从 响应式数据->普通数据,返回变量   (只能用于reartive数据)
            p.age++             //该变量可以修改,页面不会响应
          }

          function addCar(){
            let car = {name:'奔驰',price:40}
            person.car = car                //proxy对象的响应式追加属性,该属性及其内容也会是响应式的。(vue2中响应式直接追加对象,不会有响应式)
            person.car = markRaw(car)       //限定添加的属性不做响应式。
          }

4.customRef

- 作用:创建一个自定义的 ref,能够自定义添加ref的get/set触发时的功能。(customRef相当于不完整的ref)

例子:实现修改数据后,延迟更新,且自带防抖。
  <template>
      <input type="text" v-model="keyword">
      <h3>{{keyword}}</h3>
    </template>
    
   <script>
    import {customRef} from 'vue'
    export default {
        name: 'App',
        setup() {
            //自定义一个ref——名为:myRef
            function myRef(value,delay){
                let timer
                return customRef((track,trigger)=>{    //第一个return是将customRef函数的值返回,第二个return是语法要求。
                    return {
                        get(){
                            track() //通知Vue追踪value的变化,后面set里改变value和模版更新时,才会重新调get(),否则数据改变 视图仍不变(特殊)
                            return value
                        },
                        set(newValue){
                            clearTimeout(timer)  //定时器实现延迟更新,只保留最后一个延时器实现防抖。
                            timer = setTimeout(()=>{

                                value = newValue
                                trigger() //通知Vue去重新解析模板

                            },delay)
                        },
                    }
                })
            }

            let keyWord = myRef('hello',500) //使用程序员自定义的ref,能实现修改数据后指定秒数后再更新数据
            return {keyWord}
        }
    }
</script>

5.provide 与 inject(添加)

- 作用:实现祖与后代组件间通信
- 套路:父组件有一个`provide`选项来提供数据,后代组件有一个`inject`选项来开始使用这些数据(子/孙组件都可以接收获取,只是传子一般用props简单)


-具体写法:(provide 与 inject:使用前都是有组合式API的引入)

  1. 祖组件中:

     ```js
     setup(){
         ......
         let car = reactive({name:'奔驰',price:'40万'})
         provide('car',car)   //第一个参数是自定义的名称
         ......
     }
     ```

  2. 后代组件中:

     ```js
     setup(props,context){
         ......
         const car = inject('car')
         return {car}
         ......
     }
     ```


6.响应式数据的判断

- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由 `reactive` 创建的响应式代理
- isReadonly: 检查一个对象是否是由 `readonly` 创建的只读代理

- isProxy: 检查一个对象是否是由reactive/readonly创建的代理(准确的说,判断其类型是不是代理对象proxy,readonly只是加工了reactive,没有改变其类型)


  <script>
    import {ref, reactive,toRefs,readonly,isRef,isReactive,isReadonly,isProxy } from 'vue'
    export default {
      name:'App',
      setup(){
        let car = reactive({name:'奔驰',price:'40W'})
        let sum = ref(0)
        let car2 = readonly(car)
        //全部是true
        console.log(isRef(sum))
        console.log(isReactive(car))
        console.log(isReadonly(car2))
        console.log(isProxy(car))
        
        return {...toRefs(car)}
      }
    }
  </script>


  # 四、Composition API 的优势

  1.vue2的Options API 存在的问题(配置式的api)
  使用传统OptionsAPI中,一个功能涉及到的内容分散在数据、方法、计算属性等等,后期维护就需要分别在data,methods里零散修改 。

  2.Composition API 的优势
  将一个功能相关的数据、方法、配置、相关的生命周期钩子等相关的内容都抽离到一起,不仅在页面引入该功能方便,维护该功能也方便。(需借助hook)


  # 五、新的组件

  ## 1.Fragment

  - 在Vue2中: 组件必须有一个根标签
  - 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
  - 好处: 减少标签层级, 减小内存占用


  ## 2.Teleport(传送)

  - Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

  - Teleport传送的内容是到目标选择器之下。还可以disabled 属性接收一个 Boolean 值,true 代表不允许传送,false 代表传送


  场景:当页面多层嵌套时,当孙组件中出现弹框,需要相对整个页面开启绝对定位时,很难控制html、body标签为其包含块。

  孙组件:
     <template>
        <div>
          <button @click="isShow = true">点我弹个窗</button>
          <teleport to="body">  //或者接任意css选择器
            <div v-if="isShow" class="mask">
              <div class="dialog">
                <h3>我是一个弹窗</h3>
                <h4>一些内容</h4>
                <button @click="isShow = false">关闭弹窗</button>
              </div>
            </div>
          </teleport>
        </div>
      </template>

      <script>
        import {ref} from 'vue'
        export default {
          name:'Dialog',
          setup(){
            let isShow = ref(false)
            return {isShow}
          }
        }
      </script>

      <style>
        .mask{
          position: absolute;         // html根元素为默认初始包含块,当传送到body下时,绝对定位偏移量相对根标签。
          top: 0;bottom: 0;left: 0;right: 0;      //能实现宽高撑满屏幕(注意,绝对布局公式,待实验)
          background-color: rgba(0, 0, 0, 0.5);
        }
        .dialog{
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%,-50%);
          text-align: center;
          width: 300px;
          height: 300px;
          background-color: green;
        }
      </style>

  ## 3.Suspense(悬念,处于实验阶段)


- 使组件异步加载时,等待时能显示其它提示内容,更好的用户体验

- 使用步骤:

  -使用Suspense标签包裹子组件,并配置好Suspense的插槽`default` 与 `fallback`

      <template>
        <div class="app">
          <h3>我是App组件</h3>

          <Suspense>
            <template v-slot:default>   //名字不能改
              <Child/>
            </template>
            <template v-slot:fallback>
              <h3>稍等,加载中...</h3>
            </template>
          </Suspense>

        </div>
      </template> 

      <script>
        // import Child from './components/Child'     //静态引入。页面加载的时候,如果该子组件没加载完,整个页面都等待着<child>标签。
        import {defineAsyncComponent} from 'vue' 
        const Child = defineAsyncComponent(()=>import('./components/Child'))   //(动态)异步引入,会在页面加载完后再出现,比页面的同步加载晚。
        export default {
          name:'App',
          components:{Child},
        }
      </script>


    子组件:
    <template>
        <div class="child">
        <h3>我是Child组件</h3>
        {{sum}}
      </div>
    </template>
      
      <script>
        import {ref} from 'vue'
        export default {
          name:'Child',
          async setup(){        //前面说setup不能返回一个promise,因为那样模版会拿不到定义的数据;但是此处可以,因为该组件嵌套在<Suspense>组件里。(特殊)
            let sum = ref(0)
            let p = new Promise((resolve,reject)=>{
              setTimeout(()=>{
                resolve({sum})
              },3000)           //3秒后渲染该组件
            })
            return await p
          }
        }
      </script>


    # 六、其他

    ## 1.全局API的转移

    - Vue 2.x 有许多全局 API和配置, Vue3.0中对这些API做出了调整:

      - 将全局的API,即:Vue.xxx调整到应用实例(app)上

        | 2.x 全局 API(`Vue`) | 3.x 实例 API (`app`)                        |
        | ------------------------- | ------------------------------------------- |
        | Vue.config.xxxx           | app.config.xxxx                             |
        | Vue.config.productionTip  | 移除 (vue3自动去掉生产提示)
        | Vue.component             | app.component                               |
        | Vue.directive             | app.directive                               |
        | Vue.mixin                 | app.mixin                                   |
        | Vue.use                   | app.use                                     |
        | Vue.prototype             | app.config.globalProperties   (注)              |
      

    ## 2.其他改变

    - data选项应始终被声明为一个函数。  (vue2中, data选项可以对象式也可以函数式,但是后面复用同一组件时,会出现数据存在引用关系,所以统一使用函数式)

    - 过度类名的更改:

      - Vue2.x写法

        .v-enter,
        .v-leave-to {
          opacity: 0;
        }
        .v-leave,
        .v-enter-to {
          opacity: 1;
        }

      - Vue3.x写法

        .v-enter-from,
        .v-leave-to {
          opacity: 0;
        }
        
        .v-leave-from,
        .v-enter-to {
          opacity: 1;
        }

    - 移除keyCode(按键编码)作为 v-on 的修饰符,同时也不再支持config.keyCodes(自定义按键编码)。(原因:兼容性较差)
      (实际开发 @keyup.enter.native="xxxx" 依然有效)

    - (注意)移除`v-on.native`修饰符,原来vue2里面,会出现子组件标签@click='xxx'时,vue分不清是自定义事件还是原生事件,所以需要@click.native='xxx'。

      - 父组件中绑定事件

        <my-component
          v-on:close="handleComponentEvent"
          v-on:click="handleNativeClickEvent"
        />

      - 子组件中声明自定义事件

        <script>
          export default {
            emits: ['close']    //vue3的解决方案:只要子组件没有接收的事件,就认为是原生事件
          }
        </script>

        小结:
          在vue3框架click、change(这类原生DOM事件),不管是在标签、组件上都是原生DOM事件,只要没有emits接收。
          vue2中却不是这样的,在vue2中组件标签需要通过native修饰符才能变为原生DOM事件。
          

    - 移除过滤器(filter)
      建议用方法调用或计算属性去替换过滤器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值