Skip to content
目录

ref、reactive

ref

基础数据类型绑定响应式数据
ref是一个函数,接收一个参数,返回一个对象,对象有 value 属性,value 属性的值就是传入的参数
即js中需要通过 .value 的方式访问,template 中会自动解析不需要 .value

TIP

ref 内部其实是调用了 reactive ,只是 reactive 的参数是一个对象,而 ref 的参数是一个基础数据类型

reactive

INFO

复杂数据类型绑定响应式数据
ref是一个函数,接收一个参数,返回一个对象,对象有 value 属性,value 属性的值就是传入的参数
即 js 中需要通过.value 的方式访问,template 中会自动解析不需要 .value

vue
<script lang="ts" setup>
import { ref, reactive } from 'vue'
interface Person {
  name: string
  age: number
}
const count = ref<Number>(0)
const list = reactive<Persion[]>([
  {
    name: "李四",
    age: 16
  }  
])
</script>

toRef、toRefs、toRaw

toRef

使一个对象指定的属性具有数据层面的响应性,不会更新视图

vue
<script lang="ts" setup>
import { toRef } from 'vue'
const obj = {
  name: "张三",
  age: 18
}
const name = toRef(obj, 'name')
const changeName = () => {
  name.value = '李四'
}
</script>

toRefs

使一个对象所有属性具有数据层面的响应性,不会更新视图\

TIP

批量版本的 toRef ,源码中也是通过对象循环调用 toRef 实现的

vue
<script lang="ts" setup>
import { toRefs } from 'vue'
const obj = {
  name: "张三",
  age: 18
}
let { name, age } = toRefs(obj)
</script>

toRaw

将响应式对象修改为普通对象
数据能变化,视图不更新

TIP

源码中是通过 WeakMap 实现的,将响应式对象作为 key,普通对象作为 value 存储在 WeakMap 中,通过 get 方法获取

vue
<script lang="ts" setup>
import { toRefs } from 'vue'
const obj = {
  name: "张三",
  age: 18
}
const data = toRefs(obj)
</script>

computed

vue
<template>
  <h1>{{total}}</h1>
</template>
<script setup lang="ts">
import { ref,computed } from "vue";
const count = ref(0)
const total = computed<Number>(() => count.value +1)
</script>

watch

vue
watch(data, ()=>{}, {})
  • 参数一,监听的数据,多个时为数组。监听对象的某个属性时候需要使用 ()=>xx.xx 的方式。监听 reactive 定义的引用数据时,需要手动开启深度监听
  • 数据改变时的回调函数 (newV, oldV)
  • 配置项
    • immediate: true, // 立即执行一次回调函数
    • deep: true, // 监听对象的深度变化

watchEffect

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行,类似计算属性
用到哪些变量就会监听哪些变量 当 watchEffect在setup() 函数或生命周期钩子中使用时候,会在组件卸载时自动停止监听。但是如果采用异步的方式创建了一个监听器,那么就需要手动停止监听

  • 手动停止
vue
<script setup lang="ts">
import { ref, watchEffect } from "vue"
let count = ref(0)
setTimeout(() => {
  count.value++
}, 1000)
const stop = watchEffect(() => {
  console.log(count.value)
})
// 在需要的位置手动停止
stop()
</script>
  • 清除副作用 watchEffect的第一个参数——effect函数——可以接收一个参数:叫 onInvalidate ,也是一个函数,用于清除 effect 产生的副作用,就是在触发监听之前会调用一个函数处理你的逻辑,例如防抖
vue
<script setup lang="ts">
import { ref, watchEffect } from "vue"
let count = ref(0)
setTimeout(() => {
  count.value++
}, 1000)
watchEffect((onInvalidate) => {
  onInvalidate(() => {
    console.log('执行');
  });
})
</script>

控制台依次输出:0 => 执行 => 1

  • 配置选项 watchEffect 的第二个参数,用来定义副作用刷新时机,可以作为一个调试器来使用。 flush(更新时机):
    • pre:组件更新前执行
    • sync:强制效果始终同步触发
    • post:组件更新后执行
vue
<script setup lang="ts">
import { ref, watchEffect } from "vue";
let count = ref(0)
setTimeout(() => {
  count.value++
}, 1000)
watchEffect((onInvalidate) => {
  console.log(count.value)
  onInvalidate(() => {
    console.log('执行');
  });
}, {
  flush: "post",
  // 调试工具
  onTrigger(e) {
    console.log('触发', e);
  },
})
</script>

ref获取节点

在循环中精准获取到节点

vue
<template>
  <ul>
    <li v-for="item in 5" :key="item" :ref="(el) => setItemRefs(el)">
      {{ item }}
    </li>
  </ul>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
let itemRefs: Array<any> = [];
const setItemRefs = (el) => {
  itemRefs.push(el);
}
onMounted(() => {
  console.log(itemRefs);
});
</script>

生命周期

与 vue2 相比,vue3 除了 setup 以外所有生命周期名前加 on ,setup 代替了 beforeCreate 和 created beforeDestroy 和 destroyed 更名为 onBeforeUnmount 和 onUnmounted ,以及新增了 onRenderTracked 和 onRenderTriggered 用于开发环境调试的钩子。

生命周期执行时机
setup组件初始化时执行
onBeforeMount组件挂载前执行
onMounted组件挂载后执行
onBeforeUpdate组件更新前执行
onUpdated组件更新后执行
onBeforeUnmount组件卸载前执行
onUnmounted组件卸载后执行
onActivated组件激活时执行
onDeactivated组件停用时执行
onErrorCaptured组件发生错误时执行
onRenderTracked组件渲染时执行
onRenderTriggered组件渲染后执行

父子组件传参

defineProps

无需引入,直接使用 父组件传参

vue
<template>
  <Child :title="title" />
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import Children from './Children.vue'
const title = ref('起飞')
</script>

子组件 defineProps 接收

vue
<template>
  <h1>{{title}}</h1>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
defineProps({
  title: String
})
</script>

子组件使用 withDefaults 定义 props 默认值

vue
<template>
  <h1>{{title}}</h1>
</template>
<script setup lang="ts">
type Props = {
  title: string
}
withDefaults(defineProps<Props>(), {
  title: '默认值'
})

</script>

defineEmits

子组件使用 defineEmits 向父组件抛出事件
通过 defineEmits 注册了一个自定义事件,点击按钮的时候触发 emit 调用我们注册的事件,传递参数

vue
<template>
  <h1>{{title}}</h1>
  <button @click="onChange">botton</button>
</template>
<script setup lang="ts">
type Props = {
  title: string
}
withDefaults(defineProps<Props>(), {
  title: '默认值'
})
const emits = defineEmits(['test'])
const onChange = () => {
  emits('test', '子组件向父组件抛出事件')
}
</script>

父组件接收子组件抛出事件,跟 vue2 一样,通过@事件名接收

vue
<template>
  <Child :title="title" @test="onTest" />
</template>

defineExpose 获取子组件的实例和内部属性

定义对外暴露的属性和方法
在 script-setup 模式下,所有数据只是默认 return 给 template 使用,不会暴露到组件外,所以父组件是无法直接通过挂载 ref 变量获取子组件的数据。

  • 子组件
vue
<template>
  <h1>子组件</h1>
</template>
<script setup lang="ts">
const name = ref('子组件')
// 则父组件通过ref能拿到name
defineExpose({
  name
})
</script>
  • 父组件
vue
<template>
  <h1>父组件</h1>
  <child ref='childRef' />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import child from './Child.vue'
// 子组件ref(TypeScript语法)
const childRef = ref<InstanceType<typeof child>>()
const getName = () => {
  console.log(childRef.value.name);
}
</script>

TIP

defineProps、defineEmits、defineExpose、withDefaults 在 script-setup 模式可直接使用,如果是在 script 标签下使用,需要引入

插槽

vue3中使用v-slot代替了slot和slot-scope,且v-slot:slotName可简写为#slotName

vue
<template>
  <div>
    <h1>父组件</h1>
    <child>
      <template #content="slotProps">
        <h1>{{slotProps.msg}}</h1>
      </template>
    </child>
  </div>
</template>
<script setup lang="ts">
import child from './Child.vue'
</script>
vue
<template>
  <div>
    <h1>子组件</h1>
    <!-- 如果不是具名插槽,则父组件使用#default -->
    <slot name="content" msg="我是子组件内容"></slot>
  </div>
</template>

异步组件

使用defineAsyncComponent异步加载组件

vue
<template>
  <div>
    <h1>父组件</h1>
    <AsyncComponent />
  </div>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))
</script>

Suspense

Suspense 组件用于异步组件的加载,当异步组件加载完成后,Suspense 组件会自动渲染异步组件,否则渲染 fallback 插槽中的内容
Suspense 组件中的异步组件必须使用 defineAsyncComponent 异步加载
Suspense 组件带有两个插槽,fallback 和 default,fallback 插槽用于异步组件渲染前的内容,default 插槽包裹异步组件,也可省略

vue
<template>
  <Suspense>
    <template #fallback>
      <div>loading...</div>
    </template>
    <AsyncComponent />
  </Suspense>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))
</script>

Teleport

Teleport 组件用于将组件渲染到指定的 DOM 节点中,而不是渲染到当前组件的父组件中
不受父级style、v-show等影响
通过to 属性插入到指定元素位置,如 body,html,自定义 className 等等

vue
<template>
  <div>
    <!-- 会直接渲染到body下 -->
    <Teleport to="body">
      <div>我是Teleport组件</div>
    </Teleport>
  </div>
</template>

keep-alive

作用与vue2一样,生命周期名称有所更改
初次进入时,会触发 activated 生命周期,离开时,会触发 deactivated 生命周期\

provide/inject

provide/inject 用于父组件向不限层级的子组件透传数据或方法,provide 用于提供数据,inject 用于接收数据
父组件:

vue
<template>
  <div>
    <h1>父组件</h1>
    <child />
  </div>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue'
import child from './Child.vue'
const name = ref('父组件')
provide('name', name)
</script>

子组件:

vue
<template>
  <div>
    <h1>子组件</h1>
    <div>{{name}}</div>
  </div>
</template>
<script setup lang="ts">
import { inject } from 'vue'
// const name = inject('name')
const msg = inject<Ref<string>>('name',ref('我是默认值'))
</script>

TIP

inject 的第二个参数为默认值,它可以是一个工厂函数,用来返回某些创建起来比较复杂的值。如果默认值本身就是一个函数,那么必须将 false 作为第三个参数传入,表示这个函数就是默认值而不是工厂函数
如果想传入的值能响应式变化,那么就使用 ref 或 reactive 添加响应式

v-model 升级

我们都知道 v-model 是 props 和 emit 组合的语法糖,vue3 中改动如下:

  • 变更:value => modelValue
  • 变更:update:input => update:modelValue
  • 新增:一个组件可以设置多个 v-model
  • 新增:开发者可以自定义 v-model 修饰符
  • v-bind 的.sync 修饰符和组件的 model 选项被移除 子组件:
vue
<template>
  <div>
    <h1>子组件</h1>
    <input type="text" :value="msg" @input="textChange" />
    <div>{{msg}}</div>
  </div>
</template>
<script setup lang="ts">
import { defineEmits } from 'vue'
defineProps({
  msg: String
})
const emit = defineEmits(['update:msg'])
const textChange = (e: any) => {
  const value = e.target.value
  console.log(value)
  emit("update:msg", value)
}

</script>

父组件:

vue
<template>
  <div>
    <h1>父组件</h1>
    <child v-model:msg="msg" />
    <div>{{msg}}</div>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import child from './components/myChild.vue'
const msg = ref('我是父组件')
</script>

自定义指令

生命周期: // 表格

钩子说明
created元素初始化时
beforeMount指令第一次绑定到元素时调用,只调用一次
mounted指令第一次绑定到元素时调用,只调用一次
beforeUpdate指令所在组件的 VNode 更新之前调用,可以调用多次
updated指令所在组件的 VNode 及其子 VNode 全部更新后调用,可以调用多次
beforeUnmount指令与元素解绑之前调用,只调用一次
unmounted指令与元素解绑之后调用,只调用一次

自定义拖拽指令:

vue
<template>
  <div v-drag class="box">
    <div class="header"></div>
    <div>
      内容
    </div>
  </div>
</template>
 
<script setup lang='ts'>
import type { Directive } from "vue";
const vDrag: Directive = {
  mounted(el: HTMLElement) {
    let moveEl = el.firstElementChild as HTMLElement;
    const mouseDown = (e: MouseEvent) => {
      let X = e.clientX - el.offsetLeft;
      let Y = e.clientY - el.offsetTop;
      const move = (e: MouseEvent) => {
        el.style.left = e.clientX - X + "px";
        el.style.top = e.clientY - Y + "px";
        console.log(e.clientX, e.clientY, "位置改变");
      };
      document.addEventListener("mousemove", move);
      document.addEventListener("mouseup", () => {
        document.removeEventListener("mousemove", move);
      });
    };
    moveEl.addEventListener("mousedown", mouseDown);
  },
};
</script>
<style >
.box {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 200px;
  height: 200px;
  border: 1px solid #ccc;
}
.header {
  height: 20px;
  background: black;
  cursor: move;
}
</style>

位置图解

自定义 hooks

vue3 的 hook 函数相当于 vue2 的 mixin,不同在于hooks是函数,而mixin是对象

  • 窗口改变时获取宽高 hook
ts
import { onMounted, onUnmounted, ref } from "vue";

function useWindowResize() {
  const width = ref(0);
  const height = ref(0);
  function onResize() {
    width.value = window.innerWidth;
    height.value = window.innerHeight;
  }
  onMounted(() => {
    window.addEventListener("resize", onResize);
    onResize();
  });
  onUnmounted(() => {
    window.removeEventListener("resize", onResize);
  });
  return {
    width,
    height
  };
}

export default useWindowResize;

使用:

vue
<template>
  <h3>屏幕尺寸</h3>
  <div>宽度:{{ width }}</div>
  <div>高度:{{ height }}</div>
</template>

<script setup lang="ts">
import useWindowResize from "../hooks/useWindowResize.ts";
const { width, height } = useWindowResize();
</script>
  • 权限按钮 hook
vue
<template>
  <button v-has-show="'create'">创建</button>
</template>
<script setup lang='ts'>
import type { Directive } from 'vue'
//mock用户权限
const permission = [
  'create',
]
const vHasShow:Directive<HTMLElement,string> = (el,bingding) => {
  if(!permission.includes(bingding.value)){
    el.style.display = 'none'
  }
}
</script>

插件

项目插件

  • unplugin-auto-import/vite
    自动导入用到的组件

vscode插件

  • volar
    vue3 的语法高亮,需关闭 vetur 插件

be someone