从0到1:使用Vue 3 + TypeScript + Vite + Element Plus搭建现代Web项目

前端技术日新月异的今天,Vue 3以其强大的组合式API(Composition API)、TypeScript的静态类型检查能力以及Vite的极速开发体验,成为了构建现代Web应用的热门选择。结合Element Plus这一为Vue 3设计的UI组件库,我们可以快速搭建出既美观又高效的前端项目。本文将带你一步步从零开始,使用Vue 3、TypeScript、Vite和Element Plus搭建一个基本的Web项目。

一、环境准备

1、安装Node.js。运行node -vnpm -v来检查Node.js和npm是否已安装及版本信息

傻瓜式起项目就不多说了,官网说得很清楚: https://cn.vitejs.dev/

由此可知webpack老大哥不管你用不用看不看都给你从路由—>模块—>处理全部加载处理完成出来,当项目资源堆积起来越来越多的时候就会变得非常非常慢。而vite项目一启动直接Server ready,从入口文件一进来看你从什么路由进来,vite就给加载当前路由链路的资源,有点按需加载 懒加载那意思。

3、安装Element Plus: npm install element-plus --save

4、配置下我们的main.ts

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import App from './App.vue'
import router from './router'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const app = createApp(App)

app.use(createPinia())
app.use(router)
app.use(ElementPlus, { size: 'small', zIndex: 3000 })
// 全局注册所有图标组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}
app.mount('#app')

5、举例:建房子

import { createApp } from 'vue':可以看作是房子的地基

import App from './App.vue':房子的主体构造,是一个组件(根)

import { createPinia } from 'pinia':把房子的主体构造存储思想全局共享给每个房子部位,pinia 和 vuex 具有相同的功效, 是 Vue 的存储库,它允许您跨组件/页面共享状态。设计使用的是 Composition api,更符合vue3的设计思维。

import router from './router':房子的楼梯,路,门口标识,

app.mount('#app'):将房子的主体构造放到地基上

6、安装Element Plus

https://element-plus.org/zh-CN/

7、安装router: npm install vue-router --save

optionsAPl与CompositionAPl

optionsAPl配置项API:当想要完成某功能,功能A和功能B都得在 data、methods 、
computed分别拆散去写,不利于维护和复用,可读性弱。

CompositionAPl组合式API:集中式的风格,Composition API主要围绕setup函数展开,将相关逻辑集中在一起,不再分散于不同的Vue选项。这使得维护和理解组件变得更加容易,代码复用,类型支持非常适合使用TypeScript,因为它从根本上是函数式的,易于集成类型系统

1什么是setup?

setup 函数是 Vue 3 组合式 API 的入口点。当一个组件实例被创建时,setup 函数会在组件被解析之前(以及 data、 computed 和 methods 被处理之前)被调用。它是处理组件逻辑的主要地方,返回的数据和方法可以直接在模板中使用。

setup特性其在created之前执行,内部无this。以前放在data里面的 直接可以在setup里面定义变量,方法和数据需要return交出去。放在methods里面的直接可以在setup里面写一个个的函数。setup的返回值也可以对象也可以是渲染函数。

setup语法和optionsAPl语法可以共存,但是setup语法不能读optionsAPl语法里面的东西,而optionsAPl语法可以读到setup语法的东西使用。(不建议混写,太混乱了)

2、setup语法糖

它是组件内使用 Composition API 的入口点。setup 函数在组件实例被创建时执行,此时 datacomputedmethods 和 生命周期钩子 还未被初始化。

插件安装:npm i vite-plugin-vue-setup-extend

vite.config.ts引入setup插件

setup 函数的主要作用:

  1. 定义组件的 datacomputedmethods 等,并不再需要 return 返回这些选项。

  2. 通过 ref 或 reactive API 来响应式地管理数据。

  3. 使用 watch 或 watchEffect 来监听响应式数据的变化。

  4. 使用 provide 和 inject 实现组件之间的通信。

  5. 使用 onMountedonUnmountedonUpdated 等生命周期钩子

ref和reactive

响应式编程是非常重要的概念,其中RefReactive是两个关键的API。

宏观角度看:
ref 用来定义:基本类型数据、对象类型数据;
reactive 用来定义:对象类型数据。2.
区别:
ref 创建的变量必须使用.value(可以使用 volar 插件自动添加.value)。1.
2.reactive 重新分配一个新对象,会失去响应式(可以使用 0bject.assign 去整体替换)
使用原则:
1.若需要一个基本类型的响应式数据,必须使用ref。
2.若需要一个响应式对象,层级不深,ref、reactive 都可以。
3.若需要一个响应式对象,且层级较深,推荐使用reactive。

<script setup>
import { ref } from 'vue';
const count = ref(0); // 创建一个Ref对象,初始值为0
count.value = 1; // 修改Ref对象的值
console.log(count.value); // 访问Ref对象的值
</script>

<script setup>语法是一种简洁的写法,可以在单文件组件中更便捷地使用ref

<template>
  <div>
    <p>User Name: {{ user.name }}</p>
  </div>
</template>

<script setup>
import { reactive } from 'vue';

const user = reactive({
  name: 'Alice',
});

</script>

使用Reactive能够管理复杂对象数据,确保对象属性的变化能够被追踪

toref和torefs

<script setup lang="ts">
import {reactive,toRefs,toRef} from 'vue'
let person = reactive({
        name:'张三'
        age:18
       })
let {name,age}= toRefs(person)
let nl = toRef(person,'age')
console.log(nl.value)// 18
console.log(name.value)// '张三'
</script>

作用:将一个响应式对象中的每一个属性,转换为ref 对象。
备注:toRefs与toRef 功能一致,但 toRefs 可以批量转换。

computed

计算属性computed,vue2和vue的写法不同

vue2

<template>
姓:<input type="text"v-model="params.fristName"/>
<br/>
名:<input type="text"v-model="params.lastName"/>
<br/>
<h3 v-show="params.lastName || params.fristName">全名:{{ get }}</h3></template>
<script>
export default{
data(){
return{
// 数据
params :{
    fristName:""
    lastName:""
      }
   }
},
// 计算属性
computed:{
get(){
// 不要在 getter 中做异步请求或者更改 DOM
return this.params.fristName +''+ this.params.lastName //只读,get函数中的值,被调用或被修改就调用},
set(newvalue){
console.log('newvalue',newvalue)}// 要想可改写,就在set,监听修改值的变化
</script>

vue3

<template>
姓:<input type="text"v-model="params.fristName"/>
<br/>名:<input type="text"v-model="params.lastName"/>
<h3 v-show="params.lastName params.fristName">
全名:{{ params.fullName }}</h3></template>
<script setup>
import {computed,reactive} from 'vue'//引入vue中的computed,reactive//
let params =reactive({
    fristName:""
    lastName:""
})
// 计算属性 简写
// params.fullName = computed(()=>{return params.fristName ++ params.lastName
}
// })
// 完整写法
params.fullName = computed({
get(){
// 不要在 getter 中做异步请求或者更改 DOM
return params.fristName +'-'+ params.lastName}//只读,get函数中的值,被调用或被修改就调用
set(newvalue)f
console.log('newvalue',newvalue)}//要想可改写,就在set,监听修改值的变化
</script>
watch

vue3watch只能监听的以下四种数据:

  1. 一个getter函数(一个能返回值的函数)
  2. ref定义的值
  3. reactive定义的值
  4. 包含以上内容的数组



watch监听ref简单的基本类型数据,需要手动开启深度监视

<template>
    <div class="itemStyle">
          <div>
              姓名1: <input type="text" v-model="dataRef.name">
              姓名2: <input type="text" v-model="dataReactive.name">
          </div>
        <div>
            <button type="button" @click="handleChangeData">修改数据</button>
        </div>
    </div>
</template>

<script setup lang="ts" name="item">
    import {ref,reactive,toRefs,toRef,watch} from "vue"

    let dataRef = ref({
        name:"小张",
    })
 let dataReactive = reactive({
        name:"小张",
        other:{
            a:"1111",
            b:"2222",
            c:{
                d:"1111",
                e:"2222",
            }
        }
    })

    const handleChangeData = ()=>{
        data.value = {
            name:"小红",
        }
         Object.assign(dataReactive,{
            name:"小红",
        })
    }
//ref的监听
    watch(dataRef,(newVal,oldVal)=>{
        console.log("新值:",newVal);
        console.log("旧值:",oldVal);
    if(newVal == '小红'){
            stopWatch()//解除监听
        }
    },{
        deep:true,//开启深度监听
    })
//reactive的监听
	//此时是有问题的:oldVal会和newVal数据保持一致,当data里面的任意值改变,都会触发该监听,强制开启深度监听
    //watch(dataReactive,(newVal,oldVal)=>{
     //   console.log("新值:",newVal);
    //    console.log("旧值:",oldVal);
    //})
    
    //使用()=>箭头函数监听data对象中name属性
    watch(()=>data.name,(newVal,oldVal)=>{
        console.log("新值:",newVal);
       console.log("旧值:",oldVal);
    })
//函数形式
//监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
    watch(()=>dataReactive.other.c,(newVal,oldVal)=>{
        console.log("新值:",newVal);
        console.log("旧值:",oldVal);
    })
//多个数据
	//监视,情况五:监视上述的多个数据
    watch([dataReactive.other.c,()=>dataReactive.name,()=>dataReactive.other.c,()=>dataReactive.other.c.d],(newVal,oldVal)=>{
        console.log("新值:",newVal);
        console.log("旧值:",oldVal);
    },{deep:true})

</script>
watchEffect 

watchEffect 是 Vue3 中提供的一个新特性,用于监听响应式数据的变化,并在数据发生变化时执行指定的回调函数。

和watch 不同,watchEffect 不需要指定要监听的数据,而是会自动追踪函数中使用的响应式数据,并在这些数据发生变化时重新执行回调函数。这种自动追踪的特性可以简化代码,并提高应用的性能

标签的ref属性

vue2.x中,可以通过给元素添加ref='xxx'属性,然后在代码中通过this.$refs.xxx获取到对应的元素

然而在vue3中时没有$refs这个东西的,因此vue3中通过ref属性获取元素就不能按照vue2的方式来获取

<template>
    <div ref='info'>hello</div>
    <button @click='handleClick'>点击</button>
</template>
<script  setup lang="ts" name="item">
import { ref } from 'vue'
// Vue3中通过ref操作DOM
// 1、定义一个响应式变量
const info = ref(null)
const handleClick = ()=>{
  // 4、此时可以通过info变量操作DOM
  console.log(info.value.innerHTML)
}
</script>
vue3 ts中的泛型 

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类泛型接口泛型方法

//定义一个接口,用于限制person对象的具体属性
export interface PersonInter {
id:string,
name:string,
age:number
}
//一个自定义类型
export type Persons= Array<PersonInter>
<template>
<div class="person"
???
</div>
</template>
<script lang="ts" setup name="Person">
import {type PersonInter, type PersonInterList} from '@/types
let person:PersonInter = {id:'asyud7asfd01',name:'张三',age:60}
let personList:Array<PersonInterList>=[
{id:'asyud7asfd01',name:'张三',age:60},
{id:'asyud7asfd02',name:'李四',age:18},
{id:'asyud7asfd03',name:'王五',age:5}]
</script>

props的使用

props 是组件间通信的一种方式,允许父组件向子组件传递数据。Vue 3 保留了 Vue 2 中 props 的基本概念,但在一些细节和性能上进行了改进。需要使用 defineProps 宏来声明 props。这是 Vue 3 Composition API 的一部分

假设我们有一个父组件 ParentComponent.vue,它需要向子组件 ChildComponent.vue 传递一个用户的信息,包括用户的ID和姓名

父组件 ParentComponent.vue
<template>  
  <div>  
    <!-- 使用 v-bind (或简写为 ':') 将 user 对象传递给 ChildComponent 的 userProp 属性 -->  
    <ChildComponent :userProp="user" />  
  </div>  
</template>  
  
<script setup>  
import { ref } from 'vue';  
import ChildComponent from './ChildComponent.vue';  
  
// 创建一个响应式的数据对象 user  
const user = ref({  
  id: 1,  
  name: '张三'  
});  
</script>

子组件 ChildComponent.vue

<template>  
  <div>  
    <p>用户ID: {{ userProp.id }}</p>  
    <p>用户名: {{ userProp.name }}</p>  
  </div>  
</template>  
  
<script setup>  
import { defineProps } from 'vue';  
  
// 使用 defineProps 来声明接收的 props  
const props = defineProps({  
  userProp: {  
    type: Object,  
    required: true,  
    // 可以添加更复杂的验证逻辑  
    validator: (value) => {  
      return value.id !== undefined && typeof value.name === 'string';  
    }  
  }  
});  
</script>

单向数据流props 是单向数据流的,父组件可以传递数据到子组件,但子组件不能直接修改 props 的值。如果子组件需要修改这些数据,它应该定义自己的局部数据属性或使用计算属性,并基于 props 的值来更新这些属性

vue2和vue3生命周期

1.命名变化:

  • Vue 2:生命周期钩子函数的名称通常以"before"和"after"为前缀,例如"beforeCreate"、"created"、"beforeMount"、"mounted"、"beforeUpdate"、"updated"、"beforeDestroy"和"destroyed"。
  • Vue 3:在Vue 3中,这些钩子函数的名称被修改为以"on"为前缀,并增加了一些新的命名规则,如"onBeforeCreate"、"onCreated"、"onBeforeMount"、"onMounted"、"onBeforeUpdate"、"onUpdated"、"onBeforeUnmount"和"onUnmounted"。这一变化使得Vue 3的生命周期钩子函数命名更加统一和直观。

2. 新增的生命周期钩子函数:

  • Vue 3引入了一些新的生命周期钩子函数,这些函数在Vue 2中是不存在的。"onBeforeMount"和"onBeforeUpdate"这两个钩子函数允许开发者在组件挂载和更新之前执行一些操作。此外,Vue 3还引入了"onRenderTracked"和"onRenderTriggered"等钩子函数,用于在组件的渲染跟踪和触发过程中执行特定的逻辑。

3. 生命周期的触发时机

  • Vue 2:在Vue 2中,生命周期钩子函数的触发时机是固定的,按照组件的创建、挂载、更新和销毁等阶段依次触发。
  • Vue 3:Vue 3引入了基于组合API的组件写法,并使用"setup"函数来配置组件。这导致了生命周期的触发时机发生了变化。特别是,"setup"函数在组件创建过程中被较早地调用,甚至早于"beforeCreate"和"created"钩子函数。此外,Vue 3采用了异步更新的机制,这也可能影响到生命周期函数的触发时机。
vue3的hook

Vue 3中的hook(或称为Composition API)具有多种重要作用

1.函数式编程:以函数式编程的方式组织和复用逻辑。这意味着你可以将相关的逻辑封装成独立的函数(即hook),然后在多个组件中重复使用这些函数,低代码,复用性强。

2.避免命名冲突:与Vue 2中的mixins相比,Composition API中的hook通过函数调用的方式避免了命名冲突的问题。因为每个hook都是一个独立的函数,所以它们之间的变量和方法不会相互干扰。

3.分离关注点和可读性:每个hook都专注于一个特定的功能或关注点,使得组件的结构更加清晰、易于理解。

4.按需引入和组合性:Composition API允许你按需引入所需的hook,而不是像Vue 2那样必须将所有选项都放在组件的options对象中。这种按需引入的方式可以减少代码的冗余和不必要的复杂性。也可多个hook组合在一起,以创建一个功能更强大的组件

import { ref } from 'vue';  
import axios from 'axios';  
  
export function useLogin() {  
  const isLoading = ref(false);  
  const error = ref('');  
  const login = async (username, password) => {  
    isLoading.value = true;  
    try {  
      await axios.post('/api/login', { username, password });  
      // 登录成功后的逻辑,比如跳转到首页  
      console.log('登录成功');  
    } catch (e) {  
      error.value = e.message;  
    }  
    isLoading.value = false;  
  };  
  
  return { isLoading, error, login };  
}  
  
// 在组件中使用  
<template>  
  <div>  
    <button @click="login(username.value, password.value)">登录</button>  
    <p v-if="error">{{ error }}</p>  
  </div>  
</template>  
  
<script setup>  
import { ref } from 'vue';  
import { useLogin } from './useLogin';  
  
const { isLoading, error, login } = useLogin();  
const username = ref('');  
const password = ref('');  
</script>
路由

Vue 2中,通过new Router()函数创建路由实例,并传入配置对象,包括路由规则(routes)等。

import Vue from 'vue'  
import Router from 'vue-router'  
Vue.use(Router)  
const router = new Router({  
  routes: [  
    // 路由规则  
  ]  
})

 Vue 3中,使用createRouter函数创建路由实例,并需要显式地指定history类型(如createWebHistorycreateWebHashHistory)。

import { createApp } from 'vue'  
import { createRouter, createWebHistory } from 'vue-router'  
const router = createRouter({  
  history: createWebHistory(),  
  routes: [  
    // 路由规则  
  ]  
})

Vue 2中,通过new Vue()的选项对象中直接挂载路由实例。

new Vue({  
  router,  
  render: h => h(App),  
}).$mount('#app')

Vue 3中,使用createApp创建的应用实例上有一个.use()方法来注册插件,包括路由

const app = createApp(App)  
app.use(router)  
app.mount('#app')

Vue 2和Vue 3中,关于路由的嵌套、参数配置、属性以及重定向的写法,虽然两者在基本概念上相似,但在具体实现和语法细节上存在一些细微差异,不就一一举例了

Pinia 的理解

Pinia 是 Vue.js 生态系统中的一个状态管理库,专为 Vue 3 设计,但也可以与 Vue 2 兼容(通过适当的适配器)。Pinia 旨在提供一种比 Vuex 更简单、更灵活的方式来管理 Vue 应用程序的状态。

通过调用 defineStore 函数并传入一个配置对象来创建 Store。这个配置对象包含了 State、Getters 和 Actions 的定义。defineStore 函数内部会创建一个新的 Store 实例,并使用 Vue 3 的响应式系统来使 State 变得响应式。

和vuex一样 也是项目的状态管理器,但是有不同的区别:

1.vuex是集中式的架构将所有的状态存储在一个单一的全局状态树中。Vuex通过mutations和actions来修改和处理状态。pina是去中心化的架构分布在多个模块每个模块拥有自己的状态

vuex

// store.js  
import Vue from 'vue'  
import Vuex from 'vuex'  

Vue.use(Vuex)  

export default new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increment(state) {  
      state.count++  
    },  
    decrement(state) {  
      state.count--  
    }  
  }  
})
<template>  
  <div>  
    <p>{{ count }}</p>  
    <button @click="increment">Increment</button>  
    <button @click="decrement">Decrement</button>  
  </div>  
</template>  

<script>  
export default {  
  computed: {  
    count() {  
      return this.$store.state.count  
    }  
  },  
  methods: {  
    increment() {  
      this.$store.commit('increment')  
    },  
    decrement() {  
      this.$store.commit('decrement')  
    }  
  }  
}  
</script>

pinia

// counterStore.js  
import { defineStore } from 'pinia'  

export const useCounterStore = defineStore('counter', {  
  state: () => ({  
    count: 0  
  }),  
  actions: {  
    increment() {  
      this.count++  
    },  
    decrement() {  
      this.count--  
    }  
  }  
})
<template>  
  <div>  
    <p>{{ counter.count }}</p>  
    <button @click="counter.increment()">Increment</button>  
    <button @click="counter.decrement()">Decrement</button>  
  </div>  
</template>  

<script setup>  
import { useCounterStore } from './store/counterStore'  

const counter = useCounterStore()  
</script>

Pinia的API设计更加符合Vue 3的组合式API风格,而Vuex的API则更偏向于Vue 2的Options API风格

组件通信

1. Props

Vue 2 和 Vue 3:

  • Props 在两个版本中的使用方式基本相同。父组件通过props向子组件传递数据,子组件通过props接收数据。Vue 3中继续支持这种方式,没有显著变化。加了setup语法的写法不同
2. 自定义事件

Vue 2 和 Vue 3:

  • 自定义事件 也基本保持一致。子组件通过this.$emit('eventName', data)触发事件,父组件通过@eventName="handler"监听事件。Vue 3中继续支持这种方式,并且没有显著变化。加了setup语法的写法不同
3. _mitt (非Vue官方)

Vue 2 和 Vue 3:

  • _mitt 或类似的事件总线库在两个版本中的使用方式相同,因为它们不依赖于Vue的内部实现。你可以在Vue 2或Vue 3项目中以相同的方式使用_mitt来跨组件通信。
  • <template>  
      <ChildComponent v-model:modelValue="parentValue" />  
    </template>  
      
    <script setup>  
    import { ref } from 'vue';  
    import ChildComponent from './ChildComponent.vue';  
      
    const parentValue = ref('');  
    </script>

4. v-model

Vue 2 和 Vue 3:

  • Vue 2 中的v-model默认是基于value属性和input事件的语法。
  • Vue 3 引入了自定义v-model的能力,允许开发者指定prop和event。例如,v-model:customModel="someData"可以映射到customModel prop和update:customModel事件。
  •  vue2通常用于表单输入,但也可以自定义。
  • vue3:子组件 ChildComponent.vue
    <template>  
      <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />  
    </template>  
      
    <script setup>  
    import { defineProps, defineEmits } from 'vue';  
      
    const props = defineProps(['modelValue']);  
    const emit = defineEmits(['update:modelValue']);  
    </script>

    父组件 Parent.vue

5. $attrs

Vue 2 和 Vue 3:

  • attrs在两个版本中都包含了父作用域中不作为prop被识别(且获取)的特性绑定(class和style除外)。在Vue3中,‘attrs包含组件标签上所有未声明的属性(包括class和style,但可以通过inheritAttrs: false`来阻止它们出现在根元素上)。
6. $refs

Vue 2 和 Vue 3:

  • refs在两个版本中的使用方式基本相同。它们用于访问组件实例或DOM元素。Vue3中,‘refs的访问方式(通过this.refs‘或模板引用)没有变化,但Vue3的响应式系统(Proxy)使得对‘refs`的访问更加灵活和高效。
7. $parent

Vue 2 和 Vue 3:

  • parent在两个版本中都允许你访问一个组件实例的父实例。然而,Vue官方通常不推荐直接使用‘parent进行组件间的通信,因为这会导致组件间的紧密耦合。Vue 3中继续支持$parent`,但同样建议避免使用。
8. provide / inject

Vue 2 和 Vue 3:

  • provide / inject 在Vue 2.2.0+中引入,用于跨组件通信,无论组件层次结构有多深。在Vue 3中,provide 和 inject 的API保持不变,但Vue 3的响应式系统使得inject可以接收响应式的数据。
shallowRef和shallowReactive

shallowRefshallowReactive是两种用于创建浅响应式数据的API,提供了浅响应式的支持。shallowRef适用于基本数据类型或不需要对象内部属性响应式的场景;shallowReactive适用于对象数据,但只需要顶层属性响应式的场景。

 shallowRef 示例

<template>  
  <div>  
    <p>用户名: {{ user.value.name }}</p>  
    <p>邮箱: {{ user.value.email }}</p>  
    <button @click="updateUser">更新用户信息</button>  
  </div>  
</template>  
  
<script setup>  
import { shallowRef } from 'vue';  
  
// 使用shallowRef创建浅响应式引用  
const user = shallowRef({  
  name: '张三',  
  email: 'zhangsan@example.com'  
});  
  
// 更新用户信息时,替换整个对象  
function updateUser() {  
  user.value = {  
    name: '李四',  
    email: 'lisi@example.com'  
  };  
}  
</script>

shallowReactive 示例

<template>  
  <div>  
    <p>用户名: {{ userInfo.name }}</p>  
    <p>详情: {{ userInfo.details.bio }}</p>  
    <button @click="updateUserName">更新用户名</button>  
    <!-- 注意:以下按钮不会触发视图更新,因为details.bio是深层属性 -->  
    <button @click="updateBio">更新简介(不会触发视图更新)</button>  
  </div>  
</template>  
  
<script setup>  
import { shallowReactive } from 'vue';  
  
// 使用shallowReactive创建浅响应式对象  
const userInfo = shallowReactive({  
  name: '张三',  
  details: {  
    bio: '这是张三的简介'  
  }  
});  
  
// 更新用户名时,会触发视图更新  
function updateUserName() {  
  userInfo.name = '李四';  
}  
  
// 更新简介时,不会触发视图更新(因为details是浅响应式的)  
function updateBio() {  
  userInfo.details.bio = '这是李四的新简介';  
}  
</script>
  readonly 与 shallowReadonly

readonlyshallowReadonly是另外两个用于创建响应式数据的辅助函数,但它们与shallowRefshallowReactive不同,主要用于创建只读响应式数据。这意味着你可以读取这些数据,但不能修改它们(尝试修改时,Vue会警告你,但不会抛出错误)。

    readonly

        ​​​​​​​readonly函数接收一个响应式对象(无论是由reactiverefshallowReactive还是shallowRef创建的)并返回一个新的只读代理对象。这个新对象保持原始对象的响应性,但你不能修改它。任何尝试修改它的操作都不会影响原始对象,但Vue会警告你尝试进行了非响应式操作

注意,虽然copy是只读的,但它仍然是响应式的,

import { reactive, readonly } from 'vue';  
  
const original = reactive({ count: 0 });  
const copy = readonly(original);  
  
// 尝试修改copy将不会生效,并且Vue会警告你  
copy.count++; // 警告: Set operation on key "count" failed: target is readonly.  
  
// 但original仍然可以修改  
original.count++;  
console.log(original.count); // 1  
console.log(copy.count); // 1(因为copy是响应式的,所以它会反映original的变化)

   

shallowReadonly

​​​​​​​readonly类似,shallowReadonly也创建一个只读代理对象,但它只保持顶层属性的只读性。如果顶层属性的值是对象或数组,那么这些内部对象的属性仍然可以被修改(尽管这不是推荐的做法,因为它违反了只读性的原则)

import { reactive, shallowReadonly } from 'vue';  
  
const original = reactive({  
  nested: { foo: 1, bar: 2 }  
});  
const copy = shallowReadonly(original);  
  
// 尝试修改顶层属性将不会生效,并且Vue会警告你  
copy.nested = {}; // 警告: Set operation on key "nested" failed: target is readonly.  
  
// 但你可以修改嵌套对象的属性(不推荐)  
copy.nested.foo = 10; // 这将修改original.nested.foo,因为shallowReadonly只保护顶层属性  
  
console.log(original.nested.foo); // 10  
console.log(copy.nested.foo); // 10
toRaw与markRaw

toRawmarkRaw是两个与响应式系统紧密相关的实用函数,它们各自扮演着不同的角色。

 toRaw

toRaw函数用于获取一个由Vue 3响应式系统(如reactivereadonlyshallowReactiveshallowReadonly)创建的代理对象的原始版本。这意味着,通过toRaw,你可以绕过Vue的响应式追踪,直接访问和操作原始对象。

import { reactive, toRaw } from 'vue';  
  
// 创建一个响应式对象  
const state = reactive({  
  count: 0,  
  nested: {  
    value: 'Hello, Vue!'  
  }  
});  
  
// 使用toRaw获取原始对象  
const rawState = toRaw(state);  
  
// 现在rawState是state的原始对象,修改它不会触发Vue的响应性更新  
rawState.count = 10; // 这不会更新视图,因为Vue的响应性系统没有追踪到这次修改  
  
// 但是,如果你通过原始对象访问嵌套对象,并修改它,那么由于Vue的响应性系统追踪了嵌套对象,视图可能会更新(取决于你如何使用它)  
rawState.nested.value = 'Hello, Raw!'; // 这可能会触发依赖于nested.value的视图的更新  
  
// 注意:虽然修改nested.value可能会触发更新,但这并不是因为直接修改了rawState(Vue不追踪rawState),  
// 而是因为nested对象本身仍然是响应式的,Vue追踪了它的变化。  
  
// 如果你想要完全避免这种情况,可以使用markRaw来标记nested对象,使其非响应式  
const nonReactiveNested = markRaw({ value: 'Non-reactive' });  
  
// 更新state以包含非响应式对象  
state.nested = nonReactiveNested;  
  
// 现在,无论你怎么修改nonReactiveNested,都不会触发Vue的响应性更新  
nonReactiveNested.value = 'This change will not trigger updates.';  
  
// 注意:markRaw在这个示例中没有直接和toRaw一起使用,但它们是处理Vue响应性系统的两个有用工具。

   markRaw

​​​​​​​markRaw函数用于标记一个对象,使其永远不会被Vue的响应式系统代理。即使你尝试使用reactiveref来转换它,它也会保持原样,不会变成响应式对象。

import { reactive, markRaw } from 'vue';  
  
const largeData = markRaw({ /* 大量数据 */ });  
const state = reactive({  
  data: largeData  
});  
  
// 修改 largeData 的属性不会触发视图更新  
state.data.someProperty = 10;
customRef

customRef 是一个高级功能,允许你创建自定义的响应式引用(ref)。这在你需要更细致地控制响应式行为时非常有用,比如实现自定义的依赖追踪逻辑、防抖(debounce)、节流(throttle)等。

function useDebouncedRef(value, delay = 300) {  
  let timeout = null;  
  
  return customRef((track, trigger) => {  
    return {  
      get() {  
        track();  
        return value;  
      },  
      set(newValue) {  
        clearTimeout(timeout);  
        timeout = setTimeout(() => {  
          value = newValue;  
          trigger();  
        }, delay);  
      }  
    };  
  });  
}  
  
// 在组件的setup函数中使用  
export default {  
  setup() {  
    const debouncedCount = useDebouncedRef(ref(0), 500);  
  
    // 注意:这里我们实际上是将ref(0)作为初始值传给了useDebouncedRef,  
    // 然后useDebouncedRef内部会创建一个新的响应式引用,并通过customRef返回。  
    // 但为了简化示例,我们假设useDebouncedRef直接返回了一个响应式引用。  
  
    // 你可以通过debouncedCount.value来访问和修改这个防抖的引用  
    // ...  
  }  
};

在实际应用中,你可能希望 useDebouncedRef 直接接收一个初始值,并在内部使用 ref 来封装它。不过,为了展示 customRef 的工作原理,上面的例子已经足够清晰了

Teleport

Teleport 组件是一个强大的工具,它允许开发者将组件的内容渲染到 DOM 中的任何位置,而不受组件嵌套结构的限制,将内部的部分模板“传送”到该组件的 DOM 结构外层的位置,处理全局提示、弹窗、模态框等需要脱离当前组件层级渲染的场景时非常有用

<template>  
  <div>  
    <!-- 组件的其他内容 -->  
    <teleport to="body">  
      <!-- 模态框、弹窗等内容 -->  
      <div class="modal">  
        <!-- 模态框内容 -->  
      </div>  
    </teleport>  
  </div>  
</template>

<teleport to="body"> 标签内的内容(即模态框)将被渲染到 <body> 标签下,而不是在组件自身的位置。这样,模态框就可以脱离组件的嵌套结构,灵活地渲染到页面的任何位置

Suspense

<Suspense> 组件,它是一个内置组件,用于处理异步组件的加载状态。<Suspense> 允许你定义一个等待异步组件时渲染的备用内容(fallback),以及异步组件加载完成后渲染的内容。这有助于提供更好的用户体验,尤其是在处理路由懒加载或大型组件加载时

<template>  
  <Suspense>  
    <!-- 异步组件加载完成后渲染的内容 -->  
    <template #default>  
      <AsyncComponent />  
    </template>  
  
    <!-- 异步组件加载时渲染的备用内容 -->  
    <template #fallback>  
      Loading...  
    </template>  
  </Suspense>  
</template>  
  
<script>  
import { defineAsyncComponent } from 'vue';  
  
export default {  
  components: {  
    AsyncComponent: defineAsyncComponent(() =>  
      import('./AsyncComponent.vue')  
    )  
  }  
}  
</script>

<Suspense> 使用作用域插槽来接收 #default 和 #fallback 插槽的内容,还支持一个可选的 #error 插槽,用于在异步组件加载失败时渲染内容。<Suspense> 也可以与 Vue Router 的路由懒加载功能结合使用,以在路由切换时提供加载状态反馈

注意:使用 defineAsyncComponent 函数时,你需要​​​​​​​提供一个加载函数,该函数应该返回一个 Promise,该 Promise 在解析时返回组件的定义。Vue 会在组件需要渲染到 DOM 时调用这个加载函数,并等待 Promise 解析完成

const AsyncComponentWithOptions = defineAsyncComponent({  
  loader: () => import('./AsyncComponent.vue'),  
  loadingComponent: LoadingComponent, // 加载时的备用组件  
  errorComponent: ErrorComponent, // 加载失败时渲染的组件  
  delay: 200, // 延迟显示加载中组件的时间  
  timeout: 3000, // 超时时间,单位毫秒  
  // 可以是一个错误处理函数,接收错误信息和解析器作为参数  
  onError(error, retry, fail, attempts) {  
    if (attempts <= 1) {  
      retry();  
    } else {  
      fail();  
    }  
  }  
});

搭配路由

// router/index.js  
import { createRouter, createWebHistory } from 'vue-router';  
  
const routes = [  
  {  
    path: '/',  
    name: 'Home',  
    component: () => import('../views/Home.vue') // 异步组件  
  },  
  {  
    path: '/about',  
    name: 'About',  
    // 带有加载和错误组件的异步组件示例  
    component: defineAsyncComponent({  
      loader: () => import('../views/About.vue'),  
      loadingComponent: () => import('../components/Loading.vue'),  
      errorComponent: () => import('../components/Error.vue'),  
      delay: 200,  
      timeout: 3000  
    })  
    // 注意:通常你不需要在路由配置中直接使用 defineAsyncComponent,  
    // 除非你需要上述的加载和错误处理选项。  
    // 对于简单的懒加载,直接使用 () => import(...) 就足够了。  
  },  
  // 其他路由...  
];  
  
const router = createRouter({  
  history: createWebHistory(process.env.BASE_URL),  
  routes  
});  
  
export default router;

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 3是Vue.js的最新版本,它带来了许多令人兴奋的新特性和改进。Element Plus是一套基于Vue 3的UI组件库,它是对Element UI的升级和重构。TypeScript是一种静态类型检查的编程语言,可以帮助我们在开发过程中更加安全和高效地编写代码。Vite是一个新型的前端构建工具,它可以提供快速的开发体验和更好的性能。 如果你想在Vue 3项目中使用Element Plus和TypeScript,可以按照以下步骤进行: 1. 创建一个新的Vue 3项目。你可以使用Vue CLI来创建项目: ``` $ vue create my-project ``` 2. 在项目中安装Element Plus。你可以使用npm或yarn来安装: ``` $ npm install element-plus 或 $ yarn add element-plus ``` 3. 在项目的入口文件(通常是main.ts)中引入Element Plus的样式和组件: ```typescript import { createApp } from 'vue'; import ElementPlus from 'element-plus'; import 'element-plus/lib/theme-chalk/index.css'; const app = createApp(App); app.use(ElementPlus); app.mount('#app'); ``` 4. 开始在项目中使用Element Plus的组件。你可以在Vue组件中按需引入所需的组件,例如: ```vue <template> <el-button type="primary">Button</el-button> </template> <script> import { defineComponent } from 'vue'; import { ElButton } from 'element-plus'; export default defineComponent({ components: { ElButton } }); </script> ``` 5. 如果你想在项目中使用TypeScript,可以将项目的脚手架配置为支持TypeScript。在Vue CLI创建项目时,可以选择TypeScript作为预设选项。如果你已经创建了项目,可以手动添加TypeScript支持,具体步骤可以参考Vue官方文档。 希望以上步骤对你有帮助!如果还有其他问题,请随时向我提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值