Vue3知识点

环境准备

npm init vue@latest     基于vite

Vue2和Vue3的区别

vue2 选项式api

缺点:功能分散;项目大不易于维护和阅读

vue3 组合式api

优点:功能组合在一起

Vue2 0bject.defineProperty
Vue3 Vue基础语法+Vue-router+vuex(pinia)+组件库
语法不同(组合式api)、体积更小、按需引入、proxy实现响应式diff优化,vue3与ts配合等

入口函数 

setup

执行时机:beforeCreate

内部没有this

定义的数据和方法在模板中通过使用return{}

script setup

在vue3.2使用

语法:在script标签上添加setup

不用return 和 export default

<script setup>
import { ref } from 'vue'
// 组合式api
// 创建响应式数据age,初始值是10
const age = ref(10)
const sname = ref('zs')
// 修改年龄的方法
const increase = () => {
  age.value++
}
</script>

ref函数

ref函数创建响应式数据,返回值是一个对象

模版中使用ref数据,省略.value,js代码中不能省略

使用步骤:

1.从vue中导入ref

2.在 setup 函数中,使用 ref 函数,传入数据,返回一个响应式数据

3.使用 ref 创建的数据,js 中需要 .value来取值 ,template模板 中可省略

<script setup>
import { ref } from 'vue'
// 组合式api
// 创建响应式数据age,初始值是10
const age = ref(10) // age是对象
console.log(age);
const sname = ref('zs')
const obj =ref({
  name: '北京',
  desc: '政治文化中心'
})
// 修改年龄的方法
const increase = () => {
  age.value++ // ref数据通过.value获取其值
}
// 修改城市的方法
const change = () => {
  obj.value.name = '上海'
}
</script>

<template>
  <div>Hello Vue3</div>
  <p>年龄 {{ age }}</p>
  <p>姓名 {{ sname }}</p>
  <p>城市 {{ obj.name }}</p>
  <p @click="increase">年龄+1</p>
  <button @click="change">修改城市</button>
</template>

<style scoped></style>

reactive函数

reactive函数创建响应式数据,只支持引用数据类型

作用:定义复杂数据类型的响应式数据,不能定义简单数据类型

使用步骤:

1.从vue中导入 reactive

2.在 setup 函数中,使用 reactive 函数,传入复杂数据类型数据,返回一个响应式数据

3.使用 reactive 创建的数据,js 和 template 模板中都可以直接使用
 

<script setup>
import { reactive } from 'vue'
const user = reactive({
  name: 'admin',
  pwd: '123456'
})
const changeUserName = () => {
  user.name = 'admin666' 
}
</script>

<template>
  <div>Hello Vue3</div>
  <p>姓名 {{ user.name }}</p>
  <button @click="changeUserName">修改姓名</button>
</template>

<style scoped></style>

reactive对比ref

相同点  都可以创建响应式数据

不同点  reactive只支持引用数据类型,ref支持基本和引用数据类型

             ref通过.value获取数据,reactive不需要.value

             ref创建响应式引用数据类型低层依赖reactive

computed函数 

作用:计算属性

使用步骤:

1.从vue中导入 computed

2.在 setup 函数中,使用 computed 函数,参数是一个函数,函数返回计算好的数据

3.计算好的数据在 js 中需要.value取值,template 模板中可以直接使用

<script setup>
import { ref, computed } from 'vue'
// 创建一个响应式数据
const num = ref(1)
const doubleNum = computed(()=> {
    return num.value*2
})

const goods = ref([
 {
  id: 1001,
  price: 5000,
  name: '小米手机'
},
{
  id: 1002,
  price: 4000,
  name: '魅族手机'
},
{
  id: 1003,
  price: 6000,
  name: '三星手机'
}  
])

// 筛选出价格大等于5000的商品 
const filterGoods = computed(()=> {
    return goods.value.filter(item => item.price >= 5000)

})


</script>
<template>
<div>Hello Vue3</div>
<p>{{ num }}  - {{ doubleNum }}</p>
  <p>{{ filterGoods }}</p>
</template>

 watch函数 

作用:监听数据的变化

使用步骤:

1.从vue中导入 watch

2.在 setup 函数中,使用 watch 函数,参数有以下几种情况:

 (1)侦听一个数据:第一个参数监听的数据 第二个回调函数

 (2) 侦听多个数据第一个参数监听的数据构成的数组 第二参数回调函数

 (3)立刻调用 immediate:是否立即侦听 默认是false 如果是 true 代表页面一加载 会先执行一               次处理程序

 (4) 深度监听:如果是true 代表深度侦听 不仅会侦听地址的变化 , 还会在侦听对象内部的属性变化

<script setup>
import { ref, watch } from 'vue'
// 创建一个响应式数据
const num = ref(1)
const age = ref(10)
const obj = ref({
   id: 1,
  name: '电视',
  price: 3000  
})
// 1 侦听一个数据
// 第一个参数监听的数据 第二个回调函数
// watch(num,(newV, oldV)=> {
//     console.log(newV, oldV);
// })

// 2 侦听多个数据
//  第一个参数监听的数据构成的数组
//  第二参数回调函数 
// watch([num,age],([newNum, newAge],[oldNum, oldAge])=>{
//     console.log(newNum, newAge);
// })

// 3 立刻调用 immediate
// watch(num,(newV, oldV)=>{
//     console.log('立刻调用')
//     console.log(newV, oldV);
// },{
//     immediate:true
// })

// 4 深度监听
watch(obj,(newV, oldV)=>{
    console.log(newV,oldV);
},{
    deep:true
})
const changeObj = () =>{
    obj.value.price -= 200
}
</script>

<template>
<div>Hello Vue3</div>
 <p>{{ num }} </p>
 <button @click="changeObj">修改obj</button>
</template>
<style scoped></style>

计算属性完整写法

语法:

const   自定义计算属性名 = computed: ({

    get() { return 结果 },
    set(val) {}
  }
})

  1. 如果要访问计算属性 会自动执行get

  2. 如果要修改计算属性 会自动执行set

说明:

(1)get函数就等同于简单写法的函数 计算属性必须要有 get 而且需要返回结果

(2)set方法第一个参数 可以监听用户输入 新值 与 旧值

注意:

修改计算属性时需要用完整写法

修改时会自动执行get函数

获取数据时会自动化执行get函数

<script setup>
import { ref, computed } from 'vue'
const firstName = ref('')
const lastName = ref('')
// 简写 只提供get 
// const fullName = computed(()=>{
//   return  firstName.value + lastName.value
// })
const fullName = computed({
    get() {
        return firstName.value + lastName.value
    },
    set(newV) {
        firstName.value = newV.substring(0,1)
        lastName.value = newV.substring(1)
    }
})
</script>

<template>
  <div>Hello Vue3</div>
  <input type="text" v-model="firstName">
  <input type="text" v-model="lastName">
  <input type="text" v-model="fullName">
</template>

<style scoped></style>

watch监听对象具体的属性 

 默认情况下 watch 只能监听到简单类型的数据变化 如果侦听的是复杂类型 只会侦听地址是否发生变化 不会侦听对象内部属性的变化

watch:{
     data属性名:{
         deep:true,

        immediate: true
         handler(newVal,oldVal){
      
         }
     }
   }

watch监听reactive

侦听的是reactive数据,默认对第一层属性开启deep: true,此时无论有没有传入deep选项

侦听的是ref引用数据 默认deep: false,监控的对象属性发生改变不会被监控到

<template>
  <div>
    <p>{{ user }}</p>
    <button @click="changeAge">修改age</button>
  </div>
</template>

<script setup>
import  { reactive, watch } from  'vue'
const user  = reactive({
  name: 'admin',
  age: 18,
  job: {
    jobName: 'web前端工程师',
    salary: 6000
  }
})
// 侦听的是reactive数据,默认对第一层属性开启deep: true,此时无论有没有传入deep选项
// 侦听的是ref引用数据 默认deep: false,监控的对象属性发生改变不会被监控到
watch(user,(newV, oldV)=>{
  console.log(newV);
},{
  deep: false // 关闭深度监控无效
})

const changeAge = ()  =>  {
 // user.age++
 // 假如需要侦听深层次数据 需要手动开启deep:true
  user.job.salary += 2000
}
</script>

<style lang="scss" scoped>

</style>

reactive丢失响应式

场景

解构赋值会丢失响应式

场景:
1.你定义了一个数据:let data=reactive({
          name:"",
          age:""
})
2.然后你解构了
let {name}=data; //解构赋值

重新赋值会丢失响应式

场景:
1.你定义了一个数据:let data=reactive({
          name:"",
          age:""
})
2.然后你请求了接口,赋值给data
let res=await getUserApi();  //请求接口
data=res.data;                     //将返回的结果赋值给data

解决办法

  1. 避开直接赋值和结构,reactive直接包裹一个对象。

    let data=reactive({
        userData:{}        //里面定义一个对象,这样赋值就不会丢失响应式了。
    })
     
    //获取接口数据
    let res=await getUserApi();  //请求接口
    data.userData=res.data;      //将返回的结果赋值给data

  2. 简单数据类型使用ref()来进行定义

watchEffect函数

1回调函数立即调用

2 回调函数依赖的的数据都会被监控 深度监控

 watch vs watchEffect

1 相同点 都可以对数据进行侦听

2 watch 和 watchEffect 都能监听响应式数据的变化,不同的是它们监听数据变化的方式不同。

 (1)watch 会明确显式监听某一个响应数据,而 watchEffect 则是隐式的监听回调函数中响应数据。

 (2) watch 在响应数据初始化时是不会执行回调函数的,watchEffect 在响应数据初始化时就会立即执行回调函数。

<template>
  <div>
     <button @click="num++">num++</button>
  </div>
</template>

<script setup>
import { watchEffect, ref, reactive } from 'vue'
// watchEffort监控数据
const num = ref(10)
const obj = reactive({
    name: 'zs',
    age: 18,
    boyFriend: {
        name: 'lisi',
        age: 19
    }
})

watchEffect(()=> {
    console.log(num.value);
    console.log(obj.boyFriend.name);
})
</script>
<style lang="scss" scoped>

</style>

 onInvalidate副作用清理函数

 watchEffect接收一个副作用函数

执行时机: 下一次副作用执行前

 副作用-发网络请求、开启定时器、控制台输出

onInvalidate清除副作用函数注意点

1.该函数总是在watchEffect执行的时候再次执行

2.当组件被销毁的时候该函数再次执行

3.该函数总是优先于watchEffect中的同步/异步代码执行

4.Promize函数的执行应该在该函数下面

flush:'post'

onInvalidate清除副作用函数的执行时机由flush控制

通过 flush:post可以避免副作用,在DOM更新后运行副作用,确保模板引用与DOM保持同步,并引入正确的元素。

watchPostEffect

watchEffect 的别名,带有 flush: ‘post’ 选项。

watchSyncEffect

watchEffect 的别名,带有 flush: ‘sync’ 选项。

nextTick使用

nextTickVue3中的一个非常有用的函数,它可以在下一次DOM更新循环结束后执行回调函数。这个函数可以用来解决一些异步更新视图的问题,例如在修改数据后立即获取更新后的DOM节点

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="changeMessage">changeMessage</button>
  </div>
</template>

<script setup lang="ts">
import { ref, nextTick } from "vue";

const message = ref("Hello, world!");

const changeMessage = () => {
  message.value = "Hello, nextTick!";
  nextTick(() => {
    console.log(document.querySelector("p")!.textContent); // 'Hello, nextTick!'
  });
};
</script>

<style scoped></style>

 在这个示例中,我们定义了一个名为message的响应式变量,并创建了一个名为changeMessage的函数来修改这个变量的值。在函数中,我们调用了nextTick函数,并传入一个回调函数。这个回调函数将在DOM更新循环结束后执行,并且我们可以在回调函数中获取到更新后的DOM节点。


生命周期

1 选项式api

beforeCreate/created

beforeMount/mounted

beforeUpdate/updated  

beforeUnmount/unmounted

 2 组合式api

 组合式 api生命周期函数可以调用多次

发网络请求可以在onMounted发送或者直接调用

 获取dom在onMounted使用

setup->组合式api

onBeforeMount/onMounted

onBeforeUpdate/onUpdated  

onBeforeUnmount/onUnmounted

在vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有两个被更名:

beforeDestroy改名为beforeUnmount

destoryed改名为unmounted

vue3.0也提供了Composition形式的生命周期钩子与Vue2.x中钩子对应关系如下:

beforeCreate ====> setup()

created ====> setup()

beforeMount ====> onBeforeMount

mounted ====> onMounted

beforeUpdate====>onBeoforeUpdate

updated ====>onUpdated

beforeUnmount ==>onBeforeUnmounnt

unmounted ===> onUnmounted

onMounted的优先级会比mounted高

父子组件间通信

一、父传子 defineProps

组件传值给子组件主要是由父组件为子组件通过v-bind绑定数值,而后传给子组件;子组件则通过defineProps接收使用。

如下为父组件Father.vue

<template>
  <div class="fa">
    <div style="margin: 10px;">我是父组件</div>
    <Son :fatherMessage="fatherMessage"></Son>
  </div>
</template>

<script setup lang="ts">
import Son from './Son.vue'
import {ref} from "vue";

const fatherMessage = ref<string>("我是父组件传过来的值")

</script>

<style scoped>
.fa{
  border: 3px solid cornflowerblue;
  width: 400px;
  text-align: center;
}
</style>

如下为子组件Son.vue

<template>
  <div style="margin: 10px;border: 2px solid red">
    我是子组件
    <div style="margin: 5px;border: 2px solid gold">
      父组件传值接收区:{{fatherMessage}}
    </div>
  </div>
</template>

<script setup lang="ts">
interface Props {
  fatherMessage?: string,
}
defineProps<Props>()

</script>

父组件Father.vue中在调用Son.vue这个子组件时,使用v-bind绑定参数fatherMessage,并传给Son.vue

子组件Son.vue使用defineProps接收fatherMessage这个参数,而后就可以正常使用该参数。

二、子传父 defineEmits

子组件传值给父组件主要是子组件通过defineEmits注册一个自定义事件,而后触发emit去调用该自定义事件,并传递参数给父组件。

在父组件中调用子组件时,通过v-on绑定一个函数,通过该函数获取传过来的值。

如下为子组件Son.vue

<template>
  <div style="margin: 10px;border: 2px solid red">
    我是子组件
    <button @click="transValue" style="margin: 5px">传值给父组件</button>
  </div>
</template>

<script setup lang="ts">
import {ref} from "vue";

// 定义所要传给父组件的值
const value = ref<String>("我是子组件传给父组件的值")

// 使用defineEmits注册一个自定义事件
const emit = defineEmits(["getValue"])

// 点击事件触发emit,去调用我们注册的自定义事件getValue,并传递value参数至父组件
const transValue = () => {
  emit("getValue", value.value)
}

</script>

如下为父组件Father.vue 

<template>
  <div class="fa">
    <div style="margin: 10px;">我是父组件</div>
    父组件接收子组件传的值:{{sonMessage}}
    <Son @getValue="getSonValue"></Son>
  </div>
</template>

<script setup lang="ts">
import Son from './Son.vue'
import {ref} from "vue";

const sonMessage = ref<string>("")
const getSonValue = (value: string) => {
  sonMessage.value = value
}
</script>

<style scoped>
.fa{
  border: 3px solid cornflowerblue;
  width: 400px;
  text-align: center;
}
</style>

使用defineEmits注册一个事件getValue,而后设置点击事件transValue去触发emit,去调用我们注册的自定义事件getValue,并传递value参数至父组件。

父组件Father.vue在获取子组件Son.vue传过来的值时,通过在子组件上使用v-on设置响应函数getValue(该函数与子组件中的注册自定义事件getValue名称需一致),并绑定一个函数getSonValue来获取传过来的值。

三、子组件暴露属性给父组件 defineExpose

当父组件想直接调用父组件的属性或者方法时,子组件可以使用defineExpose暴露自身的属性或者方法,父组件中使用ref调用子组件暴露的属性或方法。
如下为子组件Son.vue

<template>
  <div style="margin: 10px;border: 2px solid red">
    我是子组件

  </div>
</template>

<script setup lang="ts">
import {ref, defineExpose} from "vue";

// 暴露给父组件的值
const toFatherValue = ref<string>("我是要暴露给父组件的值")

// 暴露给父组件的方法
const toFatherMethod = () => {
  console.log("我是要暴露给父组件的方法")
}
// 暴露方法和属性给父组件
defineExpose({toFatherMethod, toFatherValue})

</script>

 如下为父组件Father.vue

<template>
  <div class="fa">
    <div style="margin: 10px;">我是父组件</div>
    <button @click="getSonMethod">获取子组件的方法</button>
    <Son ref="sonMethodRef"></Son>
  </div>
</template>

<script setup lang="ts">
import Son from './Son.vue'
import {ref} from "vue";

const sonMethodRef = ref()

const getSonMethod = () => {
  sonMethodRef.value.toFatherMethod()
  console.log(sonMethodRef.value.toFatherValue)
}

</script>

<style scoped>
.fa{
  border: 3px solid cornflowerblue;
  width: 400px;
  text-align: center;
}
</style>

在子组件中定义属性toFatherValue和方法toFatherMethod,而后通过defineExpose暴露出来。
父组件调用时,为子组件绑定一个ref,并定义一个ref变量sonMethodRef,通过调用sonMethodRef,来获取子组件暴露出来的属性和方法。 【】

四、依赖注入Provide / Inject

 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。

如下为父组件Root.vue

<template>
  <div>
    我是root组件
    <Footer></Footer>
  </div>
</template>

<script setup lang="ts">
import { provide, ref } from 'vue'
import Footer from './Footer.vue'

const toChildValue= ref<string>("我是给所有子组件的值")

// 将toChildValue注入到所有子组件中
provide(/* 注入名 */ 'toChildValue', /* 值 */ toChildValue)

</script>

如下为子组件Footer.vue 

<template>
  <div>
    我是footer组件
    <div>
      接收父组件的值:{{getFatherValue}}
    </div>
    <DeepChild></DeepChild>
  </div>
</template>

<script setup lang="ts">
import DeepChild from "./DeepChild.vue"
import {ref,inject,Ref} from "vue";

// 获取父组件提供的值
// 如果没有祖先组件提供 "toChildValue"
// ref("") 会是 "这是默认值"
const getFatherValue = inject<Ref<string>>(/* 注入名 */"toChildValue",/* 默认值 */ ref(""))

</script>

 如下为孙子组件DeepChild.vue

<template>
  <div>
    我是deepChild组件
    <div>
      接收爷爷组件的值:{{getGrandFatherValue}}
    </div>
  </div>
</template>

<script setup lang="ts">
import {inject, ref, Ref} from "vue";

// 获取爷爷组件提供的值
// 如果没有爷爷组件提供 "toChildValue"
// value 会是 ""
const getGrandFatherValue = inject<Ref<string>>(/* 注入名 */"toChildValue",/* 默认值 */ ref(""))
</script>

当最顶层的组件Root.vue传值给所有子组件时,使用provide进行注入

provide(/* 注入名 */ 'toChildValue', /* 值 */ toChildValue)

 而后无论哪个子组件想要获取toChildValue的值,只需使用inject即可

inject<Ref<string>>(/* 注入名 */"toChildValue",/* 默认值 */ ref(""))


 

toRef与toRefs

toRef

toRef将对象中的属性变成响应式数据,修改响应式数据是会影响到原始数据的

<template>
  <div>
    <p>{{ name }}-{{ obj.name }}</p>
    <p>{{ job }}</p>
    <p>{{ city }}</p>
  </div>
</template>

<script setup>
import  { ref, toRef,reactive} from 'vue'
const obj = reactive({
   name: 'zs',
   sex:'男',
   job: {
    jobname: 'h5工程师',
    city: 'shanghai'
   }
})

 const { name } = obj
 const name = toRef(obj, 'name') // 返回值是一个ref对象
 const city = toRef(obj.job, 'city')
 console.log(name.value);


</script>

<style lang="scss" scoped>

</style>

toRefs 

使用toRefs(可以批量创建多个ref对象)

<template>
  <div>
    <p>{{ name }}-{{ obj.name }}</p>
    <p>{{ job }}</p>
    <p>{{ city }}</p>
  </div>
</template>

<script setup>
import  { ref, toRefs,reactive} from 'vue'
const obj = reactive({
   name: 'zs',
   sex:'男',
   job: {
    jobname: 'h5工程师',
    city: 'shanghai'
   }
})

const  { name, job} = toRefs(obj)
const { city } = toRefs(obj.job)

</script>

<style lang="scss" scoped>

</style>

总结


作用: 创建一个ref对象,其value值指向另一个对象中的某个属性
语法: const name = toRef(person, 'name')
应用: 要将响应式对象中的某个属性单独供应给外部使用时
扩展: toRefs与toRef功能一致,但可以批量创建多个ref对象,
语法:toRefs(person) 
 

shallowReactive与shallowRef

shallowReactive

shallowReactive:只处理对象最外层属性的响应式(浅响应式)

相对于reactiveshallowReactive只有最外层的属性是响应的

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 改变 state 本身的性质是响应式的
state.foo++
// ...但是不转换嵌套对象
isReactive(state.nested) // false
state.nested.bar++ // 非响应式

shallowRef

1.shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理。

2.当使用shallowRef为一个基础类型数据创建响应性时,行为是和ref一样的。

3.不同的是,当使用shallowRef为复杂类型创建响应性时,修改深层属性,不具备响应性

const a = shallowRef({b:1})
a.value.b = 2  //视图不会更新
console.log(a.value) //{b : 2} 但是能追踪到值得变化

a.value={b:2} //一整个替换时,视图会变化

toRow与markRow

toRow

是将一个响应式的对象完全转化为一个普通的对象
普通对象的改变不会引起页面的变化,当我们改变数据不影响页面的时候一般会使用他

<script setup>
import {reactive, readonly, shallowReadonly, toRaw } from 'vue';

let person = reactive({
    name: "sunWuKong",
    age: 18
})

const personToRow = () => {
    person = toRaw(person) // 转化成了普通对象,不再是响应式对象
    person.age++ // 页面不会更新
    console.log(person);
}


</script>

<template>
    <h2>person</h2><span>{{ person }}</span>
    <button @click="personToRow">toRow</button>
    
</template>


markRow

标记一个对象,不会被vue的响应式函数转化为响应式对象
有些值不应该被设置成响应式的,比如第三方的类库
当有一些比较大的列表的时候,跳过响应式的转化会增加性能
 

<script setup>
import { markRaw, reactive, readonly, shallowReadonly } from 'vue';

let person = reactive({
    name: "sunWuKong",
    age: 18
})



const personMarkRow = () => {
  
    {
        name:"sunWuKong",
        age:18,
        otherMsg:{
            sex:"male",
            children:2
        }
    }
    // 标记otherMsg不要转化成响应式对象
    
    person.otherMsg = markRaw({
        sex: "male",
        children: 0
    })

    person.otherMsg.children++
    console.log(person)
}
</script>

<template>
    <h2>person</h2><span>{{ person }}</span>
    <button @click="personToRow">toRow</button>
    <button @click="personMarkRow">MarkRow</button>
</template>

customRef

ref:普通数据转成响应式数据

customRef : 自定义ref,创建响应式数据

hooks函数

一、hooks 是什么

vue3 中的 hooks 就是函数的一种写法,就是将文件的一些单独功能的 js 代码进行抽离出来进行封装使用。

二、hooks 和 utils 区别

相同点:

通过 hooks 和 utils 函数封装, 可以实现组件间共享和复用,提高代码的可重用性和可维护性。

异同点:
表现形式不同:

hooks 是在 utils 的基础上再包一层组件级别的东西(钩子函数等);utils 一般用于封装相应的逻辑函数,没有组件的东西;


数据是否具有响应式:

hooks 中如果涉及到 ref,reactive,computed 这些 api 的数据,是具有响应式的;而 utils 只是单纯提取公共方法就不具备响应式;

作用范围不同:

hooks 封装,可以将组件的状态和生命周期方法提取出来,并在多个组件之间共享和重用;utils 通常是指一些辅助函数或工具方法,用于实现一些常见的操作或提供特定功能。
总结:
utils 是通用的工具函数,而 hooks 是对 utils 的一种封装,用于在组件中共享状态逻辑和副作用。

通过使用 hooks,您可以简化代码,并使其更具可读性和可维护性。

Vue3中的防抖和节流

customRef 接收一个函数作为参数这个函数接收两个函数作为参数 

track (通知vue需要追踪后续内容的变化)

trigger (通知vue重新解析模板)

防抖函数(debounce):

import { customRef, ref } from 'vue';
 
const debounceRef = (data, delay = 500) => {
    // 创建定时器
    let timer = null;
    // customRef 中会返回两个函数参数。一个是:track 在获取数据时收集依赖的;一个是:trigger 在修改数据时进行通知派发更新的。
    return customRef((track, trigger) => {
        return {
            get() {
                // 收集依赖
                track();
                // 返回当前数据
                return data;
            },
            set(val) {
                if (timer) {
                    clearTimeout(timer);
                }
                timer = setTimeout(() => {
                    // 修改数据
                    data = val;
                    // 派发更新
                    trigger();
                }, delay);
            }
        }
    });
}
 
const val = debounceRef('', 1000);

节流函数(throttle)

const throttleRef = (data, delay = 500) => {
    // 创建定时器
    let timer = null;
    // customRef 中会返回两个函数参数。一个是:track 在获取数据时收集依赖的;一个是:trigger 在修改数据时进行通知派发更新的。
    return customRef((track, trigger) => {
        return {
            get() {
                // 收集依赖
                track();
                // 返回当前数据
                return data;
            },
            set(val) {
                if (timer) {
                    return;
                }
                timer = setTimeout(() => {
                    timer = null;
                }, delay);
                // 修改数据
                data = val;
                // 派发更新
                trigger();
            }
        }
    });
};
 
const inputVal = throttleRef('', 1000);

Vue3 全局注册组件

全局注册组件分为三个文件
一、组件本身,在components文件夹下面,需要自己来写
二、main.js文件,用于注册文件,调用 component 第一个参数组件名称 第二个参数组件实例
三、App.vue用于挂载所有的组件

例:我这儿封装一个Card组件想在任何地方去使用

<template>
  <div class="card">
     <div class="card-header">
         <div>标题</div>
         <div>副标题</div>
     </div>
     <div v-if='content' class="card-content">
         {{content}}
     </div>
  </div>
</template>
 
<script setup lang="ts">
type Props = {
    content:string
}
defineProps<Props>()
 
</script>
 
<style scoped lang='less'>
@border:#ccc;
.card{
    width: 300px;
    border: 1px solid @border;
    border-radius: 3px;
    &:hover{
        box-shadow:0 0 10px @border;
    }
 
    &-content{
        padding: 10px;
    }
    &-header{
        display: flex;
        justify-content: space-between;
        padding: 10px;
        border-bottom: 1px solid @border;
    }
}
</style>

在main.js 引入我们的组件跟随在createApp(App) 后面 切记不能放到mount 后面这是一个链式调用用

import { createApp } from 'vue'
import App from './App.vue'
import './assets/css/reset/index.less'
import Card from './components/Card/index.vue'
 
 
createApp(App).component('Card',Card).mount('#app')

直接在其他vue页面 立即使用即可 无需引入

<template>
 <Card></Card>
</template>

vue3自定义指令

两种作用域

自定义指令可以定义全局的,也可以定义局部的。

在正式开搞之前,小伙伴们需要先明白,自定义指令有两种作用域,一种是局部的自定义指令,还有一种是全局的自定义指令。局部的自定义指令就只能在当前 .vue 文件中使用,全局的则可以在所有的 .vue 文件中使用。

局部指令

直接在当前 .vue 文件中定义即可,如下

<template>
    <div>
        <button v-onceClick="10000" @click="btnClick">ClickMe</button>
    </div>
</template>

<script>

    import {ref} from 'vue';

    export default {
        name: "MyVue01",
        setup() {
            const a = ref(1);
            const btnClick = () => {
                a.value++;
            }
            return {a, btnClick}
        },
        directives: {
            onceClick: {
                mounted(el, binding, vnode) {
                    el.addEventListener('click', () => {
                        if (!el.disabled) {
                            el.disabled = true;
                            setTimeout(() => {
                                el.disabled = false;
                            }, binding.value || 1000);
                        }
                    });
                }
            }
        }
    }
</script>

这里我自定义了一个名叫 onceClick 的指令,给一个 button 按钮加上这个指令之后,可以设置这个 button 按钮在点击多久之后,处于禁用状态,防止用户重复点击。

全局指令

全局指令我们一般写在 main.js 中,或者写一个单独的 js 文件然后在 main.js 中引入,下面的例子1.src/   新建文件夹 directives,新建index.js

export default (app) => {
  //自定义组件
  app.directive('demo', (el, binding) => {
    el.addEventListener('click', () => {
      console.log(binding.value.color) // => "white"
      console.log(binding.value.text) // => "hello!"
    })
  })
}

 2.main.js中

import directives from './directives/index.js'
 
const app = createApp(App)
directives(app)

3.页面使用 

<el-button v-demo="{ color: 'white', text: 'hello!' }">自定义指令</el-button>

参数

el:指令所绑定的元素,可以用来直接操作 DOM,我们松哥说想实现一个可以自动判断组件显示还是隐藏的指令,那么就可以通过 el 对象来操作 DOM 节点,进而实现组件的隐藏。
binding:我们通过自定义指令传递的各种参数,主要存在于这个对象中,该对象属性较多,如下属性是我们日常开发使用较多的几个:
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-hasPermission="['user:delete']" 中,绑定值为 'user:delete',不过需要小伙伴们注意的是,这个绑定值可以是数组也可以是普通对象,关键是看你具体绑定的是什么,在 2.1 小节的案例中,我们的 value 就是一个数字。
expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
arg:传给指令的参数,可选。例如 v-hasPermission:[name]="'zhangsan'" 中,参数为 “name”。
 

vue3路由

vue2路由与vue3路由的区别

路由模式不同

vue2 中使用 new Router() 创建 router

vue3 中不再使用 new Router() 创建 router,而是调用 createRouter 方法:

路由的使用

1、路由的安装

npm install vue-router@4

2、路由的模式

createWebHashHistory():Hash模式

它在内部传递的实际URL之前使用了一个哈希字符#,如 https://example.com/#/user/id
由于这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理


createWebHistory():history模式

当使用这种历史模式时,URL会看起来很“正常”,如 https://example.com/user/id,(推荐使用)

3、创建路由模块 

  1. 在项目中的src文件夹中创建一个router文件夹,在其中创建index.js模块
  2. 采用createRouter()创建路由,并暴露出去
  3. 在main.js文件中初始化路由模块app.use(router)

在router/index.js文件下

import { createRouter, createWebHistory } from "vue-router";
import HomeView from '@/views/HomeView.vue'

// 创建路由规则
let routes = [
    {
        path: '/', // URL 地址
        name: 'home',  // 名称
        component: HomeView  // 渲染该组件
    },
]

// 创建路由
const router = createRouter({
    // 使用 history 模式
    history: createWebHistory(),
    // 路由规则
    routes
})

// 将路由对象暴露出去
export default router

在main.js文件下

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'  // 引入路由模块
let app = createApp(App)

app.use(router) // 初始化路由插件

app.mount('#app')

pinia

Pinia 是 Vue 的专属状态管理库,它可以跨组件或页面共享状态,类似 Vuex, 是 Vue 的另一种状态管理方案,支持 Vue2 和 Vue3。

1.pinia是什么?

我们都知道Vuex在Vue2中主要充当状态管理的角色,所谓状态管理,简单来说就是一个存储数据的地方,存放在Vuex中的数据在各个组件中都能访问到,它是Vue生态中重要的组成部分。

在Vue3中,可以使用传统的Vuex来实现状态管理,也可以使用最新的pinia来实现状态管理。

2.Pinia 的优势

1 Vue2和Vue3都支持
2 pinia中action支持同步和异步,Vuex不支持
3 良好的Typescript支持
4 体积非常小,只有1KB左右
5 pinia支持插件来扩展自身功能
6 pinia中只有state、getter、action,抛弃了Vuex中的Mutation

7 pinia分模块不需要modules(之前vuex分模块需要modules)

3.安装 Pinia

# 使用 npm
npm install pinia
# 使用 yarn
yarn add pinia

4. pinia的基本使用与概念

4.1准备工作

在main.js,引入pinia提供的createPinia方法,创建根存储。

// main.js
 
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
//引入pinia中的createPinia方法
import {createPinia} from "pinia"
//创建根储存
const pinia  = createPinia();
const app = createApp(App);
app.use(pinia);
app.mount('#app')

4.2 store

store就是数据仓库的意思,我们数据都放在store里面,可以读写操作

//src/store/goods.js
//引入函数
import { defineStore } from "pinia";
// 调用defineStore函数创建Store 实体
export const goodsStore = defineStore('users', {
    
  })

创建store实体的defineStore函数有两个参数值

  • name:字符串,必传项,该store的唯一id。
  • options:对象,store的配置项,比如state等。

4.3添加state

我们需要存放的数据就放在state属性内

在src/store/goods.js下

//src/store/goods.js
 
import { defineStore } from "pinia";
export const goodsStore = defineStore('users', {
    // state
    state: () => {
        return {
            goods:["商品1","商品2"]
        }
    },
  })

4.4使用store

这时我们store里面的配置项state就存在数据了,我们就能够在App.vue去使用它

//App.vue
<script setup>
 import {goodsStore} from "./store/store";
 const store = goodsStore()
 console.log(store)
</script>

5.getters属性

getter就相当于Vue中的计算属性,它的作用就是返回一个新的结果

在src/store/goods.js

//src/store/goods.js
export const goodsStore = defineStore('users', {
    // state
    state: () => {
        return {
            goods:["商品1","商品2"],
            list : ["手机","电脑"]
        }
    },
    getters:{
        getGoods(state){
            return state.goods[0] + state.list[0]
        }
    }
  })

在App.vue中使用getters

//app.vue
<template>
  <div>
    <ul>
      <li v-for="(item,index) in goods" :key="index">
         {{ item }}
      </li>
      //使用使用这样的方式
      {{ store.getGoods }}
    </ul>
  </div>
</template>

 getters传参

//src/store/goods.js
export const goodsStore = defineStore('users', {
    // state
    state: () => {
        return {
            goods:["商品1","商品2"],
            list : ["手机","电脑"]
        }
    },
    getters:{
        getGoods(state){
            return state.goods[0] + state.list[0]
        },
        //返回一个函数在调用的时候直接传递参数
        getass(state){
            return (num)=>{
                return num + state.goods[0]
            }
        }
    }
  })
//app.js 中调用
{{ store.getass("1:") }}

6.Actions属性

Action 相当于组件中的 method,用来放置一些处理业务逻辑的方法,我们有业务代码的话最好是写在Actions中。action支持同步和异步

在src/store/goods.js

export const goodsStore = defineStore('users', {
    // state
    state: () => {
        return {
            goods:["商品1","商品2"],
            list : ["手机","电脑"]
        }
    },
    getters:{
        getGoods(state){
            return state.goods[0] + state.list[0]
        },
        getass(state){
            return (num)=>{
                return num + state.goods[0]
            }
        }
    },
    actions:{
        setList(){
              //通过this.xx 访问相应状态
            this.list = [
                "麻将",
                "扑克"
            ]
        }
    }
  })

在app.vue中

//app.vue
<script setup>
 import {goodsStore} from "./store/store";
 import { reactive } from "vue";
 import {storeToRefs} from 'pinia'
 const store = goodsStore()
 const {goods} = storeToRefs(store)
 store.setList()
</script>

vue2和vue3中vuex的区别

创建 Store不同

vue2: new Vuex.Store

vue3: import { createStore } from 'vuex'

获取 Vuex 的状态和方法

vue 2中,组件中需要使用 $store 方法来获取 Vuex 的状态和方法

vue3是使用 import { useStore } from 'vuex' 在组件中引入 Vuex 的 store 对象,并通过调用 useStore() 来访问 store 的状态和方法

ts

ts的基本介绍

ts的全称type script

TS是JS的超集,JS所拥有的,TS都有。

JS是弱类型语言,天生存在很多缺陷。而TS是强类型语言,有了TS加持,JS应用会变得更加稳定且强大。

浏览器只能识别JS,TS只能通过编译成JS才能被浏览器运行

TS有在编译时有错误提示,便于维护

许多新项目,如果选择VUE3或React进行开发时,大多都会带上TS。

TS是前端领域大势所趋

类型注解

约束变量的类型 语法 :类型

基础类型

变量声明时,加上一个类型

使用变量时,类型必须是前面定义好的

let name: string
let age: number
let isVip: boolean
//变量声明时,加上一个类型
name = '234'
age = 234
isVip = true
//使用变量时,类型必须是前面定义好的

数组类型

数组类型两种写法 :类型[] 或: Array<类型>

// 写法一
let arr: number[] = [234, 15, 1]
let arr2: boolean[] = [true, false]
// 写法二
let arra: Array<string> = ['234', 'sdges']
let arrb: Array<number> = [234, 124, 7456]
let arrc: Array<boolean> = [false, false, true]

 元组-数组的类型和长度都限定

let arr: [number,string] = [1,'3']

联合类型

数组存放数字或字符串

let arr5: (number | string)[] = [1,2,'3']
let timer: number | null  = null
timer = setTimeout(()=>{},1000)

arr6类型是数字或字符串数组

let arr6: number | string[] = ['j']

类型别名  

type 类型别名 = 具体类型  类型别名起名用大驼峰

type CustomType = (number | string)[]
let arr7:CustomType =  [1,'2']

函数类型

 分别指定参数和返回值的类型

 function getSum(a: number,b:number):number {
   return a + b
 }

 const result:number = getSum(8,2)
 const getSum = function(a:number,b:number):number {
      return a + b
 }
 const getSum = (a:number,b:number):number => {
   return a + b
 }

同时指定 (参数类型列表)=>返回值类型 ()=> number

type Fn = (a:number,b:number) => number
const getSum: Fn = (a,b) => {
  return a + b
}

const getSum2: Fn = (a,b) => {
  return a + b
}

void 无返回值

 函数没有返回值(没有写return),可以把返回值类型定义为void或省略

const fn = ():void => {
  console.log('hello  world');
  // return undefined
}
console.log(fn());

可选参数 ?

const fn2 = ( b:number,num?: number) => {

}

fn2(10)
fn2(10,20)

对象类型

描述对象的属性和方法类型

let person: {name: string, age: number,  eat():void}

= {name: 'zs',age: 20, eat()  {}}

 :{
   属性名:类型
   属性名:类型
   方法名(参数类型): 返回值类型  或 方法名: (参数类型)=>返回值类型
  }

let person2: {
  name: string
  age?: number //  对象属性可选
  eat:(a:string,b:string)=>void
} 
= {name: 'zs', eat(a,b)  {}}

person2.eat('h','k')

例:

 学生对象 (必选属性) 内容包括 学号、 姓名、 性别、 成绩

 体重 (可选属性)

 方法 包括 学习、 打游戏(打游戏为可选属性)

let student: {
  sid: string
  name: string
  sex: string
  score: number
  weight?: number
  study():void
  playGame?: () => void
} = {
  sid: '20240918',
  name: 'zs',
  sex: 'nan',
  score: 90,
  study:  ()=>{}
}

接口interface 描述对象类型

作用:用于类型封装复用

语法:1.定义关键字 interface

           2.关键字后面 跟 接口名称,接口名称建议I开头

           3. 接口名称 定义 对象的具体类型

interface Person {
   eat():void
   sleep():void
}

interface Person {
  drink:  ()=> void
}

interface可以继承

extends关键字

interface Student extends Person {
  name: string
  age: number
  study: ()=> void
}

type交叉类型

type Student = {
   name: string
   age: number
   study: ()=> void
 } & Person

interface  与 type

  1  很多时候,可以自由切换

  2  interface支持对象类型,可以继承

  3  type可以支持任何类型,通过交叉类型&实现复用

  4  type不能重复定义,interface可以重复定义会合并

字面量类型

所有的字面量数据都可以作为类型

// let a:1 = 1
// const a = 1 => const a:1 = 1
let a =1 //  let a:number = 1
a = 2

字面量配合联合类型,表示一组可选值

let  degree: '专科' | '本科' | '研究生' = '专科'
let direction: 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'

any(不推荐使用)

let o1:any = 10
// o1 = {}
// o1 = []
o1()

类型断言

作用:当ts类型推断和我们预期的不一样,使用类型断言

断言语法:as

as 后是什么类型就是什么类型

 查找dom元素  <a id="link" href="">...</a>  <p id="link"></p>

 ts把alink的类型推断为HTMLElement(普通的标签类型)

const alink = document.getElementById('link')  as HTMLAnchorElement
console.log(alink.href);

Vue3项目中使用TypeScript

script标签上增加lang="ts" 才能写ts

<script setup lang="ts">
</script>

 一 ref会根据数据自动推断类型 

 1 假如是简单数据类型、利用类型推导

const msg = ref('hi')

 2 假如是引用数据类型,利用泛型

type Todo = {
  id: number
  name: string
  done: boolean
}
const task = ref<Todo[]>()
task.value = [{id:1,name:'coding',done: false}]

二 reactive的TS写法

1 默认值属性固定,可以使用类型推导

const book  = reactive({title: 'TS从入门到放弃',price:  59})

2 类型推导的类型不符合需求,手动给变量或常量指定类型

type Book = {
  title: string
  price?: number
}

const book:Book  = reactive({title: 'TS从入门到放弃', price: 999})
console.log(book);

三 计算属性的TS-可以用类型推断或泛型

类型推断

 const upperMsg  = computed(() => {
   return msg.value.toUpperCase()
 })

泛型

const upperMsg  = computed<string>(() => {
  return msg.value.toUpperCase()
})

四 事件处理的TS

函数参数e默认推断any类型

const  handleClick = (e: Event) => {
  const ele = e.target as HTMLButtonElement
}

五 ref模板引用的TS写法

const iptRef = ref<HTMLInputElement  | null>(null)
onMounted(() => {
  // 类型守卫
  // if(iptRef.value) {
  //   iptRef.value.focus()
  // }
  // iptRef.value?.focus()
  // iptRef.value&& iptRef.value.focus()
})

//  修改msg
const  changeMsg = (v: string) => {
  msg.value = v
}

const changeMsg2 = () =>{
  msg.value = 'fe666'
}

六 类型声明文件

d.ts->TS类型声明文件,描述is模块内所有导出成员的类型信息

*.ts 与*.d.ts比较

1*.ts 既可以放类型信息也可以放执行代码,*.d.ts只包括类型信息

2 *.ts -> *.js才能执行,*.d.ts不会生成is文件,尽提供类型信息


3 *.ts->写代码的地方,*.d.ts提供类型信息(类型复用时候,可以用类型声明文件)

类型声明文件

1 下载第三方包,有的自带*.d.ts,有的包没有*.d.ts(安装对应的模块 @types/包)

2 内置类型声明文件

3 自定义类型声明文件

  • 37
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值