vue3

317 阅读4分钟

1. 基础

还能使用vue2的语法(data,methods等),但是不建议

<tamplate>
// 可以不加外层div
<h2>{{ count }}</h2>
<button @click="update">更新</button>
</tamplate>

// 使用ts代码
<script lang="ts"></script>

2. setup(父子组件的交互)

在beforeCreate之前执行

父组件:

<child :msg="msg" attr="attr" @fn="fn"/>
import child from './child.vue'
components: {
  child
}

子组件:

<h3>msg: {{ msg }}</h3>
<h3>msg2: {{ $attrs.msg2 }}</h3>
// setup (props, context) {
setup (props, {attrs, emit, slots}) {
  /*
    props: 父组件传递给子组件的数据
    attr: 父组件写在子组件上的属性
    emit: 父组件传递给子组件的方法
    slots: 插槽
  */
  console.log(props.msg, attrs.msg2, slots, emit)

  function update () {
    emit('fn', '++')
  }
}

3. 计算属性

const user = reactive({
  firstName: 'A',
  lastName: 'B'
})

// 只有getter的计算属性
const fullName1 = computed(() => {
  return user.firstName + '-' + user.lastName
})

// 有getter与setter的计算属性
const fullName2 = computed({
  get () {
    // 返回给fullName2
    return user.firstName + '-' + user.lastName
  },

  set (value: string) { // value: fullName2
    const [firstName, lastName] = value.split('-')
    user.firstName = firstName
    user.lastName = lastName
  }
})

4. 监听属性

watch(user, (newVal, oldVal) => {
  fullName3.value = newVal.firstName + '-' + newVal.lastName
}, {
  immediate: true,  // 是否初始化立即执行一次, 默认是false
  deep: true, // 是否是深度监视, 默认是false
})

/* 监听多个数据:
  使用数组来指定
  如果是ref对象, 直接指定
  如果是reactive对象中的属性,必须通过函数来指定
*/
// watch([user, fullName3], (values) => {
watch([() => user.firstName, () => user.lastName, fullName3], (values) => {
  console.log('监视多个数据', values)
})

// 监听watchEffect内部的所有变量,初始化会立即执行一次
watchEffect(() => {
  fullName3.value = user.firstName + '-' + user.lastName
})

5. 生命周期

  • beforeCreate -> 使用 setup()
  • created -> 使用 setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

6. hook

hooks/useMousePosition.ts:

import { ref, onMounted, onUnmounted } from 'vue'
// 收集用户鼠标点击的页面坐标
export default function useMousePosition () {
  // 初始化坐标数据
  const x = ref(-1)
  const y = ref(-1)

  // 用于收集点击事件坐标的函数
  const updatePosition = (e: MouseEvent) => {
    x.value = e.pageX
    y.value = e.pageY
  }

  // 挂载后绑定点击监听
  onMounted(() => {
    document.addEventListener('click', updatePosition)
  })

  // 卸载前解绑点击监听
  onUnmounted(() => {
    document.removeEventListener('click', updatePosition)
  })

  return {x, y}
}

index.ts:

/* 在组件中引入并使用自定义hook
  作用: 类似于vue2中的mixin技术
  优势: 代码复用
*/
import useMousePosition from './hooks/useMousePosition'

export default {
  setup() {
    const {x, y} = useMousePosition()

    return {
      x,
      y,
    }
  }
}

7. Composition API

7.1 ref获取元素

<input type="text" ref="inputRef">
import { onMounted, ref } from 'vue'
export default {
  setup() {
    const inputRef = ref<HTMLElement | null>(null)

    onMounted(() => {
      // 让输入框自动获取焦点
      inputRef.value && inputRef.value.focus()
    })

    return {
      inputRef
    }
  },
}

7.2 ref

<template>
  // html中不需要加value
  <h2>{{ count }}</h2>
  <button @click="update">更新</button>
</template>
<script>
  import { ref } from 'vue'
  export default {
    setup () {
      // 非响应式,html没法检测js中count值的变化
      const count = 1
      // ref: 定义响应式数据(适用于基本数据类型,也可以放对象,但是要写多个value,没必要)
      const count = ref(1)

      function update () {
        // js中的使用,需要加value
        count.value = count.value + 1
      }

      // 暴露出去,给html用
      return {
        count,
        update
      }
    }
  }
</script>

7.3 reactive

<template>
  <h2>name: {{ state.name }}</h2>
  <button @click="update">更新</button>
</template>

<script>
  import { reactive } from 'vue'
  export default {
    setup () {
      // reactive: 定义响应式数据(适用于对象),深层响应(内部数组的变化都可以被响应到)
      // const state = reactive<any>({ // any类型就可以添加新属性
      const state = reactive({ // 默认是不能添加新属性的
        name: 'tom',
        wife: {
          name: 'marry',
          arr: [1, 2, 3]
        },
      })

      const update = () => {
        state.name += '--'
        state.wife.arr[4] = 4
      }

      return {
        state,
        update,
      }
    }
  }
</script>

7.4 toRef

const state = reactive({
  foo: 'a',
  bar: 'b',
})

// foo1 和 state.foo 相互影响
const foo1 = toRef(state, 'foo')
// foo2 和 state.foo 不相互影响
const foo2 = ref(state.foo)

7.5 toRefs

export default {
  setup () {
    const state = reactive({
      foo: 'a',
      bar: 'b',
    })

    /* 相当于
      const foo = ref('a');
      const bar = ref('b');
    */
    const stateAsRefs = toRefs(state)
    const { foo2, bar2 } = useReatureX()

    return {
      ...stateAsRefs,
      foo2, 
      bar2
    }
  },
}

// 应用: 当从合成函数返回响应式对象时,toRefs 非常有用
function useReatureX() {
  const state = reactive({
    foo2: 'a',
    bar2: 'b',
  })

  return toRefs(state)
}

7.6 toRaw 与 markRaw

const state = reactive<any>({
  name: 'tom',
  age: 25,
})

const testToRaw = () => {
  const user = toRaw(state)
  // state不会跟着变
  user.age++
}

const testMarkRaw = () => {
  const likes = ['a', 'b']
  // likes不再是响应式,其他属性还是响应式
  state.likes = markRaw(likes)
}

7.7 readonly 与 shallowReadonly

// 深度只读数据
const rState1 = readonly(state)
// 浅只读数据: 外层无法修改,内层可以修改
const rState2 = shallowReadonly(state)

7.8 shallowReactive 与 shallowRef(比较少用)

// 深层响应
const m1 = reactive({a: 1, b: {c: 2}})
// 浅响应,外层有响应,内层没响应
const m2 = shallowReactive({a: 1, b: {c: 2}})

// 深层响应
const m3 = ref({a: 1, b: {c: 2}})
// 浅响应: 只处理了value的响应式,不处理对象的响应式
const m4 = shallowRef({a: 1, b: {c: 2}})

7.9 customRef: 创建一个自定义的 ref

setup () {
  // 参数一: 初始值
  const keyword = useDebouncedRef('xxx', 500)

  return {
    keyword
  }
}

// 实现函数防抖的自定义ref
function useDebouncedRef<T>(value: T, delay = 200) {
  let timeout: number
  return customRef((track, trigger) => {
    return {
      get() {
        // 告诉Vue追踪数据
        track()
        return value
      },
      set(newValue: T) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          // 告诉Vue去触发界面更新
          trigger()
        }, delay)
      }
    }
  })
}

7.10 响应式数据的判断

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

8.新组件

8.1 Teleport

<template>
  <button @click="modalOpen = true">Open</button>

  // 让组件的html在父组件界面外的特定标签(很可能是body)下插入显示
  <teleport to="body">
    <div v-if="modalOpen" class="modal">
      <div>
        <button @click="modalOpen = false">Close</button>
      </div>
    </div>
  </teleport>
</template>

8.2 Suspense

应用程序在等待异步组件时渲染一些后备内容

父组件:

<template>
  <Suspense>
    <template v-slot:default>
      <AsyncAddress/>
    </template>

    <template v-slot:fallback>
      <h1>LOADING...</h1>
    </template>
  </Suspense>
</template>

子组件: AsyncAddress.vue

async setup() {
  const result = await axios.get('/data/address.json')
  return {
    data: result.data
  }
}

9. vue3和vue2的比较

  1. 新增脚手架vite(不支持vue2)
  2. 支持ts
  3. 速度更快: 虚拟DOM的优化,生命周期的优化等
  4. 打包出来的体积更小
  5. 新增 Composition API,更有利于实现代码的复用
  6. 重构响应式系统,使用 Proxy 替换 Object.defineProperty (重要)
  7. 可直接监听数组类型的数据变化
  8. 直接实现对象属性的新增/删除
  9. 监听的目标为对象本身,不需要像 Object.defineProperty 一样遍历每个属性,有一定的性能提升
  10. 可拦截apply、ownKeys、has等13种方法,而 Object.defineProperty 不行

来源

b站尚硅谷教程: 视频 笔记