Vue3

68 阅读9分钟

VueCli创建Vue3项目

1.检查vuecli版本  4以上
vue -V
2.创建
vue create 项目名

vite创建vue3项目

1.创建工程
npm init vite-app 项目名
2.进入项目工程目录
cd 项目名
3.安装依赖
npm install
4.运行
npm run dev

setup

1.理解:Vue3.0中一个新的配置项 值为一个函数

2.setup是所有Composition API(组合式API)表演的舞台

3.组件中所用到的数据、方法等均要配置在setup中

4.setup的两种返回值
1.返回一个对象 则该对象中的属性、方法、在模板中均可以直接使用
2.返回一个渲染函数则可以自定义渲染内容

5.注意点
1.尽量不要与Vue2 写法混用
2.setup不能是一个async函数 因为返回值不再是return的对象模板看不到
return对象中的属性
注意后期也可以返回一个Promise实例但需要Sespense和异步动态引入组件的配合

<template>  
  <!-- vue3中的模板结构可以没有根标签 -->
  <h2>展示个人信息</h2>
  <h2>姓名: {{name}}</h2>
  <h2>年龄:{{age}}</h2>
  <button @click="sayHello">Hello</button>

</template>

<script>
import {h} from 'vue'
export default {
  name: 'App',
  setup(){
    // 数据
    let name = '张三'
    let age = 18

    //方法
    function sayHello(){
      alert(`我叫${name},今年${age}岁了`)
    }

   //返回一个对象(常用)
   return{
    name,
    age,
    sayHello
   }
   //返回一个(渲染函数)
  //  return () =>h('h1','尚硅谷')
  }
}
</script>


注意:

setup执行的时机:
  在beforeCreate之前执行一次 this是undifind
  
setup的参数
props: 值为对象 包含组件外部传递过来的 且组件内部声明接收了的属性
context: 上下文对象
   attrs:值为对象包含组件外部传递过来的且组件内部声明接收了的属性相当于this.$attrs
   slots:收到的插槽内容 相当于this.$slots
   emit: 分发的自定义事件的函数 相当于 this.$emit
   
代码实例
Demo组件

<template>  
  <!-- vue3中的模板结构可以没有根标签 -->
  <h2>展示个人信息</h2>
  <h2>姓名: {{person.name}}</h2>
  <h2>年龄:{{person.age}}</h2>
  <button @click="test">测试触发一下Demo组件的hello事件</button>
  <slot></slot>
  <slot name="qwe"></slot>
</template>

<script>
import {reactive} from 'vue'
export default {
  name: 'Demo',
   beforeCreate() {
    // console.log('beforeCreate')
  },
   props:['msg','school'],
   emits:['hello'],
  setup(props,context){
    console.log('setup',props,context)//setup接收两个参数props,context
    console.log('setup',props,context.attrs) //$attrs
    console.log('setup',props,context.emit)//触发自动事件
    console.log('setup',props,context.slots)//插槽相关
    
    // 数据
    let person = reactive({
      name:'罗浩哲',
      age: 18,
    })
    
   function test (){
    //   触发事件方法
    context.emit('hello',666)
   }
   
   //返回一个对象(常用)
   return{
   person,
   test
   }
  }
}
</script>

App组件
<template>  
 <Demo @hello="showHello" msg="你好啊" school="北大">
  <span>北京大学</span> 
  <template v-slot:qwe>
    <span>
      清华大学
    </span>
  </template>
 </Demo>
</template>
<script>
import Demo from './components/Demo.vue'
export default {
  name: 'App',
  components:{
    Demo
  },
  setup() {
     function showHello (value){
      alert(`你触发了hello事件我收到的参数是${value}`)
    }
  return {
     showHello
  }
  
  },
}
</script>

ref函数

    1.作用:定义响应式的数据
    2.语法 
     let name = ref('张三') 
        let age = ref(18)
        let job = ref({
          type: '前端工程师',
          salary: '30k'
        })
    3js中操作数据
      //修改数值类型数据
          name.value = '李四'
          age.value = 90
          // console.log(name,age);
       //修改对象类型数据
          job.value.type = 'UI设计师',
          job.value.salary ='60k'
        }
        
        
    4.读取使用
    <h2>展示个人信息</h2>
      <h2>姓名: {{name}}</h2>
      <h2>年龄:{{age}}</h2>
      <hr>
       <h2>{{job.type}}</h2>
       <h2>{{job.salary}}</h2>
      
    5.备注
       接收的数据可以是基本类型 也可以式对象类型
       基本类型的数据 响应式依然是靠Object.defineProperty()的get()set()实现的
       对象类型的数据内部求助了 Vue3.0中的一个新函数reactive函数



reactive函数

1.作用:定义一个对象类型的响应式数据(基本类型不要用他使用ref)
2.语法
 let person = reactive({
      name:'罗浩哲',
      age: 18,
      job:{
        type:'前端工程师',
        salary:'30k',
        a:{
       b:{
         c:{
            d:666
         }
           }
         }
      },
      hobby:['抽烟','喝酒','烫头','打游戏','睡觉']
    })

    //方法
    function changeInfo(){
      //修改数值类型
      person.name = '李四'
      person.age = 90
      // console.log(name,age);
      //修改对象类型数据
      person.job.type = 'UI设计师',
      person.job.salary ='60k',
      //修改多层嵌套对象的值
      person.job.a.b.c.d = 999
    }
   //返回一个对象(常用)
   return{
   person,
   changeInfo
   }
  }
}

3.reactive定义的数据响应是深层次的

4.内部基于ES6Proxy实现 通过代理对象操作源对象内部数据

Vue2.的响应式

1.实现原理
 对象类型:通过ObjectdefineProperty()对属性的读取和修改进行拦截(数据劫持)
 
 数组类型:通过重写数组一系列方法实现拦截(对数组变更方法进行了包裹)
 
 存在问题
 新增属性 删除属性 界面不会更新
 直接通过下表修改数组界面不会更新
 

Vue3响应式

实现原理
  通过proxy代理:拦截对象中任意属性的变化 包括属性值的读写、属性的添加  删除等
  
  通过refiect(反射)对被代理对象属性进行操作
  
   <script>
        let person = {
            name:'张三',
            age: 18
        }

        // 模拟Vue3中实现响应式  person:监测对象
        const p = new Proxy(person,{
            //有人读取p的某个属性调用
            // target:原数据 propName:读取的属性值
            get(target,propName){
             console.log(`有人读取p身上的${propName}我要去更新页面了`);
            //  return target[propName]
                Reflect.get(target,propName)
            },
            //有人修改p的某个属性 或给p追加某个属性时调用
            // target:原数据 propName:读取的属性值 value:修改后的值
            set(target,propName,value){
               console.log(`有人修改了p身上的${propName},我要去更新页面了`);
            //    return target[propName] = value
                  Reflect.set(target,propName,value)
            },
            //有人删除了p身上的某个属性时调用
            deleteProperty(target,propName){
             console.log(`有人删除了p身上的${propName}属性,我要去更新页面了`);
            //  return delete target[propName]
               return Reflect.deleteProperty(target,propName)
            }
        })
    </script>
  

计算属性

    computed函数
    与Vue2中配置功能一致

    写法: 全名案例
    <template>  
      <!-- vue3中的模板结构可以没有根标签 -->
      <h2>展示全名信息</h2>
       姓: <input type="text" v-model="person.Fname">
       <br><br>
       名: <input type="text" v-model="person.Lname">
        <br><br>
        <!-- <span>全名:{{person.fullName}}</span> -->
        全名: <input type="text" v-model="person.fullName">
    </template>

    <script>
    import {reactive, computed} from 'vue'
    export default {
      name: 'Demo',
      setup(){
        // 数据
        let person = reactive({
          Fname:'罗',
          Lname: '浩哲',
        })
       //计算属性(简写 没有考虑计算属性被修改的情况)
    //    person.fullName = computed (() =>{
    //       return person.Fname + ' ' + person.Lname
    //    })

       //计算属性(完整写法 考虑了计算属性被修改的情况)
         person.fullName = computed ({
            get(){
                // 读取时调用
              return person.Fname + ' ' + person.Lname
            },
            set(value){
                //修改时调用
               //按照 空格 拆分数组
               const nameArr = value.split(' ')
               person.Fname = nameArr[0],
               person.Lname = nameArr[1]
            }
       })


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



watch函数监视


1.Vue2中watch 配置功能一致
//Vue2 写法
watch:{
    //简写
    // sum(newValue,oldValue){
    //   console.log('sum的值改变了',oldValue,newValue);
    // },

   //完整写法
   sum:{
    immediate:true,//一山来就开启监视
    deep: true,//开启深度监视
    handler(newValue,oldValue){
     console.log('sum的值改变了',oldValue,newValue);
    }
   }
},

两个小坑
   监视reactive定义的响应式数据时 oldValue无法正确获取 强制开启深度监视deep配置无效
   监视reactive定义的响应式数据中的某个属性  deep配置无效

<script>
import {ref,reactive,watch} from 'vue'
export default {
  name: 'Demo',
  setup(){
    // 数据
    let sum = ref(0)
    let msg = ref('你好')

  let person = reactive({
    name: '罗浩哲',
    age:19,
    job:{
        a:{
            b:{
                d:20000
            }
        }
    }
  }) 
   //情况一、 监视 ref定义的一个响应式数据
//    watch(sum,(newValue,oldValue)=>{
//       console.log('sum的值变化了',oldValue,newValue);
//    },{immediate:true,deep:true})

 //情况二、 监视 ref定义的多个响应式数据
   watch([sum,msg],(newValue,oldValue)=>{
      console.log('sum或msg的值变化了',oldValue,newValue);
   },{immediate:true,deep:true})

  //情况三、 监视reactive定义的全部响应式数据 
       // 1.无法正确获取oldValue的值
       //2.强制开启了深度监视 (deep配置无效)
//      watch(person,(newValue,oldValue)=>{
//       console.log('name或age的值变化了',oldValue,newValue);
//    },{deep:false})//此处的deep无效

  //情况四、 监视reactive定义的单个响应式数据 要写成一个函数
//      watch(()=>person.age,(newValue,oldValue)=>{
//       console.log('age的值变化了',oldValue,newValue);
//    },{deep:false})//此处的deep无效


  //情况五、 监视reactive定义的单些响应式数据  使用数组包裹函数
   watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
      console.log('age或name的值变化了',oldValue,newValue);
   },{deep:false})//此处的deep无效

   //特殊情况 监视reactive中的多层嵌套对象的值要开启深度监视
    watch(()=>person.job,(newValue,oldValue)=>{
      console.log('job的值变化了',oldValue,newValue);
   },{deep:true})//此处由于监视的是reactive所定义的对象中的某个属性所以要开启深度监视deep


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

watch监视ref数据需不需要.value的问题
 setup(){
    // 数据
    let sum = ref(0)
    let msg = ref('你好')
  let person = ref({
    name: '罗浩哲',
    age:19,
    job:{
        a:{
            b:{
                d:20000
            }
        }
    }
  }) 
   //监视ref定义的基本数据类型属性值后面不加.value
   watch(sum,(newValue,oldValue)=>{
    console.log('sum的值变化了',newValue,oldValue);
   })
   //监视ref定义的对象类型数据 属性后要加.value或开启深度监视
   watch(person.value,(newValue,oldValue) =>{
    console.log('person的值变化了',newValue,oldValue);
   },{deep:true})
   
   
watchEffect 监视函数体内使用到的属性

watchEffect(()=>{
    const x1 = sum.value
    const x2 = person.age
    console.log('watchEffect配置的回调执行了');
  })


Vue3 生命周期钩子

1ae3c5f487a9e53a6371f3447f1728b8.png

import {ref, onBeforeMount, onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
export default {
  name: 'Demo',
  //通过配置项的形式使用生命周期钩子
  //#region 
  //  beforeCreate() {
  //   console.log('beforeCreate');
  //  },
  //  created(){
  //   console.log('created');

  //  },
  //   beforeMount(){
  //   console.log('beforeMount');

  //  },
  //  mounted() {
  //   console.log('mounted');
    
  //  },
  //  beforeUpdate(){
  //   console.log('beforeUpdate');

  //  },
  //  updated(){
  //   console.log('updated');

  //  },
  //  beforeUnmount(){
  //   console.log('beforeUnmount');
  //  },
  //  unmounted(){
  //   console.log('unmounted');
  //  },
   //#endregion
  setup(){
    console.log('setup');
    // 数据
    let sum = ref(0)
  //通过组合式API的形式使用生命周期钩子
   onBeforeMount(()=>{
     console.log('onBeforeMount');
   })
   onMounted(()=>{
     console.log('onMounted');
    
   })
   onBeforeUpdate(()=>{
     console.log('onBeforeUpdate');
    
   })
   onUpdated(()=>{
     console.log('onUpdated');
    
   })
   onBeforeUnmount(()=>{
     console.log('onBeforeUnmount');
    
   })
   onUnmounted(()=>{
     console.log('onUnmounted');
    
   })
   //返回一个对象(常用)
   return{
   sum,
   }
  }
}
</script>

自定义hook函数

    hook————本质是一个函数 把setup函数中使用的组合式API进行了封装
    类似于 vue2 中的minix
    自定义hook的优势,复用代码 让setup中的逻辑更加清晰易懂
    鼠标点击案列 
    新建hook文件夹下新建usePoint.js配置代码
    import {reactive,onMounted, onBeforeUnmount} from 'vue'
    export default function (){
        let point = reactive({
            x:0,
            y:0
        })
     
       //方法  获取点击时x, y的坐标
       function savePoint(event){
          point.x = event.pageX
          point.y = event.pageY
       }
     //实现鼠标点击事件的生命周期钩子
        onMounted(() =>{
         //绑定点击事件事件
         window.addEventListener('click',savePoint)
       })
        onBeforeUnmount(()=>{
           //  移除点击事件事件
           window.removeEventListener('click',savePoint)
        })

        return point
    }

    在Demo组件内引入使用
    // 引入点坐标
    import usePoint from '../hocks/usePoint'
    import {reactive, ref,onMounted, onUnmounted, onBeforeUnmount} from 'vue'
    export default {
      name: 'Demo', 
      setup(){
        // 数据
      let sum = ref(0)
      
      // 调用函数
      let point = usePoint()
     
       //返回一个对象(常用)
       return{
       sum,
       point
       }
      }
    }
    </script>

## toRef  toRefs

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

    <template> 
    <h1>{{person}}</h1> 
      <h2>姓名:{{name}}</h2>
      <h2>年龄:{{age}}</h2>
      <!-- <h2>薪资:{{salary}}K</h2> -->
      <h2>薪资:{{job.j1.salary}}K</h2>
      <button @click="name+= '!'">改名</button>
      <button @click="age++">增龄</button>
      <!-- <button @click="salary++">加薪</button> -->
      <button @click="job.j1.salary++">加薪</button>
    </template>

    <script>
    // 引入点坐标
    import {reactive, ref,toRef,toRefs} from 'vue'
    export default {
      name: 'Demo', 
      setup(){
        // 数据
      let person = reactive({
        name:'张三',
        age: 18,
        job:{
          j1:{
            salary:20
          }
        }
      })
       //返回一个对象(常用)
       return{
       person,
       //toRef  处理一个
      //  name: toRef(person,'name'),
      //  age: toRef(person,'age'),
      //  salary: toRef(person.job.j1,'salary'),
      //toRefs 处理多个
       ...toRefs(person)
       }
      }
    }
    </script>

## shallowReactive,shallowRef

    shallowReactive:只处理对象最外层属性的响应式(浅响应式)
    shallowRef:只能处理基本类型的响应式不进行对象类型的响应式处理
    什么时候使用
       如果有一个对象数据 结构比较浅 但变化时只是最外层属性变化 ==>  shallowReactive
       如果有一个对象数据 后续功能不会修改该对象的属性而是生成新的对象来替换  ==>  shallowReacti

## readonly shallowReadonly

    readonly:深层只读 
    shallowReadonly  浅层只读
    应用场景
    不希望数据被修改时  
      let person = reactive({
        name:'张三',
        age: 18,
        job:{
          j1:{
            salary:20
          }
        }
      })
       person = readonly(person)
       person = shallowReadonly(person)

## toRaw  markRaw

toRaw:
  作用:将一个由reactive生成的响应式对象转为普通对象
  使用场景:用于读取响应式数据对应的普通对象 对这个普通对象的所有操作不会引起页面更新
  
markRaw 
   作用:标记一个对象 使其永远不会再成为响应式对象
   应用场景
   1,有些值不应被设置成响应式的 例如复杂的第三方库
   2.当渲染具有不可变的数据源的大列表时跳过响应式转换可提高性能
   
function showRaw (){
    const p = toRaw(person)
    console.log(p);
  }

customRef(自定义ref)

    实现防抖案列
    <template>
     <input type="text" v-model="Keyword">
     <h4>{{Keyword}}</h4>
    </template>

    <script>
    import {ref,reactive,customRef} from 'vue'
    export default {
      name: 'App',
      setup(){
        function MyRef(value,delay){
          let timer
          return customRef((track,trigger)=>{
            return {
              get(){
              console.log('有人从MyRef读取数据了',value);
              track() //3.通知vue追踪value的改变
               return value//4.重新读取value的值
              },
              set(newValue){
              console.log('有人修改MyRef数据了',newValue);
              //清除定时器
              clearTimeout(timer)
               //等待0.5s再显示
              timer = setTimeout(()=>{
                 value =  newValue //1.将修改后的值赋给 value
                 trigger()//2.触发 通知vue重新解析模板
               },delay)
              }
            }
          })
        } 
    //  let Keyword = ref('hello')//使用Vue提供的ref
     let Keyword = MyRef('hello',500) //使用程序员自定义的ref
        return {
          Keyword
        }
      }
    }
    </script>

## provide与inject

```javascript
作用:实现祖孙组件间通信
套路:父组件中有一个provide选项来提供数据 后代组件有一个inject选项来使用这些数据
具体用法
祖组件中
setup(){
  let car = reactive({
    name:'奥迪',
    price: 100
  })

  provide('car',car)
  return{
    car
  }

    }, 
后代组件中
 setup(){
     const car =  inject('car')
     return{
        car
     }
    }

响应式数据的判断

    isRef: 检查一个值是否是一个人Ref对象
    isReactive: 检查一个对象是否由reactive创建的响应式代理
    isReadonly 检查一个对象是否是由readonly创建的只读代理
    isProxy 检查一个对象是否是由reactive或者readonly方法创建的代理

Compositio API 的优势

    我们可以更加优雅的组织我们的代码,函数 让相关的代码更加有序的组织在一起

Fragment组件

    Vue3中可以没有根标签其实是内部将多个标签包含在一个Fragment虚拟元素中

Teleport组件

    Teleport 是一一种可以将我们的组件html结构移动到指定位置的技术
    <template>
        <button @click="isShow = true">弹出对话框</button>
        <teleport to="body"> //移动到body
    <div v-if="isShow" class="mask">
        <div  class="dialog">
            <h3>我是一个对话框</h3>
            <ul>
            <li>324234532</li>
            <li>324234532</li>
            <li>324234532</li>
            <li>324234532</li>
           </ul>
           <button @click="isShow = false">关闭对话框框</button>
        </div>
    </div>
          </teleport>

## Suspense

    等待异步组件时渲染一些额外内容 让应用有更好的用户体验
    使用步骤
       异步引入组件
       import { defineAsyncComponent } from "vue";
       const Child = defineAsyncComponent(() => import("./components/Child.vue")); //异步引入
       使用Suspense包裹组件 并配置好default 与fallback
        <Suspense>
          <template v-slot:fallback> 
           <h2>加载中。。。</h2>
          </template>
          <template v-slot:default>
            <Child />
          </template>
        </Suspense>