Vue3学习01 Vue3核心语法

Vue3学习

  • 1. Vue3
    • 新的特性
  • 2. 创建Vue3工程
    • 2.1 基于 vue-cli 创建
      • 项目文件说明
    • 2.2 基于 vite 创建
      • 具体操作
      • 项目文件说明
    • 2.3 简单案例(vite)
  • 3. Vue3核心语法
    • 3.1 OptionsAPI 与 CompositionAPI
      • Options API 弊端
      • Composition API 优势
    • ⭐3.2 setup
      • 小案例
      • setup返回值
      • setup 与 Options API 的关系
      • setup语法糖
    • 3.2 ref
      • ref 创建:基本类型的响应式数据
      • ref 创建:对象类型的响应式数据
    • 3.3 reactive
      • reactive 创建:对象类型的响应式数据
    • 3.4 ref 对比 reactive
      • 区别
      • 使用原则
    • 3.5 toRefs 与 toRef
    • 3.6 computed
    • 3.7 watch
      • 情况一
      • 情况二
      • 情况三
      • 情况四
      • 情况五
    • 3.8 watchEffect
    • 3.9 标签中的ref属性
    • 回顾TS
      • vue文件路径 @
    • 3.10 props使用
      • reactive和泛型一起用
      • 示例
    • 3.11 生命周期
    • 3.12 自定义hooks

1. Vue3

新的特性

  1. Composition API(组合API):

    • setup

    • refreactive

    • computedwatch

  2. 新的内置组件:

    • Fragment

    • Teleport

    • Suspense

  3. 其他改变:

    • 新的生命周期钩子

    • data 选项应始终被声明为一个函数

    • 移除keyCode支持作为 v-on 的修饰符

2. 创建Vue3工程

2.1 基于 vue-cli 创建

点击查看官方文档

备注:目前vue-cli已处于维护模式,官方推荐基于 Vite 创建项目

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version

## 安装或者升级你的@vue/cli 
npm install -g @vue/cli

## 执行创建命令
vue create vue_test

##  随后选择3.x
##  Choose a version of Vue.js that you want to start the project with (Use arrow keys)
##  > 3.x
##    2.x

## 启动
cd vue_test
npm run serve

具体步骤:

① 项目名字不可以大写英文


在这里插入图片描述

③ 进入文件夹运行

在这里插入图片描述

项目文件说明

在这里插入图片描述

  • /src/main.js 入口文件
import { createApp } from 'vue'
/* 这里引入的不再是vue构造函数了,在vue2中  import Vue from 'vue' 
vue3 引入的是一个名为 creatApp 的工厂函数
区别是 vue 构造函数需要通过new调用,工厂函数可以直接调用creatApp
*/

import App from './App.vue'

// 直接调用 createApp函数
// 把外壳组件 App 传递进去
// #app是页面上容器的 id
createApp(App).mount('#app')
/*
不简写:
const app = createApp(App)  创建应用实例对象app 类似vue2中的vm,但app更轻
app.mount('#app')           挂载
*/

/* 
vue2写法:
new Vue({
  el: '#app', //容器
})

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

或者
const vm = new Vue({
  render:(h)=>h(App)
})

vm.$mount('#app)
 */
  • vue3组件中可以没有 根标签

2.2 基于 vite 创建

vite 是新一代前端构建工具,官网地址:https://vitejs.cn。

vite的优势如下:

​ 轻量快速的热重载(HMR 热重载的意思是改代码之后局部刷新),能实现极速的服务启动。

​ 对 TypeScriptJSXCSS 等支持开箱即用。

​ 真正的按需编译,不再等待整个应用编译完成。

webpack构建 与 vite构建对比图如下:

在这里插入图片描述

webpack 分析路由、模块=>进行打包=>服务器准备(8080)。所以我们每次执行npm run serve时都需要等待一段时间(等的就是打包的过程)

vite先打开服务器

具体操作

(点击查看官方文档)

## 1.创建命令
npm create vue@latest

## 2.具体配置
## 配置项目名称
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript?  Yes
## 是否添加JSX支持
√ Add JSX Support?  No
## 是否添加路由环境
√ Add Vue Router for Single Page Application development?  No
## 是否添加pinia环境
√ Add Pinia for state management?  No
## 是否添加单元测试
√ Add Vitest for Unit Testing?  No
## 是否添加端到端测试方案
√ Add an End-to-End Testing Solution? » No
## 是否添加ESLint语法检查
√ Add ESLint for code quality?  Yes
## 是否添加Prettiert代码格式化
√ Add Prettier for code formatting?  No

具体步骤:

在这里插入图片描述

Need to install the following packages:
create-vue@3.10.2
Ok to proceed? (y) y

在这里插入图片描述

项目文件说明

在这里插入图片描述

.vscode/extensions.json 官方推荐的插件

public/favicon.ico 页签图标

src/ 源代码文件

.gitignore git的忽略文件

env.d.ts 该文件只有一一行/// <reference types="vite/client" />且飘红,飘红的原因是当前没有 node_modules(没有依赖),在命令行 npm i 安装,重新打开 vs code就不红了。 .ts文件不认识 .jpg .txt,这个文件的目的就是让ts认识他们。

index.html 入口文件(与webpack区别)

package.json 、package-lock.json 包管理文件

REANME.md可以删除

tsconfig.app.json, tsconfig.json, tsconfig.node.json是ts的配置文件

vite.config.ts 整个工程的配置文件

src文件夹说明

入口文件 index.html把 /src/main.ts 引入

  • main.ts
import './assets/main.css' // 引入样式

import { createApp } from 'vue' // 创建应用(花盆)
import App from './App.vue' // 组件(植物的根)

createApp(App).mount('#app') 
//挂载到 index.html 的 app div里面,

总结:

  • Vite 项目中,index.html 是项目的入口文件,在项目最外层。
  • 加载index.html后,Vite 解析 <script type="module" src="/src/main.ts"></script> 指向的typescript
  • Vue3 中是通过 createApp 函数创建一个应用实例。

2.3 简单案例(vite)

注意:当前笔记及之后 都是使用 vite 创建的工程

这个案例证明了vue3可以写vue2的代码

<script lang="ts">这里写明是 ts

  • /components/Person.vue
<template>
  <div class="person">
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="showTel">点击查看联系方式</button>
  </div>
</template>

<script lang="ts">
export default {
  name: 'Person',
  data() {
    return {
      name: '张三',
      age: 18,
      tel: '11111111111111'
    }
  },
  methods: {
    changeName(){
      this.name = 'zhang-san'
    },
    changeAge(){
      this.age++
    },
    showTel(){
      alert(this.tel)
    }
  },
}
</script>

<style>
.person {
  background-color: skyblue;
}
button {
  margin: 0 5px;
}
</style>
  • /App.vue

对person组件,①引用import ②注册components ③使用

<template>
  <div class="haha">
    <h1>你好啊</h1>
    <Person></Person>
  </div>
</template>

<script lang="ts">
  import Person from './components/Person.vue'
  export default {
    name: 'App',
    components: {
      Person
    }
  }
</script>

<style>
.haha{
  background-color: pink;
}
</style>

3. Vue3核心语法

3.1 OptionsAPI 与 CompositionAPI

  • Vue2API设计是Options(配置)风格的。
  • Vue3API设计是Composition(组合)风格的。

Options API 弊端

选项式/配置式写法

学习vue2就是在学习配置项 data、methods、components。

Options类型的 API,数据、方法、计算属性等,是分散在:datamethodscomputed中的,若想新增或者修改一个需求,就需要分别修改:datamethodscomputed,不便于维护和复用。

Composition API 优势

可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。

在这里插入图片描述

⭐3.2 setup

setupVue3中一个新的配置项,值是一个函数,它是 Composition API “表演的舞台,组件中所用到的:数据、方法、计算属性、监视…等等,均配置在setup中。

小案例

  • 下文案例的问题:数据不是响应式(之后会讲)

注意事项:

① setup中的函数没有维护 this。在setup函数中 console.log(this),值为undefined

② setup函数的时机:setup比vue2中的 beforeCreate时机早(之后会讲)

<template>
  <div class="person">
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="showTel">点击查看联系方式</button>
  </div>
</template>

<script lang="ts">
export default {
  name: 'Person',
  // data() {
  //   return {
  //     name: '张三',
  //     age: 18,
  //     tel: '11111111111111'
  //   }
  // },
  // methods: {
  //   changeName(){
  //     this.name = 'zhang-san'
  //   },
  //   changeAge(){
  //     this.age++
  //   },
  //   showTel(){
  //     alert(this.tel)
  //   }
  // },
  setup() {
    // 数据
    // 如果这么简单的定义数据,那就不是响应式数据
    // 响应式:后续数据变化 页面上也变化
    let name = '张三'
    let age = 18
    let tel = '1388888'

    // 方法
    function changeName(){
      // this.name = '???'
      // 这样写是错的,vue在setup中没有维护this关键字 
      // console.log(this) undefined
      name = 'zhang-san'
    }
    function changeAge(){
      age += 1
    }
    function showTel(){
      alert(tel)
    }

    // 返回数据和函数
    return {name,age,changeName,changeAge,showTel}
  }
}
</script>

<style>
.person {
  background-color: skyblue;
}
button {
  margin: 0 5px;
}
</style>

setup返回值

setup的返回值是对象,把数据、方法交出去

setup() {
  ...
  return {name,age,changeName,changeAge,showTel}
}

setup的返回值如果是一个函数的话,会直接渲染页面内容

setup(){
  ...
  return function() {
     '哈哈'
  }
}

或者
setup(){
  ...
  return () => '哈哈' //因为setup不维护this 所以可以直接简写为箭头函数
}

在这里插入图片描述

setup 与 Options API 的关系

面试题

① data 、methods 、setup可以同时存在

Vue2 的配置(datamethos…)中可以访问到 setup中的属性、方法。(因为setup是最早的生命周期,当data去读数据的时候,setup已经有数据了)

③ 但在setup不能访问到Vue2的配置(datamethos…)。

④ 如果与Vue2冲突,则setup优先。

<template>
  <div class="person">
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <hr>
    <h2>测试:data读到setup中的数据{{ b }}</h2>
    <h2>测试:setup中无法读到data中的数据</h2>
  </div>
</template>

<script lang="ts">
export default {
  name: 'Person',
  data() {
    return {
      // 测试
      a: 100,
      b: this.name,
      d: 900
    }
  },
  setup() {
    let name = '张三'
    let age = 18
    let tel = '1388888'

    // 测试
    // let s = d // Cannot find name 'd'. 

    return {name,age}
  }
}
</script>

setup语法糖

setup函数有一个语法糖,这个语法糖,可以让我们把setup独立出去,代码如下:

<template>
  <div class="person">
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="showTel">点击查看联系方式</button>
  </div>
</template>

<script lang="ts">
export default {
  name: 'Person'
}
</script>

<script lang="ts" setup>
  let name = '张三'
  let age = 18
  let tel = '1388888'

  // 方法
  function changeName(){ name = 'zhang-san'}
  function changeAge(){age += 1}
  function showTel(){alert(tel)}
</script>

扩展: 上述代码,还需要编写一个不写setupscript标签,(在开发者工具)去指定组件名字,比较麻烦,我们可以借助vite中的插件简化。

  1. 第一步:npm i vite-plugin-vue-setup-extend -D (D是开发依赖的意思)

  2. 第二步:vite.config.ts 中引入: import VueSetupExtend from ‘vite-plugin-vue-setup-extend’
    在这里插入图片描述

  3. 第三步:<script setup lang="ts" name="Person">

这样就只用一个script标签了:

<template>
  <div class="person">
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="showTel">点击查看联系方式</button>
  </div>
</template>

<script lang="ts" setup name="Person2222">
  let name = '张三'
  let age = 18
  let tel = '1388888'

  // 方法
  function changeName(){ name = 'zhang-san'}
  function changeAge(){age += 1}
  function showTel(){alert(tel)}
</script>

在这里插入图片描述

如果【不】添加这个插件,同时也只写一个script标签,开发者工具中的组件名字和文件名保持一致。

3.2 ref

上文案例中的数据方法:此时数据不是响应式的

在这里插入图片描述

vue2中,数据都存放在data里面,只要把数据放在data里面,数据就是响应式,会自动数据代理、数据劫持。

在vue3中,有两个东西定义响应式数据 :① ref ②reactive

ref 创建:基本类型的响应式数据

作用: 定义响应式变量。

语法: 先引入 import {ref} from 'vue'

​ 哪个数据是响应式就用ref包起来 let xxx = ref(初始值)

ref() 返回值: 一个RefImpl的实例对象,简称ref对象refref对象的value属性是响应式的

注意点:

  • JS中操作数据需要:xxx.value,但模板中不需要.value,直接使用即可。
  • 对于let name = ref('张三')来说,name不是响应式的,name.value是响应式的。
<script lang="ts" setup name="Person">
  // 引入ref
  import {ref} from 'vue'

  // 哪个数据是响应式就用ref包起来
  let name = ref('张三')
  let age = ref(18)
  let tel = '1388888'
  let address = '北京昌平区'

  console.log('name',name);
  console.log('age',age);
  console.log('tel',tel);
  console.log('address',address);
</script>

在这里插入图片描述

name、age是 RefImpl 的实例对象

目前先知道 下划线的属性都不是给我们用的,value才是给我们用的。而且模板(HTML)内自动帮我们解析了value,所以模板中使用的时候不需要 {{name.value}}使用,直接{{name}}就可以。但是在JS代码中需要写 name.value

ref 创建:对象类型的响应式数据

  • 其实ref接收的数据可以是:基本类型对象类型
  • ref接收的是对象类型,内部其实也是调用了reactive函数。
<template>
  <div class="car">
    <h2>一辆{{ car.brand }}车,价值{{ car.price }}万</h2>
    <button @click="changePrice">修改汽车价格</button>
  </div>
  <hr>
  <h2>游戏列表</h2>
  <ul>
    <li v-for="item in games" :key="item.id">{{item.name}}</li>
  </ul>
  <button @click="changeFirstGame">修改游戏</button>
</template>

<script lang="ts" setup name="Person">
  import {ref} from 'vue'
  let car = ref({brand: '奔驰', price: 100})
  console.log(car); // 这个对象
  
  let games = ref([
    {id:'001', name:'王者荣耀'},
    {id:'002', name:'原神'},
    {id:'003', name:'三国志'}
  ])

  function changePrice(){
    car.value.price += 10
  }
  function changeFirstGame(){
    games.value[0].name = '星穹铁道'
  }
</script>

在这里插入图片描述

3.3 reactive

reactive 创建:对象类型的响应式数据

作用:定义一个响应式对象(基本类型不要用它,要用ref,否则报错)

语法: 引入reactive import {reactive} from 'vue'

​ 哪个对象想变成响应式就包裹住哪个let 响应式对象= reactive(源对象)

​ 源对象可以是 对象、数组、函数

返回值: 一个Proxy的实例对象,简称:响应式对象。

注意点:reactive定义的响应式数据是 深层次 的。

<template>
  <div class="car">
    <h2>一辆{{ car.brand }}车,价值{{ car.price }}万</h2>
    <button @click="changePrice">修改汽车价格</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {reactive} from 'vue'
  let car = reactive({brand: '奔驰', price: 100})
  console.log(car); // 这个对象
  
  function changePrice(){
    car.price += 10
  }
</script>

打印这个car:

这个Proxy是window上就有的一个函数 Proxy。数据藏在 target 里面

在这里插入图片描述

3.4 ref 对比 reactive

宏观角度看:

1、ref用来定义:基本类型数据对象类型数据

2、reactive用来定义:对象类型数据

区别

1、ref创建的变量必须使用.value(可以使用volar插件自动添加.value)。

在这里插入图片描述

2、reactive重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)。

在这里插入图片描述

注意:在ref中整体替换就是可以的。因为 ref 的响应式体现在 car.value,这个值变了 响应式可以检测到。

在这里插入图片描述

使用原则

1、若需要一个基本类型的响应式数据,必须使用ref

2、若需要一个响应式对象,层级不深,refreactive都可以。

3、若需要一个响应式对象,且层级较深,推荐使用reactive

3.5 toRefs 与 toRef

作用:将一个响应式对象中的每一个属性,转换为ref对象。

备注:toRefstoRef功能一致,但toRefs可以批量转换

语法如下:

// 数据
let person = reactive({name:'张三', age:18, gender:'男'})
	
// 通过toRefs将person对象中的n个属性批量取出,且依然保持响应式的能力
let {name,gender} =  toRefs(person)
	
// 通过toRef将person对象中的gender属性取出,且依然保持响应式的能力
let age = toRef(person,'age')
  • 示例:

在这里插入图片描述

加入toRefs

<script lang="ts" setup name="Person">
  import {reactive, toRefs} from 'vue'
  
  let person = reactive({
    name: '张三',
    age: 18
  })
  let {name, age} = toRefs(person)

  function changeName() {
    name.value += '~'
  }
  function changeAge() {
    age.value += 1
  }
</script>

在这里插入图片描述

3.6 computed

计算属性是有缓存的、方法没有

使用方式:import {computed} from 'vue'

​ computed是一个函数,实参是函数,因为vue3中没有this,所以用箭头函数

​ 必须要使用 set get才能对计算属性进行修改


<script lang="ts" setup name="Person">
  import {ref,computed} from 'vue'
  let firstName = ref('张')
  let lastName = ref('三')
  
  // 这么定义的fullName是一个计算属性,只读、不可以修改
  let fullName =  computed(()=>{
    return  firstName.value + '-' + lastName.value 
  })
  // 上述的fullName是 ComputedRefImpl{}


  // 这么定义的fullName是一个计算属性,可读可写
  let fullName2 = computed({
    get(){
      return firstName.value + '-' + lastName.value 
    },
    set(val){
      const [str1, str2] = val.split('-')
      firstName.value = str1
      lastName.value = str2
    }
  })

  function changeName(){
    fullName2.value = '李-四'
  }

</script>

3.7 watch

watch作用:监视数据的变化(和Vue2中的watch作用一致)

特点:Vue3中的watch只能监视以下四种数据

  1. ref定义的数据。(ref能定义基本类型和对象类型)
  2. reactive定义的数据。
  3. 一个函数,返回一个值(也就是一个getter函数)。
  4. 一个包含上述内容的数组。

我们在Vue3中使用watch的时候,通常会遇到以下几种情况:

情况一

情况一:监视ref定义的【基本类型】数据:直接写数据名即可,监视的是其value值的改变。

使用:

引入watch: import {ref, watch} from 'vue'。watch是函数,有很多参数,目前只看两个 watch(监视对象,回调)

回调函数的参数是(newValue, oldValue)因为setup中没有this(undefined),所以可以直接写成箭头函数。

  • 示例
<template>
  <div class="person">
    <h1>情况一:监视【ref】定义的【基本类型】数据</h1>
    <h2>当前求和为:{{sum}}</h2>
    <button @click="changeSum">点我sum+1</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  // 引入watch
  import {ref,watch} from 'vue'
  // 数据
  let sum = ref(0)
  // 方法
  function changeSum(){
    sum.value += 1
  }
  // 监视,情况一:监视【ref】定义的【基本类型】数据
  const stopWatch = watch(sum,(newValue,oldValue)=>{
    console.log('sum变化了',newValue,oldValue)
    if(newValue >= 10){
      stopWatch()
    }
  })
</script>
  • 注意事项

① watch 里面的 sum(监视的ref的数据,不写value

② 解除监视 :watch的返回值是一个函数,调用这个函数就可以结束监视

情况二

监视ref定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。

注意:

  • 若修改的是ref定义的对象中的属性,newValueoldValue 都是新值,因为它们是同一个对象。

  • 若修改整个ref定义的对象,newValue 是新值, oldValue 是旧值,因为不是同一个对象了。

watch的第一个参数是:被监视的数据
watch的第二个参数是:监视的回调(新值,旧值)
watch的第三个参数是:配置对象(deep、immediate等等.....) 
<template>
  <div class="person">
    <h1>情况二:监视【ref】定义的【对象类型】数据</h1>
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changePerson">修改整个人</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref,watch} from 'vue'
  // 数据
  let person = ref({
    name:'张三',
    age:18
  })
  // 方法
  function changeName(){
    person.value.name += '~'
  }
  function changeAge(){
    person.value.age += 1
  }
  function changePerson(){
    person.value = {name:'李四',age:90}
  }
  /* 
    监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视deep
    watch的第一个参数是:被监视的数据
    watch的第二个参数是:监视的回调
    watch的第三个参数是:配置对象(deep、immediate等等.....) 
  */
  watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
  },{deep:true})
  
</script>
  • 注意事项 有些时候 newValue oldValue是一样的
    在这里插入图片描述

若修改的是ref定义的对象中的属性,newValueoldValue 都是新值,因为它们是同一个对象。

情况三

监视reactive定义的【对象类型】数据,且 默认开启了深度监视。且不能关闭,即不能通过deep:false关闭深度监视(隐式创建深层监听)。

<template>
  <div class="person">
    <h1>情况三:监视【reactive】定义的【对象类型】数据</h1>
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changePerson">修改整个人</button>
    <hr>
    <h2>测试:{{obj.a.b.c}}</h2>
    <button @click="test">修改obj.a.b.c</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {reactive,watch} from 'vue'
  // 数据
  let person = reactive({
    name:'张三',
    age:18
  })
  let obj = reactive({
    a:{
      b:{
        c:666
      }
    }
  })
  // 方法
  function changeName(){
    person.name += '~'
  }
  function changeAge(){
    person.age += 1
  }
  function changePerson(){
    Object.assign(person,{name:'李四',age:80})
  }
  function test(){
    obj.a.b.c = 888
  }

  // 监视,情况三:监视【reactive】定义的【对象类型】数据,且默认是开启深度监视的
  watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
  })
  watch(obj,(newValue,oldValue)=>{
    console.log('Obj变化了',newValue,oldValue)
  })
</script>

情况四

监视refreactive定义的【对象类型】数据中的某个属性,注意点如下:

  1. 若该属性值不是【对象类型】,需要写成函数形式。
  2. 若该属性值是依然是【对象类型】,可直接写,也可写成函数,建议写成函数。

结论:监视的要是对象里的属性,那么最好写函数式(因为setup中没有this,所以直接写箭头函数),注意点:如果要同时① 监视对象属性的地址值,②需要关注对象内部,需要手动开启深度监视。

<template>
  <div class="person">
    <h1>情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</h1>
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changeC1">修改第一台车</button>
    <button @click="changeC2">修改第二台车</button>
    <button @click="changeCar">修改整个车</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {reactive,watch} from 'vue'

  let person = reactive({
    name: '张三',
    age: 18,
    car: {
      c1: '奔驰',
      c2: '宝马'
    }
  })

  // 方法
  function changeName(){
    person.name += '~'
  }
  function changeAge(){
    person.age += 1
  }
  function changeC1(){
    person.car.c1 = '奥迪'
  }
  function changeC2(){
    person.car.c2 = '大众'
  }
  function changeCar(){
      // 这里可以直接赋值更换,因为这是person内部的一个属性
    person.car = {c1:'雅迪',c2:'爱玛'}
  }

  // 监视person对象里面的 name属性(不是对象类型)
  watch(()=>person.name,(newValue,oldValue)=>{
    console.log('person.name',newValue,oldValue);
    
  })

接上文代码,如果想监视 person.car(这个属性是一个对象属性),watch的第一个参数可以直接写 person.car ,但是 当执行函数changeCar的时候,无法监视。因为这段代码的意思是监视这个 person.car,当changeCar函数把整个car都换了的时候,就无法监视到原本的car。

watch(person.car,(newValue,oldValue)=>{
   console.log('person.car',newValue,oldValue);
})

因此如果需要监视原本 person.car 的地址值的时候,就需要用函数包裹起来(如下文代码)。但此时 修改第一辆第二辆车的时候又没法检测到了,因为地址值没有变化。

watch(()=>person.car,(newValue,oldValue)=>{
  console.log('person.car',newValue,oldValue);  
})

怎么同时检测呢?加一个deep:true

watch(()=>person.car,(newValue,oldValue)=>{
  console.log('person.car',newValue,oldValue);  
},{deep:true})

情况五

监视上述的多个数据
在这里插入图片描述

<template>
  <div class="person">
    <h1>情况五:监视上述的多个数据</h1>
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changeC1">修改第一台车</button>
    <button @click="changeC2">修改第二台车</button>
    <button @click="changeCar">修改整个车</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {reactive,watch} from 'vue'

  // 数据
  let person = reactive({
    name:'张三',
    age:18,
    car:{
      c1:'奔驰',
      c2:'宝马'
    }
  })
  // 方法
  function changeName(){
    person.name += '~'
  }
  function changeAge(){
    person.age += 1
  }
  function changeC1(){
    person.car.c1 = '奥迪'
  }
  function changeC2(){
    person.car.c2 = '大众'
  }
  function changeCar(){
    person.car = {c1:'雅迪',c2:'爱玛'}
  }

  // 监视,情况五:监视上述的多个数据
  watch([()=>person.name,person.car],(newValue,oldValue)=>{
    console.log('person.car变化了',newValue,oldValue)
  },{deep:true})

</script>

3.8 watchEffect

  • 官网:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。

  • watch对比watchEffect

    1. 都能监听响应式数据的变化,不同的是监听数据变化的方式不同

    2. watch:要明确指出监视的数据

    3. watchEffect:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。

  • 示例

    需求:当水温达到60度,或水位达到80cm时,给服务器发请求。

    为什么要引入watcheffect?因为如果同时检测的数据多了,每一个都要写进数据里很不方便。

在这里插入图片描述

<template>
  <div class="person">
    <h2>需求:当水温达到60度,或水位达到80cm时,给服务器发请求</h2>
    <h2>当前水温:{{temp}}℃</h2>
    <h2>当前水位:{{height}}cm</h2>
    <button @click="changeTemp">水温+10</button>
    <button @click="changeHeight">水位+10</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref,watch,watchEffect} from 'vue'

  // 数据
  let temp = ref(10)
  let height = ref(0)

  // 方法
  function changeTemp(){
    temp.value += 10
  }
  function changeHeight(){
    height.value += 10
  }

// 监视 -- watch实现
/* watch([temp,height],(value)=>{
    // 从value中获取最新的水温(newTemp)、最新的水位(newHeight)
    // 数组解构赋值
    let [newTemp,newHeight] = value 
    // 逻辑
    if(newTemp >= 60 || newHeight >= 80){
      console.log('给服务器发请求')
    }
  }) */

  // 监视 -- watchEffect实现
  watchEffect(()=>{
    if(temp.value >= 60 || height.value >= 80){
      console.log('给服务器发请求')
    }
  })
</script>

3.9 标签中的ref属性

为什么要引入ref属性?在vue文件的 template(HTML)部分,可能会用 id 表示一个标签的唯一性。但是打包合并文件的时候,不同vue文件的HTML会合并到一起,如果多个组件用了同一个id名字(例如<div id="title">哈哈</div>)会有冲突。所以要用ref属性。

用法:

① 引入import {ref} from 'vue'

② 通过 ref 创建一个东西, 用于存储其标记的内容 let title = ref()

③ HTML中 <h2 ref="title">北京</h2>

作用:

用于注册模板引用。

① 用在普通DOM标签上,获取的是DOM节点。

② 用在组件标签上,获取的是组件实例对象。

示例:

  • 用在普通DOM标签上,获取的是DOM节点
<template>
  <div class="person">
    <h1>中国</h1>
    <h2 ref="title2">北京</h2>
    <h3>尚硅谷</h3>
    <button @click="showLog">点我输出h2这个元素</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref} from 'vue'

  // 创建一个title2,用于存储ref标记的内容
  let title2 = ref()
  console.log(title2);

  function showLog(){
    console.log(title2.value) // <h2 data-v-4cadc14e>北京</h2>
    console.log(title2.value.innerHTML) // 北京
  }
</script>
控制台输出:<h2 data-v-4cadc14e>北京</h2>,输出的是DOM节点
注意这里的 data-v-4cadc14e 是因为 style 上加了 scoped 局部样式
  • ref 用在组件标签上,获取的是组件实例对象。

注意 ref 如果用在组件标签上,肯定是父标签在其文件中使用,因为父标签中才会使用子组件。因此 父组件获取了子组件的实例对象。但是不能轻易获得子组件里面的数据,必须是子组件通过defineExpose暴露的数据才可以访问

补充:vue3 中父组件中,只需① 引入组件(import)② 使用,不需要注册。因为是在setup中写的代码。

<!-- 父组件App.vue -->
<template>
  <Person ref="ren"/>
  <button @click="test">测试</button>
</template>

<script lang="ts" setup name="App">
  import Person from './components/Person.vue'
  import {ref} from 'vue'

  let ren = ref()

  function test(){
    console.log(ren.value.name)
    console.log(ren.value.age)
  }
</script>


<!-- 子组件Person.vue中要使用defineExpose暴露内容 -->
<script lang="ts" setup name="Person">
  import {ref,defineExpose} from 'vue'
	// 数据
  let name = ref('张三')
  let age = ref(18)
  /****************************/
  /****************************/
  // 使用defineExpose将组件中的数据交给外部
  defineExpose({name,age})
</script>

回顾TS

引入的时候,如果要引入这个: /types/index.js ,只用写到 /types,里面的 index.js会自动补上

vue文件路径 @

@ 表示在 src 文件夹

  • 示例:接口、泛型、自定义类型

/src/types/index.js

// 定义一个接口,用于限制person对象的具体属性
export interface PersonInter {
  id: string,
  name: string,
  age: number
}

// 一个自定义类型
// export type Persons = Array<PersonInter> 
export type Persons = PersonInter[]

/src/components/Person.vue

//因为这里不是具体的值 所以要加一个type
import {type PersonInter,type Persons} from '@/types'

// 定义一个person对象,要符合PersonInter接口规范
let person:PersonInter = {id:'1111', name:'张三',age:60}
console.log(person);

// 定义一个数组,它里面的每一项都符合PersonInter规范
let personList:Array<PersonInter> = [
  {id:'1111', name:'张三',age:25},
  {id:'2222', name:'李四',age:20},
  {id:'3333', name:'王五',age:30}
]

let personList2:Persons = [
  {id:'1111', name:'张三',age:25},
  {id:'2222', name:'李四',age:20},
  {id:'3333', name:'王五',age:30}
]

3.10 props使用

父组件要把数据给子组件

reactive和泛型一起用

  import {reactive} from 'vue'
  import {type Persons} from '@/types'

  let personList = reactive<Persons>([
    {id:'1111', name:'张三',age:25},
    {id:'2222', name:'李四',age:20},
    {id:'3333', name:'王五',age:30}
  ])

示例

父组件要把数据给子组件

  • 接口
// 定义一个接口,限制每个Person对象的格式
export interface PersonInter {
id:string,
name:string,
 age:number
}

// 定义一个自定义类型Persons
export type Persons = Array<PersonInter>

App.vue (父组件)中代码:

<template>
	<Person :list="persons"/>
</template>

<script lang="ts" setup name="App">
import Person from './components/Person.vue'
import {reactive} from 'vue'
 import {type Persons} from './types'

 let persons = reactive<Persons>([
  {id:'e98219e12',name:'张三',age:18},
   {id:'e98219e13',name:'李四',age:19},
    {id:'e98219e14',name:'王五',age:20}
  ])
</script>

Person.vue中代码:

<template>
<div class="person">
<ul>
  <li v-for="item in list" :key="item.id">
     {{item.name}}--{{item.age}}
   </li>
 </ul>
</div>
</template>

<script lang="ts" setup name="Person">
import {defineProps} from 'vue'
import {type PersonInter} from '@/types'

// 第一种写法:接收。defineProps返回值是一个对象
// const props = defineProps(['list'])

// 第二种写法:接收+限制类型
// defineProps<{list:Persons}>()
// 写法是,defineProps可以传入一个泛型,泛型里面是规定传入数据的类型的,因为父组件不一定只传递一个数据,所以用对象传递

// 第三种写法:接收+限制类型+指定默认值+限制必要性
let props = withDefaults(defineProps<{list?:Persons}>(),{
  list:()=>[{id:'asdasg01',name:'小猪佩奇',age:18}]
})
console.log(props)
</script>
  • props 用于父组件给子组件传递数据

父组件:

<Son str="helloworld" :list="list"></Son>
这里传递的两个数据是不一样的  str实为一个字符串,list通过:也就是v-bind绑定了数据

子组件中接收数据:

通过defineProps,在模板中可以直接使用

但是在script中不能使用,如果想用的话, 就需要定义变量接收。defineProps返回值是一个对象,对象的每一个元素都是父元素传递来的数据

在这里插入图片描述

<script>
  import {defineProps} from 'vue' // 引入——其实可以不引入

  defineProps(['list'])
  // 通过defineProps接收数据
    
  const father_data = defineProps(['list'])

<template>
     {{list}}
</template>

因为子组件在接收父组件的数据时,想做一些限制,比如限制类型,指定默认值,限制数据传递必要性,所以引入了第二种第三种写法

  • 第二种写法补充说明
    defineProps<{list:Persons}>()。写法是,defineProps可以传入一个泛型,泛型里面是规定传入数据的类型的,因为父组件不一定只传递一个数据,所以用对象限制类型

在这里插入图片描述

  • 子组件中如果想表明 父组件的数据可传可不传,就要加一个问号
defineProps<{list ? :Persons}>()
  • 第三种写法补充说明
// 第三种写法:接收+限制类型+指定默认值+限制必要性
let props = withDefaults(defineProps<{list?:Persons}>(),{
  list:()=>[{id:'asdasg01',name:'小猪佩奇',age:18}]
})

withDefaults配置默认值,withDefaults 是一个函数。

第一个参数defineProps获取父组件传递的数据(这里用问号设置为可传可不传递,用泛型限制了传递过来的数据的类型);第二个参数是对象,设置每个属性的默认值,要用函数返回(function xxx(){return yyy}简写为()=> yyy

  • defineProps 和 withDefaults都可以不用引入。

definexxx就是宏函数,自动引入

3.11 生命周期

  • 概念:Vue组件实例在创建时要经历一系列的初始化步骤,在此过程中Vue会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子

  • 规律:

    生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有两个钩子,一前一后。

  • Vue2的生命周期

    创建阶段:beforeCreatecreated (创建前、创建完毕)

    挂载阶段:beforeMountmounted (挂载前、挂载完毕)

    更新阶段:beforeUpdateupdated (更新前、更新完毕)

    销毁阶段:beforeDestroydestroyed (销毁前、销毁完毕)

  • Vue3的生命周期

    创建阶段:setup

    挂载阶段:onBeforeMountonMounted

    更新阶段:onBeforeUpdateonUpdated

    卸载阶段:onBeforeUnmountonUnmounted

  • 常用的钩子:onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)

<template>
  <div class="person">
    <h2>当前求和为:{{ sum }}</h2>
    <button @click="changeSum">点我sum+1</button>
  </div>
</template>

<!-- vue3写法 -->
<script lang="ts" setup name="Person">
  import { 
    ref, 
    onBeforeMount, 
    onMounted, 
    onBeforeUpdate, 
    onUpdated, 
    onBeforeUnmount, 
    onUnmounted 
  } from 'vue'

  // 数据
  let sum = ref(0)
  // 方法
  function changeSum() {
    sum.value += 1
  }
  console.log('setup')
  // 生命周期钩子
  // vue3在 挂载前,调用 onBeforeMount【里面的函数】
  onBeforeMount(()=>{
    console.log('挂载之前')
  })
  onMounted(()=>{
    console.log('挂载完毕')
  })
  onBeforeUpdate(()=>{
    console.log('更新之前')
  })
  onUpdated(()=>{
    console.log('更新完毕')
  })
  onBeforeUnmount(()=>{
    console.log('卸载之前')
  })
  onUnmounted(()=>{
    console.log('卸载完毕')
  })
</script>

子组件先挂载=>父组件再挂载, 从入口文件开始分析。App组件是最后挂载的

3.12 自定义hooks

什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装,类似于vue2.x中的mixin

自定义hook的优势:复用代码, 让setup中的逻辑更清楚易懂。

**使用:**src文件夹下新建一个hooks文件夹,里面根据功能创建文件 useDog.ts useSum.ts。ts文件向外暴露函数(hook本质是函数),而且函数内要返回值。

示例:

  • useDog.ts
import axios from 'axios'
import {reactive} from 'vue'


export default function (){
  // 数据
  let dogList = reactive([
    'https://images.dog.ceo/breeds/pembroke/n02113023_7292.jpg'
  ])
  
  // 方法
  async function getDog() {
    try {
      let res = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
      // console.log(res.data.message);
      dogList.push(res.data.message)
    } catch(err) {
      console.log(err);
    }
  }

  //给外部提供东西
  return {dogList,getDog}
}
  • useSum.ts
import {ref} from 'vue'

export default function(){
  let sum = ref(0)

  function add() {
    sum.value += 1
  } 
  //给外部提供东西
  return {sum,add}
} 
  • 组件中使用
<template>
  <div class="person">
    <h2>当前求和为:{{ sum }}</h2>
    <button @click="add">点我加一</button>

    <hr>
    <img v-for="(dog, index) in dogList" :src="dog" :key="index">
    <button @click="getDog()">再来一只狗</button>
  </div>
</template>
  
<script lang="ts" setup name="Person">
  import useSum from '@/hooks/useSum'
  import useDog from '@/hooks/useDog'

  const {sum,add} = useSum()
  const{dogList,getDog} = useDog()
  
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/533470.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mac电脑安装软件报错:无法检查更新,请检查你的互联网连接

1、点菜单栏搜索图标&#xff0c;输入&#xff1a;终端 &#xff0c;找到后&#xff0c;点击打开 2、输入以下命令&#xff1a;&#xff08;复制粘贴进去&#xff09;回车安装 /usr/sbin/softwareupdate --install-rosetta --agree-to-license 3、提示【Install of Rosetta …

vue模版字符串解析成vue模版对象

模版字符串 this.code <template><div style"width:100% ; height: 100% ;">{{resultData[0].name}}</div> </template> <script> export default {data() {return {resultData: [{ name: 图幅, value: 20 },]}},mounted(){},method…

STM32-模数转化器

ADC(Analog-to-Digital Converter) 指模数转换器。是指将连续变化的模拟信号转换 为离散的数字信号的器件。 ADC相关参数说明&#xff1a; 分辨率&#xff1a; 分辨率以二进制&#xff08;或十进制&#xff09;数的位数来表示&#xff0c;一般有 8 位、10 位、12 位、16 位…

文件输入/输出流(I/O)

文章目录 前言一、文件输入\输出流是什么&#xff1f;二、使用方法 1.FileInputStream与FileOutputStream类2.FileReader与FileWriter类总结 前言 对于文章I/O(输入/输出流的概述)&#xff0c;有了下文。这篇文章将具体详细展述如何向磁盘文件中输入数据&#xff0c;或者读取磁…

【Android】apk安装报错:包含病毒: a.gray.BulimiaTGen.f

​ 有时候apk安装或者更新时&#xff0c;显示&#xff1a;[高风险]包含病毒: a.gray.BulimiaTGen.f这种bug&#xff1b; 原因&#xff1a;这是手机管家误报病毒。 处理方法&#xff1a;我看网上其他资料可以进行申诉&#xff0c;也可以进行apk加固&#xff0c;我这边尝试用360…

微信小程序制作圆形进度条

微信小程序制作圆形进度条 1. 建立文件夹 选择一个目录建立一个文件夹&#xff0c;比如 mycircle 吧&#xff0c;另外把对应 page 的相关文件都建立出来&#xff0c;包括 js&#xff0c;json&#xff0c;wxml 和 wxcc。 2. 开启元件属性 在 mycircle.json中开启 component 属…

数据结构与算法——字符串暴力匹配

一、字符串的组成 1.数据域&#xff0c;字符串的内容 2.字符串的长度 二、模式匹配-暴力匹配原理 1.两个字符串一个主串一个模式串用两个指针对其进行匹配 2、两个指针所对应的值相同时继续匹配下一个 3、当出现不匹配的情况时&#xff0c;回溯主串的指针到刚开始起点的下一个位…

大气负氧离子自动监测系统

TH-FZ4随着旅游旺季的到来&#xff0c;越来越多的人选择走出家门&#xff0c;感受大自然的魅力。然而&#xff0c;在享受美丽风景的同时&#xff0c;我们是否也关注到了身边空气质量的变化&#xff1f;今天&#xff0c;就让我们一起了解一种神奇的监测系统——大气负氧离子自动…

【STL】list的模拟实现

目录 前言 list概述 list的节点 list的迭代器 list的结构 构造与析构 拷贝构造与赋值 list的元素操作 insert() push_back() push_front() erase() pop_back() pop_front() clear() swap() size() 完整代码链接 前言 如果你对链表还不熟悉或者忘了的话…

手机银行客户端框架之mPaaS介绍

移动开发平台&#xff08;Mobile PaaS&#xff0c;简称 mPaaS&#xff09;是源于支付宝 App 的移动开发平台&#xff0c;为移动开发、测试、运营及运维提供云到端的一站式解决方案&#xff0c;能有效降低技术门槛、减少研发成本、提升开发效率&#xff0c;协助企业快速搭建稳定…

Harmony鸿蒙南向驱动开发-SPI

SPI即串行外设接口&#xff08;Serial Peripheral Interface&#xff09;&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线。SPI是由Motorola公司开发&#xff0c;用于在主设备和从设备之间进行通信。 运作机制 在HDF框架中&#xff0c;SPI的接口适配模…

#5松桑前端后花园周刊-JavaScript引擎和JavaScript运行时之间的区别

行业动态 TC39 Signals 提案 一个早期提案&#xff1a;给 ECMAScript/JavaScript 带来一个新特性 signals&#xff0c;该提案从一系列流行的框架中引入了一些想法。提案解释 signals 是一种数据类型&#xff0c;它通过模拟状态单元和从其他状态/计算中派生的计算来实现单向数…

免费ssl证书能一直续签吗?如何获取SSL免费证书?

免费SSL证书是否可以一直续签。我们需要了解SSL证书的基本工作原理。当你访问一个使用HTTPS协议的网站时&#xff0c;该网站实际上在使用一个SSL证书。这个证书相当于一个数字身份证明&#xff0c;它验证了网站的真实性和安全性。而这个证明是由受信任的第三方机构——通常是证…

jvm中jdk常用的几个命令总结

1.jmap 此命令可以用来查询内存信息&#xff0c;实例个数及占用内存大小 1.1 查看堆内存概要信息&#xff08;内存分配统计&#xff09; jmap -histo[:live] <pid> .-histo&#xff1a;显示堆中对象的统计信息&#xff0c;包括每个类的实例数量、占用内存大小等 :live…

Vue+el-table 修改表格 单元格横线边框颜色及表格空数据时边框颜色

需求 目前 找到对应的css样式进行修改 修改后 css样式 >>>.el-table th.el-table__cell.is-leaf {border-bottom: 1px solid #444B5F !important;}>>>.el-table td.el-table__cell,.el-table th.el-table__cell.is-leaf {border-bottom: 1px solid #444B5F …

【开源社区】openEuler、openGauss、openHiTLS、MindSpore

【开源社区】openEuler、openGauss、openHiTLS、MindSpore 写在最前面开源社区参与和贡献的一般方式开源技术的需求和贡献方向 openEuler 社区&#xff1a;开源系统官方网站官方介绍贡献攻略开源技术需求 openGauss 社区&#xff1a;开源数据库官方网站官方介绍贡献攻略开源技术…

机器学习和深度学习--李宏毅 (笔记与个人理解)Day7

Day7 Regression Case study &#xff08;预测宝可梦的cp&#xff09; Regression 可以做什么&#xff1f; 股票预测 自动驾驶 推荐 预测宝可梦的cp&#xff08;能力类似这样的属性把&#xff09; 这里突然想到&#xff0c;是不是可以用洛克王国和赛尔号做事情哈哈 注意&#…

解决苹果iMac的M1芯片Node Sass does not yet support your current environment的问题

问题背景 如图所示&#xff0c;这是我的电脑&#xff0c;M1芯片 启动前端项目老是报错&#xff0c;说node Sass不支持我当前的环境&#xff0c;同事的macBook是intel芯片的&#xff0c;就能跑起项目来 很烦 但是不慌&#xff01;&#xff01;&#xff01; 咱有解决方法啦&a…

【C 数据结构】线性表

文章目录 【 1. 线性表 】【 2. 顺序存储结构、链式存储结构 】【 3. 前驱、后继 】 【 1. 线性表 】 线性表&#xff0c;全名为线性存储结构&#xff0c;线性表结构存储的数据往往是可以依次排列的&#xff08;不考虑数值大小顺序&#xff09;。 例如&#xff0c;存储类似 {1…

Visual Studio C++ 正确创建项目与更改文件名

1、创建项目 1&#xff09;打开Visual Studio&#xff0c;选择创建新项目。 2&#xff09;创建空项目 3&#xff09;配置新项目&#xff0c;注意不要勾选 " 将解决方案和项目放在同一目录中 " 。并将位置的文件夹设为与解决方案同名&#xff0c;方便管理。项目名称则…