vue3快速上手

vue3快速上手

一、创建vue3项目

1.使用 vue-cli 创建
##查看vue-cli版本,确保 @vue/cli 版本号在 4.5.0以上
vue -V

##安装或升级 vue脚手架
npm install -g @vue/cli

##创建项目
vue create project-name

##启动项目
cd project-name
npm run serve
2. 使用 vite创建

官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite

vite官网:https://vitejs.cn/guide/

二、常用的 Composition API

1.拉开序幕的 setup
  1. 理解:vue3中一个新的配置项,值为一个函数
  2. setup 是所有 Composition API(组合式API) “表演的舞台”
  3. 组件所用到的:数据、方法等等,都要配置在setup中。
  4. setup函数的两种返回值:
    1. 若返回一个对象,则对象中的属性、方法,在模板中可以直接使用。
    2. 若返回一个渲染函数,则可以自定义渲染内容。
  5. 注意点:
    1. 尽量不要与Vue2.x 配置混合使用
      • Vue2.x配置(data、methods、computed ...)中可以访问到 setup中的属性,方法
      • 在setup中不能访问到Vue2.x的配置(data、methods、computed ...)
      • 如果有重名,setup优先
    2. setup不能是一个async 函数,因为返回值不再是return的对象,而是promise,模板中看不到return对象中的属性。
2.ref函数
  • 作用:定义一个响应式的数据
  • 语法:const xxx = ref(initValue)
    • 创建一个包含响应式数据的引用对象
    • 基本数据类型:响应式依然是靠 Object.defineProperty()的get与set完成的
    • 对象类型数据:内部求助了Vue3.0中的一个新函数—— reactive函数。
3.reactive函数
  • 作用:定义一个对象类型的响应式数据(基本类型不要用,要用ref函数)
  • 语法:const 代理对象 = reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
  • reactive定义的响应式数据是“深层次的”
  • 内部基于ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作
4.Vue3.0 中的响应式原理
vue2.0的响应式
  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹)。

    Object.defineProperty(data,'count',{
      get(){},
      set(){}
    })
  • 存在问题

    • 新增属性、删除属性,界面不会更新。
    • 直接通过下标修改数组,界面不会自动更新。
Vue3.0的响应式
  • 实现原理

    • 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。

    • 通过Reflect(反射):对源对象的属性进行操作。

    • MDN文档中描述的Proxy与Reflect:

    • Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

    • Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

      let person = {
      name:'张三',
      age:20
      }
      let p = new Proxy(person,{
      get(target,prop){
        console.log(`拦截到获取person的${prop}属性`)
        console.log(target,prop)
        return Reflect.get(target,prop)
      },
      set(target,prop,value){
        console.log(`拦截到设置person的${prop}属性,设置的新值为${value}`)
        console.log(target,prop,value)
        return Reflect.set(target,prop,value)
      },
      deleteProperty(target,prop){
        console.log(`拦截到删除person的${prop}属性`)
        return Reflect.deleteProperty(target,prop)
      }
      })
      //获取name
      console.log(p.name)
      //修改name
      p.name = '李四'
      //新增属性
      p.sex = '男'
      //删除属性
      delete p.sex
5.reactive对比ref
  • 从定义数据的角度对比
    • ref用来定义:基本类型数据。
    • reactive用来定义:对象(或数组)类型的数据。
    • 备注:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)
    • reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时,模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据均不需要.value
6.setup的两点注意
  • setup执行的时机
    • 在beforeCreate之前执行一次,this是undefined。
  • setup的参数
    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
    • context:上下文对象
    • attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs
    • slots:收到的插槽内容,相当于this.$slots
    • emit:分发自定义事件的函数,相当于this.$emit
7.计算属性与监视
1.computed函数
  • 与Vue2.x中的配置功能一致

  • 写法

    import { reactive,computed } from 'vue'
    
    export default {
    setup(){
      let person = reactive({
        firstName:'张',
        lastName:'三'
      })
      //计算属性简写(没有考虑计算属性被修改的情况)
      let fullName = computed(()=>{
        return person.firstName + '-' + person.lastName
      })
      //计算属性完整写法(考虑读写)
      let fullName1 = computed({
        get(){
          return person.firstName + '-' + person.lastName
        },
        set(value){
          const nameArr = value.split('-')
          person.firstName = nameArr[0]
          person.lastName = nameArr[1]
        }
      })
      return {
        person
      }
    }
    }
2.watch函数
  • 与vue2.x中watch配置功能一致

  • 两个小”坑“

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。

    • 监视reactive定义的响应式数据中的某个属性时:deep配置有效。

    import {ref,reactive,watch} from 'vue'
    export default {
      setup(){
        let name = ref('张三')
        let age = ref(20)
        //情况一:监听ref定义的响应式数据
        watch(name,(newValue,oldValue)=>{
          console.log(`name变化了,新值是${newValue},老值是:${oldValue}`)
        })
        //情况二:监听多个ref定义的响应式数据
        watch([name,age],(newValue,oldValue)=>{
          console.log(`数据发生变化了,新值是${newValue},老值是${oldValue}`)
          //newValue 与 oldValue 数据类型为数组
        })
        let person = reactive({
          name:'张三',
          age:20,
          list:[{name:'张三',age:10},{name:'李四',age:20}]
        })
        /**
        *   情况三:监视reactive定义的响应式数据
        *   1.无法获取到oldValue
        *   2.监听是深度监听的 (deep配置无效)
        */
        watch(person,(newValue,oldValue)=>{
          console.log('person变化了',newValue,oldValue)
        })
        /**
        *   情况四:监听reactive定义的响应式数据中的某个属性
        *   可以正确获取到  oldValue
        */
        watch(()=>person.name,(newValue,oldValue)=>{
          console.log('name属性变化了',newValue,oldValue)
        })
        /**
        *   情况五:监听reactive 定义的响应式数据中的某些属性
        *   可以正确获取到  oldValue
        *   newValue与oldValue为数组,顺序为watch传入的顺序
        */
        watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
          console.log('person改变了',newValue,oldValue)
        })
        /**
        *   特殊情况:监听reactive定义的响应式数据中的某个属性,并且这个属性是对象或数组
        *   deep有效 (不使用deep无法监听深层属性的变更)
        *   无法正确获取 oldValue
        */
        watch(()=>person.list,(newValue,oldValue)=>{
          console.log('list属性变化了',newValue,oldValue)
        },{deep:true})
    
      }
    }
3.watchEffect函数
  • watch:既要指明监视的属性,也要指明监视的回调

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

  • watchEffect有点像computed

    • 但computed注重的是计算出来的值(回调函数的返回值),所以必须要写返回值
    • 而watchEffect注重的是过程(回调函数的函数体),所以不用写返回值
    //watchEffect所指定的回调中用到的数据只要发生变化了,则执行函数
    watchEffect(()=>{
    const name = person.name;
    console.log('watchEffect配置的回调执行了')
    })
8.生命周期
  • Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有两个被更名
    • beforeDestory 改名为 beforeUnmount
    • destoryed 改名为 unmounted
  • Vue3.0提供了Composition API 形式的生命周期钩子,与Vue2.x 中钩子对应关系如下
    • beforeCreate > setup
    • created==> setup
    • beforeMount > onBeforeMount
    • mounted => onMounted
    • beforeUpdate ===> onBeforeUpdate
    • updated===> onUpdated
    • beforeUnmount => onBeforeUnmount
    • unmounted ==> onUnmounted
9.自定义hook函数
  • 什么是hook?——本质是一个函数,把setup函数中使用的Composition API进行了封装
  • 类似于vue2.x中的mixin
  • 自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂
10. toRef 与 toRefs
  • toRef作用:创建一个ref对象,其value值指向另一个对象中的某个属性

    • 语法:const name = toRef(person,'name')
    • 应用:需要将响应式对象中的某个属性单独提供给外部使用时
  • toRefstoRef功能一致,但可以创建多个ref对象,(解构,但不失去响应式)

    const person = reactive({
    name:'张三',
    age:20
    })
    //用展开运算符获取  name,age
    return {
    ...toRefs(person)
    }
    

三、其他的Composition API

1.shallowReactive与shallowRef
  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)
  • shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
  • 什么时候使用?
    • 如果有一个对象数据,结构比较深,但变化只是最外层属性变化 > shallowReactive
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换 ===> shallowRef
2.readonly 与 shallowReadonly
  • readonly :让一个对象(响应式或纯对象)或ref变为只读的 (深层次)
  • shallowReadonly : 让一个对象(或响应式对象)数据变为只读的 (浅只读)
3.toRaw与 markRaw
  • toRaw :
    • 作用:将一个由reactive生成的响应式对象转化为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw :
    • 作用:标记一个对象,使其永远不会再成为响应式对象。
    • 使用场景:
      1. 有些值不应该被设置为响应式的,例如复杂的第三方类库。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换,可以提高性能。
4.customRef
  • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。

  • 例如:实现防抖效果

    • 注意:如果自定义ref是一个对象的话,需要手动 使用reactive.value处理为深层的响应式对象(个人理解)
    <template>
    <div>
      <input type="text" v-model="text">
      <br />
      <p>{{text}}</p>
      <br />
      <br />
      <p v-if="person.name">{{person.name}}-----{{person.age}}</p>
      <br />
      <br />
      <div>
        <button type="button" @click="addPerson">添加人</button>
    
        <button type="button" @click="changePerson">修改人</button>
      </div>
    </div>
    </template>
    
    <script>
    import {customRef, reactive} from 'vue'
    export default {
    setup(){
      //自定义ref
      function myRef(value,delay = 200){
        let timer
        return customRef((track,trigger)=>{
          return {
            get(){
              track() //通知vue追踪value的变化
              return value
            },
            set(newValue){
              clearTimeout(timer)
              timer = setTimeout(()=>{
                value = newValue
                console.log('修改值',newValue)
                trigger()//通知vue去重新解析模板
              },delay)
            }
          }
        })
      }
      const text = myRef('',0)
      const person = myRef({},200)
      function addPerson(){
        person.value = reactive({name:'张三',age:20})
      }
      function changePerson(){
        person.value.name = '李四'
      }
      return {
        text,
        person,
        addPerson,
        changePerson
      }
    
    }
    }
    </script>
    
    <style>
    
    </style>
5.provide与inject
  • 作用:实现祖孙组件间通信

  • 父组件中有一个 provide选项来提供数据,子组件有一个inject选项来开始使用这些数据

  • 具体写法

    1. 祖组件中:

      setup(){
      let person = reactive({name:'张三',age:20})
      provide('person',person)
      }
    2. 孙组件中:

      setup(props,context){
      const person = inject('person')
      return {
       person
      }
      }
6.响应式数据的判断
  • isRef 检查一个值是否为一个ref或者shallowRef对象
  • isReactive检查一个对象是否是由reactiveshallowReactive创建的响应式代理
  • isReadonly 检查一个对象是否是由 readonly创建的只读代理
  • isProxy检查一个对象是否是由reactive或者readonly方法创建的代理

四、Composition API 的优势

1.Options API 存在的问题

使用传统optionsAPI中,新增或者修改一个需求,就需要分别在data、methods、computed里修改

2.Composition API 的优势

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

五、新的组件

1.Fragment
  • 在Vue2中:组件必须有一个根标签
  • 在Vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中(不参与渲染)
  • 好处:减少标签层级,减少内存占用
2.Teleport
  • 什么是 Teleport?——Teleport是一种能够将我们的组件html结构移动到指定位置的技术。

    • to -string 必须是有效的查询选择器 或者 HTMLElement,例如 body, #some-id, .some-class
    • disabled-boolean 。此选项可用于禁用 <teleport>的功能,这意味着其插槽内容将不会移动到任何位置,而是在父组件中指定了<teleport>的位置渲染。
    <template>
    <div>
      <p>移动组件html结构</p>
      <teleport to="移动位置" :disable="displayValue">
        </teleport>
    </div>
    </template>
3.Suspense 实验中
  • 等待异步组件时渲染一些额外的内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件
    import {defineAsyncComponent} from 'vue'
    const Child = defineAsyncComponent(()=>import('@/components/Child.vue'))
    • 使用Suspense包裹组件,并配置好defaulefallback 插槽的内容 (defaule为异步组件、fallback为异步组件渲染成功前的展示内容)
    <template>
        <div class="app">
        <Suspense>
          <Child></Child>
          <template v-slot:fallback>
            <p>加载中...</p>
                </template>
        </Suspense>
      </div>
    </template>

六、其他

1.全局API的转移
  • Vue2.x中有许多全局API和配置

    • 例如:注册全局组件、注册全局指令等。
    //注册全局组件
    Vue.components('MyButton',{
      data:()=>({
        count:0
      }),
      template:'<button @click="count ++">{{count}}</button>'
    })
    //注册全局指令
    Vue.directive('focus',{
      inserted:el=> el.focus()
    })
  • Vue3.0中对这些API做出了调整:

    • 将全局API,即:Vue.xxx 调整到应用实例app
    2.x全局API(Vue 3.x实例API(app
    Vue.config.xxx app.config.xxx
    Vue.config.productionTip 移除
    Vue.component app.component
    Vue.directive app.directive
    Vue.use app.use
    Vue.prototype app.config.globalProperties
2.其他改变
  • 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

  • 移除v-on.native修饰符

    • 父组件中绑定事件
    <myComponent v-on:close="handleClose" v-on:click="handleNativeClick"></myComponent>
    • 子组件中声明自定义事件
    <script>
      export default{
        emits:['close']
      }
    </script>
  • 移除过滤器(filter)