Vue3+TS+Vite+Pinia最全学习总结

VUE3介绍

Vue3+TS+Vite+Pinia学习总结

vue2和vue3之间的区别

  1. 因为需要遍历data对象上所有属性,所以如果data对象属性结构嵌套很深,就会存在性能问题。
  2. 因为需要遍历属性,所有需要提前知道对象上有哪些属性,才能将其转化为getter和setter,所以vue2中无法将data新增的属性转为响应式,只能通过vue提供的vue.set或者this.$set向data中嵌套的对象新增响应式属性,而这种方式并不能添加根级别的响应式属性。
  3. 不能通过下表或者length属性响应式地改变数组,而是必须用数组的方法push,pop,shift,unshift,splice来响应式的改变数组。
  4. 在vue3中使用proxy这个特性,替换了Object.defineProperty重构响应式系统,使用Proxy优势,直接可以监听数组类型的变化,监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升,可以拦截apply,ownKeys,has等13种方法,而Object.defineProperty不行。直接实现对象属性的新增删除。

Vue3新特性

向下兼容

vue3支持大多数vue2的特性,vue2添加Object数据需this.$set或Vue.set,删除需要Vue.delete或者可能得使用其他得一些办法来解决这个问题。vue3添加Obect数据删除都是响应式。

Composition Api

a. setup函数是一个新的组件选项,作为CompositionApi的入口点。
b. 接受两个参数props和context(attrs,slots,emit)
       1. attrs:值为对象,包含组件外部传递过来,但没有在props配置中声明的属性,相当于vue2种的this.$attrs。

       2. slots:收到的插槽内容,相当于this.$slots。

       3. emit:分发自定义的事件的函数,相当于this.$emit。
执行时机:setup处于beforeCreate生命周期钩子之前的函数
返回值:
      1. 返回一个对象,对象中的属性,方法,在模板中均可以直接使用
      2. 返回一个渲染函数,子当以渲染内容代替模板

Ref和Reactive都是用来创建响应式对象的

ref
  1. ref使用的时候需要.value。
  2. 生命基本数据类型时使用Object.definePropety的原型对象中的get set方法来修改数据实现响应式。
  3. 声明Object类型时内部通过reactive来转为代理对象。
  4. template模板中不需要.value。
reactive
  1. 用来定义Object类型数据。
  2. 操作数据与读取数据都不需要.value。
  3. 通过使用proxy来实现响应式,并通过reflect操作元素对象内部的数据。

生命周期钩子

setup执行时机在beforeCreate之前执行,将beforeDestroy改名为beforeUnmount,destroyed改名为unmounted

钩子说明
setup()开始创建组件之前,在beforeCreate之前执行,创建的是data,method。
onBeforeMount()组件挂载到节点上之前执行的函数
onMounted()组件挂载完成后执行的函数
onBeforeUpdate()组件更新之前执行的函数。
onUpdated()组件更新完成之后执行的函数
onBeforeUnmount()组件卸载之前执行的函数。
onUnmounted()组件卸载完成后执行的函数。
onActivated()若组件实例是 缓存树的一部分,当组件被插入到 DOM 中时调用。
onDeactivated()若组件实例是 缓存树的一部分,比如从 A 组件,切换到 B 组件,A 组件消失时执行。
onErrorCaptured()在捕获了后代组件传递的错误时调用。

watch

watch可以接收三个参数,第一个是要监听的对象,第二个是数据处理变化,第三个是配置项(options),其中options:immediate:true刚一进去就监听一次。

六、setup语法糖

  1. <script setup>
  2. 更少的模板语法
  3. 组件只需要引入不需要注册,属性和方法也不用返回,setup函数也不需要,export default不用写,数据,计算属性和方法,自定义指令都是在template中直接可获取。
  4. 可以直接写await 组件的setup会自动生成 async setup
    defineProps:用来接收父组件传来的值
    propsdefineEmits:用来声明触发的事件表

模板指令

  1. v-model用法更改,在一个组件上支持双向绑定多个属性,例如v-model:forst-name=“first” v-model:last-name="lase"还有子组件defineProps([‘’]),defineEmits([‘’])用来接收和响应事件来更新数据。
  2. 移除keyCode,v-on的修饰符,同事也不再支持config.keyCode。
  3. 移除过滤器vue3认为这个有实现成本,官方目前建议用方法调用或计算属性。
  4. v-if和v-for的优先级对比,V2版本中在一个元素上同时使用v-if和v-for时,v-for会优先作用。V3版本中v-if总是优先于v-for生效。
  5. v-bind合并行为,在V2中如果一个元素同时定义了v-bind="object"和一个相同的单独的property,那么这个单独的property总是会覆盖Object中的绑定,单独的属性覆盖v-bind,V3中这,如果一个元素同时定义了v-bind="object"和一个单独的property,那么声明绑定的顺序决定了它们如何合并。

vue3模板语法

创建工程

  1. 在电脑上创建存放vue3学习代码的文件夹: vue3-study。
  2. 使用vscode打开创建的vue3-01-study
  3. 打开vue3-study文件夹,运行终端,进行项目初始化,执行" npm init "回车即可,会生成package.json文件。
  4. 安装vue模块npm install vue@3.2.47,安装完成后在package.json中的dependencies会增加相关依赖信息。
  5. 在vue-3-01-study中创建一个01-helloworld.html文件

编写HTML页面

  1. 采用 <script> 标签引入 Vue 3 库
  2. 定义一个根节点元素 <div id="app">
  3. vue.global.js中会暴露出全局对象 Vue所有顶层 API 都以属性的形式暴露在了全局的 Vue 对象上。
  4. 从 Vue 对象中解构出 createApp 函数,用于实例化 Vue 应用程序:const { createApp } =Vue;
  5. mount 函数:用于挂载应用,指定被 Vue 管理的 Dom 节点入口,节点必须是一个普通的 HTML标签节点,一般是 div。参数值:可以是 DOM 元素或 CSS 选择器字符串。注意:参数值不要指定html 或 body。 注意: .mount() 函数应该始终在整个应用配置和资源注册完成后被调用。data 必须是一个函数:指定初始化数据,在 Vue 所管理的 Dom 节点下,可通过模板语法来进行使用。
  6. 标签体显示数据:{{}}
  7. 表单元素双向绑定数据:"v-model"
    代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><!-- 1.定义根节点元素 --><div id="app"><p>Hellow Word,{{msg}}</p><input type="text" v-model="msg"></div>
</body>
<script src="./node_modules/vue/dist/vue.global.js"></script>
<script type="text/javascript">//Vue是vue.global.js中暴露出来的全局对象,所有顶层API都以属性的形式暴露在了全局的vue对象上const {createApp} = Vue//每个Vue应用都是通过createApp函数创建一个新的应用实例const app = createApp({data(){return {msg:'hellow Vue3'}}}).mount('#app')// mount挂在应用节点,mount函数的参数值是dom元素或者css选择器字符串,不要指定html或body,注意".mount"函数应该始终在整个用用配置和支援注册完成后被调用console.log("app",app)
</script>
</html>

直接运行html文件:
Vue3+TS+Vite+Pinia入门到高级学习

分析MVVM模型

什么是MVVM模型?

    MVVM是Model-view-viewModel的缩写,它是一种软件架构风格,其中model为模型,数据对象,view为视图,视图模板页面,viewModel为视图模型,其实本质上就是vue实例。
    把需要改变视图的数据初始化到vue中,然后再通过修改Vue中的数据,从而实现对视图的更新,按照vue的特定语法进行声明开发,就可以实现对应功能,不需要直接操作Dom元素,JQuery就是,需要手动去操作DOM才能实现对应功能。
Vue3+TS+Vite+Pinia入门到高级学习

模板数据绑定渲染

可生成动态的HTML页面,页面中使用嵌入Vue.js语法可动态生成

  1. {{}} 双大括号文本绑定。
  2. v-标签属性名,以v-开头用于标签属性绑定,成为指令。

双大括号语法{{}}

vue3-01-study下创建一个02-模板数据绑定渲染.html

格式{{表达式}}
作用使用在标签体中用于获取数据可以使用JavaScript表达式

代码:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><!-- 1.定义根节点元素 --><div id="app"><p>Hellow Word,{{msg}}</p><h2>1、双大括号输出文本内容</h2><p>普通文本:{{ name }}</p><p>JS表达式1{{ salary + 10 }}</p><p>JS表达式2{{ Math.abs(salary) }}</p></div>
</body>
<script src="./node_modules/vue/dist/vue.global.js"></script>
<script type="text/javascript">//Vue是vue.global.js中暴露出来的全局对象,所有顶层API都以属性的形式暴露在了全局的vue对象上const {createApp} = Vue//每个Vue应用都是通过createApp函数创建一个新的应用实例const app = createApp({data(){return {msg:'hellow Vue3',salary:-100,}}}).mount('#app')// mount挂在应用节点,mount函数的参数值是dom元素或者css选择器字符串,不要指定html或body,注意".mount"函数应该始终在整个用用配置和支援注册完成后被调用console.log("app",app)
</script>
</html>

运行后:
Vue3+TS+Vite+Pinia入门到高级学习

文本显示指令v-text和v-cloak

使用说明
v-text等价于{{}}用于显示内容,且会覆盖元素中所有现有的内容。区别在于{{}}会有闪烁双大括号表达式问题,v-text不会闪烁。例:

v-cloak用于吟唱未完成编译的DOM模板,默认一开始被Vue管理的模板是隐藏的,当Vue解析处理完DOM模板之后,会自动把这个样式去除,然后就显示出来。如果还想用{{}}又不想又闪烁问题,则使用v-cloak处理,1. 添加一个属性选择器[v-cloak]{display:none}。2.在vue管理的模板入口节点上作用v-cloak,指令也可以做用到子元素上

Vue3+TS+Vite+Pinia入门到高级学习

输出HTML指令 v-html

v-html
  1. 如果是HTML格式数据,双大括号会将数据解释为普通文本,为了输出真正的 HTML,你需要使用 v-html 指令。
  2. v-html 的内容直接作为普通 HTML 插入—— Vue 模板语法不会被解析的。
  3. Vue 为了防止 XSS 攻击,在此指令上做了安全处理,当发现输出内容有Vue 模板语法、script 标签等,则不被解析。

注意:
<style scoped> msg: <p class="name" > 张三</p>,在单文件组件, <scoped> 样式将不会作用于 v-html 里的内容,因为 HTML 内容不会被 Vue 的模板编译器解析。 如果你想让 v-html 的内容也支持
scoped CSS,你可以使用 CSS modules 或使用一个额外的全局<style>元素。
代码

<div id="app"><h2>3、v-html 指令输出真正的 HTML 内容</h2><p>双大括号:{{ contentHtml }}</p><!-- 指令的值不需要使用双大括号获取,直接写获取的属性名,错误写法:v-html="{{contentHtml}}" --><p>v-html指令:<span v-html="contentHtml"></span></p></div>
<script src="./node_modules/vue/dist/vue.global.js"></script>
<script type="text/javascript">//Vue是vue.global.js中暴露出来的全局对象,所有顶层API都以属性的形式暴露在了全局的vue对象上const {createApp} = Vue//每个Vue应用都是通过createApp函数创建一个新的应用实例const app = createApp({data(){return {contentHtml: '<span style="color:red">红色HTML代码渲染<script>alert("hello vue")'}}}).mount('#app')// mount挂在应用节点,mount函数的参数值是dom元素或者css选择器字符串,不要指定html或body,注意".mount"函数应该始终在整个用用配置和支援注册完成后被调用console.log("app",app)
</script>

例图:
Vue3+TS+Vite+Pinia入门到高级学习

一次性插值v-once

v-once:一次性插值,当后面数据更新后,视图数据不会更新。
<h2>4、v-once 一次性插值</h2><p v-once> 这个将不会改变: {{ name }} </p>

属性动态绑定:v-bind
完整格式:v-bind:元素的属性名=“xxx”
缩写格式::元素的属性名=“xxx”
作用:将数据动态绑定到指定元素上,活绑定自组建的Prop属性,书写格式一致 修饰符

  1. .camel:将短横线命名的attribute转变为驼峰式明明。由于HTML特性是不区分大小写的,.camel修饰符允许在使用DOM模板时将v-bind属性名称驼峰化,
  2. .attr:强制绑定为DOMattribute
  3. .prop:强制绑定为DOMproprty

class与style属性绑定v-bind

通过class和sty指定样式式数据绑定的一个常见需求。他们都是元素需求,都用v-bind处理,其中表达式结果的类型可以是:字符串,对象或数组。
语法格式: v-bind:class='表达式’或:class=‘表达式’

  1. class的表达式可以为
    字符串:class="active"
    对象:class="{active:'isActive',error:hasError}"
    数组:class="['active','error']" 要加上单引号,不然式获取data中的值

  2. style的表达式支持绑定对象和数组
    :style=“{color:activeColor,fontSize:fibtSize+‘px’}”,注意,对象中的value值activeColor和fontSize是data中的属性。

事件处理指令 v-on

v-on给元素绑定事件监听器,如点击事件onclick,输入框失去事件onblur等,当用于普通元素,只监听原生DOM事件,当用于自定义元素组件,则监听子组件触发的自定义事件。
完整格式:v-on:时间名=“函数名” 或 v-on:时间名=“函数名(参数)” 如:v-on:blur="doNumBlur"、v-on:click="add('hello')"
缩写格式:@时间名="函数名" 或 @事件名="函数名(参数...)"
注意,@后面没有冒号,如@blur,@click
event:函数中的默认形参,代表原生DOM事件,当调用的函数有多个参数传入时,需要使用原生DOM事件,则通过$event作为实参传入
作用:用来将i安亭DOM事件。

事件修饰符:

功能说明
.stop阻止单机事件继续传播 event.stopPropagation()
.prevent阻止时间默认行为event.preventDefault()
once点击事件将只会触发一次

按键修饰符

格式:v-on:keyup.按键名 或者 @keyup.按键名
常用按键名:enter,table,delete,esc,space,up,down,left,right

表单数据双向绑定 v-model

单向绑定:数据变视图变,视图变数据不变,数据不变,上面的都是单向绑定。
<input :value="name" > <span>数据绑定:{{name}}</span>
双向绑定:数据变,视图变,视图变,数据便,表单输入框的内容同步给javaScript中相应的变量,手动连接值绑定和更改事件监听器可能会很麻烦。
<input :value="name" @input="event => name = event.target.value"> <span>数据绑定:{{name}}</span>
使用 v-model 双向绑定,可以简化上面操作。v-model 指令用于表单数据双向绑定,针对以下类型:text 文本,textarea 多行文本,radio 单选按钮,checkbox 复选框,select 下拉框
代码例子:

<body><div id="app"><form action="#" @submit.prevent="submitForm">姓名(文本)<input v-model="name" name="name" type="text"><br><br>性别(单选按钮)<input v-model="sex" name="sex" type="radio" :value="0"/><input v-model="sex" name="sex" type="radio" :value="1"/><br><br>技能(多选框)<input v-model="skills" type="checkbox" name="skills" value="java">Java开发<input v-model="skills" type="checkbox" name="skills" value="vue">Vue.js开发<input v-model="skills" type="checkbox" name="skills" value="python">Python开发<br><br>城市(下拉框)<select name="city" v-model="city"><option value="bj">北京</option><option value="sz">深圳</option><option value="nc">南昌</option></select><br><br>说明(多行文本)<textarea v-model="remark" name="remark" cols="30" rows="5"></textarea><br><br><button type="submit" >提交</button></form></div></div>
</body><script src="./node_modules/vue/dist/vue.global.js"></script>
<script type="text/javascript">const { createApp } = Vue;// 创建应用实例const app = createApp({data() {return {name: "",sex: 0, //默认选中:女skills: ["vue"], //默认勾选:vue.js开发city: "sz", //默认选中:深圳remark: "",};},methods: {// 是个对象,不要写小括号submitForm: function () {// 发送ajax请求alert(this.name +"," +this.sex +"," +this.skills +"," +this.city +"," +this.remark);},},}).mount("#app");
</script>

效果:
Vue3+TS+Vite+Pinia入门到高级学习

v-model修饰符

v-modle.lazy:默认情况下, v-model 会在每次 input 事件后更新数据。你可以添加 lazy 修饰符来改为在每次
change 事件后更新数据。
v-modle.number:自动将输入值转成数字(Vue底层会通过parseInt转数字,不能转数字返回原始值).number 修饰符会在输入框有 type=“number” 时自动启用。

Vite构建项目

单文件组件(SFC)

大多数vue项目中,使用一种类似HTML格式的文件来书写Vue组件,目前可以简单的理解为:一个Vue组件对应的是一个html文件,他被称为单文件组件,也被称为*.vue文件,英文single-File Components 缩写为SFC。
简单理解,vue的单文件组件会将一个组件的逻辑,模板,和样式封装在同一个文件里。每一个*.vue文件主要有三种顶层语言模块构成<script> 、 <template> 和 <style>

  1. 每个 *.vue 文件最多可以包含一个顶层 <template> 块。
  2. 每个 *.vue 文件最多可以包含一个 <script> 块。
  3. 每个 *.vue 文件最多可以包含一个 <script setup>
  4. 每个 *.vue 文件可以包含多个 <style> 标签。 scoped 限制当前定义的样式只在当前组件有效。

单文件组件的格式示例:

<script>// JS 逻辑代码export default {data() {return {count: 0,};},};</script><template><!-- HTML 模板代码 --><button @click="count++">Count is: {{ count }}</button></template><style scoped>/* CSS 样式代码 */button {font-weight: bold;}
</style>

为什么要使用单文件组件SFC,及其优点

  1. 使用熟悉的HTML,css,和JavaScript语法编写模块化的组件。
  2. 在使用组合式API时语法更简单。
  3. 让本来就强相关的关注点自然内聚。
  4. 预编译模板,避免运行时的编译开销。
  5. 组件作用域的CSS
  6. 通过交叉分析模板和逻辑代码能进行更多编译时优化。
  7. 更好的IDE支持,提供自动补全和对模板中表达式的类型检查。
  8. 开箱即用的模块热更新(HMR)支持。
    需要使用SFC必须使用构建工具,Vite是Vue官方提供的Vue构建工具,内置了Vue项目脚手架,直接使用Vite很方便的构建Vue单页面应用。

Vite介绍

Vue3+TS+Vite+Pinia入门到高级学习
Vite(法语意为 “快速的”,发音 /vit/ ,发音同 “veet”)是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:

  1. 一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)。
  2. 一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态
    资源。

Vite的优点

  1. 急速的服务启动:使用原生的ESM文件,ems标准通过import,export语法实现模块变量的导入和导出。
  2. 轻量快速的热重加载,无论应用程序大小如何,都始终快速的模块热替换。
  3. 对TypeScript,JSX,CSS等支持开箱即用。
  4. 灵活的API和完整的TypeScript类型。

Vite创建第一个Vue项目

  1. 打开窗口命令,cd到要创建项目的文件夹
  2. 在命令行中运行npm init vue@3.6.0
    Vue3+TS+Vite+Pinia入门到高级学习
  3. 项目创建后,通过以下步骤安装依赖并启动开发服务器
	# 进入到项目目录# cd <your-project-name>cd vue3-02-vite# 安装依赖npm install# 启动项目npm run dev

Vue3+TS+Vite+Pinia入门到高级学习
效果:
Vue3+TS+Vite+Pinia入门到高级学习

Vite脚本项目结构

不同版本的vite创建出来的项目,可能目录文件会有所差距,但是没关系的,没有目录和文件你手动创建出来即可。

|-- .vscode: vscode工具相关配置
| |-- extensions.json
|-- node_modules: 存放下载依赖的文件夹
|-- public: 存放不会变动静态的文件,打包时不会被编译
| |-- favicon.ico: 在浏览器上显示的图标
|-- src: 源码文件夹
| |-- App.vue: 应用根主组件
| |-- main.ts: 应用入口JS文件
| |-- components: Vue 子组件及其相关资源文件夹
| |-- assets: 静态文件,会进行编译压缩,如css/js/图标等
|-- .gitignore: Git 版本管制忽略的配置
|-- env.d.ts: 针对环境变量配置,如:声明类型可类型检查&代码提示
(.env.development、.env.production)
|-- index.html: 主页面入口文件
|-- package-lock.json: 用于记录实际安装的各个包的具体来源和版本号等,其他人在 npm install 项目时大家的
依赖能保证一致
|-- package.json: 项目基本信息,包依赖配置信息等
|-- README.md: 项目描述说明文件
|-- tsconfig.config.json: TypeScript 相关配置文件(在tsconfig.json中被引用了)
|-- tsconfig.json: TypeScript 相关配置文件
|-- vite.config.ts: vite 核心配置文件

解决 main.ts 中 import App from ‘./App.vue’ 有红线问题,原因是typescript只能理解.ts文件,无法理解.vue文件,在env.d.ts文件生命*.vue文件是component类型。
Vue3+TS+Vite+Pinia入门到高级学习
Vue3+TS+Vite+Pinia入门到高级学习

项目运行流程分析

  1. http://127.0.0.1:5173/请求到了项目根目录下的index.html页面
  2. index.html页面中,指定渲染出口,并引入了/src/main.ts文件。
  <body><div id="app"></div><script type="module" src="/src/main.ts"></script></body>
  1. main.ts入口文件代码
a. 通过vue导出createApp方法,用来创建一个应用实例。
b. 导入应用根组件App.vue
c. 挂在节点#app`(index.html中的id="app")`
d. 最终将app.vue组件代码,在index.html中的`<div id="app"> `渲染出口 `<div>` 中进行渲染。
e. 最后将 App.vue 组件页面效果渲染到浏览器

选项式和组合式API语法

创建新的vite项目

  1. 执行 npm init vue@latest 创建项目 vue3-03-vite-api (创建时选择 TypeScript)存放学习Vue3API代码。
  2. cd 进入项目目录,安装依赖 npm i ,启动项目 npm run dev。
  3. 保留 App.vue 组件,其他 .vue 后缀的组件文件全部删除。
  4. 将 App.vue 组件只保留三大顶级元素,多余代码都删除。
<script>
</script>
<template>
<div>hello</div>
</template>
<style scoped>
</style>

比较选项式和组合式API

通过计算器案例,演示不同风格的API编写方式。

选项式

前面知识我们一直使用的是选项式 API。 选项式 API 我们可以用包含多个选项的对象来描述组件的逻辑,例如 data 、 methods
和 computed 等选项。 通过 选项 所定义的属性都会暴露在函数内部的this上,它会指向当前的组件实例。 在<temlate></temlate>模板代码中可以省略 this ,直接写变量名{{ count }}或者方法名@ click="add"

Vue3+TS+Vite+Pinia入门到高级学习

<template><div><div>count值为:{{ count }}</div><button @click="add">点击+1</button></div>
</template>
<script>
export default {data() {return {count: 0,};},methods: {add() {// 通过 `this.变量名` 来操作当前组件中 data 选项的变量this.count++;},add2() {// 通过 `this.方法名` 调用当前组件的方法this.add();},},
};
</script>
<style scoped>
</style>
组合式 API (Composition API)-setup()

组合式 API 是 Vue3 推出的全新特性,目前 Vue 2.7 也已兼容支持。

先简单介绍下 setup 函数:

  1. setup 函数是在组件中使用组合式 API 的入口
  2. setup 函数中没有 this
  3. setup 函数只会在组件初始化的时候执行一次

组合式 API 我们可以使用导入的 API 函数来描述组件逻辑,如: const { createApp } = Vue 前面html用的也是组合式 API。

  1. 使用组合式API + setup 选项
    在模板中访问从 ref 声明的响应式变量,不需要 .value ,直接引用导出的属性名即可,因为Vue会自动浅层解包。
<template><div><!--不需要 `.value `,直接引用导出的属性名即可,因为Vue会自动浅层解包--><div>count值为:{{ count }}</div><button @click="add">点击+1</button></div>
</template>
<script>
// 组合式API
import { ref } from "vue";
export default {// `setup` 是一个专门用于组合式 API 的特殊钩子函数setup() {// 通过 ref 定义响应式变量,ref参数是初始值const count = ref(0);// 定义方法function add() {// 要获取通过ref声明的变量值,需要后面加上 `.value`,即 count.vuluecount.value++;}// 返回一个对象,返回值会暴露给模板和其他的选项式 API 钩子// return {count: count, add: add}return { count, add };},
};
</script>
<style scoped>
</style> 
  1. 在 setup() 函数中手动暴露大量的状态和方法非常繁琐,幸运的是,我们可以通过使用构建工具来简化该操作;
    在单文件组件(SFC)中,组合式 API 通常会与 <script setup> 搭配使用,使用它可以大幅度地简化代码。
    这个 setup 属性是一个标识,会告诉 Vue 需要在编译时进行一些处理,比如: <script setup> 中的导入和顶层变量/函数都能够在模板中直接使用
    我们基本上都会在组合式 API 中使用单文件组件 +<script setup>的语法,因为大多数 Vue 开发者
    都会这样使用。
<template><div><!--不需要 `.value `,直接引用导出的属性名即可,因为Vue会自动浅层解包--><div>count值为:{{ count }}</div><button @click="add">点击+1</button></div>
</template>
<script setup>
//  组合式API + setup 属性
import { ref } from "vue";
// 通过 ref 定义响应式变量,ref参数是初始值
const count = ref(0);
// 定义方法
function add() {// 要获取通过ref声明的变量值,需要后面加上 `.value`,即 count.vuluecount.value++;
}
// 不需要导出变量和方法了
</script>
<style scoped>
</style> 

Vue3+TS+Vite+Pinia入门到高级学习

如何选择组合式和选项式风格
  1. 在学习的过程中:推荐采用更易于自己理解的风格。再强调一下,大部分的核心概念在这两种风格之间都是通用的。熟悉了一种风格以后,你也能够很快地理解另一种风格。
  2. 在生产项目中:当你不需要使用构建工具,或者打算主要在低复杂度的场景中使用 Vue,例如渐进增强的应用场景,推荐采用选项式 API。当你打算用 Vue 构建完整的单页应用,推荐采用组合式 API + 单文件组件 .vue。

Vue3新项目毫无疑问用 组合式API

data 选项声明状态-选项式API

在选项式API中,会用 data 选项来声明组件的响应式状态,此选项的值必须为是返回一个对象的函数。
Vue 在创建组件实例的时候会调用此函数,并将函数返回的对象用响应式系统进行包装。
此对象的所有顶层属性都会被代理到组件实例 (即方法和生命周期钩子中的 this ) 上。

  1. data选项返回对象中的属性名:不能以 $ 和 _ 开头。Vue 在组件实例上暴露的内置 API 使用 $ 作为前缀。它同时也为内部属性保留 _ 前缀。因此,你应该避免在顶层 data 上使用任何以这些字符作前缀的属性。
export default {data() {return {// 属性名不能以 `$` 和 `_` 开头msg: "提示内容",staff: {// 对象id: 1,name: "张三",hobbies: ["篮球", "足球"],},};},methods: {say() {this.msg = "陪你学习,伴你梦想";},},
};
reactive 函数声明状态-组合式API
  1. 使用 reactive() 函数创建一个响应式对象或数组。
  2. 仅对对象类型有效(对象、数组和 MapSet 这样的集合类型),而对 string numberboolean 这样的 原始类型 无效。
  3. reactive() 返回的是一个原始对象的 Proxy 代理对象,其行为表现与一般对象相似;不同之处在于Vue 能够跟踪对响应式对象属性的访问与更改操作。
<template><div>ID{{ state.id }},名称:{{ state.name }},爱好:{{ bobbies }}</div>
</template>
<script setup>import { reactive } from "vue";// reactive函数的参数值为对象或数组const state = reactive({id: 1,name: "张三",});// 数组const bobbies = reactive(["篮球", "足球"]);// 响应式对象其实是代理对象 Proxy,其行为表现与一般对象相似。// 不同之处在于 Vue 能够跟踪对响应式对象属性的访问与更改操作bobbies.push("台球");console.log("state", state, bobbies);
</script>
<style scoped>
</style>
  1. reactive() 返回的是一个原始对象的 代理对象 Proxy,它和原始对象是不相等的。只有代理对象是响应式的,更改原始对象不会触发更新。
<script setup>import { reactive } from "vue";// reactive函数的参数值为对象或数组const state = reactive({id: 1,name: "张三",});// 数组const bobbies = reactive(["篮球", "足球"]);// 响应式对象其实是代理对象 Proxy,其行为表现与一般对象相似。// 不同之处在于 Vue 能够跟踪对响应式对象属性的访问与更改操作bobbies.push("台球");console.log("state", state, bobbies);const obj = { salary: 10000 };const state2 = reactive(obj);// 代理对象和原始对象不是全等的:falseconsole.log("是否为同一对象", obj === state2);// 在同一个对象上调用 reactive() 会返回相同的代理:trueconsole.log("同一个对象返回相同的代理", reactive(obj) === state2);
</script>
<template><div>ID{{ state.id }},名称:{{ state.name }},工资:{{ state2.salary }}<button @click="add">点击工资+1</button></div>
</template>
<style scoped>
</style>

Vue3+TS+Vite+Pinia入门到高级学习

reactive + TypeScript 标注类型

TypeScript 是一种基于 JavaScript 构建的强类型编程语言 。

  1. <script>根元素上添加属性 lang="ts" 来声明代码块采用 TypeScript语法 :<script setup lang="ts">
  2. reactive() 会根据参数中声明的属性,自动推导出数据类型。
<script setup lang="ts">import { reactive } from 'vue';// 推导得到的类型:{ id: number, name: string }const user = reactive({ id: 10, name: 'zhangsan' });
</script>
  1. 显式地标注一个 reactive 变量的类型,我们可以使用接口声明类型
<script setup lang="ts">import { reactive } from 'vue';// TS + reactive 声明属性// 推导得到的类型:{ id: number, name: string }//const user = reactive({ id: 10, name: 'zhangsan' })// 使用接口声明类型interface User {id: number,name: string}// 显式地标注一个 reactive 变量的类型const user: User = reactive({id: 10,name: 'zhangsan'});console.log('user', user);
</script>
ref 函数声明状态-组合式API

reactive() 只能声明对象类型(对象,数组,Map,Set等),不能声明原始类型(stringnumberboolean 等)。
reactive() 的种种限制归根结底是:因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。 为此,Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref:ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象

<script setup>import { ref } from "vue";// ref 可以声明任意类型的响应式属性const count = ref(0);// 数组const user = ref({id: 1,name: "哈哈",});const hobbies = ref(["篮球", "足球"]);// ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象:console.log("count", count, user, hobbies); // RefImpl { value: 0 }// 获取值 .valueconsole.log(count.value, user.value, hobbies.value); // 0function add() {// 操作值,一样要 .valuecount.value++;user.value.id = 2;hobbies.value.push("跑步");}
</script>
<template><div><!-- 不用 .value,因为Vue会自动解包 -->数量:{{ count }},用户: {{ user }},爱好:{{ hobbies }}<button @click="add">新增</button></div>
</template>
<style scoped>
</style>
ref + TypeScript 标注类型

<script> 根元素上添加属性 lang="ts" 来声明代码块采用 TypeScript语法 :<script setup lang="ts">,ref() 会根据参数中声明的属性,自动推导出数据类型。

<!-- 不要少了 lang="ts" -->
<script setup lang="ts">
import { ref } from 'vue';
// 自动推导出的类型:Ref<number>
const age = ref(18);
// 会有ts错误提示:TS Error: Type 'string' is not assignable to type 'number'.
age.value = '20';
</script>

手动显式地为 ref 内的值指定一个更复杂的类型,可以通过使用 Ref (大写字母 R )指定类型。

<!-- 不要少了 lang="ts" -->
<script setup lang="ts">import { ref } from 'vue';// 要加 `type` 表示导入 Ref 接口类型import type { Ref } from 'vue';// 手动指定类型const age: Ref<number | string> = ref(18);age.value = '20'; // 成功,无ts错误提示
</script>

在调用 ref() 时传入一个泛型参数,来覆盖默认的推导行为:

<!-- 不要少了 lang="ts" -->
<script setup lang="ts">import { ref } from 'vue';// 得到的类型:Ref<number | string>const age = ref<number | string>('18');age.value = 2020 // 成功,无ts错误提示
</script>

如果你指定了一个泛型参数但没有给出初始值,那么最后得到的就将是一个包含 undefined 的联合类型:

<!-- 不要少了 lang="ts" -->
<script setup lang="ts">import { ref } from 'vue';// 推导得到的类型:Ref<number | undefined>const age = ref<number>();console.log('age', age.value); // undefined
</script>
$ref 响应性语法糖 (实验性功能)

value 则感觉很繁琐,并且一不小心就很容易漏掉 .value 。使用 $ref 响应式的变量,可以像普通变量那样被访问和重新赋值,但这些操作在编译后都会变为带 .value 的ref。

响应性语法糖目前是一个实验性功能(具体设计在最终定稿前仍可能发生变化),它是组合式 API 特有的功能,且必须通过构建步骤使用。
响应性语法糖目前默认是关闭状态,需要你显式选择启用。
支持的版本(package.json中可查看:

  1. vue 版本大于等于 3.2.25 ,小于等于 Vue 3.3 ,将在 3.4 及以上版本中被移除
  2. @vitejs/plugin-vue 版本大于等于 2.0.0

每一个会返回 ref 的响应式 API 都有一个相对应的、以 $ 为前缀的宏函数。包括以下这些 API:

  1. ref -> $ref
  2. computed -> $computed
  3. shallowRef -> $shallowRef
  4. customRef -> $customRef
  5. toRef -> $toRef
  6. 当启用响应性语法糖时,这些宏函数都是全局可用的、无需手动导入。

但如果你想让它更明显,你也可以选择从 vue/macros 中引入:

import { $ref } from 'vue/macros'
let count = $ref(0)

使用步骤:
vite.config.ts 文件中启用对语法糖的支持:

	plugins: [vue({// 开启对语法糖的支持 $refreactivityTransform: true})],

代码实现:

<script setup>// 在 `vite.config.ts` 文件中启用对语法糖的支持:reactivityTransform: truelet count = $ref(0);console.log('count', count); // 0function add() {count++;}</script><template><button @click="add">{{ count }}</button>
</template>
shallowReactive 和 shallowRef 浅层响应式对象
  1. reactive() 和 ref() 是深层响应式对象:也就是声明的对象属性不管是多少层级的,会深度监听其所有属性,从而所有属性都是响应式的。

  2. shallowReactive() 是浅层响应式对象:一个浅层响应式对象里,只有根级别的属性是响应式的,嵌套子属性不是响应式的。shallowReactive注意:如果只修改了子属性的值不会响应式,但是一旦修改了根属性值,对应所有属性的新值都会更新到视图中。

  3. shallowReactive()应用场景:我们可以把需要展现在视图层的数据,放置在第一层。而把内部数据放置第二层及以下。

  4. shallowRef():是浅层响应式对象,只处理原始类型的响应式,对象类型是浅层监听不进行响应式处理。不是监听状态的第一层数据的变化, 而是监听 .value 的变化。

    state.value.name = 'xx' //不会被监听到,视图不更新
    state.value = {name: 'xx'} //才会被监听到,视图会更新
    

5.shallowRef()应用场景:如果有一个对象数据,当修改该对象中的属性值不进行响应式更新视图,而是希望当生成新对象来替换旧对象时才进行响应式,则使用shallowRef。

<script setup>
import { shallowReactive, shallowRef } from "vue";
/*** shallowReactive 和 shallowRef 是浅层响应式监听* shallowReactive:* 只监听第一层数据变化,也就是只有根级别的属性是响应式的,嵌套子属性不是响应式的。* (注意:如果只修改了子属性的值不会响应式,但是一旦修改了根属性值,* 对应所有属性的新值都会更新到视图中。)* shallowRef:* 对象类型才能浅层监听,对原始类型都是响应式;* 不是监听状态的第一层数据的变化, 而是监听 .value 的变化* 如 state.value.name = 'xx' 不会被监听到;* 如:state.value = {name: 'xx'} 才会被监听到;*/
// 浅层响应式:只对根属性是响应式的
const shallowState = shallowReactive({id: 1,name: "张三",car: {price: 100000,color: "red",},
});
function testShallowReactive() {// 直接改非第一层数据,视图不更新非响应式的shallowState.car.price++;shallowState.car.color = "yellow";// 当修改了第一层数据,也修改了其他层数据,此时会将所有数据都更新到视图// 因为当改为第一层会触发此状态的监听器,从而将此状态的所有数据全部更新到视图//shallowState.id++;
}
// shallowRef 对原始类型都是响应式
const count = shallowRef(0);
// 浅层监听不是监听状态的第一层数据的变化;而是监听 .value 的变化,如:state.value = {}
const shallowRefState = shallowRef({id: 1,name: "李四",mobile: {name: "华为meta60",price: 7888,},
});
function testShallowRef() {console.log("testShallowRef");// 原始类型是响应式的count.value++;// 修改 shallowRefState.value 属性的对象值无响应式//shallowRefState.value.id++;//shallowRefState.value.mobile.price = '5999';// 重新向.value赋值一个全新的的对象,响应式生效shallowRefState.value = {id: 11,name: "meng",mobile: {name: "赵四",price: 5888,},};
}
</script>
<template><div><p>{{ shallowState.id }} == {{ shallowState.car }}</p><button @click="testShallowReactive">测试shallowReactive浅层响应</button><p>count:{{ count }}</p><p>{{ shallowRefState.id }} == {{ shallowRefState.mobile }}</p><button @click="testShallowRef">测试shallowRef浅层响应</button></div>
</template>
<style scoped>
</style>

效果:
Vue3+TS+Vite+Pinia总结

只读代理 - readonly和shallowReadonly
  1. readonly() 深层次只读代理

readonly() 接收一个对象 (不论是响应式对象还是普通对象) 或是一个 ref,返回一个原值的深层次只读代理。是深层次只读代理:对象的任何层级的属性都是只读的,不可修改的。它的 ref() 解包行为与 reactive() 相同,但解包得到的值是只读的。

<script setup lang="ts">
import { reactive, readonly } from "vue";
/*** readonly() 接受一个对象 (不论是响应式还是普通的对象) 或是一个 ref,返回一个原值的只读代理。*/
const original = reactive({count: 0,user: {name: "张三",age: 18,},
});
const copy = readonly(original);
function add() {// 源属性可修改original.count++;// 更改该只读副本将会失败,并会得到一个警告copy.count++; // [Vue warn] Set operation on key "count" failed: target is readonlycopy.user.age++; // 深层次只读代理,不可修改
}
</script>
<template><div><span>count:{{ copy.count }}</span><button @click="add">新增</button></div>
</template>
<style scoped>
</style>

Vue3+TS+Vite+Pinia学习总结

  1. shallowReadonly() 浅层次只读代理
  • shallowReadonly() 浅层级只读代理:只有根层级的属性变为了只读,子层级的属性可修改。
  • 谨慎使用:shallowReadonly() 浅层次只读应该只用于组件中的根级状态。请避免将其嵌套在深层次的响应式对象中,因为它创建的树具有不一致的响应行为,这可能很难理解和调试。
import { reactive, shallowReadonly } from 'vue';
const original = reactive({count: 0,user: {name: '赵四',age: 18}
});
/**
* `shallowReadonly()` 浅层级只读代理:只有根层级的属性变为了只读
*/
const shallowCopy = shallowReadonly(original);
// 得到一个警告,对象的根属性 count 不允许修改
// shallowCopy.count++;
shallowCopy.user.age++; // 修改成功
响应式API-工具函数
  1. isRef()
    检查某个值是否为 ref。常用于条件判断中,返回值布尔类型:truereffalse不是ref

    <script setup>import { ref, isRef, unref } from 'vue';const count = ref(0);// `isRef` 判断是否为 ref,从而是否.value获取值if (isRef(count)) {console.log('是ref', count.value);} else {console.log('非ref', count);}
    </script>
    
  2. unref()
    如果参数是 ref,则返回内部值,否则返回参数本身。等价于 isRef(count) ? count.value : count注意:r 是小写的

// unref(注意:r是小写),参数是ref则会返回.value的值,否则直接返回值本身
//testUnref,接受一个参数x,该参数可以是number类型或者是一个Ref<number>类型的值。
function testUnref(x: number | Ref<number>) {// 保证val是具体的值,直接用于逻辑处理const val = unref(x);console.log('unref', val); // 1
}
<button @click="testUnref(ref(1))">测试unref函数</button>
  1. toRef()
    针对响应式对象的某个属性,创建一个对应的ref,这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然:修改ref的值将更新原属性的值。
import { reactive, toRef } from 'vue';// `toRef` 将响应对象的某个属性,创建一个对应的ref
const state = reactive({name: '张三',age: 6
});// 针对 name 属性创建一个对应的 ref
const ageRef = toRef(state, 'age');// 更改该 ref 会更新源属性
ageRef.value++;
console.log('state.age', state.age) // 7// 更改源属性也会更新该 ref
state.age++;
console.log('ageRef', ageRef.value); // 8//即使源属性当前不存在, toRef() 也会返回一个可用的 ref。
//这让它在处理子组件的可选 props 的时候格外实用,不然为空逻辑处理时可能报错。
const other = toRef(state, 'other');
console.log('other', other);
  1. toRefs()
    将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

toRefs 是创建响应式对象的每个属性的 ref , toRef 是创建响应式对象中的单个属性的 ref 。
toRefs 在调用时只会为源对象上的属性创建 ref。如果要为可能还不存在的属性创建 ref,请改用toRef 。

import { reactive, toRefs } from 'vue';
const user = reactive({name: '张三',salary: 10000
});
// `toRefs` 是创建响应式对象的每个属性的 `ref`。
const userRefs = toRefs(user);
// userRefs 的类型:{ name: Ref<string>, salary: Ref<number> }
// 这个 ref 和源属性已经双向奔赴了
user.salary++;
// 不要少了.value
console.log('userRefs.salary', userRefs.salary.value) // 10001
// 修改ref值, 源属性也同步了
userRefs.salary.value++;
console.log('user.salary', user.salary) // 10002

toRefs将每个属性转为ref后再进行解构属性,这样可以在模板中直接通过属性名引用

const user = reactive({name: 'John Doe',salary: 50000,
});
// 直接解构对象属性就不是响应式的
// const {name, salary} = {...user};
// 先将响应式对象user的每个属性转成ref:{ name: Ref<string>, salary: Ref<number> },然后
解构出每个ref属性,
// 在模板中就不需要 {{ user.name }} 获取,直接 {{ name }}获取
const {name, salary} = {...toRefs(user)};
function testToRefs() {user.name = '小四';user.salary++;
}
<!-- 响应式对象名.属性名 -->
<div>姓名:{{ user.name }},工资:{{ user.salary }} </div>
<!-- 每个属性转成ref后,可直接通过属性名获取 -->
<div>姓名:{{ name }},工资:{{ salary }} </div>
<button @click="testToRefs()">测试toRefs函数</button>
  1. isProxy()
    isProxy() 检查一个对象是否是由 reactive() 、 readonly() 、 shallowReactive() 、 shallowReadonly()创建的代理。

    import { reactive, isProxy } from 'vue';
    const state2 = reactive({
    username: '123456',
    });
    console.log('isProxy', isProxy(state2)); // true
    
  2. isReactive()
    检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。

    import { reactive, isReactive } from 'vue';
    const state2 = reactive({username: '123456',
    });console.log('isReactive', isReactive(state2)); // true
    /**
    * isReadonly() 检查传入的值是否为只读对象,
    * 通过 `readonly()` 和 `shallowReadonly()` 创建的代理都是只读的。*/
    const stateCopy = readonly(state2);
    console.log('isReadonly', isReadonly(state2)); // false
    console.log('isReadonly', isReadonly(stateCopy)); // true
    
  3. isReadonly()
    检查传入的值是否为只读对象,通过 readonly() 和 shallowReadonly() 创建的代理都是只读的。
    没有 set 函数的 computed() 计算属性也是只读的,反之则不是只读。

import { reactive, isReactive, isReadonly, readonly, computed } from 'vue';
const state2 = reactive({username: '123456',
});
// 深层只读
const stateCopy = readonly(state2);
console.log('isReadonly', isReadonly(state2)); // false
console.log('isReadonly', isReadonly(stateCopy)); // true
// 没有set的计算属性也是只读的,后续会讲
const statusText = computed(() => '张三');
console.log('isReadonly', isReadonly(statusText)); // true
同一组件同时存在两种风格API

在同一组件中,组合式api和选项式api可以同时出现使用。选项式API和组合式API存在相同属性时,引用的是组合式API的属性。两个<script>语言要保持一致,如果指定了 lang="ts" <script lang="ts"> ,另外一个也要指定 <script setup lang="ts"> 。但是不建议这样一起使用,容易造成代码混乱。一般是为了兼容以前vue2的代码才可能会这样子。在vue3强烈建议使用组合式API,可能以后更新把选项式API废除也有可能。

<script lang="ts">
export default {data() {return {message: '选项式API',name: '111'}}
}
</script><!-- 组合式api和选项式api可以同时存在,但是不建议这样作 -->
<script setup lang="ts">import { ref } from 'vue';// 选项式API和组合式API存在相同属性时,引用的是组合式API的属性const message = ref('组合式API');
</script>
<template><div ><input v-model="message"><input v-model="name"></div>
</template>
<style scoped>
</style>

计算属性和监听器

计算属性 computed 选项式API

computed 选项定义计算属性,类似于 methods 选项中定义的函数。
计算属性和函数的区别

  1. 函数每次都会执行函数体进行计算。
  2. 计算属性值会基于其响应式依赖缓存,只在相关响应式依赖发生改变时它们才会重新求值。

计算属性-单向绑定(只读)

需求:输入数学与英语分数,采用 methods 与 computed 分别计算出总得分。

<script >
export default {data() {return {mathScore: 80,englishScore: 90,};},// methods 选项,用于定义函数methods: {// 函数只支持单向绑定getSumScore() {console.info("getSumScore函数被调用");// 选项式API调用时都要加上 `this`。减 0 是为了字符串转为数字运算return this.mathScore - 0 + (this.englishScore - 0);},},// computed 选项,用于定义计算属性computed: {// 计算属性默认是get函数仅单向绑定;sumScore() {console.info("sumScore 计算属性被调用");return this.mathScore - 0 + (this.englishScore - 0);},},
};
</script>
<template><div><div>数学:<input type="text" v-model="mathScore" /></div><div>英语:<input type="text" v-model="englishScore" /></div><div>总分(方法-单向){{ getSumScore() }}</div><div>总分(计算属性-单向)<input type="text" v-model="sumScore" /></div></div>
</template>
<style scoped>
</style>

Vue3+TS+Vite+Pinia学习总结
computed 选项内的计算属性默认是 getter 函数,所以上面只支持单向绑定,当修改数学和英语的数据才会更新总分,而修改总分不会更新数据和英语。

计算属性(双向绑定)

计算属性默认是 getter 函数 ,要实现计算属性双向绑定,需要手动显式声明 getter 和 setter 函数。

<script >
import { ref } from "vue";
export default {data() {return {mathScore: 80,englishScore: 90,};},// methods 选项,用于定义函数methods: {// 函数只支持单向绑定getSumScore() {console.info("getSumScore函数被调用");// 选项式API调用时都要加上 `this`。减 0 是为了字符串转为数字运算return this.mathScore - 0 + (this.englishScore - 0);},},// computed 选项,用于定义计算属性computed: {// 计算属性默认是get函数仅单向绑定;sumScore() {console.info("sumScore 计算属性被调用");return this.mathScore - 0 + (this.englishScore - 0);},// 计算属性如果要双向绑定,需要手动显式指定get和set函数sumScore2: {// 要指定对象{},就不是函数了// 当get函数体的响应式数据变化后,则会触发更新sumScore2计算属性值,get函数一定要有返回值get() {console.info("sumScore2 计算属性被调用");return this.mathScore - 0 + (this.englishScore - 0);},// 当sumScore2计算属性变化后,会触发set函数处理set(newValue) {// value 为更新后的新值// 被调用则更新了sumScore2,然后将数学和英语更新为平均分const avgScore = newValue / 2;this.mathScore = avgScore;this.englishScore = avgScore;},},},
};
</script>
<template><div><div>数学:<input type="text" v-model="mathScore" /></div><div>英语:<input type="text" v-model="englishScore" /></div><div>总分(函数-单向){{ getSumScore() }}</div><div>总分(计算属性-单向)<input type="text" v-model="sumScore" /></div><div>总分(计算属性-双向)<input type="text" v-model="sumScore2" /></div></div>
</template>
<style scoped>
</style>

Vue3+TS+Vite+Pinia学习总结

计算属性 computed 组合式API

在组合式API中,计算属性通过 computed 函数进行定义。 声明一个计算属性,调用一次 computed
函数,默认提供了get单向绑定(只读),返回值是一个只读的响应式 ref对象,在setup中需要使用计算属性要使用 计算属性名.value,模板中使用则会自动解包不需要加.value。
要实现计算属性双向绑定,需要手动显式提供计算属性的get和set方法。

<script setup>
// 组合式API,从 vue 中导入 computed 函数
import { ref, computed } from "vue";
const mathScore = ref(88);
const englishScore = ref(99);
// 定义函数,与js中定义函数一样
function getSumScore() {return mathScore.value - 0 + (englishScore.value - 0);
}
// 声明一个计算属性,调用一次 computed 函数,默认提供了get单向绑定(只读)
// const sumScore = computed(function () {
// return (mathScore.value - 0) + (englishScore.value - 0);
// });
// // es6 箭头函数简写
// const sumScore = computed(() => {
// return (mathScore.value - 0) + (englishScore.value - 0);
// });
// es6 函数体只有一条语句,且它的返回值作为结果返回,可以简写成如下
const sumScore = computed(() => mathScore.value - 0 + (englishScore.value - 0));
// setup中使用计算属性要:计算属性名.value
console.log("sumScore", sumScore.value);
// 给计算属性提供get和set方法,实现双向绑定
const sumScore2 = computed({// get函数体返回的结果就是sumScore2计算属性的值,get函数一定要有返回值get() {return mathScore.value - 0 + (englishScore.value - 0);},set(newValue) {const avgScore = newValue / 2;mathScore.value = avgScore;englishScore.value = avgScore;},
});
</script>
<template><div><div>数学:<input type="text" v-model="mathScore" /></div><div>英语:<input type="text" v-model="englishScore" /></div><div>总分(函数-单向){{ getSumScore() }}</div><div>总分(计算属性-单向)<input type="text" v-model="sumScore" /></div><div>总分(计算属性-双向)<input type="text" v-model="sumScore2" /></div></div>
</template>
<style scoped>
</style>

效果和以上效果一样

计算属性最佳实战

  1. Getter不应有副作用
    计算属性的 getter 应只做计算,而没有任何其他的”副作用“(例如:不要在 getter 中做异步请求或者更改DOM!),一个计算属性,主要是根据其他值来派生出一个新值,因此 getter 的职责应该仅为计算和返回该值。如果需要根据响应式状态的变更,来创建副作用,可以使用后续我们讲解的 监听器 来创建副作用 避免直接修改计算属性值。

  2. 避免直接修改计算属性值
    从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改,应该更新它所依赖的源状态以触发新的计算。

监听器(侦听器) watch 选项式API

不建议在计算属性的 getter 函数中执行一些”副作用“,而是采用 监听器去执行一些”副作用“。在选项式API中,我们可以使用 watch 选项来监听一个或多个响应式属性,当响应式属性变化时,对应监听器的回调函数会自动调用。通过 watch 选项在每次被监听的响应式属性发生变化时触发。

watch 选项基本使用

用例:监听输入的关键字,如果关键字包含 zhangsan 则模拟请求数据接口获取数据。

<script >
/*** 选项式API - watch 选项实现响应式属性的监听*/
export default {data() {return {keyword: "", // 关键字result: "", // 查询结果};},// 监听器,值是个对象{}watch: {// 指定要监听的:响应式属性,如:当keyword改变时,会触发keyword(newVal, oldVal) {// 接收两个参数(新值,旧值)if (newVal.includes("zhangsan")) {// 输入的关键字包含 `zhangsan` 则查询请求结果this.searchResult();} else {this.result = "";}},},methods: {searchResult() {this.result = "查询结果中";// 模拟发送请求接口,获取结果setTimeout(() => {this.result = "hello,zhangsanxuegu!";}, 2000);},},
};
</script>
<template><div><input v-model="keyword" placeholder="请输入带zhangsan的关键字" /><div>搜索结果:{{ result }}</div></div>
</template>
<style scoped>
</style>

用例:
Vue3+TS+Vite+Pinia学习总结
监听对象某个属性,注意:用 . 分隔对象属性路径,只能是简单的路径,不支持表达式。

<script >
export default {data() {return {query: {username: ''}}},// 监听器,值是个对象{}watch: {// 监听对象某个属性;注意:不要少了引号,只能是简单的路径,不支持表达式。'query.username'(newVal) {console.log('username', newVal);}},
}
</script>
<template><div><input v-model="query.username" placeholder="请输入用户名"></div>
</template>
<style scoped>
</style>

watch 选项深层监听 deep

watch
默认是浅层监听的:被监听的属性,仅在被重新赋值时,才会触发回调函数,而嵌套属性的变化不会触发。如果想监听所有嵌套的变更,你需要深度监听器:要将函数形式改为对象,handler 为监听的回调函数,再加上 deep: true 开启深度监听。谨慎使用:深度监听需要遍历被监听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。

<script >
export default {data() {return {query: {username: ''}}},// 监听器,值是个对象{}watch: {/*** 监听 query 对象属性* 1. 默认是浅层监听,不会监听子属性的改变,所以当query.username改变不会被监听到。* 2. 使用 deep 可以深度监听,被监听对象的任意层级子属性改变后都会被监听到*/// 浅层监听// 只会监听到对象自身改变 this.query = {username: 123};query(newVal, oldVal) {console.log('浅层监听对象query',newVal, oldVal);},// 深度监听,要将函数形式改为对象,handler 为监听的回调函数,deep: truequery: {handler(newVal, oldVal) { // 监听回调函数名必须是handlerconsole.log('深度监听对象query', newVal, oldVal);},deep: true, // 开启深度监听},}
}
</script>

watch 即时回调的监听器

watch 默认是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建监听器时,立即执行一遍回调。
比如:我们想请求一些初始数据,然后在相关状态更新时再重新请求数据。我们可以用一个对象来声明监听器,这个对象有 handler 函数和 immediate: true 选项,这样便能强制回调函数初始化立即执行:

watch: {// query数据源监听器query: {handler(newVal, oldVal) { // 监听回调函数名必须是handlerconsole.log('深度监听对象query', newVal, oldVal);},deep: true, // 开启深度监听immediate: true, // 强制初始化立即执行回调},
}

watch 回调的触发时机 flush

当你更改了响应式状态,它可能会同时触发监听器回调和 Vue 组件更新。默认情况下,监听器回调都会在 Vue 组件更新之前被调用。这意味着你在监听器回调中访问的 DOM 将是被 Vue更新之前的状态。如果想在监听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: ‘post’ 选项(默认是 flush:‘pre’ )

<script >
export default {data() {return {query: {username: "",},};},// 监听器,值是个对象{}watch: {// 深度监听,要将函数形式改为对象,handler 为监听的回调函数,deep: truequery: {handler(newVal, oldVal) {// 监听回调函数名必须是handler// 通过ref指定的元素都会暴露在 `this.$refs` 上,通过它来获取元素后进行操作console.log("this.$refs.spanRef", this.$refs.spanRef?.innerHTML);console.log("深度监听对象query", newVal, oldVal);},deep: true, // 开启深度监听immediate: true, // 初始化立即执行// 默认`pre`:在 Vue 组件更新之前被调用,所以在handler回调中访问的 DOM 将是被 Vue 更新之前的状态。// 取值:pre 在组件`更新前`回调,post 在组件`更新后`回调flush: "pre",},},
};
</script>
<template><!-- ref 是一个特殊的属性,类型元素上的id属性,通过ref值直接引用操作此 DOM 元素或组件 --><span ref="spanRef">显示:{{ query.username }}</span>
</template>

Vue3+TS+Vite+Pinia学习总结

监听器 watch() 组合式API

在组合式API中,我们可以使用 watch 函数 在每次响应式状态发生变化时触发回调函数。


监听单个数据源:参数一:source 是数据源,可以是以下类型:1. 一个函数,且函数要返回一个值2. 一个 ref (包括计算属性computed等声明)3. 一个响应式对象 reactive等4. 或是由以上类型的值组成的数组参数二:callback 在数据源(source)发生变化时要调用的回调函数,且回调函数会传递三个参数(新值,旧值,以及一个用于注册副作用清理的回调函数-该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求),一般用接收前两个参数就行了。参数三:options 是可选的参数,是一个对象,即:{immediate?:boolean, // 默认false,true在监听器创建时立即触发回调函数。第一次调用时旧值是undefined。deep?:boolean, // 默认false,如果数据源是对象,强制深度遍历,以便在深层级变更时触发回调。flush?: 'pre' | 'post' // 默认:'pre'在组件`更新前`回调,post 在组件`更新后`回调}
function watch<T>(source: WatchSource<T>,callback: WatchCallback<T>,options?: WatchOptions
)
// 监听多个数据源:
function watch<T>(// 数据源是数组类型sources: WatchSource<T>[],// 回调函数接受两个数组,分别对应数据源数组中的新值和旧值。callback: WatchCallback<T[]>,// 同上面单个数据源一样的options?: WatchOptions
)

watch() 函数基本使用

<script setup>
/*** 组件式API:* 1. watch() 函数实现响应式属性的监听*/
import { ref, watch } from "vue";
const keyword = ref("");
const result = ref("未查询到");
/*** 参数1:监听的响应式属性;* - 普通类型的ref直接写变量名,不用加.value** 参数2:回调函数,响应式属性值发生变化时触发*/
watch(keyword, (newVal, oldVal) => {if (newVal.includes("wu")) {// 输入的关键字包含 `wu` 则查询请求结果searchResult();} else {result.value = "";}
});
function searchResult() {result.value = "查询结果中";// 模拟发送请求接口,获取结果setTimeout(() => {result.value = "hello,wuyong!";}, 2000);
}
</script>
<template><div><input v-model="keyword" placeholder="请输入带wu的关键字" /><div>搜索结果:{{ result }}</div></div>
</template>
<style scoped>
</style>

Vue3+TS+Vite+Pinia学习总结

wach() 监听不同类型数据源

监听响应式对象、带返回值的函数、数组方式监听多个数据源。

<script setup>
/*** 组件式API:* 1. watch() 函数实现响应式属性的监听*/
import { ref, reactive, watch } from "vue";
const state = reactive({query: {username: "",},
});
const keyword = ref("");
// 监听响应式对象
watch(state.query, (newVal, oldVal) => {console.log('state.query', newVal);
});
// 错误,不能直接监听响应式对象的属性值,需要传递一个函数将属性值作为返回值
// watch(state.query.username, (newVal, oldVal) => {
// 改为函数方式,即可监听响应式对象的属性值
watch(() => state.query.username,(newVal, oldVal) => {console.log("state.query.username", newVal);}
);
watch(keyword,(newVal, oldVal) => {console.log("keyword【newVal,oldVal】", newVal, oldVal);},{ immediate: true }
);
// 采用数组方式,同时监听多个;
watch([keyword, () => state.query.username],([newKeyword, newUsername], [oldKeyword, oldUsername]) => {// 此回调函数的参数使用了解构数组方式不要少了中括号:参数1是监听的每个属性的新值数组,参数2是监听的每个属性的旧值数组;console.log("数组方式监听多个",newKeyword,newUsername,oldKeyword,oldUsername);},{ immediate: true }
);
</script>
<template><div><input v-model="state.query.username" placeholder="请输入用户名" /><br /><input v-model="keyword" placeholder="请输入keyWord" /></div>
</template>

wach() 深层监听&立即监听&回调触发时机

watch函数的第3个参数为对象,其对象属性有:

deep: true, // 深度监听,多嵌套属性值修改后监听
immediate: true, // 立即监听
flush: ‘pre’ // 默认 pre:在组件 更新前 回调,post: 在组件 更新后 回调

<script setup>
import { ref, watch } from "vue";
// 与模板元素的ref属性进行绑定,通过此ref进行DOM操作等
const spanRef = ref();
// 深度监听、立即监听、回调时机
watch(() => state,(newVal, oldVal) => {console.log("spanRef", spanRef.value?.innerHTML);// console.log('监听state', newVal, oldVal)},{deep: true, // 深度监听,多嵌套属性值修改后监听immediate: true, // 立即监听flush: "pre", // 默认 pre:在组件`更新前`回调,post: 在组件`更新后`回调}
);
</script>
<template><div><input v-model="keyword" placeholder="请输入带wu的关键字" /><div>搜索结果:{{ result }}</div><input v-model="state.query.username" placeholder="请输入用户名" /><span ref="spanRef">{{ state.query.username }}</span></div>
</template>

监听器 watchEffect() 组合式API

watchEffect() 立即运行一个回调函数,会自动监听回调函数中响应式属性,并在响应式属性更改时重新执行。

  1. 会立即执行监听器的回调函数,不用指定 immediate: true
  2. 会自动监听回调函数中使用到的响应式属性,而不会递归地监听所有的属性,没有使用到的不会被监听。
  3. 适用于多个监听依赖项进行业务处理:
    只有一个依赖项的例子来说, watchEffect() 的好处相对较小。但是对于有多个依赖项的监听器来说,使用 watchEffect() 可以消除手动维护依赖列表的负担。
/**
* effect 传递监听触发的函数
* options 选项:
* { flush?: 'pre' | 'post' } // 默认:'pre'在组件`更新前`回调,post 在组件`更新后`回调
*/
function watchEffect(effect, options);

watchEffect() 实现计算购物车的总价

<script setup>
/*** 组件式API:watchEffect() 函数实现响应式属性的监听* 1. 立即监听* 2. 自动监听回调函数中使用到的响应式属性数据;不会深度监听所有的属性,没有使用到的不会被监听* 3. 针对同时监听多个依赖项*/
import { reactive, toRefs, watchEffect, computed } from "vue";
const state = reactive({totalMoney: 0, // 总金额cartList: [// 购物车商品{ name: "手机", price: 3999, num: 1 },{ name: "平板", price: 5999, num: 1 },{ name: "电脑", price: 6999, num: 1 },],
});
// 每个属性转为ref后,再解构出每个ref
const { totalMoney, cartList } = { ...toRefs(state) };
// 自动监听回调函数中使用到的所有属性的值
watchEffect(() => {console.log("watchEffect被触发");state.totalMoney = state.cartList.reduce((prev, curr) => prev + (curr.price || 0) * (curr.num || 0),0);},{flush: "post", // 'post' 回调中能访问被 Vue 更新之后的 DOM,'pre' (默认) 访问Vue更新前的DOM}
);
</script>
<template><div><li v-for="(item, index) in cartList" :key="index"><span>{{ item.name }}</span> &nbsp;&nbsp;<input v-model="item.price" placeholder="请输入单价" /><input v-model="item.num" placeholder="请输入数量" /></li><h3>合计总价:{{ totalMoney }}</h3></div>
</template>
<style scoped>
</style>

Vue3+TS+Vite+Pinia学习总结

比较 watch 和 watchEffect

watch 和 watchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:

  1. watch 只监听指定的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。
  2. watch 可手动配置选项来控制:是否立即监听,是否深度监听等,因此我们就能更加精确地控制回调函数的触发时机。
  3. watchEffect 会自动监听回调函数中所有使用到的响应式属性数据。同时监听多个数据源时代码更简洁,但有时其响应性依赖关系会不那么明确。

监听器 watchPostEffect()

watchPostEffect 在Vue更新后的DOM执行监听器回调函数。为了简写 watchEffect() 省去 flush: ‘post’ 。

// 自动监听回调函数中使用到的所有属性的值
watchEffect(() => {console.log('watchEffect被触发');state.totalMoney = state.cartList.reduce((prev, curr) => prev + (curr.price || 0) *(curr.num || 0), 0);
}, 
{flush: 'post' // 'post' 回调中能访问被 Vue 更新之后的 DOM,'pre' (默认) 访问Vue更新前的DOM
});
// 使用 watchPostEffect 简写:回调函数中访问Vue更新后的DOM,
watchPostEffect(() => {console.log('watchPostEffect在Vue更新之后执行')// vue更新之后执行state.totalMoney = state.cartList.reduce((prev, curr) => prev + (curr.price || 0) *(curr.num || 0), 0);
});

Vue生命周期函数

Vue 生命周期介绍

每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤。比如设置好数据监听,编译模板,挂载实例到DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。

Vue 3.x 中可以继续使用 Vue 2.x 中的生命周期钩子,但有两个钩子被更名:

  1. beforeDestroy 改名为 beforeUnmount
  2. destroyed 改名为 unmounted

生命周期主要分为四大阶段:初始化阶段、挂载阶段、更新阶段、销毁组件实例

三大阶段选项式API组合式API声明周期钩子说明
初始化阶段beforeCreate 不需要,直接写到setup函数中解析之后,data()computed 等选项处理之前立即调用。setup() 最先被调用,钩子会在所有选项式 API 钩子之前调用,也就是在选项式API的beforeCreate() 前面调用。
初始化阶段created不需要(直接写到setup函数中)created钩子被调用时,会完成的设置有:响应式数据、计算属性、方法和监听器 。
挂载阶段 beforeMountonBeforeMount在组件被挂载之前调用。组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。
挂载阶段mountedonMounted在组件被挂载之后调用。数据和DOM都已被渲染出来。
更新阶段beforeUpdateonBeforeUpdate响应式状态修改,而更新其 DOM 树之前调用。
更新阶段updatedonUpdated响应式状态修改,而更新其 DOM 树之后调用。要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循!
销毁阶段beforeUnmount (Vue2是beforeDestroy )onBeforeUnmount在一个组件实例被卸载之前调用。当这个钩子被调用时,组件实例依然还保有全部的功能。
销毁阶段unmounted(Vue2是 destroyed )onUnmounted在一个组件实例被卸载之后调用。一个组件在以下情况下被视为已卸载:1. 其所有子组件都已经被卸载。2. 所有相关的响应式作用 (渲染作用以及 setup()时创建的计算属性和监听器) 都已经停止。

Vue3+TS+Vite+Pinia学习总结

生命周期钩子示例-选项式API

为了演示组件卸载从而触发生命钩子,我们在 src/components 目录下创建子组件 lifeOption.vue

<script>
/*** 选项式API:Vue 生命周期*/
export default {data() {return {message: "hello, Vue选项式生命钩子",};},beforeCreate() {// $el 该组件实例管理的 DOM 根节点,$el 到组件挂载完成 (mounted) 之前都会是空的console.log("beforeCreate()", this.$el, this.$data);},// 已初始化 data 数据,但数据未挂载到模板中created() {console.log("created()", this.$el, this.$data);},// 组件被挂载之前调用,已经完成了其响应式状态的设置,但还没有创建 DOM 节点beforeMount() {console.log("beforeMount()", this.$el, this.$data);},// 挂载完成,数据和DOM都已被渲染出来mounted() {console.log("mounted()", this.$el, this.$data);},// 响应式状态变更,更新 DOM 前调用beforeUpdate() {// 使用 this.$el.innerHTML 获取更新前的 Dom 模板数据console.log("beforeUpdate()", this.$el.innerHTML, this.$data);},// 响应式状态变更,更新 DOM 后调用updated() {// data 被 Vue 渲染之后的 Dom 数据模板console.log("updated()", this.$el.innerHTML, this.$data);},// 卸载组件实例前调用beforeUnmount() {console.log("beforeUnmount()");},// 卸载组件实例后调用unmounted() {console.log("unmounted()");},
};
</script>
<template><div><span>{{ message }}</span></div>
</template>
<style scoped>
</style>

引入改组件使用:

<script>
// 导入子组件
import LifecycleOptions from "./components/lifeOption.vue";
/*** 选项式API:Vue 生命周期*/
export default {components: {// 引用子组件// LifecycleOptions: LifecycleOptionsLifecycleOptions, // 简写},data() {return {isShow: true,};},methods: {handleUnmount() {this.isShow = false;},},
};
</script>
<template>  <div><!-- false 会销毁组件 --><LifecycleOptions v-if="isShow" /><button @click="handleUnmount">销毁子组件</button></div>
</template>
<style scoped>
</style>

Vue3+TS+Vite+Pinia学习总结

生命周期钩子示例-组合式API

为了演示组件卸载从而触发生命钩子,我们在 src/components 目录下创建子组件 lifeOption.vue

<script setup>
/*** 组件式API:vue声明周期*/
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted,
} from "vue";
const message = ref("hello,Vue组合式API生命钩子");
const divRef = ref();// setup替代选项式API的beforCreate,created声明钩子
console.log("setup", divRef.value);// 组件被挂载之前调用,为创建DOM元素
onBeforeMount(() => {console.log("onBeforeMount", divRef.value);
});
//组件被挂载之后调用,已完成数据和DOM渲染
onMounted(() => {console.log("onBeforeMount", divRef.value);
});
//响应状态变更,更新DOM前调用
onBeforeUpdate(() => {console.log("onBeforeUpdate", divRef.value.innerHTML);
});
// 响应式状态变更,更新 DOM 后调用
onUpdated(() => {console.log("onUpdated", divRef.value.innerHTML);
});
// 卸载组件实例前调用
onBeforeUnmount(() => {console.log("onBeforeUnmount");
});
// 卸载组件实例后调用
onUnmounted(() => {console.log("onUnmounted");
});
</script>
<template><div ref="divRef"><span>{{ message }}</span></div>
</template>
<style scoped>
</style>

调用该组件:

<script>
// 导入子组件
import LifecycleOptions from "./components/lifeOption.vue";
import { ref } from "vue";
const isShow = ref(true);
function handleUnmount() {isShow.value = false;
}
</script>
<template><LifecycleOptions v-if="isShow" /><button @click="handleUnmount">销毁组件</button>
</template>
<style scoped>
</style>

效果:
Vue3+TS+Vite+Pinia学习总结

nextTick 等待 DOM 更新完成后执行

当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
使用 nextTick() 可以在状态改变后立即使用,就是等待 DOM 更新完成后执行相关代码。类似于 updated 生命钩子,更新后dom操作。

  1. 选项式API: nextTick() 函数绑定了组件实例上的,使用 this.$nextTick() 传递一个回调函数作为参
    数。
  2. 组合式API:向 nextTick() 传递一个回调函数作为参数,或者 await 返回的 Promise。
<!--nextTick:等待DOM更新完成后,再执行相关代码
-->
<script>
import { nextTick } from "vue";
export default {data() {return {count: 0,};},methods: {// 方法体使用了 await,对应方法前要加上 awaitasync add() {this.count++;// DOM 未更新,0console.log("DOM 未更新", this.$refs.counterRef.innerHTML);// 等待dom更新完成后再执行后面代码,// 方式1:使用通过 vue 导入的 nextTick,// await nextTick(); // 方法前要加 async// console.log('DOM 已更新', this.$refs.counter.innerHTML);// 方式2:使用实例上的$nextTick函数,传递一个回调函数,更新完成dom的会自动调用回调函数this.$nextTick(() => {// DOM 已更新,1console.log("DOM 已更新", this.$refs.counterRef.innerHTML);});},},
};
</script>
<!-- 组合式api和选项式api可以同时存在,但是不建议这样作 -->
<script setup>
import { ref, nextTick } from "vue";
const age = ref(100);
const ageRef = ref();
async function sub() {age.value--;// DOM 未更新,99console.log("DOM 未更新", ageRef.value.innerHTML);// 使用全局 nextTick 是上面通过 vue 导入的// await nextTick(); // 方法前要加 async// console.log('DOM 已更新', ageRef.value.innerHTML);// 传递回调函数nextTick(() => {console.log("DOM 已更新", ageRef.value.innerHTML);});
}
</script>
<template><button ref="counterRef" @click="add">选项式API{{ count }}</button><button ref="ageRef" @click="sub">组合式API{{ age }}</button>
</template>
<style scoped>
</style>

nextTick 等待 DOM 更新完成后执行

自定义指令 v-xxx 和 插件

Vue 内置指令

名称作用
v-text更新元素的文本内容。
v-html内容按普通 HTML 插入渲染, Vue 模板语法是不会被解析的,可防止 XSS 攻击。
v-show根据表达式的真假值,切换元素的 display CSS 属性来显示隐藏元素。
v-clock用于隐藏尚未完成编译的 DOM 模板,解决双大括号闪烁等问题。
v-if根据表达式的真假值,来渲染元素。
v-else前面必须有 v-if 或 v-else-if 。
v-else-if前面必须有 v-if 或 v-else-if 。
v-for遍历的数组或对象
v-on给元素绑定事件监听器。
v-bind动态的绑定一个或多个 attribute,也可以是组件的 prop。
v-model在表单输入元素或组件上创建双向绑定。.lazy - 监听 change 事件而不是 input,.number - 将输入的合法符串转为数字,.trim - 移除输入内容两端空格
v-once一次性插值,当后面数据更新后视图数据不会更新
v-pre元素内具有 v-pre ,所有 Vue 模板语法都会被保留并按原样渲染。会跳过这个元素和它的子元素的编译过程,加快编译。最常见的用例就是 显示原始双大括号标签及内容。例如:网页中的一篇文章,文章内容不需要被 Vue 管理渲染,则可以在此元素上添加 v-pre 忽略文章编译提高性能。
v-memoVue 3.2+版本新增指令,它的值为数组,用于缓存一个节点及子节点,在元素和组件上都可以使
用,只作性能的提升。

自定义指令语法

有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候使用自定义指令更为方便。其他情况下应该尽可能地使用 v-bind 这样的内置指令来声明式地使用模板,这样更高效,也对服务端渲染更友好。

自定义全局指令

自定义全局指令是在项目的所有组件中都可以使用。

  1. 注册全局指令: 在 main.ts 文件中添加如下代码,注意:注册时,指令名不要带 v-
import { createApp } from 'vue'
import App from './App.vue'import './assets/main.css'
const app = createApp(App)// 全局指令
app.directive('指令名',{// 在绑定元素的 属性 前,或事件监听器应用前调用created(el, binding){},// 在元素被插入到 DOM 前调用beforeMount(el, binding) {},//(一般用这个) 在绑定元素的父组件,及他自己的所有子节点都挂载完成后调用mounted(el, binding) {// 逻辑代码},// 绑定元素的父组件更新前调用beforeUpdate(el, binding) {},// 在绑定元素的父组件,及他自己的所有子节点都更新后调用updated(el, binding) {},// 绑定元素的父组件卸载前调用beforeUnmount(el, binding) {},// 绑定元素的父组件卸载后调用unmounted(el, binding) {}
})// 挂载要放到最后
app.mount("#app")

例子:当还未点击页面的其他地方,input元素自动聚焦。

import { createApp } from 'vue'
import App from './App.vue'import './assets/main.css'
const app = createApp(App)// 全局指令
app.directive('focus', {// 在绑定元素的 属性 前,或事件监听器应用前调用created(el, binding) { },// 在元素被插入到 DOM 前调用beforeMount(el, binding) { },//(一般用这个) 在绑定元素的父组件,及他自己的所有子节点都挂载完成后调用// el:v-focus 指令绑定到的dom元素// binding:可获取使用了此指令的绑定值 等信息mounted(el, binding) {// 逻辑代码el.focus(); // 自动获取焦点},// 绑定元素的父组件更新前调用beforeUpdate(el, binding) { },// 在绑定元素的父组件,及他自己的所有子节点都更新后调用updated(el, binding) { },// 绑定元素的父组件卸载前调用beforeUnmount(el, binding) { },// 绑定元素的父组件卸载后调用unmounted(el, binding) { }
})// 挂载要放到最后
app.mount("#app")

使用指令:

  1. 引用指令时,指令名前面加上v-
  2. 直接在元素上在使用即可:v-指令名="表达式"
<input v-focus>

自定义指令简化形式
自定义指令一般实在mounted和updated上实现相同的行为,除此之外并不需要其他钩子,这种情况下可以直接用一个函数来定义指令。

app.directive('指令名', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
})

按钮级别权限控制,也就是按钮显示或隐藏

// 简写形式: 在 `mounted` 和 `updated` 时都调用
app.directive('auth', (el, binding) => {// value 传递的值:v-auth="传递的值"const {value} = binding;if (value !== 'add:user') {// 为了旧版本IE浏览器,不能直接使用el.remove(),要先获取父组节点再删除当前节点el.parentNode.removeChild(el);}
})

使用:

<!-- 指令值:不能少了单引号,没有单引号就认为变量,要定义对应变量才行 -->
<button v-auth="'add:user'">新增用户</button>

在这里插入图片描述

自定义局部指令

局部指令只能在当前组件中使用,其他组件中无法使用,

  1. 注册局部指令-选项式API,使用 directives 选项,来注册指令
export default {
// 注册局部指令directives: {'指令名': { // 指令名不要带 vmounted(el, binding) {// 逻辑代码}}}
}

示例:注册 v-upper-text 指令,将指令中获取到的值,变成大写输出到标签体中,且通过指令来设置字体颜色。

<script>
export default {data() {return {message: "",};},// 注册局部指令directives: {"upper-text": {// 指令名不要带 v-// 因为是样式,所以不需要元素插入到DOM中,就好像link引入CSS文件时并不关心元素是否加载created(el, binding) {el.style.color = "red";},beforeUpdate(el, binding) {// 将在 v-upper-text 指令中获取到的值,变成大写输出到标签体中el.innerHTML = binding.value.toUpperCase();},},},
};
</script>
<template><div><!-- 使用指令,指令名前面加 v- --><input v-model="message" />输入的内容转成大写:<span v-upper-text="message"></span></div>
</template>

效果:
Vue3+TS+Vite+Pinia最全学习总结
2. 注册局部指令-组合式API
<script setup> 中,任何以 v 开头的驼峰式命名的变量,都可以被用作一个自定义指令。
示例:如果指令值不是 ‘add:staff’ ,按钮就会被移除

<script setup>
/*** 组合式:自定义指令* 在 <script setup> 中,任何以 v 开头的驼峰式命名的变量,都可以被用作一个自定义指令。*/
// 声明vPermission变量,对应的就是 v-permission 指令
const vPermission = {mounted: (el, binding) => {console.log(binding)if (binding.value !== "add:staff") {el.parentNode.removeChild(el);}},
};
</script>
<template><div><!-- 如果指令值不是 'add:staff',按钮就会被移除 --><button v-permission="'add:staff'">新增员工</button></div>
</template>

效果:
Vue3+TS+Vite+Pinia最全学习总结

自定义插件

Vue 插件的作用
  1. 插件通常会为 Vue 添加全局功能,一般是添加全局指令、全局实例属性或方法、全局组件等。

向 app.config.globalProperties 中添加一些全局实例属性或方法
通过 app.component() 和app.directive() 注册一到多个全局组件或自定义指令。
通过 app.provide() 使一个资源可被注入进整个应用。

  1. 一个插件有一个公开方法 install() ,通过 install() 方法添加全局功能,方法接收到安装它的应用实例
    app 和额外选项作为参数。
  2. 通过全局方法 app.use() 使用插件。
自定义插件演示-国际化插件
  1. 开发插件, 在 src 目录下创建 plugins 目录,在 plugins 目录建一个 i18n.ts 文件,src/plugins/i18n.ts ,将 I want to learn vue汉化为 我想学习vue
import type {App} from 'vue';//定义全局插件:中英转换
export default{//app式应用实例,options绑定到应用实例时传递的对象install:(app:App,options:any)=>{console.log("插件选项options:",options)// 1.注入全局方法app.config.globalProperties.$translate = (key:string) => {console.log("插件:注册全局方法生效")// 使用key作为索引const obj = {a:"我想学vue"} as anyreturn obj[key]}//2. 注入全局指令app.directive("my-directive",(el,bindling)=>{console.log("插件:注册全局指令生效")el.innerHTML = "i18n插件v-my-directive指令:" + options.name})}
}
  1. 挂载到应用实例app上,要在 main.ts
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './plugins/i18n'
import './assets/main.css'
const app = createApp(App)// 全局指令
app.directive('focus', {// 在绑定元素的 属性 前,或事件监听器应用前调用created(el, binding) { },// 在元素被插入到 DOM 前调用beforeMount(el, binding) { },//(一般用这个) 在绑定元素的父组件,及他自己的所有子节点都挂载完成后调用// el:v-focus 指令绑定到的dom元素// binding:可获取使用了此指令的绑定值 等信息mounted(el, binding) {// 逻辑代码el.focus(); // 自动获取焦点},// 绑定元素的父组件更新前调用beforeUpdate(el, binding) { },// 在绑定元素的父组件,及他自己的所有子节点都更新后调用updated(el, binding) { },// 绑定元素的父组件卸载前调用beforeUnmount(el, binding) { },// 绑定元素的父组件卸载后调用unmounted(el, binding) { }
})app.use(i18n,{name:"汉化插件"})// 挂载要放到最后
app.mount("#app")
  1. 使用插件,在组件中使用,在选项式API中,使用this获取全局方法变量,在组合式API中,要使用getCurrentInstance()来获取。
<script >
export default {mounted() {// 在选项式api中,直接this.全局变量/方法console.log("onMounted", this.$translate("a"));},
};
</script>
<script setup>
// 在 setup 中调用全局方法
import { getCurrentInstance } from "vue";
const instance = getCurrentInstance();
const chinese = instance.appContext.config.globalProperties.$translate("a");
console.log("汉化结果:", chinese);
</script>
<template><div><!-- 在 i18n.ts 插件中定义的全局指令 --><div v-my-directive></div><!-- 调用全局方法,直接方法名即可 --><div>I want to learn vue汉化结果:{{ $translate("a") }}</div></div>
</template>

效果:
Vue3+TS+Vite+Pinia最全学习总结

经典实战项目-TodoMVC

效果链接

TodoMVC源码地址:https://github.com/tastejs/todomvc-app-template

git clone https://github.com/tastejs/todomvc-app-template.git

https://todomvc.com/examples/vue/dist/#/

Vite 构建 TodoMVC 项目

基于 Vue3+Vite+TypeScript 来开发 TodoMVC 项目。

初始化 Vite 脚手架项目
  1. 执行命令 npm init vue@3.6.0
    Vue3+TS+Vite+Pinia最全学习总结
  2. 初始化项目后,进入项目目录安装依赖
cd vue3-vite-todomvc
npm install
  1. 启动项目
npm run dev
添加 TodoMVC 模板代码
  1. todomvc-app-template/index.html 页面中的 <section><footer class="info"> 元素体的代码复制到 vue3-vite-todomvc/src/App.vue 文件里的 <template> 元素中。
    Vue3+TS+Vite+Pinia最全学习总结
    Vue3+TS+Vite+Pinia最全学习总结
  2. 安装 TodoMVC 相关依赖
npm install todomvc-app-css@2.4.2 todomvc-common@1.0.5
  1. 在 src/main.ts 文件中,全局方式引入样式
import { createApp } from 'vue'
import App from './App.vue'
import '/node_modules/todomvc-app-css/index.css'
import '/node_modules/todomvc-common/base.css'
createApp(App).mount('#app')

效果:
Vue3+TS+Vite+Pinia最全学习总结

数据列表渲染实战

列表中的记录有3种状态且 <li> 样式不一样:未完成(没有样式)、已完成( .completed )、编辑中( .editing )任务字段 : id (主键) 、 content (内容)、 (状态 :true 已完成, false 未完成 )。无数据.main 和 .footer 标识的标签应该被隐藏 ( v-show ),无数据列表及其下面操作标签都要隐藏。

有数据列表功能实现
<script setup lang="ts">
import { reactive, toRefs } from "vue";
// 声明列表记录的数据类型
interface Todo {id: number;content: string;completed: boolean;
}
interface State {todoList: Array<Todo>;
}
const state: State = reactive({todoList: [{id: 1,content: "1",completed: true,},{id: 2,content: "2",completed: false,},],
});
const { todoList } = { ...toRefs(state) };
</script>
<template><section class="todoapp"><header class="header"><h1>todos</h1><input class="new-todo" placeholder="What needs to be done?" autofocus /></header><!-- This section should be hidden by default and shown when there are todos --><section class="main"><input id="toggle-all" class="toggle-all" type="checkbox" /><label for="toggle-all">Mark all as complete</label><ul class="todo-list"><!-- These are here just to show the structure of the list items --><!-- List items should get the class `editing` when editing and `completed` when marked as completed --><li class="completed" v-for="(item,index) in todoList" :key="item.id" :class="{'completed':item.completed}"><div class="view"><input class="toggle" type="checkbox" v-model="item.completed" checked /><label>{{item.content}}</label><button class="destroy"></button></div><input class="edit" value="Create a TodoMVC template" /></li></ul></section><!-- This footer should be hidden by default and shown when there are todos --><footer class="footer"><!-- This should be `0 items left` by default --><span class="todo-count"><strong>0</strong> item left</span><!-- Remove this if you don't implement routing --><ul class="filters"><li><a class="selected" href="#/">All</a></li><li><a href="#/active">Active</a></li><li><a href="#/completed">Completed</a></li></ul><!-- Hidden if no completed items are left ↓ --><button class="clear-completed">Clear completed</button></footer></section><footer class="info"><p>Double-click to edit a todo</p><!-- Remove the below line ↓ --><p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p><!-- Change this out with your name and url ↓ --><p>Created by <a href="http://todomvc.com">you</a></p><p>Part of <a href="http://todomvc.com">TodoMVC</a></p></footer>
</template><style scoped>
</style>

效果:
Vue3+TS+Vite+Pinia最全学习总结

无数据隐藏功能

只要判断todoList数组length等于0,则表示没有数据,结合v-show即可。
修改模板中的<section class=main> <footer class="footer">
Vue3+TS+Vite+Pinia最全学习总结

添加任务功能实现

需求:在最上面的文本框中添加新的任务,不允许添加非空数据,按Enter键添任务列表中,并清空文本框。
分析:在添加到列表前,使用.trim()去除空格,如果去除后是空的就不添加,在页面添加Enter按键监听事件,最后像文本框赋空值。

  1. 在文本框中添加v-model.trim="content" 双向绑定且去除空格,且绑定Enter按键监听事件@keyup.enter="addData"

代码:

<script setup lang="ts">
import { reactive, toRefs } from "vue";
// 声明列表记录的数据类型
interface Todo {id: number;content: string;completed: boolean;
}
interface State {content: string;todoList: Array<Todo>;
}
const state: State = reactive({content: "",todoList: [// {//   id: 1,//   content: "1",//   completed: true,// },// {//   id: 2,//   content: "2",//   completed: false,// },],
});
function addData() {//  1.获取文本框输入的数据,手动再次去除空格,防止没有使用v-model.trimconst content = state.content.trim();// 2. 判断数据如果为空,则什么都不做if (!content.length) return;// 3. 判断如果不为空,则添加到数组中,自动生成ID值const id = state.todoList.length + 1;state.todoList.push({id,content,completed: false,});// 4.清空文本框内容state.content = "";
}
const { content, todoList } = { ...toRefs(state) };
</script>
<template><section class="todoapp"><header class="header"><h1>todos</h1><inputclass="new-todo"v-model="content"@keyup.enter="addData"placeholder="What needs to be done?"autofocus/></header><!-- This section should be hidden by default and shown when there are todos --><section class="main" v-show="todoList.length"><input id="toggle-all" class="toggle-all" type="checkbox" /><label for="toggle-all">Mark all as complete</label><ul class="todo-list"><!-- These are here just to show the structure of the list items --><!-- List items should get the class `editing` when editing and `completed` when marked as completed --><liclass="completed"v-for="(item, index) in todoList":key="item.id":class="{ completed: item.completed }"><div class="view"><inputclass="toggle"type="checkbox"v-model="item.completed"checked/><label>{{ item.content }}</label><button class="destroy"></button></div><input class="edit" value="Create a TodoMVC template" /></li></ul></section><!-- This footer should be hidden by default and shown when there are todos --><footer class="footer" v-show="todoList.length"><!-- This should be `0 items left` by default --><span class="todo-count"><strong>0</strong> item left</span><!-- Remove this if you don't implement routing --><ul class="filters"><li><a class="selected" href="#/">All</a></li><li><a href="#/active">Active</a></li><li><a href="#/completed">Completed</a></li></ul><!-- Hidden if no completed items are left ↓ --><button class="clear-completed">Clear completed</button></footer></section><footer class="info"><p>Double-click to edit a todo</p><!-- Remove the below line ↓ --><p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p><!-- Change this out with your name and url ↓ --><p>Created by <a href="http://todomvc.com">you</a></p><p>Part of <a href="http://todomvc.com">TodoMVC</a></p></footer>
</template><style scoped>
</style>

Vue3+TS+Vite+Pinia最全学习总结

显示所有未完成任务数功能实现

需求:在左下角要显示未完成的任务数量,数字是由<strong>标签包装的。还要将item单词多元化(1没有s,其他的数字均有s):0 items,1 item,2 items
分析:当todoList数组中的元素有改变,则重新计算未完成任务数量,可以通过计算属性来获取未完成的任务数量,通过数组函数filter过滤未完成任务,然后进行汇总,当数据为1不显示s,否则显示。

  1. 从 vue 中导入 computed 方法,通过此方法定义一个计算属性 remaining
import { reactive, toRefs,computed } from "vue";
const remaining = computed(()=> state.todoList.filter(item=>!item.completed).length)
  1. 在模板添加剩余任务数remaining,将模板中显示的 item 单词多元化( 1 没有 s , 其他数字均有 s )
    添加 {{ remaining === 1 ? ‘’ : ‘s’ }}
<span class="todo-count"><strong>{{remaining}}</strong> item{{ remaining === 1 ? '' : 's' }} left</span>

Vue3+TS+Vite+Pinia最全学习总结

切换所有任务状态功能实现

需求:点击复选框v后,将所有任务状态标记为复选框相同的状态,当“选中/取消”某个任务后,复选框v也应该同步更新状态。
分析:复选框状态发生变化后,就迭代出每一个任务项,再将复选框状态赋值给每个任务项即可,为复选框绑定一个计算属性,通过这个计算属性的set方法监听复选框更新后就更新任务状态。
当所有未完成任务数remaining为0的时候,表示所有任务都完成了,就可以选中复选框,在复选框绑定的计算属性get方法中判断remaining是否为0,为0则返回true,复选框会自动选中,反之补选中,当remaining发射管变化后,会自动更新复选框状态)。
代码:

const toggleAll = computed({get(){return remaining.value === 0},set(newStatus){console.log("newStatus",newStatus)state.todoList.forEach(item=>item.completed=newStatus)}
})

模板代码:

<input id="toggle-all" v-model="toggleAll" class="toggle-all" type="checkbox" />
<label for="toggle-all">Mark all as complete</label>

效果:
Vue3+TS+Vite+Pinia最全学习总结

移除任务项

需求:悬停在某个任务项顶上显示x移除按钮,可点击移除当前任务项。
分析,x移除按钮处添加点击事件,通过数组函数splice移除任务项。

  1. 模板中添加点击事件:@click="removeData(index)"

    <button class="destroy" @click="removeData(index)"></button>
    
  2. 添加函数 removeData , 通过 state.todoList.splice(index, 1) 移除

    function removeData(index: number) {// 移除索引为index的一条记录state.todoList.splice(index, 1);
    }
    
清除所有已完成任务实现

需求:单机右下角clear completed按钮时候,移除所有已安城任务。当列表中没有已完成的任务时,应该吟唱clear completed按钮。
分析:页面增加点击事件:@click="removeCompleted",添加removeCompleted函数实现过滤处所有未完成的任务项,将过滤出来的未完成数据赋值给items数组,已完成的任务就被删除。
clear completed按钮上使用v-show,当总任务数(items.length)>未完成数(remaining),说明列表中还有已完成数据,则是显示按钮,反之不显示,实现v-show="items.length>remaining"

  1. 模板中添加点击事件:@click="removeCompleted"

    <button class="clear-completed" @click="removeCompleted">Clear completed</button>
    
  2. 添加函数 removeCompleted , 通过数组的 filter 函数过滤出所有未完成的任务项,将过滤出来的未完成数据赋值给 todoList 数组.

    //移除所有已完成任务项
    function removeCompleted() {// 过滤出所有未完成的任务(这样已完成的就没有了),然后赋值过滤后的数组即可state.todoList = state.todoList.filter((item) => !item.completed);
    }
    
  3. 在模板中使用 v-show="todoList.length > remaining" 进行切换显示/隐藏 Clear completed 按钮.

     <button v-show="todoList.length > remaining" class="clear-completed" @click="removeCompleted">Clear completed</button>
    

效果:
Vue3+TS+Vite+Pinia最全学习总结

编辑任务项

需求1:双击<label>(某个任务项)进入编辑状态,在<li>上通过.editing进行状态切换。
分析1:为label绑定双击事件@dbclick=toEdit(item),当item任务项=== current,当前点击任务项,新定义的响应式属性时,在<li>上就显示.editing样式,格式class={editing:item===current}
需求2:进入编辑状态后,输入框显示原内容,并会自动获取编辑焦点。
分析2:在 <input> 单向绑定输入框的值即可 :value="item.content"
通过自定义指令获取编辑焦点
需求3:输入状态按esc取消编辑,.editing样式应该被移除。
分析3:为 <input> 绑定 Esc 按键事件 @keyup.esc=cancelEdit ,将 current 值变为 null
需求4:按Enter键或市区焦点时,保存改变数据,移除.edition样式。
分析4:添加事件@keyup.enter=finishEdit(item, $event)@blur="finishEdit(item, $event)",通过 $event 变量获取当前输入框的值,使用.trim()去空格后进行判断是否为空,如果为空则清除这条任务,否则修改任务项;添加数据保存任务项中将current值变为 null ,移除 editing 样式。

  1. <label>绑定双击事件 @dblclick="toEdit(item)" , 将迭代出来的每个 item 传入当前行的 toEdit函数中, 在 <li> 上判断是否显示 .editing 样式 :class={editing: item === current}

    <liclass="completed"v-for="(item, index) in todoList":key="item.id":class="{ completed: item.completed,editing:item===current}"
    ><div class="view"><inputclass="toggle"type="checkbox"v-model="item.completed"checked/><label  @dblclick="toEdit(item)">{{ item.content }}</label><button class="destroy" @click="removeData(index)"></button></div><input class="edit" value="Create a TodoMVC template" /></li>
    
  2. . 在State接口中添加 current ,state 中添加属性 current,添加函数 toEdit(item) 接收到点击的那个 item 后,将它赋值给 current ,那对应的任务项就会进入编辑状态 this.current = item。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一起畅玩!幻兽帕鲁服务器1分钟开服,不服来战!

玩转幻兽帕鲁服务器&#xff0c;阿里云推出新手0基础一键部署幻兽帕鲁服务器教程&#xff0c;傻瓜式一键部署&#xff0c;3分钟即可成功创建一台Palworld专属服务器&#xff0c;成本仅需26元&#xff0c;阿里云服务器网aliyunfuwuqi.com分享2024年新版基于阿里云搭建幻兽帕鲁服…

Facebook的社交影响力:用户行为解析与趋势

在当今数字时代&#xff0c;社交媒体已经成为人们日常生活中不可或缺的一部分&#xff0c;而Facebook作为全球最大的社交平台之一&#xff0c;其社交影响力愈发显著。本文将深入分析Facebook的社交影响力&#xff0c;解析用户行为&#xff0c;同时探讨当前和未来的社交趋势。 社…

vue项目线上页面刷新报404 解决方法

一.修改配置文件 nginx.conf &#xff0c;并重新加载或重启 我的nginx版本是1.9.9 location / {try_files $uri $uri/ /index.html; }原因&#xff1a; 打包后的dist下只有一个 index.html 文件及一些静态资源&#xff0c;这个是因为Vue是单页应用(SPA)&#xff0c;只有一个…

学习Spring的第十三天

非自定义bean注解开发 设置非自定义bean : 用bean去修饰一个方法 , 最后去返回 , spring就把返回的这个对象,放到Spring容器 一 :名字 : 如果bean配置了参数 , 名字就是参数名 , 如果没有 , 就是方法名字 二 : 如果方法产生对象时 , 需要注入数据 , 在方法参数设置即可 , …

SOME/IP SD 协议介绍(四)服务发现通信行为

服务发现通信行为 启动行为 服务发现将根据服务实例处于以下三个阶段之一&#xff1a; • 初始等待阶段 • 重复阶段 • 主要阶段 一旦系统启动并且用于服务实例的接口连接已建立&#xff0c;服务发现将进入该服务实例的初始等待阶段。 在进入初始等待阶段并在发送第一条服…

VScode+PlatformIO 物联网Iot开发平台环境搭建

1.vscode &#xff08;1&#xff09;安装platformIO插件 &#xff08;2&#xff09;新建项目或导入已有的arduino项目 Name&#xff1a;需要填写你项目的名称&#xff1b; Board&#xff1a;点开是一个下拉框&#xff0c;但是可以输入你想要的开发板&#xff0c;这里选择&quo…

你所不知道的关于库函数和系统调用的那些事

系统调用和库函数的区别 相信大家在面试或者刷面试题的时候经常能看到这样的问题&#xff0c;“简述一下系统调用和库函数的区别”。 系统调用是操作系统提供给用户的接口&#xff0c;能让用户空间的程序有入口访问内核。而库函数数一组标准函数&#xff0c;比如复合 POSIX 或…

90.网游逆向分析与插件开发-游戏窗口化助手-项目需求与需求拆解

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;实现物品使用策略的功能-CSDN博客 项目需求&#xff1a; 在游戏窗口化时&#xff0c;可以在游戏之外弹出一个窗口&#xff0c;可以隐藏或者显示游戏窗口&#xff0c;显示游戏人物的基本状态&#xff…

LeetCode--代码详解 2.两数相加

2.两数相加 题目 难度&#xff1a;中等 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数…

【JavaScript + CSS】随机生成十六进制颜色

效果图 实现 <template><div class"year_area"><div class"year_list"><el-row :span"24"><div :class"showAll"><el-col :span"5" v-for"(item, index) in defaulList" :key&…

电动汽车充放电V2G模型(matlab代码)

目录 1 主要内容 1.1 模型背景 1.2 目标函数 1.3 约束条件 2 部分代码 3 效果图 4 下载链接 1 主要内容 本程序主要建立电动汽车充放电V2G模型&#xff0c;采用粒子群算法&#xff0c;在保证电动汽车用户出行需求的前提下&#xff0c;为了使工作区域电动汽车尽可能多的消…

迁移学习实现图片分类任务

导入工具包 import time import osimport numpy as np from tqdm import tqdmimport torch import torchvision import torch.nn as nn import torch.nn.functional as Fimport matplotlib.pyplot as plt %matplotlib inline# 忽略烦人的红色提示 import warnings warnings.fi…

okhttp 的 拦截器

拦截器有很多作用&#xff0c;实现就是责任链模式&#xff0c;细节&#xff0c;等我有时间补上。 后面有时间更新一下。 OkHttp最核心的工作是在 getResponseWithInterceptorChain() 中进行&#xff0c;在进入这个方法分析之前&#xff0c;我们先来了 解什么是责任链模式&…

Java split 分割字符串避坑

使用split进行字符串分割时需要注意2点 1、特殊字符作为分隔符时需要使用\\进行转义(如\\ -> \\\\; | -> \\| ) 特殊字符 .$|()[{^?*\\ 例如对"|"分隔 未转义 String str "01|02|03"; String[] strArr str.split("|");System.out.…

点击按钮打开自定义iframe弹窗

1、效果 点击按钮打开弹窗&#xff1a; 打开弹窗后&#xff1a; 2、代码 <!DOCTYPE html> <html><head><title>iframe弹窗</title><style>/* 使用媒体查询来实现响应式设计 */media (min-width: 768px) {.popup {width: 80%; /* 设置…

【c/python】GtkBox

一、GtkBox及C语言示例 GtkBox是一个容器部件&#xff0c;用于在GTK&#xff08;GIMP Toolkit&#xff09;应用程序中水平或垂直地排列多个子部件。以下是一个简单的例子&#xff0c;展示了如何在一个基本的GTK应用程序中使用GtkBox来垂直排列两个按钮&#xff1a; 首先&#…

用Python Tkinter打造的精彩连连看小游戏【附源码】

文章目录 连连看小游戏&#xff1a;用Python Tkinter打造的精彩游戏体验游戏简介技术背景MainWindow类:职责:方法:Point类: 主执行部分:完整代码&#xff1a;总结&#xff1a; 连连看小游戏&#xff1a;用Python Tkinter打造的精彩游戏体验 在丰富多彩的游戏世界中&#xff0c…

左旋字符串的三种方法,并判断一个字符串是否为另外一个字符串旋转之后的字符串。(strcpy,strncat,strcmp,strstr函数的介绍)

一. 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到BCDA ABCD左旋两个字符得到CDAB 通过分析&#xff0c;可以知道实际的旋转次数&#xff0c;其实是k%&#xff08;字符串长度&#xff09;。假设一个字…

西瓜书学习笔记——流形学习(公式推导+举例应用)

文章目录 等度量映射&#xff08;仅保留点与其邻近点的距离&#xff09;算法介绍实验分析 局部线性嵌入&#xff08;不仅保留点与其邻近点的距离还要保留邻近关系&#xff09;算法介绍实验分析 等度量映射&#xff08;仅保留点与其邻近点的距离&#xff09; 算法介绍 等度量映…

树莓派5一键安装C++版本OpenCV

安装环境 本人当前的安装环境&#xff1a; 树莓派5Raspberry Pi os (64-bit) Debian12 Bookworm 镜像下载地址 我这里是将镜像安装好后直接安装opencv&#xff0c;如果不是刚安装好的镜像需要注意是否有openCV的python之类的安装过&#xff0c;不然可能出现编译错误 一、扩展内…