Vue3笔记

165 阅读2分钟

对VUE组件如果要加class、绑定事件,要在外面包一个标签,不要直接加在组件标签上,会不生效。

响应式ref和reactive

一个简单例子:计数器

vue2

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
  <h1>{{count}}</h1>
  <button @click="handleClick">👍🏻 +1</button>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import HelloWorld from './components/HelloWorld.vue'

export default defineComponent({
  name: 'App',
  data () {
    return {
      count: 0
    }
  },
  methods: {
    handleClick () {
      this.count++
    }
  },
  components: {
    HelloWorld
  }
})
</script>

<style>
...
</style>

将其改写为vue3,使用refcount转化为响应式,并增加计算属性。

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
  <h1>{{count}}</h1>
  <h2>{{double}}</h2>
  <button @click="handleClick">👍🏻 +1</button>
</template>

<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
import HelloWorld from './components/HelloWorld.vue'

export default defineComponent({
  name: 'App',
  components: { HelloWorld },
  setup () {
    const count = ref(0)
    const double = computed(() => {
      return count.value * 2
    })
    const handleClick = () => {
      count.value++ /* 使用ref包裹后count是一个响应式对象,修改值时要修改里面的value,但是在模板中显示的时候可以直接用count */
    }
    return {
      count,
      handleClick
    }
  }
})
</script>

<style>
...
</style>

ref一般处理的是原始类型,而引用类型一般使用reactive

<script lang="ts">
import { defineComponent, computed, reactive } from 'vue'
import HelloWorld from './components/HelloWorld.vue'

export default defineComponent({
  name: 'App',
  components: { HelloWorld },
  setup () {
    // const count = ref(0)
    // const double = computed(() => {
    //   return count.value * 2
    // })
    // const handleClick = () => {
    //   count.value++
    // }

    const data = reactive({
      count: 0,
      /* 这时const data会报错,因为computed里的回调使用了data.count
      会造成类型的循环推断,data被推断为any类型。vue3暂时无法解决该问题 */
      double: computed(() => data.count * 2),
      handleClick: () => { data.count++ }
    })

    return {
      data
    }
  }
})
</script>

解决办法:定义一个interface

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
  <h1>{{data.count}}</h1>
  <h2>{{data.double}}</h2>
  <button @click="data.handleClick">👍🏻 +1</button>
</template>

<script lang="ts">
import { defineComponent, computed, reactive } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
interface dataProps {
  count: number;
  double: number;
  handleClick: () => void
}
export default defineComponent({
  name: 'App',
  components: { HelloWorld },
  setup () {
    // const count = ref(0)
    // const double = computed(() => {
    //   return count.value * 2
    // })
    // const handleClick = () => {
    //   count.value++
    // }

    const data:dataProps = reactive({
      count: 0,
      double: computed(() => data.count * 2),
      handleClick: () => { data.count++ }
    })

    return {
      data
    }
  }
})
</script>

<style>
...
</style>

但是发现在使用时需要data.×××,可不可以利用ES6的解构语法呢?答案是不行,一旦进行解构,解构出来的变量都是js/ts的普通类型,而不是响应式对象了。要解决这个问题,需要使用toRefs

const refData = toRefs(data);
return {
  ...refData
}

生命周期函数

// vue2 -> vue3
beforeCreate -> use setup()
created -> use setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
deactivated -> onDeactivated
errorCaptured -> onErrorCaptured

// 新增
onRenderTracked
onRenderTriggered

watch

假设在setup()函数里写了一个方法,每次点击按钮,就执行这个方法,修改document.title。但由于setup()只在一开始页面加载时执行,以后每次虽然都执行了这个方法,修改了document.title,但是标签页上看到的title不会发生变化(没有重新渲染)。解决这个问题,可以使用watch来监听数据的变化。

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
  <h1>{{count}}</h1>
  <h2>{{double}}</h2>
  <button @click="handleClick">👍🏻 +1</button>
  <button @click="updataGreeting">Update Greetings</button>
</template>

<script lang="ts">
import { defineComponent, computed, reactive, toRefs, ref, watch } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
interface dataProps {
  count: number;
  double: number;
  handleClick: () => void
}
export default defineComponent({
  name: 'App',
  components: { HelloWorld },
  setup () {
    const data:dataProps = reactive({
      count: 0,
      double: computed(() => data.count * 2),
      handleClick: () => { data.count++ }
    })
    const greetings = ref('')
    const updataGreeting = () => {
      greetings.value += ' Hello! '
    }
    watch(greetings, () => {
      console.log(oldValue, newValue)
      document.title = 'updated' + greetings.value
    })
    const refData = toRefs(data)
    return {
      ...refData,
      updataGreeting
    }
  }
})
</script>

<style>
...
</style>

侦听一个源:

// 侦听一个getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

// 直接侦听一个ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

若要监听多个数据,则可传入一个数组。
另外,watch的回调函数还接收两个参数,分别是新的值和旧的值。

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})
watch([greetings, data], (newValue, oldValue) => {
  console.log(oldValue, newValue)
  document.title = 'updated' + greetings.value + data.count
})

还有,watch监听的值必须是:A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.
假设只想监听data.count,需要将其改成一个函数:

watch([greetings, () => data.count], (newValue, oldValue) => {
  console.log(oldValue, newValue)
  document.title = 'updated' + greetings.value + data.count
})

TypeScript对vue3的加持

在HelloWorld.vue中:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
/* 如果没有defineComponent,那么导出的就是一个普通对象,
也没有setup、methods这些语法提示 */
export default defineComponent({
  name: 'HelloWorld',
  props: {
    msg: String
  },
  /* setup()函数接收两个参数,第一个是前面定义的props,第二个参数是一个环境,
  里面包含了vue2的一些属性:this.$attr、this.$emit和slot*/
  setup (props, context) {
    console.log(props.msg)
    console.log(context.attrs) /* context包含attrs、slots、emit */
  }
})
</script>

<style scoped>
...
</style>