# vue2 遇到的难题

  • 随着功能的增长,复杂组件的代码变得难以维护。
  • 随着复杂度的上升,每个功能实现的逻辑很乱,很难按照逻辑进行分类(分散在不同的声明周期中)。
  • 虽然 mixin 可以解决但是存在命名冲突、不清楚暴露出来变量的作用、重用到其他 component 经常会遇到很多问题。
  • vue2 对于 typescript 的支持非常有限。

# vue3 新特性

# ref

可以通过 ref 定义一个响应式的值:跟 react 的 useRef 类似。 使用 ref 一般定义原始类型数据。将原始类型转换为响应式对象。

<template>
  <h1>{{count}}</h1>
  <h2>{{double}}</h2>
  <button @click="increase"></button>
</template>

<script lang="ts">
import { defineComponent, ref, computed } from 'vue';
interface DataProps {
  count: number;

}
export default defineComponent({
  name: 'App',
  // 无法访问 this,在生命周期之前运行
  setup() {
    // 响应式数据定义
    const count = ref(0)
    // 计算属性
    const double = computed(() => {
      return count.value * 2
    })
    // 定义方法
    const increase = () => {
      count.value ++
    }
    return {
      count,
      double,
      increase
    }
  }
});
</script>

# reactive

reactive 与 ref 非常类似,接收一个 Object 参数:

<template>
  <h1>{{count}}</h1>
  <h2>{{double}}</h2>
  <button @click="increase"></button>
</template>

<script lang="ts">
import { defineComponent, computed, reactive } from 'vue';
interface DataProps {
  count: number;
  increase: () => void;
  double: number;
}
export default defineComponent({
  name: 'App',
  // 无法访问 this,在生命周期之前运行
  setup() {
    const data: DataProps = reactive({
      count: 0,
      increase: () => {
        // 注意在 reactive 中不需要使用 value 属性去访问数据
        data.count ++
      },
      // 使用 computed 定义会造成类型推论的循环--解决方法就是给 data 指定类型
      double: computed(() => {
        return data.count * 2
      })
    })
    // 这里注意不能使用展开运算符 ...data
    return {
      data
    }
  }
});
</script>

需要注意的事项:

在 setUp 函数返回中,不能直接将 data 进行解构返回:

// 这样返回是获取不到响应式数据,因为结构后得到的是基本类型数据
return {
  ...data
}
// 可以直接返回
return {
  data
}

直接返回 data 在模板展示中需要 data.count 这样写;也可以通过 toRefs API 来将传入的对象的每一项转换为响应式对象。

const refData = toRefs(data)
// 这里注意不能使用展开运算符 ...data
return {
  ...refData
}

# 响应式数据监听优化

vue3 中可以监听数组等对象的属性修改监听,比如: 数组可以直接通过索引进行赋值、以及监听对象属性变化进行渲染,不在使用 $set 去改变属性值。

import { defineComponent, computed, reactive, toRefs } from 'vue';
interface DataProps {
  numbers: number[];
  person: {
    name?: string;
  };
}
export default defineComponent({
  name: 'App',
  // 无法访问 this,在生命周期之前运行
  setup() {
    const data: DataProps = reactive({
      numbers: [0, 1, 2],
      person: {}
    })
    data.numbers[0] = 5
    data.person.name = 'jiegiser'
    const refData = toRefs(data)
    // 这里注意不能使用展开运算符 ...data
    return {
      ...refData
    }
  }
});

# 生命周期的变化

主要是修改了 beforeDestory 以及 destroyed 添加了对应的两个别名:beforeUnmount 以及 unmounted;其他

下面是在 setup 函数中使用的声明周期 API;

主要是声明周期都添加了 on 前缀,以及 beforeCreate 以及 created 可以在 setup 中直接写。新增了调试相关(观察数据变化) api :onRenderTRacked、onRenderTriggered

// mapping vue2 to vue3
beforeCreate -> use setup()
created -> use setup()

beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdatae -> onBeforeUpdate
updated -> onUpdated
beforeDestory -> onBeforeUnmount
destroyed -> onUnmounted
acticated -> onActivated
deactivated -> onDeactivated
errorCaptured -> onErrorCaptured

// added 调试作用
onRenderTRacked
onRenderTriggered

例子:

import { 
  defineComponent,
  computed, reactive,
  toRefs,
  onRenderTriggered,
  onMounted,
  onUpdated
} from 'vue';
interface DataProps {
  count: number;
  increase: () => void;
  double: number;
  numbers: number[];
  person: {
    name?: string;
  };
}
export default defineComponent({
  name: 'App',
  // 无法访问 this,在生命周期之前运行
  setup() {

    onMounted(() => {
      console.log('mounted')
    })

    onUpdated(() => {
      console.log('updated')
    })
    onRenderTriggered((event) => {
      console.log(event)
      // effect: ƒ reactiveEffect()
      // key: "count"
      // newValue: 1
      // oldTarget: undefined
      // oldValue: 0
      // target: {count: 1, double: ComputedRefImpl, numbers: Array(3), person: {…}, increase: ƒ}
      // type: "set"
      // __proto__: O
    })

    const data: DataProps = reactive({
      count: 0,
      increase: () => {
        // 注意在 reactive 中不需要使用 value 属性去访问数据
        data.count ++
      },
      // 使用 computed 定义会造成类型推论的循环--解决方法就是给 data 指定类型
      double: computed(() => {
        return data.count * 2
      }),
      numbers: [0, 1, 2],
      person: {}
    })
    data.numbers[0] = 5
    data.person.name = 'jiegiser'
    const refData = toRefs(data)
    // 这里注意不能使用展开运算符 ...data
    return {
      ...refData
    }
  }
});

# watch

import { 
  defineComponent,
  reactive,
  toRefs,
  watch
} from 'vue';
interface DataProps {
  count: number;
}
export default defineComponent({
  name: 'App',
  // 无法访问 this,在生命周期之前运行
  setup() {
    const data: DataProps = reactive({
      count: 0,
    })
    const refData = toRefs(data)
    // 监听的值为响应式对象 - 使用 ref 定义的变量或者使用 toRefs 转换的变量。
    watch(refData.count, (newValue, oldValue) => {
      console.log('update count', newValue, oldValue)
    })
    // 这里注意不能使用展开运算符 ...data
    return {
      ...refData
    }
  }
});

注意监听多个值,newValue,oldValue 也是对应一个数组;如果监听的值是一个 reactive 创建的变量的属性,需要通过函数返回;

watch([refData.count, () => data.count], (newValue, oldValue) => {
  console.log(newValue, oldValue)
})

# 自定义 hooks 替换 mixin

可以通过自定义 hooks 来实现 mixin 的功能:

<template>
<h1>x:{{x}}-y:{{y}}</h1>
</template>

<script lang="ts">
import { ref, defineComponent, onMounted, onUnmounted } from 'vue';

export default defineComponent({
  name: 'App',
  // 无法访问 this,在生命周期之前运行
  setup() {
    const x = ref(0)
    const y = ref(0)
    const updateMouse = (e: MouseEvent) => {
      x.value = e.pageX
      y.value = e.pageY
    }
    onMounted(() => {
      document.addEventListener('click', updateMouse)
    })
    onUnmounted(() => {
      document.removeEventListener('click', updateMouse)
    })
    return {
      x, 
      y
    }
  }
});
</script>

可以将里面的功能进行封装 hooks ,然后引用:

新建 useMousePosition.ts 文件,封装的 hooks


import { ref, onMounted, onUnmounted } from 'vue'
function useMousePositioin() {
  const x = ref(0)
  const y = ref(0)

  const updateMouse = (e: MouseEvent) => {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    document.addEventListener('click', updateMouse)
  })
  onUnmounted(() => {
    document.removeEventListener('click', updateMouse)
  })
  return {
    x, 
    y
  }
}

export default useMousePositioin

使用:

<template>
<h1>x:{{x}}-y:{{y}}</h1>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

import useMousePositioin from './hooks/useMousePosition'
export default defineComponent({
  name: 'App',
  // 无法访问 this,在生命周期之前运行
  setup() {
    const { x, y } = useMousePositioin()
    return {
      x,
      y
    }
  }
});
</script>

可以使用泛型给 hooks 增加类型注释:

<script lang="ts">
import { defineComponent } from 'vue';
import useMousePositioin from './hooks/useMousePosition'

interface DataProps {
  data: string;
  error: string;
}
export default defineComponent({
  name: 'App',
  // 无法访问 this,在生命周期之前运行
  setup() {
    const { x, y, result } = useMousePositioin<DataProps>()
  }
});
</script>

hooks:

import { ref, onMounted, onUnmounted } from 'vue'
function useMousePositioin<T>() {
  const result = ref<T | null>(null)
  const x = ref(0)
  const y = ref(0)

  const updateMouse = (e: MouseEvent) => {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    document.addEventListener('click', updateMouse)
  })
  onUnmounted(() => {
    document.removeEventListener('click', updateMouse)
  })
  return {
    x, 
    y,
    result
  }
}

export default useMousePositioin

评 论:

更新: 11/21/2020, 7:00:56 PM