VUE3学习

发布于:2024-05-10 ⋅ 阅读:(25) ⋅ 点赞:(0)

VUE学习

文章目录

前置知识

ES6模块化

node.js 中如何实现模块化

node.js 遵循 CommonJS 的模块化规范

  • 导入其他模块使用 require()方法
  • 模块对外共享成员使用 module.exports对象
前端模块化规范

ES6 模块化之前,JavaScript 社区提出 AMD、CMD、CommonJS 等模块化规范,这些社区提出的模块化标准,存在一定的差异性与局限性,并不是浏览器与服务器通用的模块化标准

ES6模块化是官方的通用规范

ES6模块化规范

ES6模块化规范是浏览器与服务器端通用的模块开发规范

  • 每个js文件都是一个独立的模块
  • 导入其他模块成员使用import关键字
  • 向外共享模块成员使用export关键字
在node.js中体验ES6模块化

node.js中默认仅支持CommonJS模块化规范,若想基于 node.js 体验与学习ES6的模块化语法,按照如下两个步骤进行配置

  • node -v确保安装 v14.15.1或更高版本的node.js
  • package.json 的根节点中添加"type":"module"节点

npm init -y快速初始化package.json包管理文件

image-20240501105215776

ES6模块化的基本语法
  • 默认导出与默认导入

    • 默认导出语法:export default 默认导出的成员
    • 每个模块中,只允许使用唯一的一次默认导出
    • image-20240501105921084
    • 默认导入语法:import 接收名称 from '模块标识符'
    • 默认导入时的接收名称可以是任意名称,只要是合法的成员名称即可
    • image-20240501110218227
  • 按需导出与按需导入

    • 按需导出语法:export 按需导出的成员
    • image-20240501111340089
    • 按需导入语法:import {s1,s2,say} from '模块标识符'
    • image-20240501111407575
    • 每个模块中可以使用多次按需导出
    • 按需导入的成员名称必须和按需导出的名称保持一致
    • 按需导入时,可以使用 as关键字进行重命名
    • 按需导入可以和默认导入一起使用
  • 直接导入并执行模块中代码

    如果只想单纯地执行某个模块中的代码,并不需要模块中向外共享的成员,此时,可以直接导入并执行模块代码

    • import '模块标识符'直接导入并执行模块中的代码

Promise

Promise 的基本概念
  • Promise 是一个构造函数
    • 可以创建 Promise 的实例const p = new Promise()
    • new 出来的 Promise 实例对象,代表一个异步操作
  • Promise.prototype上包含一个.then()方法
    • 每一次 new Promise()构造函数得到的实例对象
    • 都可以通过原型链的方式访问到.then()方法
  • .then()方法用来预先指定成功和失败的回调函数
    • p.then(成功的回调函数,失败的回调函数)
    • p.then(result->{},error=>{})
    • 调用.then()方法时,成功的回调函数时必选的、失败的回调函数是可选的
    • 如果上一个.then()方法返回一个新的 Promise 实例对象,则可以通过下一个.then()继续进行处理。通过.then()方法的链式调用,就能解决回调地狱的问题
  • .catch()捕获错误
    • 在Promise 的链式操作中如果发生了错误,可以使用 Promise.prototype.cath 方法进行捕获和处理
    • 如果不希望前面的错误导致后续的.then()无法正常执行,则可以将.catch()的调用提前
  • Promise.all()方法
    • Promise.all() 方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束后才会执行下一步的 .then()操作
  • Promise.race()方法
    • 发起并行的Promise异步操作,只要任何一个异步操作完成,就立即执行下一步的.then()操作
import thenfs from "then-fs";

const promiseArr = [
  thenfs.readFile("./files/1.txt", "utf8"),
  thenfs.readFile("./files/2.txt", "utf8"),
  thenfs.readFile("./files/3.txt", "utf8"),
]
// 全部异步完成
Promise.all(promiseArr).then(res=>{
  console.log(res)
})
// 任意一个异步完成
Promise.race(promiseArr).then(res=>console.log(res))
基于 then-fs 读取文件内容

由于 node.js 官方提供的 fs 模块仅支持以回调函数的方式读取文件,不支持 Promise 的调用方式。因此,需要先运行如下命令npm install then-fs,安装 then-fs 这个第三方包,从而支持基于 Promise 的方式读取文件的内容:

  • then-fs 的基本使用

调用 then-fs 提供的 readFile()方法,可以异步地读取文件的内容,它的返回值是 Promise 的实例对象。因此可以调用 .then()方法为每个 Promise 异步指定成功和失败之后的回调函数

import thenfs from "then-fs";

thenfs.readFile("./files/1.txt", "utf8").then((r1) => {
  console.log(r1);
  return thenfs.readFile("./files/2.txt", "utf8");
}).then((r2) => {
  console.log(r2);
  return thenfs.readFile("./files/3.txt", "utf8");
}).then((r3) => {
  console.log(r3);
}).catch((err)=>{
  console.log(err)
});;

image-20240501183905150

基于 Promise 封装读文件的方法

方法的封装要求:

  • 方法名要定义为getFile
  • 方法接收一个形参path,表示要读取的文件的路径
  • 方法的返回值为 Promise 实例对象
import fs from "fs";

export function getFile(path) {
  // 要创建具体的异步操作,需要在new Promise()构造函数期间,传递一个 function 函数,将具体异步操作定义到 function 函数内部
  return new Promise(function (resolve, reject) {
    fs.readFile(path, "utf8", (err, dataStr) => {
      if (err) return reject(err);//读取失败,调用失败的回调函数
      resolve(dataStr);//读取成功,调用成功的回调函数
    });
  });
}

getFile('./files/1.txt').then(res=>console.log(res)).catch(err=>console.log(err))
getFile('./files/11.txt').then(res=>console.log(res)).catch(err=>console.log(err))

async/await 简化 Promise调用

  • async标记函数时异步,不会阻塞其他的流程
  • await强制等待,必须和 async配合使用

async/await 是 ES8 引入的新语法,用来简化 Promise 异步操作。在 async/await 出现之前,开发者只能通过链式.then()方式处理Promise异步操作

  • async/await 的基本操作

     import thenFs from 'then-fs'
    
    // 按照顺序读取文件 1 2 3 的内容
    async function getAllFile(){
      const r1 = await thenFs.readFile('./files/1.txt','utf8')
      console.log(r1)
    
      const r2 = await thenFs.readFile('./files/2.txt','utf8')
      console.log(r2)
    
      const r3 = await thenFs.readFile('./files/3.txt','utf8')
      console.log(r3)
    }
    
    getAllFile()
    
  • 注意事项

    • 如果在 function 中使用了 await ,则 function 必须被 async 修饰

    • 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行

    • import thenFs from 'then-fs'
      
      // 按照顺序读取文件 1 2 3 的内容
      async function getAllFile(){
      
        console.log('B')
        // awit 之后的内容会异步执行,方法调用会在此处返回
        const r1 = await thenFs.readFile('./files/1.txt','utf8')
        const r3 = await thenFs.readFile('./files/3.txt','utf8')
        const r2 = await thenFs.readFile('./files/2.txt','utf8')
        console.log(r1,r2,r3)
      
        console.log('D')
      }
      console.log('A')
      getAllFile()
      console.log('C')
      

EventLoop

JavaScript 是单线程的语言

同一时间只能做一件事情

image-20240501192655843

单线程执行任务队列的问题:

如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题

同步任务和异步任务

为了防止某个耗时任务导致程序假死的问题,JavaScript把待执行的任务分为了两类:

  • 同步任务
    • 非耗时任务,指的是在主线程上排队执行的那些任务
    • 只有前一个任务执行完毕,才能执行后一个任务
  • 异步任务
    • 耗时任务,异步任务由JavaScript委托给宿主环境进行执行
    • 当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数
  • 同步任务和异步任务的执行过程
    • image-20240501193539862
    • 1、同步任务由 JavaScript 主线程依次执行
    • 2、异步任务委托给宿主环境执行
    • 3、已完成的异步任务对应的回调函数,会被加入到任务队列中等待执行
    • 4、JavaScript 主线程的执行栈被清空后,会读取任务队列中的回调函数,依次执行
    • 5、JavaScript 主线程不断重复第4步

宏任务和微任务

JavaScript 把异步任务又做了进一步的划分,异步任务又分为两类

image-20240501200610979

  • 宏任务

    • 异步 Ajax 请求
    • setTimeout、setInterval
    • 文件操作
    • 其他宏任务
  • 微任务

    • Promise.then、.catch、.finally
    • process.nextTick
    • 其他微任务
  • 宏任务和微任务的执行顺序

image-20240501200646375

API接口案例

基于MySQL数据库 + Express 对外提供用户列表API接口服务

  • 相关技术
    • 第三方包 express 和 mysql2
    • ES6 模块化
    • Promise
    • async/await
  • 主要步骤
  • npm install -g nodemon 启动服务需要
    • 搭建项目基本结构
      • npm init -y
      • 启用ES6模块化支持,在 package.json 中声明 “type”:“module”
      • 安装第三方依赖包 npm install express@4.17.1 mysql2@2.2.5
    • 创建基本的服务器
    • 创建 db 数据库模块
    • 创建 user_ctrl 业务模块
    • 创建 user_router 路由模块
    • 使用try...catch捕获异常

webpack

webpack基本概念
  • 一个第三方模块包,用于分析,并打包代码
    • 支持所有类型文件的打包
    • 支持 less/sass=>css
    • 支持ES6/7/8=>ES5
  • 识别代码,翻译,压缩,整合打包
  • 提高打开网站的速度
环境准备
  • npm install -g yarn安装yarn

  • 初始化包环境yarn init创建一个package.json

  • 安装依赖包yarn add webpack webpack-cli -D

  • package.json 中自定义打包命令 "scripts": {"build":"webpack"},

webpack使用步骤
  • 打包

webpack默认打包入口在src/index.js,执行自定义的 yarn build命令打包,打包时终端要位于src的父级文件夹,打包生成 dist文件夹

  • webpack入口出口修改
    • 新建 webpack.config.js
    • 填入配置
    • 修改入口文件名

配置文档:http://webpack.docschina.org/concepts/#entry

  • yarn build 执行流程

image-20240501215749358

  • 插件自动生成html文件

    http://webpack.docschina.org/plugins/html-webpack-plugin

    • npm install --save-dev html-webpack-plugin
    • webpack.config.js添加配置
  • 打包css文件

    css-loader ,让webpack能处理css类型文件:http://webpack.docschina.org/loaders/css-loader/

    style-loader,把css插入到DOM中 :https://webpack.docschina.org/loaders/style-loader/

    • 安装依赖
    • 添加配置
  • 处理 less 文件

    less-loader,webpack 将 Less 编译为 CSS 的 loader: https://webpack.docschina.org/loaders/less-loader/

  • 处理图片,字体图标

    webpack5,使用assert module技术实现字体文件和图片文件处理,无需配置额外loader

    https://webpack.docschina.org/guides/asset-modules/

    小于8kb的图片,直接转base64放在js文件中

    大于8kb,单独存放

  • babel降级JS语法,兼容低版本浏览器

    babel官网:http://www.babeljs.cn/

    babel-loader:https://webpack.docschina.org/loaders/babel-loader/

webpack开发服务器
  • 每次修改代码,重新打包,才能看到最新效果,实际工作中打包耗时

    webpack开发服务器,把代码运行在内存中,自动更新,实时返回给浏览器显示

    webpack-dev-server文档:https://webpack.docschina.org/configuration/dev-server/

    1、下载依赖yarn add webpack-dev-server -D

    2、自定义webpack开发服务器启动命令 serve - 在package.json

    "scripts":{"serve":"webpack serve"}

    3、当前项目启动webpack开发服务器yarn serve

Vue

  • Vue是一个JavaScript渐进式框架

image-20240502131007181

  • Vue学习方式
    • 传统开发模式:基于html文件开发Vue
    • 工程开发方式:在webpack环境中开发Vue

vue安装

  • 全局安装@vue/cli模块包yarn global add @vue/cli
  • @vue/cli是Vue官方提供的一个全局模块包,此包用于创建脚手架项目
  • vue -V查看是否安装成功
  • VSCode安装插件Vetur

Vue创建项目

  • vue create vuecli-demo项目名不能有大写字母、中文、特殊符号

    image-20240502133129694

image-20240502133836824

  • main.js项目打包主入口
  • App.vueVue页面主入口
  • index.html浏览器运行的文件
  • App.vue---->main,js---->index.html

vue自定义配置

项目中没有 webpack.config.js文件,因为Vue脚手架项目用的vue.config.js

  • 修改端口
module.exports = {
  devServer:{
    port:3000,//修改端口
    open:true//浏览器自动打开
  }
}

eslint

一种代码检查工具,违反规定就报错

module.exports = {
  devServer:{
    port:3000,//修改端口
    open:true//浏览器自动打开
  },
  lintOnSave:false//关闭eslint检查
}

单Vue文件开发

  • Vue推荐采用.vue文件来开发项目
  • template只能有一个根标签
  • js独立作用域互不影响
  • style配合scoped属性,保证样式只针对当前template内标签生效
  • Vetur插件使用<vue>快速创建Vue文件模板

Vue语法

插值表达式
  • 标签中,直接插入vue数据变量
  • {{表达式}}
<template>
  <div>
    <h1>{{ msg }}</h1>
    <h2>{{ obj.name }}</h2>
    <h3>{{ obj.age > 18 ? "成年":"未成年" }}</h3>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: "hello world",
      obj: {
        name: "张三",
        age: 5
      }
    }
  }
}
</script>

<style></style>
MVVM设计模式
  • Mode 模型

  • View 视图

  • VM 视图模型双向关联

  • 减少DOM操作,提高效率

image-20240502143007660

指令
给标签属性设置Vue变量的值
  • 语法
    • v-bind:属性名="vue变量"
    • 简写:属性名="vue变量"

图片需要导入(import),才能使用,直接引用路径不生效

给标签绑定事件
  • 语法
    • v-on:事件名="要执行的少量代码"
    • v-on:事件名="methods中的函数名"
    • v-on:事件名="methods中的函数名(实参)"
    • 简写@事件名="方法"

vue事件处理函数中,拿到事件对象

  • 语法:

    • 函数无传参,通过形参直接接收
    • 函数有传参,通过$event指代事件对象给事件处理函数
    <template>
      <div>
        <a @click="one" href="https://www.baidu.com/">不跳转百度</a><hr/>
        <a @click="two(10, $event)" href="https://www.baidu.com/">不跳转百度</a>
      </div>
    </template>
    
    <script>
    export default {
      methods: {
        //事件触发,不传值,可以直接获取事件对象
        one(e) {
          e.preventDefault()
        },
        //事件触发,传值,使用 $event 传递事件对象
        two(num, e) {
          e.preventDefault()
        }
      }
    }
    </script>
    
    <style></style>>
    

给事件带来更强大的功能

https://cn.vuejs.org/guide/essentials/event-handling.html#event-modifiers

  • @事件名.修饰符="函数"
  • 修饰符列表,修饰符可以链式使用
    • .stop阻止事件冒泡,父级的时间不受影响
    • .prevent阻止默认行为
    • .once程序运行期间,只触发一次事件处理函数

给键盘事件,添加修饰符,增强能力

https://cn.vuejs.org/guide/essentials/event-handling.html#key-modifiers

  • @keyup.enter监测回车按键
  • @keyup.esc监测取消按键
获取表单的值

value属性和Vue数据变量,双向绑定

  • v-model="Vue数据变量"
  • 数据双向绑定,变量变化–>视图自动同步,视图变化–>变量自动同步
  • 下拉框,v-model要绑定到select标签上
  • 复选框,v-model的变量值
    • 非数组,关联的是复选框的checked属性,是否被选中
    • 数组,关联的是复选框的value属性
  • v-model.修饰符="vue数据变量"
    • .number以parseFloat转成数字类型
    • .trim去除首尾空白字符
    • .lazy在change是触发而非input时,失去焦点并且内容改变时在同步数据
更新DOM对象的innerText/innerHTML
  • v-text="vue数据变量"普通字符串原样解析
  • v-html="vue数据变量"当成html解析
  • 并且会覆盖{{}}插值表达式
标签隐藏,出现
  • v-show="vue数据变量"用的是display:none隐藏,适用于频繁切换
  • v-if="vue数据变量"直接在DOM数上移除,可以配合v-else-if v-else一起用
v-for

列表渲染,所在标签结构,按照数据数量,循环生成

  • 语法

    • v-for="(值变量,索引变量) in 目标结构" :key=""
    • v-for="值变量名 in 目标结构"
    • v-for="(value,key) in 对象"
    • v-for="变量名 in 固定数字"
  • v-for放在要循环的标签上

  • 目标结构:数组、对象、数字

目标结构变化,触发v-for更新监测

image-20240503123858974

DOM
  • 真实DOM

image-20240503124022599

  • 虚拟DOM

虚拟DOM本质是一个JS对象,保存DOM关键信息

虚拟DOM提高更新的性能,不频繁操作真实DOM,在内存中找到变化的部分,在更新真实DOM

image-20240503124305376

image-20240503124527235

  • diff算法

image-20240503124857136

image-20240503124916837

  • v-for key对更新的影响

image-20240503125548801

image-20240503125803717

image-20240503130235480

动态class

v-bind给标签class设置动态的值

  • :class="{类名:布尔值}"布尔值为true时,类名才生效
动态style

给标签动态设置style的值

  • :style="{css属性名:值}"
计算属性computed

一个变量的值,依赖另外一些数据计算而来的结果

  • computed:{ "计算属性名"(){return "值"}}
  • 计算属性也是vue数据变量,所以不要和data里重名,用法和data相同
  • 计算属性的优势:计算属性对应函数执行后,会把return值缓存起来,依赖项不变,多次调用都是从缓存取值;依赖项变化,函数会自动重新执行并缓存新的值
<template>
  <div>
    <p>sum:{{sum}}</p>
  </div>
</template>

<script>
export default {
data(){
  return{
    a:5,
    b:10
  }
},
computed:{
    //计算属性也是vue数据变量,所以不要和data里重名,用法和data相同
  sum(){
    return this.a+this.b;
  }
}
}
</script>

<style>

</style>
  • 计算属性完整写法
<template>
  <div>
    <input type="text" v-model="val">
  </div>
</template>

<script>
export default {
  computed: {
    // 属性名
    val: {
      // 设置值,触发set
      set(parm) {
        console.log(parm)
      },
      // 使用值,触发get
      get() {
        return "30";
      }

    }
  }
}
</script>

<style></style>
侦听器watch

可以侦听data、computed属性值的改变

watch:{
	"被侦听的属性名"(newVal,oldVal){

	}
}
<template>
  <div>
    <input type="text" v-model="name">
  </div>
</template>

<script>
export default {
  data() {
    return {
      name: ""
    }
  },
  //侦听器
  watch: {
    // name 要侦听的属性
    name(newName, oldName) {
      console.log(oldName, newName)
    }
  }

}
</script>

<style></style>

image-20240503165111027

<template>
  <div>
    <input type="text" v-model="user.name">
    <input type="text" v-model="user.age">
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {
        name: "",
        age: 0
      }
    }
  },
  //侦听器
  watch: {
      // name 要侦听的属性
      user: {
        handler(newVal, oldVal) {
          console.log(oldVal, newVal)
        },
        deep: true,//深度侦听,侦听对象内部的变化
        immediate: true//在侦听开始之后立即调用
      }
  }
}
</script>

<style></style>

组件

  • 可复用的Vue实例,封装标签,样式和JS代码
  • 一个页面,可以拆分成一个个组件,一个组件就是一个整体,每个组件可以有自己独立的结构样式和行为
基础使用

每个组件都是一个独立的个体,代码里体现为一个独立的.vue文件

  • 创建组件,封装要复用的标签、样式、JS代码

  • 注册组件

    • 全局注册:main.js
    import { createApp } from 'vue'
    import App from './App.vue'
    
    const app = createApp(App);
    // 引入组件
    import demo1 from "./components/demo1.vue";
    // 全局注册(组件名 : 组件对象)
    app.component("pannel",demo1)
    app.mount('#app')
    
    
    • 局部注册
    <template>
      <div>
        <pannel2/>
      </div>
    </template>
    
    <script>
    // 引入组件
    import demo1 from "./components/demo1.vue";
    export default {
      // 局部注册
      components:{
        // 组件名 : 组件对象
        "pannel2":demo1
      }
    }
    </script>
    
    <style></style>
    
    • 注册后,和标签一样使用
  • Vue组件内样式,只针对当前组件内标签生效如何做

    • 在 style上添加 scoped <style scoped></style>
    • scoped 会给所有标签添加 data-v-hash 值属性,带属性选择
组件通信
  • 父传子props
    • 子组件,props定义变量
    • 父组件,对子组件变量当做属性赋值
    • 单向数据流
      • 从父到子的数据流向,叫做单向数据流
      • Vue规定props里的变量,本身是只读的
      • 子组件修改,不通知父级,会造成数据不一致
<template>
  <div>
    <!-- 子组件 props 变量接收 -->
    <!-- 父组件,传值进去-->
    <child title="资本论" auth="马克思" price="100"></child>
    <child title="论语" auth="孔子" price="100"></child>
  </div>
</template>

<script>
// 引入组件
import child from "./components/child.vue";
export default {
  // 局部注册
  components:{
    // 组件名,组件对象 同名可简写
    child
  }
}
</script>

<style scoped></style>
<template>
  <div>
    <p>{{ title }}</p>
    <p>{{ price }}</p>
    <p>{{ auth }}</p>
    <hr>
  </div>
</template>

<script>
export default {
  props: ['title', 'price', 'auth']
}
</script>

<style></style>
  • 子传父(自定义事件)
    • 父组件内,在子标签上绑定自定义事件和事件处理函数
      • @自定义事件名="父methods里的函数名"
    • 子组件中,触发事件,在对应事件处理函数中,触发父组件绑定的自定义函数
      • this.$emit('事件名',参数)
<template>
  <div>
    <p>{{ title }}</p>
    <p>{{ price }}</p>
    <p>{{ auth }}</p>
    <button @click="subFn">增加价格1元</button>
    <hr>
  </div>
</template>

<script>
export default {
  props: ['title', 'price', 'auth'],
  methods:{
    subFn(){
      //.$emit('事件名',参数)触发指定事件
      this.$emit('subPrice',this.title,1)
    }
  }
}
</script>

<style></style>
<template>
  <div>
    <!-- subPrice 父组件自定义事件,绑定到子标签上 -->
    <child v-for="(obj, index) in arr" :key="index" :auth="obj.auth" :title="obj.title" :price="obj.price"
      @subPrice="fn" />
  </div>
</template>

<script>
// 引入组件
import child from "./components/child.vue";
export default {
  // 局部注册
  components: {
    // 组件名,组件对象 同名可简写
    child
  },
  data() {
    return {
      arr: [
        { title: "资本论", auth: "马克思", price: 100 },
        { title: "论语", auth: "孔子", price: 100 }
      ]
    }
  },
  methods: {
    // 事件要触发的函数
    fn(title, price) {
      console.log(title,price)
      let index = this.arr.findIndex(x => x.title === title);
      this.arr[index].price += price
    }
  }
}
</script>

<style scoped></style>
  • 跨组件通信 EventBus
生命周期

vue对象从创建到销毁的过程

钩子函数

框架内置函数,随着组件的生命周期阶段,自动执行

阶段 阶段之前执行的方法 阶段之后执行的方法
初始化 beforeCreate created
挂载 beforeMount mounted
更新 beforeUpdate updated
销毁 beforeDestroy destroyed
  • 初始化
    • 执行beforeCreatecreated两个函数
    • created函数触发能获取data,不能获取真实DOM

image-20240503194622883

  • 挂载

image-20240503195626890

  • 更新

image-20240503195817314

  • 销毁
    • 移除一些占用的全局资源,如计时器、定时器、全局事件

image-20240503200215858

组件属性
$refs 获取组件对象
  • 通过id或者 ref 属性获取原生DOM标签
  • 给组件起别名 ref="refStr" ,通过this.$refs.refStr获取组件对象,可以调用组件内一切
<template>
  <div>
    <h1 ref="myH1" id="h">ref/id获取原生DOM</h1>
  </div>
</template>

<script>

export default {
  mounted(){
    console.log(document.getElementById("h"))
    console.log(this.$refs.myH1)
  }
}
</script>

<style>

</style>
$nextTick 等DOM异步更新后,触发函数

Vue更新DOM是异步的

image-20240504103035289

this.$nextTick(函数)等DOM异步更新后触发方法中的函数

image-20240504103209108

组件 name 属性

image-20240504113953966

动态组件
  • 多个组件使用同一个挂载点,并动态切换
  • vue内置component组件,配合is属性(组件名称)

image-20240504123130046

组件缓存
  • 频繁的切换会导致组件频繁创建和销毁,性能不高
  • vue 内置keep-alive组件包起来要频繁切换的组件

image-20240504123917591

  • 组件的激活和非激活,生命周期函数

    • activated激活时触发
    • deactivated失去激活状态触发

    image-20240504124153383

组件插槽

组件里面数据不确定,标签不确定,通过slot标签,让组件内接收不同的标签结构显示

  • 给组件插入什么标签,组件就显示什么标签

  • 使用

    • 组件内用<slot></slot>占位
    • 使用组件时,组件夹着的地方,传入标签替换slot
    • image-20240504124930659
  • 插槽默认内容

    • <slot>内放置内容,作为默认内容
    • 不给组件传标签,slot内容原地显示
    • 给组件内传标签,则slot整体被替换掉
    • image-20240504125524080
  • 作用域插槽

    • 用途:使用插槽时,想使用子组件内变量

    • 使用方法

      • 子组件,在slot上绑定属性:row="变量"

      • image-20240504125919575

      • <template>
          <div>
            <slot :row="defaultObj">{{ defaultObj.name }}</slot>
          </div>
        </template>
        
        <script>
        export default {
          // 作用域插槽
          // 1、 slot标签,自定义属性和内变量关联
          // 2、使用组件,template 配合 v-slot="变量名"
          // 这个变量名会收集slot身上属性和值形成对象
          data() {
            return {
              defaultObj: {
                name: "张飒",
                age: 20
              }
            }
          }
        }
        </script>
        
        <style></style>
        
      • 使用组件,传入自定义标签,用templatev-slot="自定义变量名"

      • image-20240504130024101

      • 这个变量名收集自动绑定slot上所有属性和值变量={row:defaultObj}

      • <template>
          <div>
            <c>
              <template v-slot="data">
                {{ data.row.age }}
              </template>
            </c>
          </div>
        </template>
        
        <script>
        import c from './components/c1.vue'
        export default {
          components: {
            c
          }
        }
        </script>
        
        <style></style>
        
    • 使用场景

      • 自定义组件内标签 + 内容
      • image-20240504132835252

自定义指令

  • 获取标签,扩展额外的功能
自定义指令注册

image-20240504133135918

image-20240504133444340

  • inserted方法,指令所在标签,被插入到网页上触发;入参是指令所在标签
自定义指令传值

image-20240504133844162

  • update方法,指令对应数据、标签更新时,此方法执行

image-20240504134046615

axios

  • ajax 一种前端异步请求后端的技术,利用浏览器windows接口的XMLHttpRequest

  • axios 是一个专门用于发送 ajax 请求的库,基于ajax+promise

  • http://www.axios-js.com

  • 特点

    • 支持客户端发送Ajax请求
    • 支持服务端Node.js发送请求
    • 支持Promise相关用法
    • 支持请求和响应的拦截器功能
    • 自动转换JSON数据
  • 安装依赖yarn add axios

image-20240503201112325

  • axios 全局配置
    • axios.default.baseURL="xxxxx"配置基地址

路由

  • 路径和组件的映射关系
  • 单页面应用:所有功能在一个HTML页面上实现
  • 前端路由作用:实现业务场景的切换
  • 优点:
    • 整体不刷新页面,用户体验好
    • 数据传递容易,开发效率高
  • 缺点:
    • 开发成本高
    • 首次加载会慢一点,不利于seo
组件分类
  • 一种是页面组件
    • src/views文件夹
    • 配合路由使用
  • 一个是复用组件
    • src/components文件夹
    • 展示数据,复用
vue-router
  • 一个第三方包
  • https://router.vuejs.org/zh/
使用
  1. 安装依赖yarn add vue-router
  2. main.js中引入 import {createRouter,createWebHistory} from 'vue-router';
  3. 创建路由规则数组,—路径和组件名对应关系
  4. 用规则生成路由对象
  5. .use() 注册路由对象
  6. 用 router-view 作为挂载点,切换不同的路由页面
import { createApp } from 'vue'
import App from './App.vue'

import Find from '@/views/Find.vue';
import My from '@/views/My.vue';
import Part from '@/views/Part.vue';

const app = createApp(App);

//1、引入
import {createRouter,createWebHistory} from 'vue-router';
//2、创建路由规则数组
const routes =[
  {
    path:"/find",
    component:Find
  },
  {
    path:"/my",
    component:My
  },
  {
    path:"/part",
    component:Part
  }
]
//3、生成路由对象
const router = createRouter({
  history:createWebHistory(),
  routes:routes
})
// 4、注册路由对象
app.use(router);


app.mount('#app')

<template>
  <div>
    <div>
      <a href="#/find">发现音乐</a>
      <a href="#/my">我的音乐</a>
      <a href="#/part">朋友</a>
    </div>
    <div>
      <!-- 5、作为挂载点 -->
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {
}
</script>

<style></style>
声明式导航

用可用组件router-link来替代a标签

router-link必须传入 to 属性,指定路由路径值

自带激活时的类名。可以做高亮

image-20240504175818761

  • 跳转传参
    • 在跳转路由时,可以给路由对应的组件内传值
    • 方式一:router-link的to属性传值
      • to="/path?参数名=值"
      • 组件接收值:$router.query.参数名
      • image-20240504180906377
    • 方式二:path/值需要
      • 在路由对象提前配置 path:"path/:参数名/:参数名"
      • 组件接收参数$route.params.参数名
      • image-20240504190918489
路由重定向

匹配path后,强制跳转path路径

  • redirect重定向路径

image-20240504191224524

路由404

路由最后,path匹配*任意路径

//404 写在最后
{
	path:"*",
	component:NotFound //统一配置一个组件
}
路由模式设置

修改路由在地址栏的模式

  • hash路由,例如:http://localhost:8080/#/home
  • history路由,例如:http://localhost:8080/home
  • history 路由模式配置项
const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    //...
  ],
})
编程式导航

用JS代码来进行跳转,无论用pathname都要和路由规则数组里对应

image-20240504194115561

image-20240504194309364

image-20240504215039752

image-20240504215300980

路由嵌套
  • 二级路由配置
    • 创建需要的二级页面组件
    • 路由规则里children中配置二级路由规则对象
    • 一级页面中设置router-view显示二级路由页面

image-20240504220520718

路由守卫

路由跳转之前,会触发一个函数

router.beforeEach((to,from,next)=>{})

image-20240504221711450

Vant移动端组件库

http://mui.ucmed.cn/#/zh-CN/intro

Vuex

https://vuex.vuejs.org/zh/

  • Vuex状态共享框架,组件间的通信
  • 集中式管理组件依赖的共享数据的一个工具
  • image-20240504223407097
    • 修改 state 状态必须通过 mutations
    • mutations 只能执行同步代码,类似 ajax、定时器之类的代码不能再 mutations 中执行
    • 执行异步代码,要通过 actions ,然后将数据提交给 mutations 才可以完成
    • state 的状态即共享数据可以在组件中引用
    • 组件中可以调用 actions

基础功能

  • yarn add vuex@3 --save安装依赖

  • vuex初始化

  • import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    // 1、导入
    import Vuex from 'vuex'
    // 2、注册Vuex,实际上调用的是Vuex中的一个install的方法
    Vue.use(Vuex)
    // 3、实例化一个Vuex
    const store = new Vuex.Store();
    
    new Vue({
      render: h => h(App),
      // 4
      store //es6写法,key和变量名相同时可省略
    }).$mount('#app')
    
    
Store

state 是放置所有公共状态的属性,如果有一个公共状态数据,只需要定义在state对象中

// 3、实例化一个Vuex
const store = new Vuex.Store({
    state:{
        //共享数据
        count:0
    }
});
  • 插值表达式获取
<template>
  <div id="app">
    {{ $store.state.count }}
  </div>
</template>
  • 计算属性-将state属性定义在计算属性中
<template>
  <div id="app">
    {{ count }}
  </div>
</template>

<script>

export default {
  name: 'App',
  computed:{
    count(){
      return this.$store.state.count
    }
  }

}
</script>

  • 辅助函数-mapState
    • 帮助我们把store中的数据映射到组件的计算属性中,一种方便用法
    • 1、导入import {mapState} from 'vuex'
    • 2、采用数组形式引入state属性mapSate(['count'])这个代码最终得到的是类似count(){ return this.$store.state.count }
    • 3、利用延展运算符将导出的状态映射给计算属性
 computed:{
    ...mapState(['count'])
 }
<template>
  <div>
    <div id="app">
      {{ count }}
    </div>
  </div>
</template>

<script>
import { mapState } from 'vuex';
export default {
  name: 'App',
  computed: {
    ...mapState(['count'])
  }

}
</script>

mutations

state数据的修改只能通过mutations,并且mutations 必须是同步更新,目的是形成数据快照

数据快照:一次mutations的执行,立即得到一种视图状态,因为是立刻,所以必须是同步

  • 定义 mutations
// 3、实例化一个Vuex
const store = new Vuex.Store({
    state:{
        //共享数据
        count:0
    },
    //定义mutations
    mutations:{
        //修改state的mutations方法
        //每一个mutations方法都有对应参数
        //state 指的是当前vuex中的state对象
        //payload 载荷,提交mutations方法的时候,传递的参数 可以是任何形式 任何类型的值
        addCount(state,payload){
      		state.count+=payload
    	}
    }
});

//mutations是一个对象,对象中存放修改state的方法
  • 原始形式 $store
<template>
  <button @click="addCount">+1</button>
</template>

<script>
export default {

  methods:{
    addCount(){
      //调用store中的mutations 提交 mutations
      //commit('mutations名车',其他参数)
      this.$store.commit('addCount',10)
    }
  }
}
</script>

<style>

</style>
  • 辅助函数
<template>
  <div>
  <!-- vue中方法的默认第一个参数  事件参数-->
    <button @click="addCount(60)">+60</button>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'
export default {

  methods: {
    ...mapMutations(['addCount'])
  }
}
</script>

<style></style>
actions
  • 定义
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false
import Vuex from 'vuex'

Vue.use(Vuex)
const store = new Vuex.Store({
  state:{
    count:0
  },
  mutations:{
    addCount(state,payload){
      state.count+=payload
    }
  },
  //  做异步的请求
  actions:{
    //action 方法参数
    // 第一个参数 执行的上下文对象
    // context 表示当前的store的实例,
    // 可以通过 context.state 获取状态
    // 也可以通过 context.commit 来提交mutations
    // 也可以 context.diapatch 调用其他的action

    asyncCount(context,param){
      // 模拟异步,一秒钟后修改 count值
      setTimeout(function(){
        // 异步获取值 123 ,将值设置到 count中
        context.commit('addCount',123)
      },1000)
    }
  }
})

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

  • 原始调用 $store
addAsyncCount(){
    this.$store.dispatch('asyncCount')
}
  • 传参调用
addAsyncCount(){
    this.$store.dispatch('asyncCount',123)
}
  • 辅助函数
import {mapActions} from 'vuex'

methods:{
    ...mapActions(['asyncCount'])
}
<template>
  <div>
    <button @click="test1">异步调用(原始形式)</button>
    <button @click="test2">异步调用(传参调用)</button>
    <button @click="asyncCount(600)">异步调用(辅助函数)</button>
  </div>
</template>

<script>
import { mapActions } from 'vuex'
export default {

  methods: {
    test1(){
      // 原始调用
      this.$store.dispatch('asyncCount')
    },
    test2(){
      // 传参调用
      this.$store.dispatch('asyncCount',300)
    },
    // 辅助函数
    ...mapActions(['asyncCount'])
  }
}
</script>

<style></style>
getters

除了 state 之外,有时还需要从 state 中派生出一些状态,这些状态是依赖state的,此时会用到getters

  • 定义getters
getters:{
    //getters 函数的第一个参数是 state
    //必须有返回值
    filterList: state=>state.list.filter(item=>item>5)
}
import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;
import Vuex from "vuex";

Vue.use(Vuex);
const store = new Vuex.Store({
  state: {
    list: [1, 2, 3, 4, 5, 6, 7],
  },
  // 放置vuex的计算属性
  getters: {
    filterList: state => state.list.filter((item) => item > 5),
  }
});

new Vue({
  render: (h) => h(App),
  store,
}).$mount("#app");

  • 使用
<template>
  <div>
    <!-- 原始使用 -->
    <div>{{ $store.getters.filterList }}</div>
    <!-- 辅助函数 -->
    <div>{{ filterList() }}</div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {

  methods: {
    ...mapGetters(['filterList'])
  }
}
</script>

<style></style>
模块化 module

如果把所有状态都放在state中,当项目变得越来越大的时候,vuex越难维护,所以有了vuex的模块化

image-20240505120115305

  • 模块化的简单应用
import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;
import Vuex from "vuex";

Vue.use(Vuex);
const store = new Vuex.Store({
  modules: {
    // 放置子模块
    user: {
      state: {
        token: "12345",
      },
    },
    setting: {
      state: {
        name: "张三",
      },
    },
  },
});

new Vue({
  render: (h) => h(App),
  store,
}).$mount("#app");

  • 使用

    • 通过$store.state.模块名.属性名来获取子模块的状态

    • <template>
        <div>
          <div>{{ $store.state.user.token }}</div>
          <div>{{ $store.state.setting.name }}</div>
        </div>
      </template>
      
      <script>
      
      export default {
      
      }
      </script>
      
      <style>
      
      </style>
      
    • 通过getters来获取,这个getters是根级别的

    • getters:{
      	token:state=>state.user.thoken,
      	name:state=>state.setting.name
      }
      
      
      computed:{
          ...mapGetters(['token','name'])
      }
      
模块化中的命名空间 namespaced

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的,这样使得多个模块能够对同一 mutation 或 action 作出响应

image-20240505121945548

image-20240505122124874

  • 想要提高内部模块的封闭性,可以采用 namespaced 来进行设置
    • 使用带命名空间的模块action/mutations
const store = new Vuex.Store({
  modules: {
    // 放置子模块
    user: {
        // 加锁,不会挂到全局下面
      namespaced: true
      state: {
        token: "12345",
      },
      mutations: {
        updateToken(state, paramm) {
          state.token = paramm;
        },
      },
    },
    setting: {
      state: {
        name: "张三",
      },
    },
  },
});
<template>
  <div>
    <div>{{ $store.state.user.token }}</div>
    <div>{{ $store.state.setting.name }}</div>
    <button @click="test1">直接调用</button>
    <button @click="test2">辅助函数调用</button>
    <button @click="updateToken(9)">基于命名空间的辅助函数调用</button>
  </div>
</template>

<script>
import { mapMutations as GlobMapMutations,createNamespacedHelpers } from 'vuex'
//  方式3:创建基于某个命名空间的辅助函数
const {mapMutations} = createNamespacedHelpers('user')
export default {
  methods: {
    test1() {
      // 方式1:直接调用,带上模块的属性名路径
      this.$store.commit('user/updateToken', 123)
    },
//   方式2: 辅助函数
    ...GlobMapMutations(['user/updateToken']),
    test2() {
      // 调用函数名为 user/updateToken的函数  
      // 不支持这种写法 this.user/updateToken(6)
      this['user/updateToken'](6)
    },
    // 方式3:
    ...mapMutations(['updateToken'])
  }
}
</script>

<style></style>

Element-UI

https://element-plus.org/zh-CN/component/overview.html

  • 安装依赖 yarn add element-ui

  • main.js注册

  • import Vue from 'vue'
    
    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';
    Vue.use(ElementUI);
    
表单
表单校验的先决条件

image-20240505141056398

<template>
  <div id="app">
    <el-card>
        <!--1、绑定model、rules -->
      <el-form :model="ruleForm" :rules="rules">
          <!--3、设置 prop ,校验谁写谁-->
        <el-form-item label="活动名称" prop="name">
            <!--4、input 绑定字段属性-->
          <el-input v-model="ruleForm.name"></el-input>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script>


export default {
  name: 'App',
  data() {
    return {
      ruleForm: {
        name: '',
      },
        //2、定义rule规则 
      rules: {
        name: [
          { required: true, message: '请输入活动名称', trigger: 'blur' },
          { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
        ],
      }
    }
  }
}
</script>

<style>
#app {
  width: 100%;
  height: 100vh;
  background-color: pink;
}
</style>

表单校验规则

elementui表单校验规则来自第三方校验规则 https://github.com/yiminghe/async-validator

image-20240505145115244

  • 检验规则格式{字段名:校验规则 =>[{}]}

  • 自定义规则

validator 是一个函数,其中三个参数,rule 当前规则、value 当前值、callback 回调函数

let fun = (rule,value,callbacck)=>{
 //根据value进行校验
    if(true){
       //校验通过
        callback();
       }else{
           callback(new Error("错误信息"))
       }
}
手动校验的实现

from表单提供了一份API方法,可以对表单进行完整和部分校验

image-20240505150344676

<template>
  <div id="app">
    <el-card>
      <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
        <el-form-item label="活动名称" prop="name">
          <el-input v-model="ruleForm.name"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script>


export default {
  name: 'App',
  data() {
    return {
      ruleForm: {
        name: '',
      },
      rules: {
        name: [
          { required: true, message: '请输入活动名称', trigger: 'blur' },
          { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
        ],
      }
    }
  },
  methods: {
    submitForm(param) {
    // 手动校验
      this.$refs[param].validate((valid) => {
        if (valid) {
          alert('submit!');
        } else {
          console.log('error submit!!');
          return false;
        }
      })
    }
  }
}
</script>

<style>
#app {
  width: 100%;
  height: 100vh;
  background-color: pink;
}
</style>

VUE3

社区生态

组件(插件)名称 官方地址 简介
ant-design-vue https://antdv.com/docs/vue/introduce-cn/ ant-design-vue 是 Ant Design 的 Vue 实现,组件的风格与 Ant Design 保持同步
element-plus https://element-plus.gitee.io/#/zh-CN Element Plus,一套为开发者、设计师和产品经理准备的基于 Vue 3.0 的桌面端组件库
vant https://vant-contrib.gitee.io/vant/v3/#/zh-CN 有赞前端团队开源的移动端组件库,于 2016 年开源,已持续维护 4 年时间
Naive UI https://www.naiveui.com/zh-CN/ 一个 Vue 3 组件库比较完整,主题可调,使用 TypeScript,不算太慢,有点意思
VueUse https://vueuse.org/ 基于composition组合api的常用集合,小兔仙项目会部分使用

整体优化

  1. 性能提升
    • 首次渲染更快
    • diff算法更快
    • 内存占用更少
    • 打包体积更小
  2. 更好的Typescript支持
  3. Composition API (重点)

相关阅读:

  1. Vue3 中文文档 https://vue3js.cn/docs/zh/
  2. Vue3 设计理念 https://vue3js.cn/vue-composition/

vite

https://vitejs.cn/vite3-cn/guide/features.html

  • 一个更加轻量级的vue项目打包脚手架工具
  • 相对于 vue-cli 安装插件非常少,需要自己配置
  • 单纯学习vue3语法会使用,做项目用 vue-cli

vue3

创建Vue应用
// 1、导入 createApp
import { createApp } from 'vue'
// 2、准备一个根组件 APP.vue 导入 main
import App from './App.vue'
// 3、createApp创建一个实例应用
const app = createApp(App);
// 4、应用实例挂载到 #app 上
app.mount('#app')

vue3.0的单文件组件中不再强制要求必须有唯一根元素

选项API
  • 代码风格:data选项写数据,methods选项写函数,一个功能逻辑的代码分散
<template>
  <div>
    <!-- 功能一模板 -->
    <button @click="show">显示</button>
    <button @click="hide">隐藏</button>
    <div v-if="showDiv">一个被控制显隐的div</div>
  </div>
  <div>
    <!-- 功能二模板 -->
    <button @click="changeRed">红色</button>
    <button @click="changeYellow">蓝色</button>
    <div :style="`color:${fontColor}`">一个被控制字体颜色的的div</div>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      showDiv: true, // 功能一数据
      fontColor: '' // 功能二数据
    }
  },
  methods: {
    // 功能一方法
    show() {
      this.showDiv = true
    },
    hide() {
      this.showDiv = false
    },
    // 功能二方法
    changeRed() {
      this.fontColor = 'red'
    },
    changeYellow() {
      this.fontColor = 'blue'
    }
  }
}
</script>
组合式API
<template>
  <div>
    <!-- 功能一模板 -->
    <button @click="show">显示</button>
    <button @click="hide">隐藏</button>
    <div v-if="showDivFlag">一个被控制显隐的div</div>
  </div>
  <div>
    <!-- 功能二模板 -->
    <button @click="changeRed">红色</button>
    <button @click="changeBlue">蓝色</button>
    <div :style="`color:${fontColor}`">一个被控制字体颜色的的div</div>
  </div>
</template>

<script>
import { ref } from 'vue'
// 功能A
function useShow() {
  const showDivFlag = ref(true)
  function show() {
    showDivFlag.value = true
  }
  function hide() {
    showDivFlag.value = false
  }
  return { showDivFlag, show, hide }
}
// 功能B
function useColor() {
  const fontColor = ref('')
  function changeRed() {
    fontColor.value = 'red'
  }
  function changeBlue() {
    fontColor.value = 'blue'
  }
  return { fontColor, changeRed, changeBlue }
}
export default {
  name: 'App',
  setup() {
    // 功能一
    const { showDivFlag, show, hide } = useShow()
    // 功能二
    const { fontColor, changeRed, changeBlue } = useColor()
    //模板中要使用数据和函数,需要在 setup 中返回
    return { showDivFlag, show, hide, fontColor, changeRed, changeBlue }
  }
}
</script>
setup()
  • 组件中组合式API的入口
  • 只会在组件初始化的时候执行一次
  • setup()函数在beforeCreate生命周期钩子执行之前执行,this不指向当前实例
  • 模板中要使用数据和函数,需要在 setup 中返回
export default {
  setup () {
    console.log('setup执行了')
    console.log(this) 
  },
  beforeCreate() {
    console.log('beforeCreate执行了')
    console.log(this)
  }
}
生命周期

相同钩子函数,可以写多个,执行顺序和书写顺序一样

选项式API下的生命周期函数使用 组合式API下的生命周期函数使用
创建实例前 beforeCreate 不需要(直接写到setup函数中)
创建实例后 created 不需要(直接写到setup函数中)
挂载前 beforeMount onBeforeMount
挂载后 mounted onMounted
更新前 beforeUpdate onBeforeUpdate
更新后 updated onUpdated
卸载销毁前 beforeDestroyed onBeforeUnmount
卸载销毁后 destroyed onUnmounted
activated onActivated
deactivated onDeactivated

生命周期钩子函数使用场景

生命周期钩子函数 应用场景
created 发送ajax请求 / 挂载共用属性
mounted 发送ajax请求 / 依赖于dom的业务,比如地图,图表
destroyed 销毁操作,比如定时器
reactive()函数
  • 定义响应式对象数据

  • 接收一个普通的对象传入,把对象数据转化为响应式对象并返回

<template>
  <div>
    <div>{{ state.name }}</div>
    <div>{{ state.age }}</div>
    <button @click="state.name='pink'">修改name</button>
  </div>
</template>

<script>
// 1. 从vue框架中导入`reactive`函数
import {reactive} from 'vue'
export default {
setup(){
  // 2.调用reactive函数并将想要变成响应式的对象数据当成参数传入
  const state = reactive({
    name:'name',
    age:20
  })
  // 3.把reactive函数调用完毕之后的返回值以对象的形式返回出去
  return{state}
}
}
</script>

<style></style>
toRef()
  • toRef()函数转换响应对象中某个属性为单独的响应式数据,并且值是关联的
  • 基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。
//规范化签名 
// 按原样返回现有的 ref
toRef(existingRef)

// 创建一个只读的 ref,当访问 .value 时会调用此 getter 函数
toRef(() => props.foo)

// 从非函数的值中创建普通的 ref
// 等同于 ref(1)
toRef(1)


//对象属性签名:
const state = reactive({
  foo: 1,
  bar: 2
})

// 双向 ref,会与源属性同步
const fooRef = toRef(state, 'foo')

// 更改该 ref 会更新源属性
fooRef.value++
console.log(state.foo) // 2

// 更改源属性也会更新该 ref
state.foo++
console.log(fooRef.value) // 3



  • toRef() 这个函数在你想把一个 prop 的 ref 传递给一个组合式函数时会很有用
<script setup>
import { toRef } from 'vue'

const props = defineProps(/* ... */)

// 将 `props.foo` 转换为 ref,然后传入
// 一个组合式函数
useSomeFeature(toRef(props, 'foo'))

// getter 语法——推荐在 3.3+ 版本使用
useSomeFeature(toRef(() => props.foo))
</script>

toRef 与组件 props 结合使用时,关于禁止对 props 做出更改的限制依然有效。尝试将新的值传递给 ref 等效于尝试直接更改 props,这是不允许的。在这种场景下,你可能可以考虑使用带有 getsetcomputed 替代。

toRefs()
  • 将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
  • 当从组合式函数中返回响应式对象时,toRefs 相当有用。使用它,消费者组件可以解构/展开返回的对象而不会失去响应性:
const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:{
  foo: Ref<number>,
  bar: Ref<number>
}
*/

// 这个 ref 和源属性已经“链接上了”
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3
function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })

  // ...基于状态的操作逻辑

  // 在返回时都转为 ref
  return toRefs(state)
}

// 可以解构而不会失去响应性
const { foo, bar } = useFeatureX()
ref()
  • 接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

  • ref 对象是可更改的,也就是说你可以为 .value 赋予新的值。它也是响应式的,即所有对 .value 的操作都将被追踪,并且写操作会触发与之相关的副作用。

  • 如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。若要避免这种深层次的转换,请使用 shallowRef() 来替代。

const count = ref(0)
console.log(count.value) // 0

count.value = 1
console.log(count.value) // 1
computed()
  • 接受一个 getter 函数,返回一个只读的响应式 ref 对象,该 ref 通过 .value 暴露 getter 函数的返回值。
  • 它也可以接受一个带有 getset 函数的对象来创建一个可写的 ref 对象
//接受一个 getter 函数
const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2


//对象
const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0
watch()
  • 基于响应式数据的变化执行回调逻辑

    • 普通监听 数据变化之后才执行回调逻辑
    • 立即执行 一上来先执行一次 不等数据变化
    • 深度监听 对象嵌套的场景 监听整个对象的变化
  • 1. 从vue框架中导入`watch`函数 
    2. 在setup函数中执行watch函数开启对响应式数据的监听
    3. watch函数接收三个常规参数
       1. 第一个参数为函数,返回你要监听变化的响应式数据
       2. 第二个参数为响应式数据变化之后要执行的回调函数
       3. 第三个参数为一个对象,在里面配置是否开启立刻执行或者深度监听
    

    1)普通监听

<template>
  {{ age }}
  <button @click="age++">change age</button>
</template>

<script>
import { ref, watch } from 'vue'
export default {
  setup() {
    const age = ref(18)
    watch(() => {
      return age.value
    }, () => {
      // 数据变化之后的回调函数
      console.log('age发生了变化')
    })
    return {
      age
    }
  }
}
</script> 

2)开启立刻执行

watch的效果默认状态下,只有监听的数据发生变化才会执行回调,如果你需要在一上来的时候就立刻执行一次,需要配置一下immediate属性

<template>
  {{ age }}
  <button @click="age++">change age</button>
</template>
<script>
import { ref, watch } from 'vue'
export default {
  setup() {
    const age = ref(18)
    watch(() => {
      // 返回你想要监听的响应式属性(ref产生的对象必须加.value)
      return age.value
    }, () => {
      // 数据变化之后的回调函数
      console.log('age发生了变化')
    },{ immediate: true})
    return {
      age
    }
  }
}
</script> 

3)开启深度监听

当我们监听的数据是一个对象的时候,默认状态下,对象内部的属性发生变化是不会引起回调函数执行的,如果想让对象下面所有属性都能得到监听,需要开启deep配置

<template>
  {{ state.name }}
  {{ state.info.age }}
  <button @click="name = 'pink'">change name</button>
  <button @click="info.age++">change age</button>
</template>

<script>
import { reactive, toRefs, watch } from 'vue'
export default {
  setup() {
    const state = reactive({
      name: 'cp',
      info: {
        age: 18
      }
    })
    watch(() => {
      return state
    }, () => {
      // 数据变化之后的回调函数
      console.log('age发生了变化')
    }, {
      deep: true
    })
    return {
      state
    }
  }
}
</script> 

4)更好的做法

使用watch的时候,尽量详细的表明你到底要监听哪个属性,避免使用deep引起的性能问题,比如我仅仅只是想在state对象的age属性变化的时候执行回调,可以这么写

<template>
  {{ state.name }}
  {{ state.info.age }}
  <button @click="state.name = 'pink'">change name</button>
  <button @click="state.info.age++">change age</button>
</template>

<script>
import { reactive, toRefs, watch } from 'vue'
export default {
  setup() {
    const state = reactive({
      name: 'cp',
      info: {
        age: 18
      }
    })
    watch(() => {
      // 详细的告知你要监听谁
      return state.info.age
    }, () => {
      // 数据变化之后的回调函数
      console.log('age发生了变化')
    })
    return {
      state
    }
  }
}
</script> 
ref属性
  • ref 用于注册元素或子组件的引用
  • 使用组合式 API,引用将存储在与名字匹配的 ref 里
    • 如果用于普通 DOM 元素,引用将是元素本身;如果用于子组件,引用将是子组件的实例
<script setup>
import { ref } from 'vue'

const p = ref()
</script>

<template>
  <p ref="p">hello</p>
</template>



// ref 可以接收一个函数值,用于对存储引用位置的完全控制
<ChildComponent :ref="(el) => child = el" />
组件通信
  • 父子通信
    • 基础思想依旧为:父传子是通过prop进行传入,子传父通过调用自定义事件完成
    • setup函数提供俩个参数,第一个参数为props,第二个参数为一个对象context
    • props为一个对象,内部包含了父组件传递过来的所有prop数据,context对象包含了emit属性,其中的emit可以触发自定义事件的执行从而完成子传父

app.vue

<template>
  <ChildSon :msg="msg" @get-msg="getMsg"></ChildSon>
</template>

<script>
import { ref } from 'vue'
import ChildSon from './components/ChildSon.vue'
export default {
  components: {
    ChildSon
  },
  setup() {
    const msg = ref('this is msg')
    function getMsg(param) {
      msg.value= param
      console.log(param)
    }
    return {
      msg,
      getMsg
    }
  }
}
</script>

components/ChildSon.vue

<template>
  <div>
    {{msg}}
    <button @click="setMsgFromSon">set</button>
  </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String
    }
  },
  emits: ['get-msg'], // 声明当前组件触发的自定义事件
  setup(props,{emit}) {
    function setMsgFromSon(){
      emit('get-msg','这是一条来自子组件的新的msg信息')
    }
    return {
      setMsgFromSon
    }
  }
}
</script>
  • provideinject
    • 它们配合起来可以方便的完成从上层组件向任意下层组件传递数据的效果
    • 顶层组件在setup方法中使用provide函数提供数据provide('key',数据)
    • 任何底层组件在setup方法中使用inject函数获取数据const data = inject('key')

image-20240505193144137

爷爷组件 - app.vue

<template>
  <father></father>
</template>

<script>
import Father from '@/components/Father'
import { provide } from 'vue'
export default {
  components: {
    Father
  },
  setup() {
    let name = '柴柴老师'
    // 使用provide配置项注入数据 key - value
    provide('name', name)
  }
}
</script> 

孙组件 - components/Son.vue

<template> 
我是子组件
  {{ name }}
</template>

<script>
import { inject } from 'vue'
export default {
  setup() {
    const name = inject('name')
    return {
      name
    }
  }
}
</script>

只要是后代组件,都可以方便的获取顶层组件提供的数据

provide默认情况下传递的数据不是响应式的,也就是如果对provide提供的数据进行修改,并不能响应式的影响到底层组件使用数据的地方,如果想要传递响应数据也非常简单,只需要将传递的数据使用ref或者reactive生成即可

app.vue

<template>
  <father></father>
  <button @click="changeName">change name</button>
</template>

<script>
import Father from '@/components/Father'
import { provide, ref } from 'vue'
export default {
  components: {
    Father
  },
  setup() {
    // 使用ref转换成响应式再传递
    let name = ref('柴柴老师')
    function changeName(){
      name.value = 'pink'
    }
    provide('name', name)
    return {
      changeName
    }
  }
}
</script> 

果用于子组件,引用将是子组件的实例

<script setup>
import { ref } from 'vue'

const p = ref()
</script>

<template>
  <p ref="p">hello</p>
</template>



// ref 可以接收一个函数值,用于对存储引用位置的完全控制
<ChildComponent :ref="(el) => child = el" />
组件通信
  • 父子通信
    • 基础思想依旧为:父传子是通过prop进行传入,子传父通过调用自定义事件完成
    • setup函数提供俩个参数,第一个参数为props,第二个参数为一个对象context
    • props为一个对象,内部包含了父组件传递过来的所有prop数据,context对象包含了emit属性,其中的emit可以触发自定义事件的执行从而完成子传父

app.vue

<template>
  <ChildSon :msg="msg" @get-msg="getMsg"></ChildSon>
</template>

<script>
import { ref } from 'vue'
import ChildSon from './components/ChildSon.vue'
export default {
  components: {
    ChildSon
  },
  setup() {
    const msg = ref('this is msg')
    function getMsg(param) {
      msg.value= param
      console.log(param)
    }
    return {
      msg,
      getMsg
    }
  }
}
</script>

components/ChildSon.vue

<template>
  <div>
    {{msg}}
    <button @click="setMsgFromSon">set</button>
  </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String
    }
  },
  emits: ['get-msg'], // 声明当前组件触发的自定义事件
  setup(props,{emit}) {
    function setMsgFromSon(){
      emit('get-msg','这是一条来自子组件的新的msg信息')
    }
    return {
      setMsgFromSon
    }
  }
}
</script>
  • provideinject
    • 它们配合起来可以方便的完成从上层组件向任意下层组件传递数据的效果
    • 顶层组件在setup方法中使用provide函数提供数据provide('key',数据)
    • 任何底层组件在setup方法中使用inject函数获取数据const data = inject('key')

[外链图片转存中…(img-nO5427Gs-1714912678987)]

爷爷组件 - app.vue

<template>
  <father></father>
</template>

<script>
import Father from '@/components/Father'
import { provide } from 'vue'
export default {
  components: {
    Father
  },
  setup() {
    let name = '柴柴老师'
    // 使用provide配置项注入数据 key - value
    provide('name', name)
  }
}
</script> 

孙组件 - components/Son.vue

<template> 
我是子组件
  {{ name }}
</template>

<script>
import { inject } from 'vue'
export default {
  setup() {
    const name = inject('name')
    return {
      name
    }
  }
}
</script>

只要是后代组件,都可以方便的获取顶层组件提供的数据

provide默认情况下传递的数据不是响应式的,也就是如果对provide提供的数据进行修改,并不能响应式的影响到底层组件使用数据的地方,如果想要传递响应数据也非常简单,只需要将传递的数据使用ref或者reactive生成即可

app.vue

<template>
  <father></father>
  <button @click="changeName">change name</button>
</template>

<script>
import Father from '@/components/Father'
import { provide, ref } from 'vue'
export default {
  components: {
    Father
  },
  setup() {
    // 使用ref转换成响应式再传递
    let name = ref('柴柴老师')
    function changeName(){
      name.value = 'pink'
    }
    provide('name', name)
    return {
      changeName
    }
  }
}
</script> 

网站公告

今日签到

点亮在社区的每一天
去签到