Vue3 学习笔记(持续更新中...)

使用 vite 创建 Vue 3.x 工程

创建工程

npm init vite-app <project-name>

进入工程目录

cd <project-name>

安装依赖

npm install
## or
yarn

运行

npm run dev
## or
yarn dev

与 Vue 2.x 的一些不同之处

main.js 中

  • 引入的不再是 Vue 构造函数,引入的是一个名为 createApp 的工厂函数。

    import { createApp } from 'vue'
    import App from './App'
    
  • 创建应用实例对象 —— app

    const app = createApp(App)
    

    app 类似于之前的 vm,但比 vm 更 “”。

    image-20230708101513881

  • 挂载应用实例对象

    app.mount('#app')
    
  • 简写

    // 创建实例并挂载
    createApp(App).mount('#app')
    

组件中

  • vue3.x 组件中的模板结构,可以没有根标签。

  • 新增 setup 配置项。

  • 当父组件给子组件传递了参数,但子组件没有使用 props 配置项接收,会出现警告。

image-20230708165516376

  • 当父组件绑定了自定义事件,但子组件没有使用 emits 配置项接收,会出现警告。

image-20230708165226211

组合式 Composition API

setup 函数

vue 3 中的一个新配置项,是一个函数。

  • 组件中所用到的属性、方法等,都要配置在 setup 中。

  • setup 函数的两种返回值:

    • 若返回一个对象,则对象中的属性和方法,在模板中可以直接使用。(重点掌握)

      export default {
        setup() {
          // 属性
          let name = '张三'
          let age = 18
          // 方法
          function sayHello() {
            alert(`我叫${name},我${age}岁了,你好啊!`)
          }
          // 返回一个对象(常用)
          return {
            name,
            age,
            sayHello,
          }
        }
      }
      
    • 若返回一个 渲染函数,则可以直接渲染模板。(了解)

      import { h } from 'vue'
      export default {
        ...
        setup() {
        	// 返回一个函数(渲染函数)
        	return () => h('h1', '尚硅谷')
      	}
      }
      
  • 尽量不要与 vue 2 配置混用。

    • vue 2.x 配置(data,methods,computed …)中 可以访问 setup 中的属性和方法。
    • setup 配置中,无法访问 vue 2.x 配置中的 (data,methods,computed …)
    • 如有重名,以 setup 配置项优先。
  • setup 不能是一个 async 函数

    • 因为被 async 修饰的函数,返回值不再是普通的对象,而是 Promise 对象,会导致模板中获取不到 return 对象中的属性和方法。
    • 如需要返回一个 Promice 对象实例,则需要 suspense 和异步组件配合。

ref() 函数

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

作用
  • 定义 响应式 变量。
语法
const name = ref('张三')	// 	创建一个 ref 实例对象,它的 .value 值为 '张三'
const age = ref(18)			 // 	创建一个 ref 实例对象,它的 .value 值为 18
详细信息
  • 被 ref 包装后的对象,是一个 RefImpl 实例对象,它是一个包含 响应式 数据( value )的 引用对象( 简称 ref 对象 )。

image-20230708102750160

  • RefImpl 对象的 原型对象 上有 valuegetset 方法。

image-20230708103023283

  • ref 对象是可更改的,也就是说可以为 .value 赋予新的值。
  • ref 对象是响应式的,即所有对 .value 的操作都将被追踪。

ref 对象接收数据的类型

接收的数据可以是 基本类型,也可以是 对象类型

  • 基本类型 的数据,响应式原理依靠 Object.defineProperty()getset 完成。

  • 对象类型 的数据,内部 求助 了 vue 3.x 中的一个新函数 —— reactive 函数。

基本类型 赋值给 ref

读取数据
name.value		// '张三'
age.value		// 18
<template>
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
</template>
修改数据
name.value = '李四'
age.value = 48

对象 赋值给 ref

这个对象将通过 reactive() 转为具有 深层次 响应式的对象。

let job = ref({
  type: '前端工程师',
  salary: '30k',
})

.value的值是一个 Proxy(Object) 对象。

job:

image-20230708112345852

job.value:

image-20230708114829537
读取数据
job.value.type	// '前端工程师'
job.value.salary	// '30k'
<template>
  <h3>工作种类:{{ job.type }}</h3>
  <h3>工作薪水:{{ job.salary }}</h3>
</template>
修改数据
job.value.type = 'UI设计师'
job.value.salary = '15k'

reactive() 函数

定义一个 对象类型 的响应式数据。( 基本类型ref 函数 )

  • 不能将 基本类型 赋值给 reactive 函数。

    const number = reactive(666)	// 报错 value cannot be made reactive: 666
    
    image-20230708121251056
  • 这个对象将通过 reactive() 转为具有 深层次 响应式的对象。

  • 内部基于 ES6Proxy 实现,通过 代理对象 操作 源对象 内部数据。

引入 reactive

import { reactive } from 'vue'

Object 类型

定义数据
const job = reactive({
  type: '前端工程师',
  salary: '30k',
  a: {
    b: {
      c: 666,
    }
  }
})

job:

job
读取数据
job.type
job.salary
job.a.b.c
<h3>工作种类:{{ job.type }}</h3>
<h3>工作薪水:{{ job.salary }}</h3>
<h3>测试数据:{{ job.a.b.c }}</h3>
修改数据
job.type = 'UI 设计师'
job.salary = '15k'
job.a.b.c = 888

Array 类型

数组中的每一个数据都是 响应式 的。

定义数据
// Array类型
const hobby = reactive(['抽烟', '喝酒', '烫头'])

hobby:

image-20230708123334273
读取数据
hobby[0]	// '抽烟'
<h3>爱好:{{ hobby }}</h3>
修改数据
hobby[0] = '学习'

基本数据类型

可以将基本数据类型放在对象中,再赋值给 reactive 函数。

定义数据
const person = reactive({
  name: '张三',
  age: 18,
  job: {
    type: '前端工程师',
    salary: '30k',
    a: {
      b: {
        c: 666,
      },
    },
  },
  hobby: ['抽烟', '喝酒', '烫头'],
})

person:

image-20230708125025481
读取数据
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h3>工作种类:{{ person.job.type }}</h3>
<h3>工作薪水:{{ person.job.salary }}</h3>
<h3>测试数据:{{ person.job.a.b.c }}</h3>
<h3>爱好:{{ person.hobby }}</h3>
修改数据
function changeInfo() {
  person.name = '李四'
  person.age = 48
  person.job.type = 'UI 设计师'
  person.job.salary = '15k'
  person.job.a.b.c = 888
  person.hobby[0] = '学习'
}

vue 3.x 中的响应式原理

vue 2.x 中的响应式

实现原理
Object 类型
  • 通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)。
Array 类型
  • 通过重写更新数组的一系列方法,来实现拦截。
  • 对数组的变更方法进行了包装。
存在问题
export default {
  data() {
    return {
      person: {
        name: '张三',
        age: 18,
        hobby: ['抽烟', '喝酒', '烫头']
      }
    }
  }
}
  • 新增、删除属性时,界面不会更新。

    • 新增属性

      • 现象

        console.log(this.person.sex)	// undefined
        this.person.sex = '女'	
        console.log(this.person.sex)	// '女',说明属性已添加,但是视图未更新。
        
      • 解决方案

        // $set() 向某个数据对象中,追加一个响应式属性。
        this.$set(this.person, 'sex', '女')
        
    • 删除属性

      • 现象

        console.log(this.person.name)	// '张三'
        delete this.person.name
        console.log(this.person.name)	// undefined,说明属性已被删除,但是视图未更新。
        
      • 解决方案

        // $delete() 从某个数据对象中,移除一个响应式属性。
        this.$delete(this.person, 'name')
        
  • 直接通过 下标 修改数组元素,界面不会更新。

    • 现象

      console.log(this.person.hobby[0]) // '抽烟'
      this.person.hobby[0] = '学习'
      console.log(this.person.hobby[0]) // '学习',数组元素值已经修改,但是视图未更新。
      
    • 解放方案

      this.$set(this.person.hobby,0,'学习')
      // or
      this.person.hobby.splice(0,1,'学习')
      
模拟 vue 2.x 实现响应式
// 源对象
const person = {
  name: '张三',
  age: 18,
  hobby: ['抽烟', '喝酒', '烫头'],
}
// 模拟 vue2 中实现响应式
let p = {}  // 代理对象
Object.defineProperty(p,'name',{
  configurable:true,
  get(){  // 有人读取 name 时调用
    console.log('name属性值被读取了。')
    return person.name
  },
  set(value){ // 有人修改 name 时调用
    console.log('name属性值被修改了,即将更新视图。')
    person.name = value
  }
})

vue 3.x 中的响应式

实现原理
window.Proxy() 代理
  • 通过 Proxy 拦截对象中,任意属性的变化。
    • 读取属性值。
    • 修改属性值。
    • 添加属性并赋值。
    • 删除属性。
window.Reflect() 反射
  • 通过 Reflect源对象(被代理对象 target)的属性进行 CRUD 操作。
模拟 vue 3.x 实现响应式
  // 源对象
  const person = {
    name: '张三',
    age: 18,
    hobby: ['抽烟', '喝酒', '烫头'],
  }
  // 模拟 vue3 中实现响应式
  const p = new Proxy(person, {
    // 读取:当读取p的某个属性时被调用。
    get(target, propName) {
      console.log(`有人读取了 p 身上的 ${propName} 属性。`)
      return Reflect.get(target,propName)
    },
    // 新增、修改:当修改p的某个属性时被调用,或给p追加属性时被调用。
    set(target, propName, newValue) {
      console.log(`有人修改了 p 身上的 ${propName} 属性,即将更新视图。`)
      Reflect.set(target,propName,newValue)
    },
    // 删除:当移除p的某个属性时被调用。
    deleteProperty(target, propName) {
      console.log(`有人删除了 p 身上的 ${propName} 属性,即将更新视图。`)
      return Reflect.deleteProperty(target,propName)
    },
  })

ref () 与 reactive () 对比

定义数据

  • ref基本类型。
  • reactive对象或数组类型。
  • ref 也可以用来定义 对象(或数组)类型数据,它内部会自动通过 reactive 转为 代理对象。

原理

  • ref 通过 Object.defineProperty( )getset 来实现响应式(数据劫持)。
  • reactive 通过使用 Proxy 实现响应式(数据劫持),并通过 Reflect 操作 源对象 内部的数据。

使用

  • ref 定义的数据,操作数据需要 .value,读取数据时模板中直接读取不需要 .value
  • reactive 定义的数据,操作数据时,均不需要 .value

setup 两个注意点

setup 执行的时机

  • beforeCreate 之前执行一次,thisundefined

setup 的参数

props
  • 值为对象,包含组件外部传来,且组件内部 props 配置中,声明接收了的属性。
context
  • attr 值为对象,包含组件外部传来,但没有props 配置中声明的属性。
  • emit 分发自定义事件的函数,相当于 this.$emit
  • slots 收到的插槽内容,相当于 this.$slots

computed() 函数

引入

import {computed} from 'vue'

创建一个只读的计算属性

setup(){
  ...
  // 计算属性 - 简写(只读)
  const fullName = computed(()=>{
    return person.firstName + '-' + person.lastName
  })
  return { fullName }
}

创建一个可写的计算属性Ò

setup(){
  ...
  // 计算属性 - 完整(可写)
  const fullName = computed({
    get(){
      return person.firstName + '-' + person.lastName
    },
    set(value){
      const nameList = value.split('-')
      person.firstName = nameList[0]
      person.lastName = nameList[1]
    }
  })
  return { fullName }
}

watch() 函数

引入

import {watch} from 'vue'

情况一

监视 ref 定义的一个响应式数据。

sum = ref(0)
watch(sum,(newValue,oldValue)=>{
  console.log('sum的值变化了。',newValue,oldValue)
},{immediate:true})

注意:要监视 sum,不要监视 sum.value。

情况二

监视多个 ref 定义的响应式数据。

  • 分别监视

    sum = ref(0)
    msg = ref('你好')
    watch(sum,(newValue, oldValue)=>{
      console.log('sum的值变化了。', newValue, oldValue)
    })
    watch(msg,(newValue, oldValue)=>{
      console.log('sum的值变化了。', newValue, oldValue)
    })
    
  • 整体监视

    watch([sum,msg],(newValue,oldValue)=>{
      console.log('sum的值变化了。', newValue, oldValue)
    })
    

    备注:此时 newValue 和 oldValue 都是数组。newValue[0] 为 sum 的 newValue,newValue[1] 为 msg 的 newValue,oldValue[0] 为 sum 的 oldValue,oldValue[1] 为 msg 的 oldValue。

情况三

监视 reactive 定义的响应式数据。

  • 若 watch 监视的是 reactive 定义的响应式数据,则无法正确获得oldValue
  • 若 watch 监视的是r eactive 定义的响应式数据,则强制开启了深度监视 ,无法关闭。
const person = reactive({
  name: '小仙女',
  age: 18,
  job:{
    j1:{
      salary:20
    }
  }
})
watch(person,(newValue, oldValue)=>{
  console.log('person变化了。', newValue, oldValue)	// oldValue的值无法正确获取,等于 newValue。
},{immediate:true,deep:false})	// 此处的 deep 配置不生效。

备注:监视的是 reactive 定义的响应式数据本身,deep 配置不生效

情况四

监视 reactive 定义的响应式数据中的某个属性。

  • 监视某个属性,则需要使用 ()=>person.job 的方式。
watch(()=>person.name,(newValue, oldValue)=>{
  console.log('person的name变化了。', newValue, oldValue)
})
watch(
  () => person.job,
  (newValue, oldValue) => {
    console.log('person.name变化了。', newValue, oldValue)
  },
  { deep: true } // 此种写法 deep 配置生效。
)
watch(
  person.job,
  (newValue, oldValue) => {
    console.log('person.name变化了。', newValue, oldValue)
  },
  { deep: false } // 此种写法 deep 强制开启,无法关闭。
)

情况五

监视 reactive 定义的响应式数据中的某些属性。

写法一:此种写法 deep 配置生效。()=>person.job
watch([()=>person.job,()=>person.name],(newValue, oldValue)=>{
  console.log('person的job或name变化了。', newValue, oldValue)
},{immediate:true,deep:true})	// 此处的 deep 配置生效。

备注:监视的是 reactive 定义的对象中的某个属性,而这个属性是一个对象,所以 deep 配置生效。

写法二:此种写法 deep 属性强制开启,无法关闭。person.job
watch(
  [person.job, () => person.name],
  (newValue, oldValue) => {
    console.log('person的job或name变化了。', newValue, oldValue)
  },
  { immediate: true, deep: false }
) // 此处的 deep 配置不生效。

watchEffect() 函数

与 watch() 对比

  • watch() 函数:既要指明监视的对象,也要指明监视的回调。
  • watchEffect() 函数:无需指明监视哪个对象,需要指明监视的回调。回调中用到哪个对象,就监视哪个对象。

与 computed() 对比

  • computed() 注重计算出来的值(回调函数的返回值),所以必须要写返回值。
  • watchEffect() 注重过程(回调函数的函数体),所以无需写返回值。

导入

import {watchEffect} from 'vue'

使用

// watchEffect监视回调函数中应用到的数据,只要发生变化,则执行回调。
watchEffect(()=>{
  const x1 = sum.value
  const x2 = person.age
  console.log('watchEffect配置的回调执行了。')
})

生命周期函数

image-20230708201156408

生命周期选项

  • beforeCreate

  • created

  • beforeMount

  • mounted

  • beforeUpdate

  • updated

  • beforeUmount( vue 2.x 中的 beforeDestroy )

  • umounted ( vue 2.x 中的 destroyed)

Composition API 形式的生命周期钩子

  • setup() —— beforeCreate
  • setup() —— created
  • onBeforeMount —— beforeMount
  • onMounted —— mounted
  • onBeforeUpdate —— beforeUpdate
  • onUpdated —— updated
  • onBeforeUnmount —— beforeUnmount
  • onUnmounted —— unmounted

自定义 hook() 函数

什么是 hook

  • 本质是一个函数。
  • 把 setup 函数中使用的 Composition API 进行封装。
  • 类似于 vue 2.x 中的 mixin。

作用

  • 复用代码,让 setup 中的逻辑更清晰。

补充知识

#region

在 vscode 中,#region 和 #endregion 包裹的代码块,可以自定义折叠。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值