Vue3笔记

Vue 3 于 2020 年 9 月发布,是 Vue.js 的重大升级版本,主要改进包括性能优化、新特性引入和更好的 TypeScript 支持。

性能:打包大小减少41%、初次渲染快51%、更新渲染快133%,内存占用减少54%。

使用Proxy 代替Object.defineProperty 实现双向数据绑定,重写虚拟DOM的实现和Tree-Shaking

更好的 TypeScript 支持:源码使用 TypeScript 重写,提供完整的类型定义,开发体验更友好,IDE 提示更精准。

快速生成vue3项目,创建vue项目可以使用vue-cli(webpack)和vite(更推荐)

1.node版本要不低于18.3

2.npm create vue@latest 、 npm init vue@latest、npx create-vue@latest(等效 )

自动从 npm 仓库拉取最新版 create-vue 工具包,临时安装到本地,然后按提示操作即可

Vue3 的组件可以按两种不同的风格书写:选项式 API (Vue2)和组合式 API(Vue3新增加的)

选项式 API (Options API-Vue2写法)(配置风格):数据、方法、计算属性等分散在data,methods,computed中,新增或修改需求时,需要分别修改data、methods、computed,不便于维护和复用;

组合式 API (Composition API):用函数的方式更优雅的组织代码,让相关功能的代码更加有序的组织到一起,且data、methods、watch等在同一个位置,比较好找,不需要像大的Vue2组件中,从data找到对应的methods必须搜索,要不很难找到;

Vue3的变化

从vue中引入createApp创建应用 createApp(App).mount(#app)

<script lang="ts"></script>代表使用ts语言,不写默认js

vue3组件中可以有多个根标签

setup函数中的this是undefined,vue3已经弱化this了

setup和vue2中的data、methods、watch可以同时出现在vue3中,但是非常不推荐,vue2中可以读取到setup中的数据,但是setup中无法读取data配置项的数据,因为setup执行的时机更早

setup语法糖:setup也是一个特殊的钩子,专门用于组合式 API。

// 在编辑器中新建一个vue文件,输入vue可快速生成vue模板

<script lang="ts" setup>

        let name ='abc'

        function changeName(){

                name = 'aaa'

        }

</script>

组件命名

组件使用的名字和实际文件名字不一致时,可以使用vue-setup-extend插件代替组件命名:<script lang="ts"> export default {name:'ABC'} </script>

1.npm i vite-plugin-vue-setup-extend -D

2.在vite.config.ts文件中,import  VueSetupExtend from 'vite-plugin-vue-setup-extend'

在plugins配置中加上VueSetupExtend()

3.在<script lang="ts" setup name="ABC">

ref函数

声明响应式状态,使数据变成响应式,ref()接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回

import { ref } from 'vue'

基本数据类型:const count = ref(0)     function changeCount(){ count.value = +=1}

复杂数据类型:const obj = ref({name: 'a'})    function changeObj(){ obj.vaklue.name = 'b}

当在模板中使用时,ref 会自动解包,不需要附加 .value 

在script标签中需要.value操作数据,可引入volar插件自动添加value

<button @click="count++"> {{ count }} </button>

ref可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map,但基本数据只能使用ref来创建响应数据

ref 会使它的值具有深层响应性,即使改变嵌套对象或数组时,变化也会被检测到

ref在将复杂数据类型转换为响应式数据时,底层还是借助了reactive的能力

reactive()

reactive()把对象本身具有响应性,只能用于对象类型 (对象、数组和如 MapSet )。它不能持有基本数据类型

import { reactive } from 'vue'

const obj = reactive({ count: 0 })

reactive重新分配一个新对象,会丢失响应式,可以使用Object.assign(obj,{count:1})去替换整体,ref不存在类似问题

function changeObj() {

        obj = {a: '456'}                        // 不会生效,因为重新分配对象导致丢失响应式

        Object.assign(obj, {a: '456'})  // 需要利用Object.assign更新 

}

层级不深的对象,ref和reactive都可以

如果是层级较深的对象,如表单功能建议使用reactive

toRefs、toRef

obj是响应对象

let {a}  = obj, 再对a操作也不会响应式(直接解构的数据不是响应数据)

因为const {a} = obj相当于 let a=obj.a,

响应数据变量obj并没有变化,而新定义的a并不是响应式数据

let {a} = toRefs(obj)

toRefs可把解构出来多个值变成响应式,相当于使用了ref,解构出来的值需要使用.value来操作

let a2 = toRef(obj, ‘a’)  

toRef可解构单个值,并把解构出来的值变为响应式,也需要使用.value来操作,使用场景较少

计算属性computed

import { comouted } from 'vue'

// 1.此种计算属性只可读取,不可修改

let fullName = computed(()=>{

        return name1.value + name2.value

})

// 2.此种写法,可读可修改

let fullName = computed({

        get() {

                return name1.value + name2.value 

        }

        set(value){

                name1.vuale = value

        }

})

watch和wactchEffect

import { watch, ref} from 'vue'; // 支持使用数组监听多个数据

let name = ref('小明')

// 1.监听ref创建的基本响应数据不可以加value

watch('name', (new, old)=>{

        if(new === '张飞'){

                stopWatch() // 可以停止监听

        }

})

// 2.监听ref创建的对象响应数据,监听的对象地址,所以需要开启深度监听

watch('name', (new, old)=>{

// 修改的是ref响应对象的属性时,new===old,因为是同一个对象,所以old不能使用

// 若修改的是整个ref定义的对象,那么不再是同一个值了,old可以使用

        if(new === '张飞'){

                stopWatch() // 可以停止监听

        }

},deep: true)

3.监视reacive创建的响应数据,默认隐式的开启的深度监听,且old===new

4.监视ref或者reactive对象中的一个属性时:

  • 如果这个属性是基本数据类型,需要写成函数式,因为watch只能监视响应式数据,ref、reactive、computed或函数返回值

  • 如果这个属性是对象可以直接写,

  • 建议还是写成函数式,再手动开启深度监听,因为watch监听的是地址

watch(()=>return obj.a, (new,old)=> {

// a为基本数据时,new !=b

// a为对象类型,须手动开启深度监听,因为watch监听的是函数返回的地址(建议此写法)

})

watch( obj.a, (new,old)=> {

// a为对象类型时,如果a被整体替换无法被监听到,具有局限性

})

wactchEffect自动监听函数中使用的数据变化

wactchEffect(()=>{

        if(sum > 5) {console.log(sum)}

        // 上来会先执行一次,自动监视逻辑代码中的所有变量

})

标签的ref

<div ref="active"></div>

import { ref } from 'vue'

let active = ref() // active.value就可以拿到这个目标元素

在组件中使用时,父组件想获取子组件的数据,需要在子组件中使用defineExpose来导出数据,共父组件使用

import {defineExpose} from 'vue'

defineExpose({a,b})

Ts使用

可以在src文件下新建一个type文件夹,再新建一个index.ts文件

定义一个接口类型

export interface PersionType {

        name: string, // 都是小写

        age: number,

}

export type Persons = PersionType[] // 创建一个自定义类型

导入的接口需要在前边加一个type,用来和普通的方法或数据做区分

目标文件中可以直接import {type PersionType} from '@/type'导入使用

let abj:PersionType = {name: '张三',age:19}

let arr: Array<PersionType> = [{name: '张三',age:60}]

实际使用:let list = reactive<Persons>([ {name: '张三',age:19}...])

setup中添加lang="ts"时,引入组件会报错 会报错Module '"/Users/zhuzhu/Desktop/vue3/vue-project/src/components/TheWelcome.vue"' has no default export.Vetur(1192) 

这个问题是因为 TypeScript 无法正确识别 <script setup> 的默认导出,而 Vetur(VSCode 的 Vue 插件)在 TypeScript 模式下会进行更严格的检查

Vetur 对 <script setup> + TypeScript 的支持不够完善,解决办法是:VSCode 扩展中禁用禁用 Vetur,安装 Volar,重启vscode

props:

父组件:

<Person a=123 />

Person子组件:

<h2>{{a}}</h2>

import { defineProps, widthDefaults } from 'vue'  //defineProps是宏函数,可不引入直接使用

// 此时子组件的模板可以正常使用a变量了,但是在script标签中不可以使用

defineProps(['a']) 

接受并保存才能在script标签中使用:

let x = defineProps(['a'])   // 可以使用x.a来访问a

接收数据+限制类型 :defineProps<{list: Persons }>()  或 :defineProps<{list?: Persons }>() 

接收数据+限制类型+置顶默认值: :widthDefaults(defineProps<{list?: Persons }>() ,{

        a:123... // 当父组件传值为空时的兜底

})

生命周期

创建:setup 中的代码就是创建阶段执行的,所以在 Vue3 中没有针对创建阶段专门的回调函数,直接在 setup 中写就好了

挂载: onBeforeMount 、onMounted

更新:onBeforeUpdate、onUpdated

卸载:onBeforeUnmount、onUnmounted

onBeforeMount(() => {
    console.log("挂载前");
});

自定义hooks

 Composition API 中的组合式函数(Composables),它的命名灵感来自 React Hooks,但实现机制完全不同。

在 Vue 3 中,Hooks 就是以 use 开头的函数,用于将可复用的状态逻辑抽离到独立文件中。它基于 Vue 3 的响应式系统(refreactivecomputedwatch 等)和生命周期钩子(onMountedonUnmounted 等)来封装逻辑。

举个简单的例子:

// useCounter.ts
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const double = computed(() => count.value * 2)

  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  return { count, double, increment, decrement }
}

在组件中使用:

<script setup lang="ts">
import { useCounter } from './composables/useCounter'

const { count, double, increment } = useCounter(10)
</script>

它解决了 Vue 2 Options API 的几个痛点:

第一是逻辑分散问题。在 Options API 中,一个功能的 data、methods、computed、watch 分散在不同选项里,代码量大时很难看清一个功能的完整逻辑。Composition API 可以把相关逻辑聚合在一起。

第二是复用困难。Vue 2 的 mixins 有命名冲突、来源不清晰等问题。composable 函数的返回值是显式的,不存在命名冲突,来源一目了然。

第三是 TypeScript 支持。Options API 的 this 类型推断很困难,而 composable 函数就是普通函数,天然对 TS 友好。

和 React Hooks 的核心区别: Vue 的 composable 只在 setup 阶段执行一次,之后响应式系统自动追踪依赖和触发更新;而 React Hooks 在每次组件渲染时都会重新执行,需要手动管理依赖数组(useEffect 的 deps)。所以 Vue 不存在 React 中闭包陈旧值、无限渲染循环等问题。

你工作区中的 vue3-calendar-notes 项目里就有实际的例子,比如 src/composables/useCalendar.ts 和 useDragSort.ts,都是典型的 Vue 3 Hooks 用法。

可以在 src 下新建 hooks 文件夹,在其中创建单独的 .js 或 .ts 文件,将功能拆分到单独的文件中。

 

路由:使用路由可以通过不同的URL路径来访问不同的页面组件,而无需重新加载整个页面。

安装vue-route:npm install vue-router

router/index.ts

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

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('../views/AboutView.vue')
    }
  ]
})

export default router

main.js 中引入路由配置:

import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
// 引入路由
import router from './router'
const app = createApp(App)
// 使用路由
app.use(router)
app.mount('#app')

路由组件通常放在pages文件夹或views文件夹,一般组件放components文件夹

导航区to:可以使用字符串或对象

<router-link active-class="active" to="/home">主页</router-link>

<router-link active-class="active" :to="{path: '/home'}">主页</router-link>

展示区:

<RouterView></RouterView>

嵌套路由的子级不要写/,

在 Composition API 中不能直接使用 this 来访问 this.$router,因为 Composition API 是基于函数的,不使用基于类的组件结构。相反,你应该使用 setup 函数来设置组件,并通过 useRouter 钩子来获取路由实例。

import { useRouter } from 'vue-router';

const router = useRouter();

router.push('/home');

query传参:

获取:

import { useRouter, toRefs } from 'vue-router'

let { route } useRouter()

let { query } = toRefs(route) // 此时可以通过query.id读取参数了

params传参:(使用to的对象写法,必须使用name配置不能使用path,还必须提前在规则中占位)

<router-link active-class="active" to="/news/datail/hh/ss/pp">主页</router-link>

import { useRouter, toRefs } from 'vue-router'

let { route } useRouter()

let { parms } = toRefs(route) // 此时可以通过parms.id读取参数了

push会增加历史记录,replace不会增加记录(不能前进回退)

Pinia状态管理(支持vue2/3)

Vuex 有四个核心概念:state(数据)、getters(派生数据)、mutations(同步修改)、actions(异步操作)。修改数据必须通过 mutation,链路是 组件 → dispatch action → commit mutation → 修改 state。这套流程保证了可追踪性,但也带来了大量模板代码。

Pinia 简化为三个概念:stategettersactions。去掉了 mutation,action 里可以直接修改 state,也可以是异步的。每个 store 是独立的,不需要像 Vuex 那样通过 modules 嵌套。支持 Options API 和 Composition API 两种写法。

Redux 核心是 store(单一数据源)、action(描述发生了什么)、reducer(纯函数,接收旧 state 和 action 返回新 state)。修改数据必须 dispatch 一个 action,reducer 里不能有副作用,必须返回新对象(不可变数据)。异步需要中间件(redux-thunk、redux-saga)。Redux Toolkit(RTK)是官方推荐的封装,大幅减少模板代码。

MobX 核心是 observable(可观察状态)、computed(派生值)、action(修改状态的方法)、reaction(副作用)。直接修改数据即可触发 UI 更新,不需要手动 dispatch,也不需要返回新对象。原理是通过 Proxy 自动追踪哪些组件用了哪些数据,做到精准更新。

npm i pinia或yarn add pinia   // 在main.js文件中挂载到实例对象上

import { createApp } feom 'vue'

import App from  './App.vue'

import router from './router'

import { createPinia } from 'pinia'

const app = createApp(App)

const pinia = createPinia()

app.use(pinia)

app.use(router)

app.mount('#app')

pinia定义store   // src/stores/count.ts文件

import { defineStore } from 'pinia'

在 Setup Store 中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions

标准用法:

interface State {
  userList: UserInfo[]
  user: UserInfo | null
}

const useStore = defineStore('storeId', {
  state: (): State => {
    return {
      userList: [],
      user: null,
    }
  },
})

interface UserInfo {
  name: string
  age: number
}

方式2:

export const useCounterStore = defineStore('counter',{

        state(){  // 真正存储数据的地方

                return {

                        list:[]

                }

        }

})

方式3:

export const useCounterStore = defineStore('counter',{

       const count = ref(0)

        function increment(){

                count.value++

        }

        return {count, increment}

})

方式4:复杂使用

import { defineStore } from 'pinia'

export const useTodos = defineStore('todos', {
  state: () => ({
    /** @type {{ text: string, id: number, isFinished: boolean }[]} */
    todos: [],
    /** @type {'all' | 'finished' | 'unfinished'} */
    filter: 'all',
    // 类型将自动推断为 number
    nextId: 0,
  }),
  getters: {
    finishedTodos(state) {
      // 自动补全! ✨
      return state.todos.filter((todo) => todo.isFinished)
    },
    unfinishedTodos(state) {
      return state.todos.filter((todo) => !todo.isFinished)
    },
    /**
     * @returns {{ text: string, id: number, isFinished: boolean }[]}
     */
    filteredTodos(state) {
      if (this.filter === 'finished') {
        // 调用其他带有自动补全的 getters ✨
        return this.finishedTodos
      } else if (this.filter === 'unfinished') {
        return this.unfinishedTodos
      }
      return this.todos
    },
  },
  actions: {
    // 接受任何数量的参数,返回一个 Promise 或不返回
    addTodo(text) {
      // 你可以直接变更该状态
      this.todos.push({ text, id: this.nextId++, isFinished: false })
    },
  },
})

pinia使用

import { storeToRefs } from 'pinia'

import {useCountStore} from '@/store/count'

const countStore = useCountStore()

const {count, increment} storeToRefs(countStore)

counter.count++ // 自动补全!

counter.$patch({ count: counter.count + 1 })

// 或使用 action 代替

counter.increment()

自定义事件(子传父)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值