初识vue3.X之Composition API

初识第一句:为什么要升级使用3.0的Composition API?

Vue2.x 中所有数据都是定义在data中,方法定义在methods中的,计算属性定义在methods中的,并且使用this来调用对应的数据和方法。那 Vue3 中就换了一个写法, 具体怎么写等会儿看后面, 先说一下 Vue2.x 版本这么写有什么缺陷,才能直观地明白为什么要升级Vue3的Composition API语法。

在Vue2.x 实现加减

  <div class="homePage">
    <p>count: {{ count }}</p>
    <p>倍数: {{ multiple }}</p>
    <div>
      <button style="margin-right: 10px" @click="add">加1</button>
      <button @click="reduce">减一</button>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return { count: 0 };
  },
  computed: {
    multiple() {
      return 2 * this.count;
    }
  },
  methods: {
    add() {
      this.count++;
    },
    reduce() {
      this.count--;
    }
  }
};
</script>
复制代码

上面代码只是实现了对count的加减以及显示倍数, 就需要分别在 data、methods、computed 中进行操作,当我们增加一个需求,就会出现下图的情况:

be5fbebfad2b4863a8cf664843b2106e_tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.awebp

当我们业务复杂了就会大量出现上面的情况, 随着复杂度上升,就会出现下面这张图, 每个颜色的方块表示一个功能:甚至一个功能还有会依赖其他功能,全搅合在一起。
当这个组件的代码超过几百行时,这时增加或者修改某个需求, 就要在 data、methods、computed 以及 mounted 中反复的跳转,当需求变更时,修改起来要挨着挨着走读代码,这痛苦大家应该都经历过。

2fed537233174d438913ba1aee9acb91_tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.awebp

vue2.x 版本给出的解决方案就是 Mixin, 但是使用 Mixin 也会遇到让人苦恼的问题:

  1. 命名冲突问题(页面与Minxin方法,变量名重复冲突)
  2. 不清楚暴露出来的变量的作用
  3. 逻辑重用到其他 component 经常遇到问题

所以,Vue3 就推出了Composition API主要就是为了解决上面的问题,将零散分布的逻辑组合在一起来维护,并且还可以将单独的功能逻辑拆分成单独的文件。这也是Vue3的重中之重点。

下面开始正式入门Composition API

官方网站

Composition API包含的特性:

  • setup
  • reactive
  • watchEffect
  • watch
  • computed
  • ref
  • toRefs
  • 生命周期

01- setup

setup函数是一个新的组件选项。作为组件内使用Composition API的入口点,它会在beforeCreate钩子之前调用。

export default {
  setup () {
    return {}
  }
}
</script>
复制代码

02- ref

ref可以把基本的数据结构包装成响应式,可传入任意类型的值并返回一个响应式且可改变的ref对象。ref对象拥有一个指向内部值的单一属性.value,js中修改的时候操作.value属性

// 响应式变量声明 ref负责复杂一般的简单数据结构,
const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
复制代码

03-reactive

reactive:接受一个普通对象然后返回该普通对象的响应式代理。等同于2.x的Vue.obserable()

export default {
  setup(){
    // 响应式变量声明 reactive负责复杂数据结构,
    const state = reactive({
      count: 1
    });
    function add() {
      state.count++;
    }
    return { state, add};
  }
};
复制代码

04-toRefs

将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref

const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:

{
  foo: Ref<number>,
  bar: Ref<number>
}
*/

// ref 和原始 property 已经“链接”起来了
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3
复制代码

当从组合式函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行解构/展开:

function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })

  // 操作 state 的逻辑

  // 返回时转换为ref
  return toRefs(state)
}

export default {
  setup() {
    // 可以在不失去响应性的情况下解构
    const { foo, bar } = useFeatureX()

    return {
      foo,
      bar
    }
  }
}
复制代码

补充一点:可以使用isRef检查值是否为一个 ref 对象

05-computed

接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 错误
复制代码

或者,接受一个具有 get 和 set 函数的对象,用来创建可写的 ref 对象。

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0
复制代码

06-watchEffect

立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。

const num = ref(0)

watchEffect(() => console.log(count.value))
// -> 打印出 0

setTimeout(() => {
  count.value++
  // -> 打印出 1
}, 100)
复制代码
复制代码
  1. 停止监听

    隐式停止

    watchEffect 在组件的 setup() 函数或生命周期钩子被调用时, 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止

    显示停止

    在一些情况下,也可以显示调用返回值来停止侦听

    const stop = watchEffect(()=>{
      /*...*/
    })
    //停止侦听
    stop()
    复制代码
    复制代码
  2. 清除副作用

    有时候副作用函数会执行一些异步的副作用,这些响应需要在其失效时来清除(即完成之前状态已改变了)。可以在侦听副作用传入的函数中接受一个onInvalidate函数作为参数,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:

    • 副作用即将重新执行时
    • 侦听器被停止(如果在setup()或生命周期钩子函数中使用了watchEffect,则在卸载组件时)

    官网的例子:

    watchEffect((onInvalidate) => {
      const token = performAsyncOperation(id.value)
      onInvalidate(() => {
        // id 改变时 或 停止侦听时
        // 取消之前的异步操作
        token.cancel()
      })
    })
    复制代码
    复制代码

案例:实现对用户输入“防抖”效果

<template>
  <div>
    <input type="text"
           v-model="keyword">
  </div>
</template>

<script>
  import { ref, watchEffect } from 'vue'

  export default {
    setup() {
      const keyword = ref('')
      const asyncPrint = val => {
        return setTimeout(() => {
          console.log('user input: ', val)
        }, 1000)
      }
      watchEffect(
        onInvalidate => {
          //用户输入的时间间隔小于1秒,都会立刻清除掉定时,不输入结果。正因为这个,实现了用户防抖的功能,只在用户输入时间间隔大于1秒,才做打印
          const timer = asyncPrint(keyword.value)
          onInvalidate(() => clearTimeout(timer))
          console.log('keyword change: ', keyword.value)
        },
        // flush: 'pre'  watch() 和 watchEffect() 在 DOM 挂载或更新之前运行副作用,所以当侦听器运行时,模板引用还未被更新。
        //flush: 'post' 选项来定义,这将在 DOM 更新后运行副作用,确保模板引用与 DOM 保持同步,并引用正确的元素。
        {
          flush: 'post' // 默认'pre',同步'sync','pre'组件更新之前
        }
      )

      return {
        keyword
      }
    }
  }
  // 实现对用户输入“防抖”效果
</script>
复制代码

07-watch


watch API 完全等效于 2.x this.$watch (以及 watch 中相应的选项)。watch 需要侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调。

watch()接收的第一个参数被称作"数据源",它可以是:

  • 一个返回任意值的getter函数
  • 一个包装对象(可以是ref也可以是reactive包装的对象)
  • 一个包含上述两种数据源的数组

第二个参数是回调函数。回调函数只有当数据源发生变动时才会被触发:

  1. 侦听单个数据源

    const state = reactive({count: 1});
    
    //侦听一个reactive定义的数据,修改count值时会触发 watch的回调
    watch(()=>state.count,(newCount,oldCount)=>{
      console.log('newCount:',newCount);  
      console.log('oldCount:',oldCount);
    })
    //侦听一个ref
    const num = ref(0);
    watch(num,(newNum,oldNum)=>{
      console.log('newNum:',newNum);  
      console.log('oldNum:',oldNum);
    })
    复制代码
    复制代码
  2. 侦听多个数据源(数组)

    const state = reactive({count: 1});
    const num = ref(0);
    // 监听一个数组
    watch([()=>state.count,num],([newCount,newNum],[oldCount,oldNum])=>{
      console.log('new:',newCount,newNum);
      console.log('old:',oldCount,oldNum);
    })
    复制代码
    复制代码
  3. 侦听复杂的嵌套对象

    我们实际开发中,复杂数据随处可见, 比如:

    const state = reactive({
      person: {
        name: '张三',
        fav: ['帅哥','美女','音乐']
      },
    });
    watch(
      () => state.person,
      (newType, oldType) => {
        console.log("新值:", newType, "老值:", oldType);
      },
      { deep: true }, // 立即监听
    );
    复制代码
    复制代码

如果不使用第三个参数deep:true, 是无法监听到数据变化的。 前面我们提到,默认情况下,watch 是惰性的, 那什么情况下不是惰性的, 可以立即执行回调函数呢?其实使用也很简单, 给第三个参数中设置immediate: true即可

同时,watch和watchEffect在停止侦听,清除副作用(相应地onInvalidate会作为回调的第三个参数传入)等方面行为一致。

<template>
  <div>
    <input type="text"
      v-model="keyword">
  </div>
</template>

<script>
import { ref, watch } from 'vue'

export default {
  setup() {
    const keyword = ref('')
    const asyncPrint = val => {
      return setTimeout(() => {
        console.log('user input: ', val)
      })
    }

    watch(
      keyword,
      (newVal, oldVal, onCleanUp) => {
        const timer = asyncPrint(keyword)
        onCleanUp(() => clearTimeout(timer))
      },
      {
        lazy: true // 默认false,即初始监听回调函数执行了
      }
    )
    return {
      keyword
    }
  }
}
</script>
复制代码

参考watchEffect 指南

08-生命周期钩子

与2.x版本生命周期相对应的组合式API生命周期图标概览

微信图片_20220315145543.png

最后总结一下

01-Vue3.0优缺点

优点

  1. 使用Proxy进行响应式变量定义,性能提高1.2~2倍:就是11月14日-16日于多伦多举办的 VueConf TO 2018 大会上,尤雨溪发表了名为 Vue3.0 Updates 的主题演讲,对 Vue3.0 的更新计划、方向进行了详细阐述,表示已经放弃使用了 Object.defineProperty,而选择了使用更快的原生 Proxy !!这将会消除了之前 Vue2.x 中基于 Object.defineProperty 的实现所存在的很多限制:无法监听 属性的添加和删除数组索引和长度的变更,并可以支持 MapSetWeakMapWeakSet点这里上车Proxy
  2. ssr服务端渲染速度快了2~3倍。
  3. 可在Vue2.0中单独使用composition-api插件,或者直接用它开发插件
  4. 面向未来:对于尤雨溪最近创新的vite开发服务器(舍弃webpack、底层为Koa框架的高性能开发服务器),直接使用的Vue3语法
  5. 代码反而比 Vue 2 的简洁,与 TypeScript 的结合也非常渐进,几乎感觉不到任何痛苦。(提到ts的话,这时候压力马上要来到潘文龙这边)

缺点

  1. vue3将不再支持IE11,Vue 在 2.X 版本仍然支持 IE11,如果你想使用类似 Vue 3 的新特性,可以等等 Vue 2.7 版本。这次的 RFC 宣布,将会对 2.7 版本做向后兼容,移植 3.x 的部分新功能,以保证两个版本之间相似的开发体验。

  2. 初学过程中会经常忘记 return ref,从而让自己直接原地蒙两圈。

  3. 对于习惯了Vue2.0开发模式的开发者来说,增加了心智负担,经常会在reactive 和 ref 之间纠结到底用哪个,对开发者代码组织能力有很大考验,但同时,这也映射了尤大大的设计初心: “让开发者随着框架一起成长”。其实我更想强调的一点是:与其纠结3.0难不难,有什么优势劣势,不如多想想怎么应对前端门槛降低带来的激烈竞争。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享