Vue3快速上手

422 阅读11分钟

Vue3快速上手

一、创建Vue3.0工程

  1. 使用vue-cli创建

    官方文档:cli.vuejs.org/zh/guide/cr…

    ## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
    vue --version
    ## 安装或者升级你的@vue/cli
    npm install -g @vue/cli
    ## 创建
    vue create stu-vue3
    ## 启动
    cd stu-vue3
    npm run serve
    
  2. 使用vite创建

    官方文档:v3.cn.vuejs.org/guide/insta…

## 创建工程
## 注意,vite-app为固定命令, project-name如果没有的时候,当前目录需要为空,如果有值:则会同步帮你创建这个目录
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev

二、常用Composition API

  1. 拉开序幕的setup: v3.cn.vuejs.org/api/composi…

    1. 理解:vue3.0中一个新的配置项,值为一个函数
    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优先(意思就是以setup为主)
      2. setup不能是一个async函数,因为返回值不再是return的对象,而是Promise,模板看不到return对象中的属性
  2. ref 函数

    1. 作用:定义一个响应式的数据
    2. 语法:const xxx = ref(initValue)
      • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
      • JS中操作数据:xxx.value
      • 模板中读取数据:不需要xxx.value,直接<div>{{xxx}}</div>
    3. 备注:
      • 接收的数据可以是:基本类型,也可以是对象类型
      • 基本类型的数据:响应式依然是靠object.defineProperty()的get与set完成的
      • 对象类型的数据:内部"求助"了Vue3.0中的新函数----ractive函数
  3. reactive函数

    1. 作用:定义一个对象类型的响应式数据(基本类型不要用它,用ref函数)
    2. 语法:const xxx = reactive(这里是一个对象/数组),返回一个代理对象(通过Proxy代理返回的对象)
    3. reactive定义的响应式数据是深层次
    4. 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据都是响应式的
  4. Vue3.0中的响应式原理

    1. Vue2.x的响应式原理

      • 实现原理:

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

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

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

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

      • 实现原理:

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

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

        • MDN文档中描述的ProxyReflect

          • Proxy: developer.mozilla.org/zh-CN/docs/…

          • Reflect:developer.mozilla.org/zh-CN/docs/…

            // 原理代码
            const newData = new Proxy(data, {
                // 拦截读取属性值
                get (target, propName) {
                    return Reflect.get(target, propName);
                },
                // 拦截设置属性或者添加新属性
                set (target, propName, value) {
                    return Reflect.set(target, propName, value);
                },
                // 拦截删除属性
                deleteProperty (target, propName) {
                    return Reflect.deleteProperty(target, propName);
                }
            })
            
            newData.name = 'yydream';
            
  5. reactive对比ref

    1. 从定义数据角度对比:
      • ref用来定义:基本数据类型
      • reactive用来定义:对象(或数组)数据类型
      • 备注:ref也可以用来定义(对象/数组的数据类型),它内部会自动通过reactive转为代理对象
    2. 从原理角度对比:
      • ref通过Object.defineProperty()getset来实现响应式(数据劫持)
      • reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据
    3. 从使用角度对比:
      • ref定义的数据:script中操作数据需要xxx.value,读取数据时模板中直接读取,不需要xxx.value
      • reactive定义的数据:操作数据与读取数据,均不需要xxx.value
  6. setup的两个注意点

    1. setup执行的时机:
      • beforeCreate之前执行一次,thisundefined
      • setup的参数:
        • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
        • context:上下文对象
          • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置项中声明的属性,相当于this.$attrs
          • slots:收到的插槽内容,相当于this.$slots
          • emit:分发自定义事件的函数,相当于this.$emit,注意:如果想要向外触发事件,还需要在emits配置项(数组类型)中声明(事件名称),参考:v3.cn.vuejs.org/guide/migra…
  7. 计算属性与监听

    1. computed函数

      • 与Vue2.x中的computed配置项功能一致

      • 语法:

        import { reactive, computed } from 'vue';
        
        setup () {
        	let person = reactive({firstName: '', lastName: ''});
        	// 计算属性--简单写法
        	let fullName = computed(() => {
        		return person.firstName + ' - ' + person.lastName;
        	})
            // 计算属性--完整写法
            let fullName2 = computed({
                get () {
                    return person.firstName + ' - ' + person.lastName;
                },
                set (val) {
                    const nameArr = val.split('-');
                    person.firstName = nameArr[0];
                    person.lastName = nameArr[1];
                }
            })
        }
        
    2. watch函数

      • 与Vue2.x中的watch配置项一致

      • 注意“坑”:

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

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

          import { ref, reactive, watch } from 'vue';
          
          setup () {
              let sum = ref(0);
              let sum2 = ref(2);
              let person = reactive({name: 'yydream'});
              
              // 情况一, 监视一个ref定义的响应式数据
              watch(sum, (newVal, oldVal) => {
                  console.log('sum变化了', newVal, oldVal)
              }, {immediate: true}); // immediate默认进来就执行一次
              
              // 情况二,监视多个ref定义的响应式数据
              watch([sum, sum2], (newVal, oldVal) => {
                   // newVal与oldVal都是一个数组,对应下表就是传入的数据位置
                  console.log('sum或sum2变化了', newVal, oldVal);
              });
              
              /** 
              	情况三,监视reactive定义的响应式数据
              	若wath监视的是reactive定义的响应式数据,则无法正确获得oldVal值,与强制开启了深度监视
              */
              watch(person, (newVal, oldVal) => {
                  // 此时:newVal === oldVal
                  console.log('person变化了', newVal, oldVal);
              }, {immediate: true, deep: false }); // 此处的deep配置不生效
              
              /**
              	情况四,监视reactive定义的响应式数据中的某个属性
              */
              watch(() => person.name, (newVal, oldVal) => {
                  console.log('person.name变化了', newVal, oldVal);
              }, {immediate: true, deep: true}); // 此处的deep配置生效
          }
          
    3. watchEffect函数(会自动调用一次)

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

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

      • watchEffect有点类似与computed:

        • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值

        • 而watchEffect要注意的是过程(回调函数的函数体),所以不用谢返回值

          import { ref, reactive, watchEffect } from 'vue';
          
          setup () {
              let sum = ref(0);
              let person = reactive({name: 'yydream'});
              
              watchEffect(() => {
                  const x1 = sum.value;
                  const x2 = person.name;
                  console.log('这里改变了哦');
              })
          }
          
  8. 生命周期

    1. vue3.0中可以继续使用Vue2.x中的生命周期狗子,但是有两个被更名:

      Vue2.x配置项Vue3.0配置项
      beforeDestorybeforeUnmount
      destroyedunmounted
    2. Vue3.0也提供了Composition API形式的生命周期狗子,与Vue2.x中对应关系如下:

      Vue3.0配置项的Vue3.0组合式的
      beforeCreatesetup()
      createdsetup()
      beforeMountonBeforeMount
      mountedonMounted
      beforeUpdateonBeforeUpdate
      updatedonUpdated
      beforeUnmountonBeforeUnmount
      unmountedonUnmounted
  9. 自定义hook函数

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

    • 类似于vue2.x中的mixin

    • 自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂

      // 比如获取鼠标点击页面坐标  新建文件:useMousePosition.js
      import { reactive, toRefs, onMounted, onBeforeUnmount } from 'vue';
      
      /**
       * 获取点击鼠标的页面坐标
       */
      export default function () {
          const pageInfo = reactive({
              x: 0,
              y: 0
          });
      
          // 用于手机点击事件坐标的函数
          const updatePosition = (e) => {
              pageInfo.x = e.pageX;
              pageInfo.y = e.pageY;
          }
      
          // 挂载后绑定点击事件
          onMounted (() => {
              document.addEventListener('click', updatePosition);
          })
      
          // 卸载前解除点击监听
          onBeforeUnmount(() => {
              document.removeEventListener('click', updatePosition);
          })
      
          return {...toRefs(pageInfo)};
      }
      
      // 使用上面的文件
      <template>
        <h3>当前点击坐标:x: {{x}}, y: {{y}}</h3>
      </template>
      
      <script>
      import useMousePosition from './useMousePosition';
      export default {
        name: "App",
        setup() {
          let pageInfo = useMousePosition();
          return { ...pageInfo };
        },
      };
      </script>
      
  10. toRef

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

三、其它Composition Api

  1. shallowReactive与shallowRef

    1. shallowReactive:只处理对象最外层属性的响应式(浅响应式)
    2. shallowRef: 只处理基本数据类型的响应式,不进行对象的响应式处理
    3. 使用时机:
      • 如果一个对象数据,结构比较深,但变化时只是外层属性变化 ===> shallowReactive
      • 如果一个对象数据,后续功能不会修改该对象中的属性,而是用新的对象来替换 ===> shallowRef
  2. readonly与shallowReadonly

    1. readonly:让一个响应式数据变为只读的(深只读)
    2. shallowReadonly:让一个响应式数据变为只读的(浅只读)
    3. 使用时机: 不希望数据被修改时
  3. toRaw与markRaw

    1. toRaw:
      • 作用: 将一个由reactive生成的响应式对象转为普通对象
      • 使用时机:由于读取响应对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
    2. markRaw:
      • 作用:标记一个对象,使其永远不会再成为响应式对象
      • 使用时机:
        1. 有些值不应被设置为响应式的,例如复杂的第三方库等。
        2. 当渲染具有不可变数据的大列表时,跳过响应式转换可以提高性能。
  4. customRef

    1. 作用:创建一个自定义的ref, 并对其依赖项跟踪和更新触发,进行显示控制(个人理解,就是自定义一个函数,对customRef提供的get,set的操作拦截处理)

    2. 列子:实现防抖效果:

      // 新建myRef.js文件
      import { customRef } from 'vue';
      
      /**
       * 实现输入框节流
       */
      export default function (value, time) {
          let  timer = null;
          return customRef((track, trigger) => {
              return {
                  get () {
                      // 追踪
                      track();
                      return value;
                  },
                  set (newVal) {
                      timer && clearTimeout(timer);
                      timer = setTimeout(() => {
                          value = newVal;
                          // 触发更新(通知更新)
                          trigger();
                      }, time)
                  }
              }
          })
      }
      
      // 调用
      <template>
      <input type="text" v-model="inputVal">
      <h3>当前的值:{{inputVal}}</h3>
      </template>
      
      <script>
      import myRef from './myRef';
      export default {
        name: "App",
        setup() {
          let inputVal = myRef('', 300);
          return {
            inputVal
          }
        },
      };
      </script>
      
  5. provide与inject

    1. 作用:实现祖孙组件间通讯

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

    3. 语法

      // 1. 父组件中
      import { reactive, provide } from 'vue';
      setup () {
          let car = reactive({name: '宝马', price: 40});
          provide('car', car);
          return {car};
      }
      
      // 2. 子组件中
      import { inject } from 'vue';
      setup () {
          const car = inject('car');
          return {car}
      }
      
  6. 响应式数据的判断

    1. isRef:检查一个值是否为一个ref对象
    2. isReative:检查一个对象是否是由reactive创建的响应式代理
    3. isReadonly:检查一个对象是否是由readonly创建的只读代理
    4. isProxy:检查一个对象是否由reactive或者readonly方法创建的代理

四、Composition API 的优势

  1. Options API 存在的问题

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

  2. Composition API的优势

    可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起(个人理解:可以使用hook的方式书写每一个功能,之后引入使用)

五、新的组件

  1. Fragment

    • vue2.x中,组件必须有一个根标签
    • vue3.0中,组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
    • 好处就是,减少标签层级,减小内存占用
  2. Teleport

    • 啥是teleport?

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

      • 示例:

        <teleport to="xxx">
            <!-- xxx代表移动的位置,比如body,#app,.class -->
        	<div v-if="isShow" class="mask">
                <div class="dialog">
                    <h3>我是一个弹窗</h3>
                    <button @click="isShow = false">关闭弹窗</button>
                </div>
            </div>
        </teleport>
        
  3. Suspense

    1. 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

    2. 使用步骤

      • 异步引入组件

        import { defineAsyncComponent } from 'vue'
        const Child = defineAsyncComponent(() => import('./component/Child.vue'));
        
        // 别忘了注册组件
        components: { Child }
        
      • 使用Suspense包裹组件,并配置好defaultfallback插槽

        <template>
        	<div class="App">
                <h3>我是App组件</h3>
                <Suspense>
                	<template v-slot:default>
        				<Child />
                    </template>
                    <template v-slot:fallback>
                    	<h3>加载中......</h3>
                    </template>
                </Suspense>
            </div>
        </template>
        

六、其它

  1. 全局API的转移

    1. Vue2.x有许多全局API和配置

      • 例如:注册全局组件、注册全局指令等

        // 注册全局组件
        Vue.component('MyButton', {
            data: () => ({
                const: 0
            }),
            template: '<button @click="count++">Clicked {{ count }} times.</button>'
        })
        
        // 注册全局指令
        Vue.directive('focus', {
            inserted: el => el.focus()
        })
        
    2. Vue3.0中对这些API做出了调整

      • 将全局的API,即:Vue.xxx调整到应用实例app

        Vue2.x全局API(Vue)Vue3.0实例API(app)
        Vue.config.xxxapp.config.xxx
        Vue.config.productionTip(生成环境的提示)移除
        Vue.componentapp.component
        Vue.directiveapp.directive
        Vue.mixinapp.mixin
        Vue.useapp.use
        Vue.prototypeapp.config.globalProperties
  2. 其它改变

    1. data配置项应始终被声明为一个函数

    2. 过度类名的更改

      • Vue2.x写法

        .v-enter,
        .v-leave-to {
            opacity: 0;
        }
        .v-leave,
        .v-enter-to {
            opacity: 1;
        }
        
      • Vue3.0写法

        .v-enter-from,
        .v-leave-to {
            opacity: 0;
        }
        .v-leave-from,
        .v-enter-to {
            opacity: 1;
        }
        
    3. 移除keyCode作为v-on的修饰符,同时也不再支持config.keyCodes

    4. 移除v-on.native修饰符

      • 父组件中绑定事件

        <my-component v-on:close='xxxClose' v-on:click="xxxClick"></my-component>
        
      • 子组件中声明自定义事件

        <template>
        	<button @click="handleClose">
                取消
            </button>
            <button @click="handleClick">
                确认
            </button>
        </template>
        
        <script>
        export default {
            name: 'MyComponent',
            emits: ['close', 'click'], // 这里配置了代表是自定义事件,不然父组件的@click默认代表的是原生事件
            setup (props, context) {
                function handleClick () {
                    context.emit('click', '我点击了确认');
                }
                function handleClose () {
                    context.emit('close', '我点击了取消')
                }
                
                return {handleClick, handleClose}
            }
        }
        </script>