Skip to content

vue3高级技巧(持续补充并且复习版本)

ts标注类型

https://cn.vuejs.org/guide/typescript/composition-api.html#typing-ref

基础部分注意事项

v-if v-show

当需要频繁切换时,用v-show

当不需要频繁切换时,用v-if

ref 模版引用

v-for时的模版引用

javascript
<script setup>
import { ref, onMounted } from 'vue'

const list = ref([
  /* ... */
])

const itemRefs = ref([])
////////   ref 数组并不保证与源数组相同的顺序
onMounted(() => console.log(itemRefs.value))
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">
      {{ item }}
    </li>
  </ul>
</template>

组件上的ref

默认情况下,组件内部属性是私有的,想访问需要defineExpose出来

javascript
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
  a,
  b
})
</script>

reactive 局限

有限的值类型

不能替换整个对象

因为proxy响应式跟踪核心是通过属性访问实现的,不能轻易替换响应式对象

javascript
let state = reactive({ count: 0 })

// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })

解构操作不友好

shallowRef

javascript
const state = shallowRef({ count: 1 })

// 不会触发更改
state.value.count = 2

// 会触发更改
state.value = { count: 2 }

computed 对象式(避免修改)

注意:

javascript
<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

类和样式绑定

javascript
<div :class="[isActive ? activeClass : '', errorClass]"></div>

watch watchEffect watchPostEffect

三者使用情况

javascript
watch(todoId, async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
}, { immediate: true })
javascript
watchEffect(async () => {           // 自动追踪todoId.value
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
})

默认情况下,watch watchEffect在Vue组件更新前调用,即在侦听回调中得到的Dom是更新前的dom,想在侦听回调中获取更新后的DOM,需要指明flush: 'post'

javascript
watch(source, callback, {
  flush: 'post'
})

watchEffect(callback, {
  flush: 'post'
})

或者使用****

javascript
watchPostEffect(() => {
  /* 在 Vue 更新后执行 */
})

停止侦听

在setup函数中同步创建的侦听器,会自动绑定在宿主实例上,故不需要考虑停止,但是,异步创建的侦器需要手动停止,防止内存泄漏

javascript
<script setup>
import { watchEffect } from 'vue'

// 它会自动停止
watchEffect(() => {})

// ...这个则不会!
setTimeout(() => {
  watchEffect(() => {})
}, 100)
</script>

手动停止: 调用watchEffect返回的函数

javascript
const unwatch = watchEffect(() => {})

// ...当该侦听器不再需要时
unwatch()

Ref 的监听

javascript
const targetRef = ref(null);
useHeightComputed(targetRef);

我在封装一个计算高度的 hooks 的时候,当 hooks 执行时,targetRef 并未挂载,这样的话我在内部得到的其实是一个 null

其实是一个很简单的问题,但是最开始的时候钻了进去,只需要执行时传进去targetRef,不要.value,然后在内部 watch

javascript
watch(target,.....)

高级技巧

props单项数据流

当出现想直接更改父组件传递的数据的想法时,请谨慎,谨记

tips:子组件中用新的ref数据承接props数据时,此时意味着props数据只是新ref的初始数据

事件

事件绑定修饰符

.once .native

emit事件校验:

javascript
<script setup>
const emit = defineEmits({
  // 没有校验
  click: null,

  // 校验 submit 事件
  submit: ({ email, password }) => {
    if (email && password) {
      return true
    } else {
      console.warn('Invalid submit event payload!')
      return false
    }
  }
})

function submitForm(email, password) {
  emit('submit', { email, password })
}
</script>

v-model修饰符

内置修饰符

https://cn.vuejs.org/guide/essentials/forms.html#modifiers

自定义修饰符

https://cn.vuejs.org/guide/components/v-model.html#handling-v-model-modifiers

例如场景:v-model

插槽

动态插槽名

javascript
<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>

  <!-- 缩写为 -->
  <template #[dynamicSlotName]>
    ...
  </template>
</base-layout>

作用域插槽

应用:让父组件中的插槽dom中使用子组件数据

父组件:

javascript
<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
具名:
javascript
<MyComponent>
  <template #header="headerProps">
    {{ headerProps }}
  </template>

  <template #default="defaultProps">
    {{ defaultProps }}
  </template>

  <template #footer="footerProps">
    {{ footerProps }}
  </template>
</MyComponent>

子组件:

javascript
<div>
  <slot :text="greetingMessage" :count="1"></slot>
</div>
javascript
<slot name="header" message="hello"></slot>

依赖注入

https://cn.vuejs.org/guide/components/provide-inject.html#working-with-reactivity

可以用readonly包裹provide提供的数据,防止注入方修改provide('read-only-count',readonly(count))

大型项目用Symbol作为注入名,保证唯一

异步组件

正常使用:

javascript
const AdminPage = defineAsyncComponent(() =>
  import('./components/AdminPageComponent.vue')
)

高级使用

javascript
const AsyncComp = defineAsyncComponent({
  // 加载函数
  loader: () => import('./Foo.vue'),

  // 加载异步组件时使用的组件    很快时可能会造成闪烁影响体验
  loadingComponent: LoadingComponent,
  // 展示加载组件前的延迟时间,默认为 200ms
  delay: 200,

  // 加载失败后展示的组件
  errorComponent: ErrorComponent,
  // 如果提供了一个 timeout 时间限制,并超时了
  // 也会显示这里配置的报错组件,默认值是:Infinity
  timeout: 3000
})

配合Suspense组件使用

组件 v-model defineModle

https://cn.vuejs.org/guide/components/v-model.html

为组件绑定 v-modle,不论是父组件还是子组件的修改,两组件都会响应

子 dialog 组件场景下感觉很好用

vue 内 style 标签中的响应式样式

以前我都是通过动态类名完成动态演示的展示,如果用动态内联样式的话会很繁琐

简单的个别样式可以通过动态绑定完成

javascript
<template>
  <p class="demo">hello</p>
</template>

<script setup>
import { ref } from 'vue'
const theme = ref({
    color: 'red',
})
</script>

<style scoped>
.demo {
  color: v-bind('theme.color');
}
</style>

复杂的样式通过动态绑定 style 完成变量注入

javascript

<template>
   	<div class="detail" :style="styleVars" @click="changeColor">一个小测试</div>
</template>
<script setup>
  import { ref, computed } from 'vue'

  const color = ref('red');

  let styleVars = computed(() => {
  	return {
  		'--color': color.value
  	};
  });
  
  const changeColor = () => {
  	color.value = 'blue';
  };
</script>

<style scoped>
  .detail {
    color: var(--color)
  }
</style>

内置组件

https://cn.vuejs.org/guide/built-ins/keep-alive.html

keepAlive

当渲染成本过高的部分需要频繁切换时,建议使用keepAlive

Teleport

Suspense

made with ❤️ by ankang