11、vue3

一、为什么要学 Vue3

1.1 为什么要学 Vue3

1.2 Vue3的优势

1.3 Vue2 选项式 API vs Vue3 组合式API

Vue3 组合式API vs Vue2 选项式 API

二、create-vue搭建Vue3项目

2.1 认识 create-vue

2.2 使用create-vue创建项目

  1. 前提环境条件
    已安装 16.0 或更高版本的 Node.js
    node -v
  2. 创建一个Vue应用
    npm init vue@latest
    这一指令将会安装并执行 create-vue

三、熟悉项目目录和关键文件

3.1 项目目录和关键文件

关键文件:

  1. vite.config.js - 项目的配置文件 基于vite的配置
  2. package.json - 项目包文件 核心依赖项变成了 Vue3.x 和 vite
  3. main.js - 入口文件 createApp函数创建应用实例
  4. app.vue - 根组件 SFC单文件组件 script - template - style
    变化一:脚本script和模板template顺序调整
    变化二:模板template不再要求唯一根元素
    变化三:脚本script添加setup标识支持组合式API
  5. index.html - 单页入口 提供id为app的挂载点

    main.js
js 复制代码
import './assets/main.css'

// new Vue() 创建一个应用实例 => createApp()
// createRouter() createStore()
// 将创建实例进行了封装,保证每个实例的独立封闭性
import { createApp } from 'vue'
import App from './App.vue'

// mount 设置挂载点  #app(id为app的盒子)
createApp(App).mount('#app')

App.vue

html 复制代码
<!-- 加上 setup 允许在 script 中直接编写组合式API -->
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>

<template>
  <!-- 不再要求唯一根元素 -->
  <header>
    <img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />
    </div>
  </header>

  <main>
    <TheWelcome />
  </main>
</template>

<style scoped>
header {
  line-height: 1.5;
}

.logo {
  display: block;
  margin: 0 auto 2rem;
}

@media (min-width: 1024px) {
  header {
    display: flex;
    place-items: center;
    padding-right: calc(var(--section-gap) / 2);
  }

  .logo {
    margin: 0 2rem 0 0;
  }

  header .wrapper {
    display: flex;
    place-items: flex-start;
    flex-wrap: wrap;
  }
}
</style>

四、组合式API - setup选项

4.1 setup选项的写法和执行时机

1.执行时机,比beforeCreated还要早

2.setup函数中,获取不到this(this是undefined)

App.vue

html 复制代码
<script>
// setup
// 1.执行时机,比beforeCreated还要早
// 2.setup函数中,获取不到this(this是undefined)
export default {
  setup(){
    console.log('setup函数', this)
  },
  beforeCreate(){
    console.log('beforeCreate函数')
  }
}
</script>

<template>
  <div>学习vue3</div>
</template>

运行结果

4.2 setup选项中写代码的特点


App.vue

html 复制代码
<script>
// setup
// 1.执行时机,比beforeCreated还要早
// 2.setup函数中,获取不到this(this是undefined)
// 3.数据 和 函数,需要在 setup 最后 return ,才能模板中应用
//    问题: 每次都要return,好麻烦?
// 4.通过 setup 语法糖简化代码
export default {
  setup () {
    // console.log('setup函数', this)
    // 数据
    const message = 'hello Vue3'
    // 函数
    const logMessage = () => {
      console.log(message)
    }
    return {
      message,
      logMessage
    }
  },
  beforeCreate(){
    console.log('beforeCreate函数')
  }
}
</script>

<template>
  <div>{{ message }}</div>
  <button @click="logMessage">按钮</button>
</template>

数据 和 函数,需要在 setup 最后 return ,才能模板中应用

问题: 每次都要return,好麻烦?=> 语法糖

4.3 <script setup> 语法糖


App.vue

html 复制代码
<script setup>
const message = 'this is message'
const logMessage = () => {
  console.log(message)
}
</script>

<template>
  <div>{{ message }}</div>
  <button @click="logMessage">按钮</button>
</template>

4.4 <script setup> 语法糖原理\

4.5 小结

  1. setup选项的执行时机?
    beforeCreate钩子之前 自动执行
  2. setup写代码的特点是什么?
    定义数据 + 函数 然后以对象方式return
  3. <script setup>解决了什么问题?
    经过语法糖的封装更简单的使用组合式API
  4. setup中的this还指向组件实例吗?
    指向undefined

五、组合式API - reactive和ref函数

脚本区加 ".value"

非脚本区不加 ".value"

5.1 reactive()

作用:接受对象类型数据的参数传入 并返回一个响应式的对象

核心步骤:

  1. 从 vue 包中导入 reactive 函数
  2. 在 <script setup> 中执行 reactive 函数 并传入类型为对象 的初始值,并使用变量接收返回值
    App.vue
html 复制代码
<script setup>
// 1.reactive: 接收一个对象类型的数据,返回一个响应式的对象
import { reactive } from 'vue'
const state = reactive({
  count: 100
})
const setCount = () => {
  state.count++
}
</script>

<template>
  <div>{{ state.count }}</div>
  <button @click="setCount">按钮+1</button>
</template>

5.2 ref()

作用:接收简单类型或者对象类型的数据 传入并返回一个响应式的对象

核心步骤:

  1. 从 vue 包中导入 ref 函数
  2. 在 <script setup> 中执行 ref 函数 并传入初始值,使用变量接收 ref 函数的返回值
    App.vue
html 复制代码
<script setup>
// 1.reactive: 接收一个对象类型的数据,返回一个响应式的对象
//     问题: 如果是简单类型,怎么办呢?
// import { reactive } from 'vue'
// const state = reactive({
//   count: 100
// })
// const setCount = () => {
//   state.count++
// }

// 2.ref: 接收简单类型 和 复杂类型, 返回一个响应式的对象
//     本质:是在原有传入数据的基础上,外层包了一层对象
//     底层,包成复杂类型之后,再借助 reactive 实现的响应式
//     注意点:
//     1.脚本中访问数据,需要通过 .value
//     2.在template中,value不需要加(帮我们扒了一层)

// 推荐:以后在声明数据,统一用 ref => 统一了编码规范
import { ref } from 'vue'
const count = ref(0)
// console.log(count)  //是个对象
const setCount = () => {
  count.value++
}
</script>

<template>
  <div>{{ count }}</div>
  <button @click="setCount">按钮+1</button>
</template>

5.3 小结

  1. reactive和ref函数的共同作用是什么 ?
    用函数调用的方式生成响应式数据
  2. reactive vs ref ?
    1.reactive不能处理简单类型的数据
    2.ref参数类型支持更好但是必须通过.value访问修改

    3.ref函数的内部实现依赖于reactive函数
  3. 在实际工作中推荐使用哪个?
    推荐使用ref函数,更加灵活统一

六、组合式API - computed

6.1 computed计算属性函数

计算属性基本思想和Vue2的完全一致,组合式API下的计算属性只是修改了写法

核心步骤:

  1. 导入computed函数
  2. 执行函数 在回调参数中return基于响应式数据做计算的值 ,用变量接收

6.2 计算属性小案例

App.vue

html 复制代码
<script setup>
// const 计算属性 = computed(() => {
  // return 计算返回的结果
// })

import {computed, ref} from 'vue'

// 声明数据
const list = ref([1, 2, 3, 4, 5, 6, 7, 8, 9])

// 基于list派生一个计算属性,从list中过滤出 >2
const computedList = computed(() => {
  return list.value.filter(item => item > 2)
})

// 定义一个修改数组的方法
const addFn = () => {
  list.value.push(666)
}

</script>

<template>
  <div>
    <div>原始数据: {{ list }}</div>
    <div>计算后的数据: {{ computedList }}</div>
    <button @click="addFn" type="button">修改</button>

  </div>
</template>

6.3 小结

最佳实践

  1. 计算属性中不应该有"副作用"
    比如异步请求/修改dom
  2. 避免直接修改计算属性的值
    计算属性应该是只读的,特殊情况可以配置 get set

七、组合式API - watch

7.1 watch函数

作用: 侦听一个或者多个数据的变化,数据变化时执行回调函数

俩个额外参数:1. immediate(立即执行) 2. deep(深度侦听)

7.2 基础使用 - 侦听单个数据

  1. 导入watch函数
  2. 执行watch函数 传入要侦听的响应式数据**(ref对象)**和回调函数

7.3 基础使用 - 侦听多个数据

说明:同时侦听多个响应式数据的变化,不管哪个数据变化都需要执行回调

App.vue

html 复制代码
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const nickname = ref('张三')

const changeCount = () => {
  count.value++
}
const changeNickname = () => {
  nickname.value = '李四'
}

// 1.监视单个数据的变化
// watch(ref对象, (newValue, oldValue) => {...})
// watch(count, (newValue, oldValue) => {
//   console.log(newValue, oldValue)
// })

// 2.监视多个数据的变化
// watch([ref对象1, ref对象2], (newArr, oldArr) => {...})
watch([count, nickname], (newArr, oldArr) => {
  console.log(newArr, oldArr)
})
</script>

<template>
  <div>{{ count }}</div>
  <button @click="changeCount">改数字</button>
  <div>{{ nickname }}</div>
  <button @click="changeNickname">改昵称</button>
</template>

7.4 immediate

说明:在侦听器创建时立即触发回调 , 响应式数据变化之后继续执行回调

7.5 deep

默认机制:通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行 ,需要开启deep选项

App.vue

js 复制代码
// 4.deep 深度监视,默认 watch 进行的是 浅层监视
//      const ref1 = ref(简单类型) 可以直接监视
//      const ref2 = ref(复杂类型) 监视不到复杂类型内部数据的变化
const userInfo = ref({
  name:'zs',
  age: 18
})
const setUserInfo = () => {
  // 修改了 userInfo.value ,修改了对象的地址,才能监视到
  // userInfo.value = {name: 'ls', age: 50}
  userInfo.value.age++
}
watch(userInfo, (newValue) => {
  console.log(newValue)
},{
  deep: true
})
</script>
<template>
  <div>{{ userInfo }}</div>
  <button @click="setUserInfo" type="button">修改userInfo</button>
</template>

7.6 精确侦听对象的某个属性

需求:在不开启deep的前提下,侦听age的变化,只有age变化时才执行回调

App.vue

html 复制代码
// 5.对于对象中的属性, 进行监视
watch(() => userInfo.value.age, (newValue, oldValue) => {
  console.log(newValue, oldValue)
})

7.7 小结

  1. 作为watch函数的第一个参数,ref对象需要添加.value吗?
    不需要,第一个参数就是传 ref 对象
  2. watch只能侦听单个数据吗?
    单个 或者 多个
  3. 不开启deep,直接监视 复杂类型,修改属性 能触发回调吗?
    不能,默认是浅层侦听
  4. 不开启deep,精确侦听对象的某个属性?
    可以把第一个参数写成函数的写法,返回要监听的具体属性

八、组合式API - 生命周期函数

8.1 Vue3的生命周期API (选项式 VS 组合式)

8.2 生命周期函数基本使用

  1. 导入生命周期函数
  2. 执行生命周期函数 传入回调

8.3 执行多次

生命周期函数是可以执行多次的,多次执行时传入的回调会在时机成熟时依次执行
App.vue

js 复制代码
<script setup>
import { onMounted } from 'vue'

// beforeCreate 和 created 的相关代码
// 一律放在 setup 中执行
const getList = () => {
  console.log('发送请求')
}
// 一进入页面的请求
getList()

// 如果有些代码需要在mounted生命周期中执行
onMounted(() => {
  console.log('mounted生命周期函数 - 逻辑1')
})

// 写成函数的调用方式,可以多次调用,并不会冲突,而是按照顺序一次执行
onMounted(() => {
  console.log('mounted生命周期函数 - 逻辑2')
})
</script>

8.4 小结

  1. 组合式API中生命周期函数的格式是什么?
    on + 生命周期名字
  2. 组合式API中可以使用onCreated吗?
    没有这个钩子函数,直接写到setup中
  3. 组合式API中组件卸载完毕时执行哪个函数?
    onUnmounted

九、组合式API - 父子通信

9.1 组合式API下的父传子

基本思想

  1. 父组件中给子组件绑定属性
  2. 子组件内部通过props选项接收

    App.vue
html 复制代码
<script setup>
// 父传子
// 1.给子组件,添加属性的方式传值
// 2.在子组件,通过props接收
import { ref } from 'vue'
import SonCom from '@/components/son-com.vue'

const money = ref(100)
const getMoney = () => {
  money.value += 10
}

</script>

<template>
  <div>
    <h3>
      父组件 - {{ money }}
      <button type="button" @click="getMoney">挣钱</button>
    </h3>
    <!-- 给子组件,添加属性的方式传值 -->
    <SonCom car="宝马车" :money="money"></SonCom>
  </div>
</template>

components - son-com.vue

html 复制代码
<script setup>
// 注意:由于写了 setup,所以无法直接配置 props 选项
// 所以: 此处需要借助于"编译器宏"函数接收子组件传递的数组
const props = defineProps({
    car: String,
    money: Number
})
console.log(props.car)
console.log(props.money)
</script>

<template>
    <!-- 对于props传递过来的数据,模板中可以直接使用 -->
  <div class="son">我是子组件 - {{ car }} - {{ money }}</div>
</template>

<style scoped>
.son {
    border: 1px solid #000;
    padding: 30px;
}

</style>

9.2 组合式API下的父传子原理

9.3 组合式API下的子传父

基本思想

  1. 父组件中给子组件标签通过@绑定事件
  2. 子组件内部通过 emit 方法触发事件

    App.vue
html 复制代码
<script setup>
// 父传子
// 1.给子组件,添加属性的方式传值
// 2.在子组件,通过props接收

// 子传父
// 1.在子组件内部,emit触发事件(编译器宏获取)
// 2.在父组件,通过 @ 监听
import { ref } from 'vue'
import SonCom from '@/components/son-com.vue'

const money = ref(100)
const getMoney = () => {
  money.value += 10
}

const changeFn= (newMoney) => {
  money.value = newMoney
}

</script>

<template>
  <div>
    <h3>
      父组件 - {{ money }}
      <button type="button" @click="getMoney">挣钱</button>
    </h3>
    <!-- 给子组件,添加属性的方式传值 -->
    <SonCom @changeMoney="changeFn" car="宝马车" :money="money"></SonCom>
  </div>
</template>

components - son-com.vue

html 复制代码
<script setup>
// 注意:由于写了 setup,所以无法直接配置 props 选项
// 所以: 此处需要借助于"编译器宏"函数接收子组件传递的数组
const props = defineProps({
    car: String,
    money: Number
})
console.log(props.car)
console.log(props.money)

const emit = defineEmits(['changeMoney']) 
const buy = () => {
    // 需要 emit 触发事件
    emit('changeMoney', 5)
}
</script>

<template>
    <!-- 对于props传递过来的数据,模板中可以直接使用 -->
  <div class="son">
    我是子组件 - {{ car }} - {{ money }}
    <button @click="buy">花钱</button>
  </div>
</template>

<style scoped>
.son {
    border: 1px solid #000;
    padding: 30px;
}

</style>

9.4 小结

  • 父传子
  1. 父传子的过程中通过什么方式接收props?
    defineProps( { 属性名:类型 } )
  2. setup语法糖中如何使用父组件传过来的数据?
    const props = defineProps( { 属性名:类型 } )
    props.xxx
  • 子传父
  1. 子传父的过程中通过什么方式得到emit方法?
    defineEmits( ['事件名称'] )
  2. 怎么触发事件
    emit('自定义事件名', 参数)

十、组合式API - 模版引用

10.1 模板引用的概念

通过ref标识 获取真实的dom对象或者组件实例对象

10.2 如何使用(以获取dom为例 组件同理)

10.3 defineExpose()

默认情况下在<script setup>语法糖下组件内部的属性和方法是不开放 给父组件访问的,可以通过defineExpose编译宏指定哪些属性和方法允许访问

App.vue

html 复制代码
<script setup>
import TestCom from '@/components/test-com.vue'
import { onMounted, ref } from 'vue'

// 模版引用(可以获取dom, 也可以获取组件)
// 1.调用ref函数,生成一个ref对象
// 2.通过ref标识,进行绑定
// 3.通过ref对象.value即可访问到绑定的元素(必须渲染完成后,才能拿到)
const inp = ref(null)
// 生命周期钩子 onMounted
onMounted(() => {
  // console.log(inp.value)
  // inp.value.focus()
})

const clickFn = () => {
  inp.value.focus()
}
// --------------------------------------
const testRef = ref(null)
const getCom = () => {
  console.log(testRef.value.count)
  testRef.value.sayHi()
}
</script>

<template>
  <div>
    <input ref="inp" type="text">
    <button @click="clickFn">点击让输入框聚焦</button>
  </div>
  <TestCom ref="testRef"></TestCom>
  <button @click="getCom">获取组件</button>
</template>

components - test-com.vue

html 复制代码
<script setup>
const count = 999
const sayHi = () => {
    console.log('打招呼')
}
defineExpose({
    count,
    sayHi
}) 
</script>

<template>
  <div>
    我是用于测试的组件 - {{ count }}
  </div>
</template>

10.4 小结

  1. 获取模板引用的时机是什么?
    组件挂载完毕
  2. defineExpose编译宏的作用是什么?
    显式暴露组件内部的属性和方法

十一、组合式API - provide和inject

11.1 作用和场景

顶层组件向任意的底层组件传递数据和方法 ,实现跨层组件通信

11.2 跨层传递普通数据

11.3 跨层传递响应式数据

11.4 跨层传递方法


App.vue

html 复制代码
<script setup>
import CenterCom from '@/components/center-com.vue'
import { provide, ref } from 'vue';

// 1. 跨层传递普通数据
provide('theme-color', 'pink')
// 2.跨层传递响应式数据
const count = ref(100)
provide('count', count)

setTimeout(() => {
  count.value = 500
},2000)

// 3.跨层级传递函数 => 给子孙后代传递可以修改数据的函数
provide('changeCount', (newCount) => {
  count.value = newCount
})
</script>

<template>
  <div>
    <h1>我是顶层组件</h1>
    <CenterCom></CenterCom>
  </div>
</template>

components - center-com.vue

html 复制代码
<script setup>
import BottomCom from './bottom-com.vue'

</script>

<template>
  <div>
    <h2>我是中间组件</h2>
    <BottomCom></BottomCom>
  </div>
</template>

components - bottom-com.vue

html 复制代码
<script setup>
import { inject } from 'vue';

const themeColor = inject('theme-color')
const count = inject('count')
const changeCount = inject('changeCount')
const clickFn = () => {
    changeCount(1000)
}

</script>

<template>
  <div>
    <h3>我是底层组件- {{ themeColor }} - {{ count }}</h3>
    <button @click="clickFn">更新count</button>
  </div>
</template>

11.5 需求解决思考

11.6 小结

  1. provide和inject的作用是什么?
    跨层组件通信
  2. 如何在传递的过程中保持数据响应式?
    第二个参数传递ref对象
  3. 底层组件想要通知顶层组件做修改,如何做?
    传递方法,底层组件调用方法
  4. 一颗组件树中只有一个顶层或底层组件吗?
    相对概念,存在多个顶层和底层的关系

十二、Vue3.3新特性-defineOptions

12.1 Vue3.3新特性-defineOptions

背景说明:

有<script setup> 之前,如果要定义 props, emits 可以轻而易举地添加一个与 setup 平级的属性。

但是用了 <script setup> 后,就没法这么干了 setup 属性已经没有了,自然无法添加与其平级的属性。

为了解决这一问题,引入了 definePropsdefineEmits 这两个宏。但这只解决了 propsemits 这两个属性。

如果我们要定义组件的 name 或其他自定义的属性,还是得回到最原始的用法------再添加一个普通的


src-view-login-index.vue

html 复制代码
<script setup>
defineOptions({
    name: 'LoginIndex'
})

</script>

<template>
    <div>
        我是登录页
    </div>
</template>

十三、Vue3.3新特性-defineModel

13.1 Vue3 中的 v-model 和 defineModel


App.vue

html 复制代码
<script setup>
import MyInput from '@/components/my-input.vue'
import { ref } from 'vue'
const txt = ref('123456')
</script>

<template>
  <div>
    <MyInput v-model="txt"></MyInput>
    {{ txt }}

  </div>
</template>

components - my-input.vue

html 复制代码
<script setup>
import { defineModel } from 'vue'
const modelValue = defineModel()
</script>

<template>
    <div>
        <input
         type="text"
         :value="modelValue"
         @input="e => modelValue = e.target.value"
         >
    </div>
</template>

vite.config.js

js 复制代码
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
  //添加的部分
    vue({
      script: {
        defineModel: true
      }
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

运行结果:

相关推荐
腾讯TNTWeb前端团队7 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰10 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪10 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪10 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy11 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom12 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom12 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom12 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom12 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom12 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试