Vue3 快速上手

140 阅读6分钟

创建 Vue3 工程

  1. 使用 vue-cli 创建
    // @vue/cli@4.5.0 或以上
    // 创建项目
    vue create vue_test
    // 启动
    cd vue_test
    npm run serve
    // or
    yarn serve
  1. 使用 vite 创建工程
    // 创建工程
    npm init vite-app <project-name>
    // or
    yarn init vite-app <project-name>
    
    // 安装依赖
    cd <project-name>
    npm install
    // or
    yarn
    
    // 运行
    npm run dev
    // or
    yarn dev
  • vite 是下一代的前段构建工具
    • 其在开发环境中,无需打包操作,可快速的冷启动
    • 轻量快速的热重载(HMR)
    • 真正的按需编译,不再等待整个应用编译完成
  • 传统构建与 vite 构建对比图

1.png

2.png

Composition API

拉开序幕的 setup

  1. setup 是 Vue3.0 中的一个新的配置项,值为一个函数
  2. 所有的 Composition API(组合 API)都会配置在 setup 中
        <script>
            export default {
                name: 'App',
                setup(){
                    // 数据
                }
            }
        </script>
    
  3. setup 函数有两种返回值
    • 返回一个对象,则对象中的属性、方法,在模板中均可以使用
    • 如返回一个渲染函数,则可以自定义渲染内容 (了解一下就好,会覆盖模板)
        <script>
            import { h } from 'vue'
            export default {
                name: 'App',
                setup(){
                    ...
                    
                    return ()=> h('h1', 'Hello')
                }
            }
        </script>
    
  4. 注意:
  • 在 Vue3 中,可以兼容 Vue2 中的配置项,但是不建议这么做,因为在 Composition API 中读取不到 options 配置选项中的数据
  • 正常来讲 setup 不能是一个 async 函数,因为返回值不再是 return 的对象,而是 promise,模板看不到 return 对象中的属性,除非配置了异步组件(使用了 Suspense + 异步组件)
  • setup 会在 beaforeCreate 之前执行一次, this 是 undefined
  • setup 可以接收两个参数:props 和 context

ref 函数

  1. ref 函数通过 Object.defineproperty 实现 Vue3 中简单数据类型的响应式,经过 ref 处理后的数据,会成为RefImpl(引用实现)的实例对象,简称引用对象(reference 对象)
        <script>
            import { ref } from 'vue'
            
            export default {
                name: 'App',
                setup(){
                    let name = ref('张三')
                    let age = ref(18)
                }
            }
        </script>
    
  2. 通过 value 读取或者修改引用对象(在模板中读取数据不需要 .value)
        ...
        setup(){
            let name = ref('张三')
            let age = ref(18)
            
            // name.value  age.value
        }
    
  3. ref 函数接收的数据类型可以是基本类型,也可以是对象类型,对于基本类型,响应式依然是依靠 Object.defineProperty() 的 get 与 set 实现的,对于对象类型, ref 函数内部“求助了” Vue3中的 rective 函数

reactive 函数

  1. reactive 函数只能用于定义一个对象类型的响应式数据
  2. reactive 定义的响应式数据是“深层次的”
  3. 从 reactive 的实例(代理)对象中读取或者修改数据时,不需要 .value
  4. reactive 是依赖 ES6 中的 Proxy 实现的
  • 插入一个小技巧 自定义折叠代码块
    // #region
    ...
    // #endregion

Vue3 中的响应式原理

  • Vue2 的响应式
    • 实现原理:
      1. 对象类型: 通过 Object.defineProperty() 对对象的已有属性值的读取、修改进行拦截(数据劫持)
      2. 数组类型: 通过重写更新数组的一系列方法来实现拦截(对数组的变异方法进行了包裹)
          Object.defineProperty(data, 'count', {
              get(){},
              set(){}
          })
      
    • 存在的问题:
      1. 新增属性、删除属性,界面不会更新
        • 要通过 this.$set(目标对象,'要添加的属性',属性值) 和 this.$delete() 来处理
      2. 直接通过下标修改数组,界面不会自动更新
        • 要通过 this.$set(目标数组,索引,值) 和 数组的变异方法来处理
  • Vue3.0 的响应式
    • 实现原理:
      1. 通过 Proxy(代理): 拦截对象中任意属性的变化,包括属性值的读写、属性的添加和删除
      2. 通过 Reflect(反射): 对被代理对象的属性进行操作
          const person = {name: '张三', age: 18}
          
          const p = new window.Porxy(person,{
              // 数据被读取时调用
              get(target,propName){
                  // target: 源数据 person   propName:读取的属性
                  console.log(`${target}中的${propName}属性被读取了`)
                  return reflect.get(target, propName)
              },
              // 数据被更新或新增时调用
              set(target, propName, value){
                  console.log(`${target}中的${propName}属性被修改了,更新界面`)
                  reflect.set(target, propName, value)
              },
              // 数据被删除时调用
              deleteProperty(target, propName){
                  console.log(`${target}中的${propName}属性被删除了,更新界面`)
                  return reflect.deleteProperty(target, propName)
              }
          })
      

Vue3.0 中的 watch

  • Vue3.0 中的 watch 是一个 Composition API,所以使用之前需要引入
  • watch 接收三个参数:被监视的数据、回调函数、配置项
  • 坑1:当 watch 用于监视 reactive 定义个响应式数据时,无法获取正确的 oldValue
  • 坑2:强制开启深度监视,deep 配置无效
     import {watch, ref, toRef, toRefs} from 'vue'
     export default {
         setup(){
             let sum = ref(0)
             let msg = ref('hello')
             let person = reactive({
                 name: '张三',
                 age: 18,
                 job:{
                     j1:{
                         salary: 20
                     }
                 }
             })
         // 1. 场景一:监视 ref 所定义的一个响应式数据
           watch(sum,(newValue, oldValue)=>{},{immediate: true})
         // 2. 场景二:监视 ref 所定义的多个响应式数据
           watch([sum,msg],(newValue,oldValue)=>{})
         // 3. 场景三:监视 reactive 所定义的一个响应式数据的全部属性
           watch(person,(newValue,oldValue)=>{},{deep: false}) // 此时无法获取正确的 oldValue
         // 4. 场景四:监视 reactive 所定义的一个响应式数据中的一个属性
           watch(()=>person.name,(newValue,oldValue)=>{})
         // 5. 场景五:监视 reactive 所定义的一个响应式数据中的多个属性
           watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{})
         // 6. 场景六:监视 reactive 所定义的一个响应式数据中的一个对象属性
           watch(()=>person.job,(newValue,oldValue)=>{},{deep:true}) // 此时必须开启深度监视
         }
         return {
             sum,
             msg,
             person,
             /* toRef 的作用是创建一个 ref 对象,其 value 值指向另一个对象中的某个属性,用来使模板更加精简 */
             name: toRef(person, 'name'),
             age: toRef(person, 'age'),
             salary: toRef(person.job.j1, 'salary')
             // 或者直接使用 toRefs,与 toRef 功能相似,但可以批量创建多个 ref 对象
             ...toRefs(person)
         }
     }
  • watchEffect 函数
    1. watchEffect 不用知名监视哪个属性,在函数的回调中,用到了 setup 中的哪个属性,就监视哪个属性
    2. watchEffect 所指定的回调中用到的数据发生变化,则直接重新执行回调
    3. watchEffect 默认开启了 immediate 和 deep
    4. watchEffect 有点像 computed
      • 但 computed 更注重计算出来的值,所以必须写返回值
      • watchEffect 更注重过程,不需要写返回值
        watchEffect(()=>{
            const a = sum.value
            const b = person.name
            console,log('watchEffect 指定的回调执行了')
        })
    

自定义 hook 函数

  • hook 本质是一个函数,把 setup 函数中使用的 Composition API 进行封装,类似 Vue2 中的 mixin
  • 自定义 hook 的优势: 复用代码,让 setup 中的逻辑更清晰
    1. 将需要复用的代码封装在 src/hooks/xxx.js 中,比如实现一个获取鼠标坐标的功能:src/hooks/usePoint.js
        // src/hooks/usePoint.js
        import {onMount, onBeforeUnmount, reactive} from 'vue'
        export default function(){
            let point = reactive({x:0, y:0})
                
            function savePoint(event){
                point.x = event.pageX
                point.y = event.pageY
            }
            onMount(){
                window.addEventListener('click',savePoint)
            }
            onBeforeUnmount(){
                window.removeEventListener('click',savePoint)
            }
            return point
        }
    
    1. 在组件中使用自定义 hooks
        import usePoint from '../hooks/usePoint.js'
        export default {
            setup(){
                let {x, y} = usePoint()
                return {x, y}
            }
        }
    

其他 Composition API

  • shallowReactive(只处理对象最外层属性的响应式) 与 shallowRef(只处理基本类型,不进行对象的响应式处理)

    • 使用时机:
      • shallowReactive:如果有一个对象数据,结构比较深,但变化时只是外层属性变化
      • shallowRef: 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换
  • readonly (让一个响应式数据变为只读的,深只读) 与 shallowReadonly (浅只读)

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

  • markRaw: 标记一个对象,使其用于不会再成为响应式对象

    • 有些值不应被设置为响应式的,例如复杂的第三方类库
    • 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
  • customRef:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显示控制

    • 实现防抖效果:
        <template>
            <imput type="text" v-model="keyword">
            <h3>{{keyword}}</h3>
        </tamplate>
        
        <script>
            import {ref, customRef} from 'vue'
            export default {
                name: 'Demo',
                setup(){
                    // 自定义一个 myRef
                    function myRef(value,delay){
                        let timer  //节流阀
                        // 通过 customRef 实现自定义
                        return customRef((track, trigger)=>{
                            return {    // customRef 中的回调必须返回一个对象
                                get(){
                                    track()  // 通知 Vue 追踪数据的变化
                                    return value
                                },
                                set(newValue){
                                    clearTimeout(timer)
                                    timer = setTimeout(()=>{
                                        value = newValue
                                        trigger() // 通知 Vue 重新解析模板
                                    },delay)
                                }
                            }
                        })
                    }
                    let keyword = myRef(keyword, 1000)
                    
                    return {keyword}
                }
            }
        </script>
    
  • provide 与 inject : 实现祖孙组件间的通信

    1. 父组件定义数据:
           setup(){
               ...
               let car = reactive({name:'Benz',price:'40W'})
               provide('car',car)
           }
    
    1. 后代组件接收:
             setup(){
                 ...
                 const car = inject('car')
                 return {car}
             }
    
  • 响应式数据的判断

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

新的组件

  • Teleport 是一种能够将我们的组件 html 结构移动到指定位置的技术
        <teleport to="位置,可以写标签名、CSS选择器等">
            <div v-if="isShow" class="mask">
                <div>
                    <h3>我是一个弹窗</h3>
                    <button @click="isShow=false">关闭</button>
                </div>
            </div>
        </teleport>
    
  • Suspense 用来解决在异步组件加载过慢时,出现的抖动问题
    • 要理解 Suspense,首先要明白异步组件
          <template>
              <h3>App 组件</h3>
              <!-- 直接将异步组件包裹起来就可以 -->
              <Suspense>
                  <template v-slot:default>  <!-- 成功了展示这个 -->
                      <Child />
                  </template>
                  <template v-slot:fallback>  <!-- 成功之前展示这个 -->
                      <h3>加载中</h3>
                  </template>
              </Suspense>
          </tempalte>
          
          <script>
              // import Child from './components/Child.vue'   静态引入,如果引入失败,其子组件均不会展示
              // 通过 defineAsyncComponent 动态的引入组件(异步引入)
              import {defineAsyncComponent} from 'vue'
              const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
              export default {
                  name: 'App',
                  compoents:{Child}
              }
          </script>