Vue3.0 新特性学习(一)

setup 内部也可以调用生命周期钩子,但是 Vue3 并没有提供 beforeCreatecreated 对应的钩子,由上篇文章可知,setup 是早于这两个钩子执行的,因此 setup 本身就可以胜任这两个钩子的工作,并且官方也是这么说的:

因为 setup 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

其他的钩子名称有所改变,beforeMount 变成了 onBeforeMount,其他钩子的命名规则都与这个相同。

Vue.createApp({
  setup(props) {
    Vue.onBeforeMount(() => {
      console.log(1)
    })
    Vue.onMounted(() => {
      console.log(2)
    })
  },
  beforeMount() {
    console.log(3)
  },
  mounted() {
    console.log(4)
  },
})
app.mount('#app')

运行结果:

同样的钩子,setup 内部的钩子调用早于外部的钩子。

computed

const app = Vue.createApp({
  setup(props) {
    const a = Vue.ref(1)
    const b = Vue.computed(() => a.value * 2)
    const c = Vue.computed({
      get: () => a.value * 3,
      set:val => a.value = val + 3
    })
    console.log(a.value) // 1
    console.log(+ b.value) // 2
    console.log(c.value) // 3
    c.value = 2
    console.log(c.value) // 15
    console.log(a.value) // 5
    console.log(b.value) // 10
    console.log(c.value) // 15
    return {
      a,
      b,
      c
    }
  },
})
app.mount('#app')

computed 用法与之前没什么差别。

watch

const app = Vue.createApp({
  setup(props) {
    // 侦听 ref
    const a = Vue.ref(1)
    Vue.watch(a, (val, prevVal) => {
      console.log(`a 被改变了:新值为${val},旧值为${prevVal}`)
    })
    a.value = 2

    // 侦听 getter
    const b = Vue.reactive({
      x: 1,
      y: 2
    })
    Vue.watch(() => b.x, (val, prevVal) => {
      console.log(`b.x 被改变了:新值为${val},旧值为${prevVal}`)
    })
    b.x = 2

    // 监听多个源
    const { c, d } = Vue.toRefs(Vue.reactive({ c: 3, d: 4 }))
    Vue.watch([c, d], (newValArr, preValArr) => {
      console.log(newValArr, preValArr)
    })
    c.value = 7
    d.value = 7
    return {
      a,
      b
    }
  },
})
app.mount('#app')

运行结果:

watchEffect

watchEffect 接受一个回调函数,在这个回调函数中用到的任意一个响应式数据更新时,这个回调函数都会被执行,也就是说它可以监听多个响应式数据。它与 watch 很相似,但也有需要注意的地方:

  • watchEffect 无需指定要监听的属性,它自动会收集依赖,而 watch 必须指定。
  • watchEffect 因为需要收集依赖。所以它在组件初始化的时候就会运行一遍,而 watch 默认不会运行,除非手动配置 immediate: true
  • watchEffect 无法知道是哪个值被更新,因此获取不到变化的新值与旧值。
const app = Vue.createApp({
  setup(props, context) {
    // 响应式
    const a = Vue.ref(0)
    // 非响应式
    let b = 1
    Vue.watchEffect(() => {
      console.log(`有值更新了!a:${a.value};b:${b}。`)
    })
    setTimeout(() => {
      // 不触发 watchEffect
      b = 5
    }, 1000)
    setTimeout(() => {
      // 触发 watchEffect
      a.value ++
    }, 2000)
    return {
      listLoading,
      toggleLoading
    }
  },
})
app.mount('#app')

运行结果:

watchwatchEffect 都返回了一个 unwatch 方法用于取消侦听

const app = Vue.createApp({
  setup(props, context) {
    const unWatch = Vue.watch(() => {})
    // 取消侦听
    unWatch()
    const unWatchEffect = Vue.watchEffect(() => {})
    // 取消侦听
    unWatchEffect()
    return {}
  },
})
app.mount('#app')

组合式函数

以前我们需要在组件之间共享代码时,一般使用 mixins 或作用域插槽。但它们都有一些硬伤:

  • mixins 多了之后变量会十分混乱,在引用文件中根本无法区分哪个变量或方法来自哪个 mixins
  • 作用域插槽没有变量混乱的问题,因为它的数据只能在模板中访问,但这恰好也是它的缺点。

在 Vue3 中,我们配合组合式 API可以编写组合式函数,可以清楚地知道该变量来自哪个组合式函数。

下面是两个简单的例子:

<div id="app" v-cloak>
  <div>
    {{listLoading}}
    <button @click="toggleLoading">改变状态</button>
  </div>
  <div>
    {{count}}
    <button @click="addCount">+1</button>
  </div>
</div>
// listLoading 状态的切换
function useToggleLoading(user) {
  const listLoading = Vue.ref(false)
  const toggleLoading = () => {
    listLoading.value = !listLoading.value
  }
  return {
    listLoading,
    toggleLoading
  }
}
// count 计数
function useAddCount(user) {
  const count = Vue.ref(0)
  const addCount = () => {
    count.value++
  }
  return {
    count,
    addCount
  }
}

const app = Vue.createApp({
  setup(props, context) {
    // 来自 useToggleLoading
    const { listLoading, toggleLoading } = useToggleLoading()
    //来自 useAddCount
    const { count, addCount } = useAddCount()
    return {
      listLoading,
      toggleLoading,
      count,
      addCount
    }
  },
})
app.mount('#app')

Vue2 与 Vue3 的主要区别就写到这里,还有其他很多我觉得用得相对较少的 API 文章中就不多介绍。