跳至主要內容

基础语法

xiaoye大约 15 分钟Vuevue3基础语法

Vue3基础语法

1 setup 函数

  • 新的 option, 所有的组合 API 函数都在此使用, 只在初始化时执行一次(在created之前,所有代码往里面写)
  • 函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用

setup 中你应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取,这也是为了避免setup()和其他选项式API混淆。

<template>
    {{msg}}
    <button @click='sayHi'>点击我</button>
</template>

//setup 表示script里面的代码全部都写在setup函数内部  而vue3代码全部可以在里面跑完
<script setup>
const msg = '小樱'
const sayHi = () => {
    return 'hi'
}
</script>

2 ref 数据响应

作用: 基本数据类型(通常是基本类型,对于对象有更好的方式)的数据响应定义 或者dom 连接

  • 基本数据类型绑定
    • 创建 RefImpl 响应式 Proxy 对象 ref 内部: 通过给 value 属性添加 getter/setter 来实现对数据的劫持
    • 定义数据之后,模板里面直接使用值
    • 修改数据 用.value 属性修改
  • 响应式状态需要明确使用响应式 APIsopen in new window 来创建。和从 setup() 函数中返回值一样,ref 值在模板中使用的时候会自动解包
<template>
    {{变量名}}
    <button @click='change'>点击我</button>
</template>

<script setup>
//引入ref 函数
import { ref } from 'vue'

//创建响应式数据
const 变量名 = ref(初始值)

//修改响应式数据   注意别修改原数据
const change = ()=> {
  变量名.value = 修改之后的值
}
</script>
  • dom 绑定
<template>
   <标签 ref='变量名'></标签>
</template>

<script setup>
#引入ref 函数
import { ref } from 'vue'

#创建dom 绑定必须变量名 相同
const 变量名 = ref(null)
</script>

3 reactive 数据响应

作用: **引用类型的数据响应定义 **

  • 作用: 定义多个数据的响应式
  • const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
  • 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
  • 语法
<template>
    {{state.属性1}}
</template>

<script setup>
//引入reactive 函数
import { reactive } from 'vue'

//创建响应式数据
const state = reactive({
    属性1:1,
})
</script>
<template>
    {{state.name}}
    {{state.k1.k2}}
    <button @click='change'>点击我</button>
</template>

<script setup>
//引入reactive 函数
import { reactive } from 'vue'

//创建响应式数据
const state = reactive({
    name:'小樱',
    k1:{
        k2:666
    }
})

//修改响应式数据   注意别修改原数据
const change = ()=>{
    state.name = '小狼'
    state.k1.k2 = 999
}
</script>
// 定另一个data,我要求此data下有三个属性:a(string), b(number), c(布尔)
type data1type = {
    atest: string
    bxx: number
    czz: boolean
}
const data1 = reactive<data1type>({
    atest: 'test',
    bxx: 10,
    czz: true
})

以上代码是用来限制reactive接收的类型,通过泛型的方式

4 toRefs

作用:将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref open in new window

当从组合式函数返回响应式对象时, toRefs 非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行解构/展开:

  • 语法
<script setup>
//引入reactive toRefs 函数
import { reactive, toRefs } from 'vue'

//创建响应式数据
const state = reactive({
    属性1:1,
    属性2:2
})

//数据解构出来,创建属性的ref,都通过value来获取值
const { 属性1, 属性2 } = toRefs(state)
</script>
  • 示例
<template>
    {{name}}
    {{age}}
    <button @click='change'>点击我</button>
</template>

<script setup>
//引入reactive toRefs 函数
import { reactive, toRefs } from 'vue'

//创建响应式数据
  const state = reactive({
    name: '小樱',
    age: 11,
    hobs: ['唱歌', '跳舞'],
    hands: {
        left: 100
    }
})

//数据解构出来 创建属性的ref 都通过value来获取值

//响应的ref数据改变会影响原代理对象
const { name, age, hobs, hands } = toRefs(state)

const change = () => {
    name.value = '小狼'
    age.value = 12
    hobs.value[0] = '洗澡澡'
    hands.value.left = 200
}
</script>

5 toRef

可以用来为源响应式对象上的某个 property 新创建一个 ref open in new window。然后,ref 可以被传递,它会保持对其源 property 的响应式连接(拿出来的变了,源对象也会变, 源对象变了,拿出来的这个也会变)。

  • 语法
const state = reactive({
    属性1:1
})

const ref变量 = toRef(state, '属性1')

  • 示例
const state = reactive({
    foo: 1,
    bar: 2
})

const fooRef = toRef(state, 'foo')

fooRef.value++
console.log(state.foo) // 2

state.foo++
console.log(fooRef.value) // 3

6 计算属性

  • computed函数:
    • 与computed配置功能一致
    • 只有getter 【了解】
    • 有getter和setter 【了解】
  • 语法
//简单用法 只有getter形式
const 计算属性变量 = computed(() => {
  return 处理好的数据
});

//有getter 有 setter形式
const 计算属性变量 = computed({
  get() {
   	return xx
  },

  set(value) {
	  
  },
});
  • 示例
//getter
<template>
    <h1>计算属性 computed</h1>
    <div>{{ msg }}</div>
    <div>取反数据:{{ reverseMsg }}</div>
    <button @click="msg = '我是一个新数据'">修改原数据</button>
</template>

<script setup>
import { computed, ref } from 'vue';
const msg = ref('我是一个数据')

//写法1 只有getter 得到计算属性的结果数据
const reverseMsg = computed(() => [...msg.value].reverse().join(''))

</script>


//getter & setter
<template>
    <h1>计算属性 computed</h1>
    <div>{{ msg }}</div>
    <div>取反数据:{{ reverseMsg }}</div>
    <button @click="msg = '我是一个新数据'">修改原数据</button>
    <button @click="changeRes">修改计算属性</button>
</template>

<script setup>
import { computed, ref } from 'vue';
const msg = ref('我是一个数据')

//写法2 getter函数 & setter函数  可以修改计算属性的值必须用到setter
const reverseMsg = computed({
    //取值 触发 getter函数
    get() {
        return [...msg.value].reverse().join('')
    },
    //改值触发 setter函数
    set(value) {
        msg.value = value
    }
})

const changeRes = () => {
    //修改计算属性 触发计算属性的setter函数
    reverseMsg.value = '我是计算属性的setter'
}
</script>

7 侦听器 watch

  • watch函数

    • 与watch配置功能一致

    • 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调

    • 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次

    • 通过配置deep为true, 来指定深度监视

  • watchEffect函数 【理解】

    • 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
    • 默认初始时就会执行第一次, 从而可以收集需要监视的数据
    • 监视数据发生变化时回调
  • 语法

//简单用法 
watch(ref 或者reactive 数据,(newV,oldV)=>{
     //观察数据变化执行 里面代码
 },{
    immediate:true, //立即执行
    deep:true //深度监听
 })
 
//观察多个数据变化
watch([()=>state.属性1,()=>state.属性2],()=>{
        
})
 
//watchEffect 会立即执行里面代码  监视所有回调中使用的数据
watchEffect(()=>{
     
})
  • 示例
<template>
    <h1>watch & watchEffect 侦听器</h1>
    <h3>watch监听基本类型</h3>
    <input type="text" v-model="msg" name="" id="">
    <div>{{ msg }}</div>
    <h3>监听引用类型</h3>
    <div>{{ state }}</div>
    <button @click="state.fa.fa = '奶奶'; state.age = 13">深度改变state</button>
</template>

<script setup>
import { reactive, ref, watch, watchEffect } from 'vue';

//基本类型
const msg = ref('我是初始值')

//引用类型
const state = reactive({
    name: '小樱',
    age: 11,
    fa: {
        fa: '爷爷'
    }
})

//监听基本类型
watch(msg, (newV, oldV) => {
    //观察数据执行你想执行的一切代码
    console.log('oldV :>> ', oldV);
    console.log('newV :>> ', newV);
}, {
    immediate: true,//立即执行
    deep: true//深度监听
})

//监听引用类型  可以监听深度改变
watch(state, (newV) => {
    console.log('newV :>> ', newV);
})

//监听多个类型 基本类型直接写,引用类型用回调函数形式监听属性变化
watch([msg, () => state.age], (newV) => {
    console.log('多个监听 :>> ', newV);
})

//只要回调函数里面有数据被引用 就会执行回调函数代码 --[注重过程]
watchEffect(() => {
    console.log(666);
    console.log('观察 :>> ', msg.value);
})

</script>

8 生命周期

因为 setup 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

下表包含如何在 setup ()open in new window 内部调用生命周期钩子:

选项式 API(vu2)Hook inside setup (vue3)
beforeCreateNot needed* 不需要
createdNot needed* 不需要
beforeMountonBeforeMount 挂载之前
mountedonMounted 页面加载完成时执行 ### 【常用】
beforeUpdateonBeforeUpdate: 更新前
updatedonUpdated 【更新完成】
beforeUnmountonBeforeUnmount 【组件卸载前】
unmountedonUnmounted 页面销毁时执行 ###
errorCapturedonErrorCaptured 【了解】
renderTrackedonRenderTracked【了解】
renderTriggeredonRenderTriggered【了解】
activatedonActivated 【keep-alive】 激活
deactivatedonDeactivated【keep-alive】 失活
<script setup >
import { onMounted, onActivated, onUnmounted, onUpdated, onDeactivated } from 'vue';

 onMounted(() => {
	 console.log("组件挂载")
 })

 onUnmounted(() => {
 	console.log("组件卸载")
 })

 onUpdated(() => {
 	console.log("组件更新")
 })
 onActivated(() => {
 	console.log("keepAlive 组件 激活")
 })

 onDeactivated(() => {
	 console.log("keepAlive 组件 非激活")
 })
</script>

9 defineProps 和 defineEmits 父子传参

注意:definePropsdefineEmits 都是只在 <script setup> 中才能使用的编译器宏

为了声明 propsemits 选项且具备完整的类型推断,可以使用 definePropsdefineEmits API,它们在 <script setup> 中都是自动可用的:

  • definePropsdefineEmits 都是只在 <script setup> 中才能使用的****编译器宏。他们不需要导入,且会在处理 <script setup> 的时候被编译处理掉。
  • defineProps 接收与 props 选项相同的值,defineEmits 也接收 emits 选项相同的值。
  • definePropsdefineEmits 在选项传入后,会提供恰当的类型推断。
  • 传入到 definePropsdefineEmits 的选项会从 setup 中提升到模块的范围。因此,传入的选项不能引用在 setup 范围中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块范围内。

父传子

//父组件
<template>
  <子组件 自定义属性1='静态' :自定义属性2='动态值'></子组件>
</template>

//子组件
<template>
  {{自定义属性1}}
  {{自定义属性1}}
</template>
<script setup>
  import { toRefs } from 'vue';
  //通过defineProps 接受的数据都在左侧自定义变量 props上[props变量是响应式的]
  //props接收的数据可以直接渲染
  const props = defineProps({
        自定义属性1:{
          type: 类型,
          default: () => 默认值
        },
        自定义属性2:{
          type: 类型,
          default: () => 默认值
        }
  })

</script>

通过ts去校验props

// 定义一个接口或者类型去约束defineProps执行之后返回的props, 没有默认值
type childprops = {
    name: string,
    age?: number
}
const props = defineProps<childprops>()

以上写法没得默认值

// 定义一个接口或者类型去约束defineProps执行之后返回的props, 
type childprops = {
    name: string,
    // ? 表示可有可无
    age: number
}
// 一下写法没得默认值
// const props = defineProps<childprops>()

const props = withDefaults(defineProps<childprops>(), {
    // 默认值
    name: '李四111',
    age: 24
})

有默认值的写法,借助withDefaults这个方法

子传父

//子组件
<script setup>

//定义子组件的自定义事件 -- 注册自定义事件
const emits = defineEmits(['自定义事件1','自定义事件2'])

//触发子传父事件
emits('自定义事件1', 数据)
</script>

//父组件
<template>
    <子组件 @自定义事件1="处理函数"></子组件>
</template>

<script setup>

//子组件 触发自定义事件1 父组件的处理函数 收到子传父的数据
const 处理函数 = (data) => {
    
}
</script>

bus传值

借助第三方库mitt (发布订阅模式)

  • 安装mitt: yarn add mitt

  • 找一个文件,放bus。

    • 创建文件utils/mitt.ts (楼盘)

      import mitt from 'mitt'
      
      // 创建了一个bus,用于跨组件通信
      const bus = mitt()
      
      export default bus
      
  • 发送消息 (发布)

    • 拿到bus,执行emit

      bus.emit('test', '通过mitt发送的数据')
      
  • 接收消息 (订阅)

    • 拿到bus,执行on

      // 等着bus给我数据
      bus.on('test', data => {
          console.log(data)
      })
      

10 defineExpose 子组件暴露数据方法

使用 <script setup> 的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。

为了在 <script setup> 组件中明确要暴露出去的属性,使用 defineExpose 编译器宏:

  • 语法
//子组件
<script setup>

//子组件暴露
defineExpose({
  属性1:1,
  方法1() {}
})

</script>

//父组件
<template>
    <子组件 ref='子组件ref'></子组件>
</template>

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

const 子组件ref = ref(null)
//注意 在生命周期onMounted 才能接受子组件暴露的数据和方法
onMounted(() => {
    子组件ref.value.方法1();
    子组件ref.value.属性1
})

</script>

11 自定义hooks函数

  • 使用Vue3的组合API封装的可复用的功能函数
  • 用来取代vue2 mixin 目的也是抽离公共js逻辑
  • 命名 userXxx开头的函数 一般会把一个功能封装在一个js中

vue2的代码重用方式mixin

  • 提取一部分代码出去,用对象包起来
  • 然后在要使用的地方,通过mixins进行注册
  • 注意:这玩意在vue3肯定不用了,在vue2会用,但是要注意有重名的危险,及其容易出现bug
  1. 定义一个mixin(.js)
export const nameMixin = {
    data() {
        return {
            name: '张三',
            age: 24
        }
    },
    computed: {
        info() {
            return `我叫${this.name}, 今年${this.age}`
        }
    },
    methods: {
        changeName() {
            this.name = '李四'
        }
    }
}

  1. 在vue文件里面引入注册,页面就可以使用混入的方法与属性

<script>
    // 引入mixin不好用
    import { nameMixin } from './mixin/nameMixin.js'
    export default {
        // 注册mixin
        mixins: [ nameMixin ],
       
    }
</script>

Vue3hooks函数

封装一个table宽度动态变化功能的hooks函数

  • hooks/useTableWidth.js
/* table 宽度动态变化 */
import { onMounted, onUnmounted, ref } from 'vue'

/* 表单宽度设置 */
const useTableWidth = () => {
  //dom
  const tableRef = ref(null)

  const w = ref('')

  //计算宽度
  const calcTableWidth = () => {
    // 这个不能自动修改 有点怪
    // table.value.style.width = document.body.clientWidth - 280 + 'px'
    w.value = document.body.clientWidth - 280 + 'px'
  }

  //挂载后
  onMounted(() => {
    //初始化计算一次
    calcTableWidth()
    //窗口宽度变化计算一次
    window.addEventListener('resize', calcTableWidth)
  })

  //销毁后
  onUnmounted(() => {
    window.removeEventListener('resize', calcTableWidth)
  })

  return {
    tableRef,
    w,
  }
}

export default useTableWidth
  • 使用
<template>
<!-- 表格 -->
<el-table ref="tableRef"  :style="{ width: w }" ></el-table>
</template>

<script setup>
    
import useTableWidth from '@/hooks/useTableWidth';
//计算table宽度

const { tableRef, w } = useTableWidth()
</script>

12 内置组件cn.vuejs.org/guide/built-ins/transition.htmlopen in new window

Transition(过渡和动画)

  • v-if 所触发的切换
  • v-show 所触发的切换
  • 由特殊元素 <component> 切换的动态组件
  • 改变特殊的 key 属性
<button @click="show = !show">Toggle</button>
<Transition>
  <p v-if="show">hello</p>
</Transition>

<script setup>
    import { ref } from "vue"
	const show = ref(false);
</script>

<style>
/* 下面我们会解释这些 class 是做什么的 */
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}
</style>

基于 CSS 的过渡效果open in new window

CSS 过渡 classopen in new window

一共有 6 个应用于进入与离开过渡效果的 CSS class。

过渡图示
过渡图示
  1. v-enter-from:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。
  2. v-enter-active:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。
  3. v-enter-to:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。
  4. v-leave-from:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。
  5. v-leave-active:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。
  6. v-leave-to:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。

v-enter-activev-leave-active 给我们提供了为进入和离开动画指定不同速度曲线的能力,我们将在下面的小节中看到一个示例。

KeepAlive(缓存)

<KeepAlive> 是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例

<!-- 非活跃的组件将会被缓存! -->
<KeepAlive>
  <component :is="activeComponent" />
</KeepAlive>

<!-- 你也可以包裹整个路由! -->

包裹后会有两个新的钩子

<script setup>
import { onActivated, onDeactivated } from 'vue'

onActivated(() => {
  // 调用时机为首次挂载
  // 以及每次从缓存中被重新插入时
})

onDeactivated(() => {
  // 在从 DOM 上移除、进入缓存
  // 以及组件卸载时调用
})
</script>

Fragment(片断)

  • 在Vue2中: 组件必须有一个根标签
  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处: 减少标签层级, 减小内存占用
//其实 两个div被包在Fragment 虚拟元素中 感觉没有根元素
<template>
   <div></div>
   <div></div>
</template>

Teleport(传送门)-弹框

  • Teleport 提供了一种干净的方法, 让组件的html在父组件界面外的特定标签
    • 例如:可以把一个组件里面的弹窗popUp 直接插进body标签里面
//相当于 body.appendChild(弹窗)
<teleport to="body">
   <div class="pop">
      弹窗
   </div>
</teleport>