一、vue3简介
vue3的优点
(1)、最火框架,它是国内最火的前端框架之一
(2)、性能提升,运行速度是vue2.x的1.5倍左右
(3)、体积更小,按需编译体积比vue2.x要更小
(4)、类型推断,更好的支持Ts(typescript)这个也是趋势
(5)、高级给予,暴露了更底层的API和提供更先进的内置组件
(6)、★组合API (composition api)★ ,能够更好的组织逻辑,封装逻辑,复用逻辑
Composition API 又名组合式API,我们要知道 我们常用的vue2使用的是OptionAPI,简单的说就是我们熟悉的 data, computed , method等等,但是在vue3中 我们并不建议使用OptionAPI。
在Vue2中 我们一个功能得分不同的许多地方 比如数据放在data中 方法放在methods中 分开的特别散乱 一两个功能还好 但是页面可能功能很多 这就会显得特别的杂乱无章
为此,Vue3 提出了 Composition API,它可以把 **一个逻辑的代码都收集在一起 **,然后单独写个hook 并引入 这样就不会到处分布
Vue3展望
这是趋势,越来越多的企业将来肯定会升级到Vue3.0
大型项目,由于对Ts的友好越来越多大型项目可以用Vue3.0
我们广州前端学科已经安排好vue3的项目,一共有15天左右
vite介绍
vite是vue 官方提供另外一种快速创建工程化的 SPA 项目的方式(一共有两种方式)
1、 基于 vite 创建 SPA 项目
2、基于 vue-cli 创建 SPA 项目
它是一个更加轻量(热更新速度快,打包构建速度快)的vue项目脚手架工具。相对于vue-cli它默认安装的插件非常少,随着开发过程依赖增多,需要自己额外配置。
所以: 在单纯学习vue3语法会使用vite,后面做项目的时候我们还是使用vue-cli
这里我们还是使用脚手架创建项目,注意选择版本为3.X
改造项目的目录结构
1.删除App中对HelloWorld组件的引入和使用
2.删除HelloWorld组件
3.下载normalize.css重置样式文件并在main.js中引入
4.将提供的图片和项目中的公共样式添加到assets目录下(静态资源)
5.引入common.less文件
基于vite创建vue3的项目
按照顺序执行如下的命令,即可基于vite创建 wue 3.x 的工程化项目
npm init vite-app 项目名称 cd 项目名称 npm install npm run dev
npm init vite-app报错
D:\project\vue3Projects>npm init vite-app vue3-vite-project npm ERR! code ENOLOCAL npm ERR! Could not install from "Files\nodejs\npm_cache\_npx\2992" as it does not contain a package.json file. npm ERR! A complete log of this run can be found in: npm ERR! C:\Program Files\nodejs\npm_cache\_logs\2023-01-29T10_54_38_363Z-debug.log Install for [ 'create-vite-app@latest' ] failed with code 1
解决办法:
- 管理员身份运行powershell
- npm config set cache C:\Users\miracle\AppData\Roaming\npm-cache
- npm config set prefix C:\Users\miracle\AppData\Roaming\npm
如下所示:
项目结构
使用 vite 创建的项目结构如下
其中:
node_modules 目录用来存放第三方依赖包
public 是公共的静态资源目录
src 是项目的源代码目录(程序员写的所有代码都要放在此目录下)
.gitignore 是 Git 的忽略文件
index.html 是 SPA 单页面应用程序中唯一的 HTML 页面
package.json 是项目的包管理配置文件
梳理项目的结构
在 src 这个项目源代码目录之下,包含了如下的文件和文件夹
其中:
assets 目录用来存放项目中所有的静态资源文件(css、fonts等)
components 目录用来存放项目中所有的自定义组件
App.vue 是项目的根组件
index.css 是项目的全局样式表文件
main.js 是整个项目的打包入口文件
vite 项目的运行流程
在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。
其中:
① App.vue 用来编写待渲染的模板结构
② index.html 中需要预留一个 el 区域
③ main.js 把 App.vue 渲染到了 index.html 所预留的区域中--入口
单文件组件的渲染
1.通过解构的方式获取createApp
2.通过createApp创建应用
const app = createApp(App)表示:创建一个应用,渲染指定的组件App.vue
app.mount('#app1')表示:将组件挂载到页面指定位置(App.vue中id为app1的标签)。
单文件组件的template
vue 规定:每个组件对应的模板结构,需要定义到 <template> 节点中
在 vue 3.x 的版本中,<template> 节点内的 DOM 结构支持多个根节点。
注意:<template> 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素。
vue2和vue3的数据定义也是不一样的。
vue2中定义数据采用如下方式:
export default { data() { return { } }, }
在vue3中这样写一样可以使用,但是这是vue2的写法,下面我们将讲到用vue3来定义数据。vue3中可以写vue2的代码,如果我们用vue3,一般很少用vue2来写代码。
App.vue文件修改如下:
<template> </template> <script> export default { name: 'App' } </script>
组合API-Compositon API
(1)、在vue3.0项目中将会使用 组合API 写法;
(2)、代码风格:一个功能逻辑的代码组织在一起(包含数据,函数,计算属性,侦听器...);
(3)、优点:功能逻辑复杂繁多情况下,各个功能逻辑代码组织再一起,便于阅读和维护;
(4)、缺点:需要有良好的代码组织能力和拆分逻辑能力;
补充:为了能让大家较好的过渡到vue3.0的版本来,也支持vue2.x选项API写法。
参考:https://www.vue3js.cn/docs/zh/guide/composition-api-introduction.html
一、setup
(1)、setup() 函数是 vue3 中专门为组件提供的新属性。它为我们使用vue3的新特性提供了统一的入口(很多代码都会在这个函数中,当然代码可以写出去);
<template> </template> <script> export default { name: 'App', setup(){ console.log("运行了setup") } } </script>
浏览器控制台打印:运行了setup
说明setup是自动触发的钩子函数。
(2)、setup函数在生命周期函数 beforeCreate(组件实例创建之前)之前触发,所有无法获取一this,意味着setup函数中是无法 使用 data 和 methods 中的数据和方法的;
注意beforeCreate是vue2的钩子函数。
<template> </template> <script> export default { name: 'App', setup(){ console.log("运行了setup") }, beforeCreate() { console.log("运行了beforeCreate") } } </script>
浏览器控制台打印如下:
运行了setup
运行了beforeCreate
测试能否拿到data中的数据
<template> </template> <script> export default { name: 'App', setup(){ console.log("运行了setup") console.log(this.name) }, beforeCreate() { console.log("运行了beforeCreate") }, data() { return { name: 'jack' } }, } </script>
结果报错如下:
(3)、在setup函数中定义的属性和方法最后都是需要 return 出去的,这样我们就可以在模板中直接访问该对象中的属性和方法;
setup中定义变量
(1)、定义数据
在setup函数中可以按传统方式定义变量和像变量一样定义函数,使用插值表达式用变量。如下:
定义变量时,用let表示变量,const表示常量。
<template> <p>{{msg}}</p> </template> <script> export default { name: 'App', setup(){ let msg = 'hello' const say = () =>{ console.log("我对你说" + msg); } }, data() { return { } }, } </script>
此时浏览器控制台打印:[Vue warn]: Property "msg" was accessed during render but is not defined on instance.
这是因为setup中定义的变量和函数一定要return
<template> <p>{{msg}}</p> </template> <script> export default { name: 'App', setup(){ let msg = 'hello'return { msg } }, data() { return { } }, } </script>
浏览器中显示:
定义函数:函数中使用变量时,不需要使用this,因为变量就在函数的上面,他们在一起。
<template> <p>{{msg}}</p> <button @click="say">点我</button> </template> <script> export default { name: 'App', setup(){ let msg = 'hello' const say = () =>{ console.log("我对你说" + msg); } return { msg,say } }, data() { return { } }, } </script>
点击按钮,浏览器控制台打印:我对你说hello
(2)、一定要记得在setup中返回;
(3)、只有return了才可以在模板中使用,如果不return只可以在setup函数内使用;
注意:这种方式定义的数据(let msg = 'hello'),在后期进行操作之后,模板不会自动的刷新--它是非响应式数据。
<template> <p>{{msg}}</p> <button @click="say">点我</button> <button @click="change">点我修改变量</button> </template> <script> export default { name: 'App', setup(){ let msg = 'hello' const say = () =>{ console.log("我对你说" + msg); } const change = () =>{ msg = 'hi' } return { msg,say,change } }, data() { return { } }, } </script>
效果如下:
此时点击“点我修改变量”,发现msg并没有变化。
setup中定义响应式数据
(1)、引入reactive函数
(2)、通过reactive定义对象类型的响应式数据
通过reactive函数定义数据。
<template> <p>{{obj.msg}}</p> <button @click="say">点我</button> <button @click="change">点我修改变量</button> </template> <script> import { reactive } from 'vue' export default { name: 'App', setup(){ const obj = reactive({ msg: 'hello' }) const change = () =>{ obj.msg = 'hi' } return { change,obj } }, data() { return { } }, } </script>
效果如下:
点击之后,变为
如果我们只需要一个变量msg,而不想要一个对象,如上的obj,但是又不能写成let msg = 'hello',那怎么样才能变成响应式的呢?此时可以用ref函数:用于简单数据定义为响应式。
<template> <p>{{msg}}</p> <button @click="say">点我</button> <button @click="change">点我修改变量</button> </template> <script> import { ref } from 'vue' export default { name: 'App', setup(){ let msg = ref('hello')
console.log(msg) const change = () =>{ msg = 'hi' } return { change,msg } }, data() { return { } }, } </script>
msg打印如下:
由于数据会进行包装,修改时需要修改ref变量的value属性
const change = () =>{ msg.value = 'hi' }
注意:在模板中使用的时候不用加value,但是在setup函数中该ref变量的时候需要加value。
<template> <p>{{count}}</p> <button @click="add">点我+1</button> </template> <script> import { ref } from 'vue' export default { name: 'App', setup(){ let count = ref(0) const add = () =>{ count.value++ } return { add,count } }, } </script>
(3)、一定要记得返回
我们在组件中用到的数据、方法等等,都要配置在setup中。setup函数的返回值返回一个对象,对象中的属性和方法在模板中可以直接使用
setup函数的参数
现在我们来演示父组件给子组件传递数据
注意:子组件注册到父组件还是和vue2中一样注册到components中
父组件
<template>
<h2>我是父组件</h2>
<son :mymoney="money"></son>
</template>
<script>
import son from './son.vue'
export default {
name: 'App',
components: { son },
setup(){
const money = 100
return {
money
}
},
data() {
return {
}
},
}
</script>
子组件
<template>
<h3>我是子组件</h3>
<p>父组件传递过来的数据:{{mymoney}}</p>
</template>
<script>
export default {
name: "son",
props: {
mymoney: {
type: Number
}
}
}
</script>
效果如下:
如果父组件传递的不是数值100,而是字符串“一套房”,假如一套房对应100万,我们就需要在子组件中进行处理
注意:setup中通过props中获取父传子的数据
<template> <h3>我是子组件</h3> <p>父组件传递过来的数据:{{money}}</p> </template> <script> import {ref} from 'vue' export default { name: "son", props: { mymoney: { type: Number } }, setup(props){ console.log(props) console.log(props.mymoney) const money = ref(0) if (props.mymoney === '一套房') { money.value = 100000 } return { money } } } </script>
控制台打印如下:
效果如下:
对于 setup 函数来说,它接收两个参数,分别为:
(1)、props(名称可以自定义):通过 prop 传递过来的所有数据(父传子的数据),我们都可以在这里进行接收。并且获取到的数据将保持响应性;
(2)、context(名称可以自定义):context 是一个 JavaScript 对象,这个对象暴露了三个组件的属性,我们可以通过 ‘解构 ’的方式来分别获取这三个属性
attrs: 它是绑定到组件中的 非 props 数据(没有用props接收的值),并且是非响应式的,如果后期需要需要对这个值进行处理需要变成响应式数据。
slots: 是组件的插槽,同样也不是 响应式的。
emit:是一个方法,相当于 vue2 中的 this.$emit 方法,可用于实现子传父;
目标:子传父的实现
1.子组件发出事件
2.父组件进行监听
3.在父组件监听处理函数中接收子组件传递的数据
父组件
<template> <h2>我是父组件</h2> <son :mymoney="money" name ="jack"></son> </template> <script> import son from './son.vue' export default { name: 'App', components: { son }, setup(){ const money = '一套房' return { money } }, data() { return { } }, } </script>
子组件
<template> <h3>我是子组件</h3> <p>父组件传递过来的数据:{{money}}</p> </template> <script> import {ref} from 'vue' export default { name: "son", props: { mymoney: { type: Number }, name: { type: String } }, setup(props,context){ console.log(props,context) console.log(props.mymoney) const money = ref(0) if (props.mymoney === '一套房') { money.value = 100000 } return { money } } } </script>
控制台打印如下:
如果子组件中的props不接受name,如下所示:
props: {
mymoney: {
type: Number
},
},
那么name就不是props中的属性
此时控制台打印如下:
发现props中没有name的值,但是context中的attrs有name的值。
当打印context.attrs.name时
setup(props,context){ console.log(props,context.attrs.name) console.log(props.mymoney) const money = ref(0) if (props.mymoney === '一套房') { money.value = 100000 } return { money } }
结果为:
我们还可以解构attrs
setup(props, {attrs}){ console.log(props,attrs.name) console.log(props.mymoney) const money = ref(0) if (props.mymoney === '一套房') { money.value = 100000 } return { money } }
现在来实现子组件给父组件传值
父组件
<template> <h2>我是父组件</h2> <son :mymoney="money" name ="jack" @getValue="deal"></son> </template> <script> import son from './son.vue' export default { name: 'App', components: { son }, setup(){ const money = '一套房' const deal = (data)=>{ console.log("子组件传递的数据" + data) } return { money,deal } }, data() { return { } }, } </script>
子组件
<template> <h3>我是子组件</h3> <p>父组件传递过来的数据:{{money}}</p> <button @click="sendValue">单击实现子传父</button> </template> <script> import {ref} from 'vue' export default { name: "son", props: { mymoney: { type: Number }, }, setup(props, {attrs,emit}){ console.log(props,attrs.name) console.log(props.mymoney) const money = ref(0) if (props.mymoney === '一套房') { money.value = 100000 } const sendValue = () =>{ // 发出事件 emit('getValue',200) } return { money,sendValue } } } </script>
效果:
单击后控制台打印:子组件传递的数据200
(1)在vue2的配置中可读取到vue3配置中的属性和方法
methods: { test1() { console.log(this.sex); console.log(this.sayHello); } }, setup() { const sex = ref('女') function sayHello() { alert('你好啊') } return { sex, sayHello } }
(2)在vue3的配置中不能读取vue2配置中的属性和方法
data() { return { sex:'男' } }, methods: { sayHello() { alert('你好啊') } }, setup() { function test2() { console.log(this.sex); console.log(this.sayHello); } return { test2 }
(3)如果vue2和vue3的配置有冲突,则vue3的setup优先
data() { return { sex:'男' } }, setup() { const sex = ref('女') return { sex } }
注意点:
(1)vue2和vue3的配置尽量不要混用;
- Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。
- 但在setup中不能访问到Vue2.x配置(data、methos、computed…)。
- 如果有重名, setup优先。
(2)setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
setup执行的时机
在beforeCreate之前执行一次,this是undefined。
beforeCreate(){ console.log('beforeCreate'); }, setup(){ console.log('setup',this); }
setup的参数
1、props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
在父组件中给子组件传递数据
<Demo msg="你好啊" name="tom" />
在子组件中接收
props:['msg','name'], // 需要声明一下接受到了,否则会报警告 setup(props){ console.log(props) }
并且接收到的数据被包装成一个代理对象,能够实现响应式。
二、<script setup>
我们可以在组件的script中添加 setup
相比普通script语法的优势
<script setup>
是在单文件组件 (SFC)
中使用组合式 API
的编译时语法糖
。相比于普通的 <script> 语法
,它具有更多优势
- 更少的样板内容,更简洁的代码
- 能够使用纯 Typescript 声明 props 和抛出事件。
- 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
- 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。
基本语法
- 要使用这个语法,需要将 setup attribute 添加到
<script> 代码块上
- 里面的代码会
被编译成组件 setup() 函数的内容
。这意味着与普通的<script>
只在组件被首次引入
的时候执行一次
不同,<script setup>
中的代码会在每次
组件实例被创建
的时候执行。
顶层的绑定会被暴露给模板
- 当使用
<script setup>
的时候,任何在<script setup> 声明的顶层的绑定
(包括变量,函数声明,以及 import 引入的内容) 都能在模板
中直接使用
<script setup> // 变量 const msg = 'Hello!' // 函数 function log() { console.log(msg) } </script> <template> <div @click="log">{{ msg }}</div> </template>
- import 导入的内容也会以同样的方式暴露。意味着
可以在模板表达式中直接使用导入的 helper 函数
,并不需要通过 methods 选项来暴露它:
<script setup> import { capitalize } from './helpers' </script> <template> <div>{{ capitalize('hello') }}</div> </template>
响应式
- 响应式状态需要
明确使用响应式 APIs 来创建
。和从 setup() 函数中返回值一样,ref 值在模板中使用的时候会自动解包
:
<script setup> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++">{{ count }}</button> </template>
使用组件
<script setup>
范围里的值也能被直接作为自定义组件的标签名使用:
<script setup> import MyComponent from './MyComponent.vue' </script> <template> <MyComponent /> </template>
动态组件
- 由于组件被引用为变量而不是作为字符串键来注册的,在
<script setup> 中要使用动态组件
的时候,就应该使用动态的:is
来绑定
<script setup> import Foo from './Foo.vue' import Bar from './Bar.vue' </script> <template> <component :is="Foo" /> <component :is="someCondition ? Foo : Bar" /> </template>
递归组件
- 一个单文件组件可以通过它的文件名被其自己所引用。例如:名为 FooBar.vue 的组件可以在其模板中用
<FooBar/>
引用它自己。
- 请注意这种方式相比于 import 导入的组件优先级更低。如果有命名的 import 导入和组件的推断名冲突了,可以使用 import 别名导入:
import { FooBar as FooBarChild } from './components'
命名空间组件
- 可以使用带点的组件标记,例如
<Foo.Bar>
来引用嵌套在对象属性中的组件。这在需要从单个文件中导入多个组件的时候非常有用:
<script setup> import * as Form from './form-components' </script> <template> <Form.Input> <Form.Label>label</Form.Label> </Form.Input> </template>
使用自定义指令
- 全局注册的自定义指令将以符合预期的方式工作,且本地注册的指令可以直接在模板中使用,就像上文所提及的组件一样。
- 但这里有一个需要注意的限制:
必须以 vNameOfDirective 的形式来命名本地自定义指令
,以使得它们可以直接在模板中使用
<script setup> const vMyDirective = { beforeMount: (el) => { // 在元素上做些操作 } } </script> <template> <h1 v-my-directive>This is a Heading</h1> </template>
<script setup> // 导入的指令同样能够工作,并且能够通过重命名来使其符合命名规范 import { myDirective as vMyDirective } from './MyDirective.js' </script>
三、reactive
和ref类似,但是reactive函数定义的是对象类型的响应式数据
语法
const 代理对象 = reactive (源对象)接受一个对象或者数组
返回一个代理对象 (Proxy的实例对象 简称Proxy对象)
<template> <p>姓名:{{obj.name}}</p> <p>年龄:{{obj.age}}</p> <button @click ='change()'>点击变化</button> </template> <script setup> import { reactive } from 'vue' // 语法 const obj = reactive({ name: '张三', age:18 }) function change(){ obj.name = '小丑' obj.age = 24 } </script>
点击之后变成
reactive是Vue3中提供实现响应式数据的方法;在Vue2中响应式数据是通过defineProperty来实现的;而在Vue3响应式数据是通过ES6的Proxy来实现的
使用reactive注意事项
- reactive参数必须是对象(json/arr)
- 如果给reactive传递了其他对象,默认情况下修改对象,界面不会自动更新,如果想更新,可以通过重新赋值的方式.
下面我们来演示给reactive传递了其他对象,页面不会自动更新的情况
<template> <div> <div @click="handleClick"> {{state}} </div> </div> </template> <script> import {reactive} from 'vue'; export default { name: "reactiveTest", setup() { // 本质是转成proxy对象 let state = reactive(1); function handleClick() { state ++ console.log(state) } return {state,handleClick} } } </script>
启动后,单击页面上的1,发现页面不会自动更新,但是控制台会更新
正确使用
<template> <div> <div @click="handleClick"> {{state.age}} </div> </div> </template> <script> import {reactive} from 'vue'; export default { name: "reactiveTest", setup() { // 本质是转成proxy对象 let state = reactive({age:1}); function handleClick() { state.age ++ console.log(state) } return {state,handleClick} } } </script>
启动后,单击页面上的1,发现页面和控制台都会更新
四、defineProps和defineEmits
- 在
<script setup>
中必须
使用defineProps
和defineEmits
API 来声明 props 和 emits
,它们具备完整的类型推断并且在 <script setup> 中是直接可用的
<script setup> const props = defineProps({ foo: String }) const emit = defineEmits(['change', 'delete']) // setup code </script>
defineProps
和defineEmits
都是只在 <script setup> 中才能使用的编译器宏
。他们不需要导入
且会随着 <script setup> 处理过程一同被编译掉
。
- defineProps 接收与 props 选项相同的值,defineEmits 也接收 emits 选项相同的值
- defineProps 和 defineEmits 在选项传入后,会提供恰当的类型推断
- 传入到 defineProps 和 defineEmits 的选项会从 setup 中提升到模块的范围。因此,传入的选项不能引用在 setup 范围中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块范围内
vue2中父组件给子组件传参是这样的:
props: { content: { type: String, default: "" }, name: { type: String, default: "查看" }, },
vue3中父组件给子组件传参是这样的:
<script setup> import {ref, defineProps} from 'vue' const props = defineProps({ content: { type: String, default: "" }, name: { type: String, default: "查看" }, })</script>
vue2中子组件触发父组件的事件是这样子的:
子组件
this.$emit('pagination', { page: this.currentPage, limit: val }, { page: this.page, limit: this.limit })
父组件
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" :pager-count="5" @pagination="getList" />
vue3中子组件触发父组件的事件是这样子的:
子组件
import { defineEmits} from 'vue' const emits = defineEmits(['pageChange']);
父组件
<pagination :total="total" @pageChange="handleChangePage" :pageNum="listQuery.page" :pageSize="listQuery.limit"/>
五、组合API-ref函数
(1)、ref函数,常用于简单数据类型定义为响应式数据
(2)、细节:在修改值,获取值的时候,需要.value
(3)、注意:在模板中使用ref申明的响应式数据,可以省略.value
在我们vue2中 ref被拿来打标识 类似于id,但在vue3中 ref是作为函数来定义一个响应式的数据
语法:
先引入
import { ref } from 'vue'
然后使用
let(const或者var 也可以)xxx = ref(数据的初始值)
我们把ref定义的数据 叫做引用实现的实例对象,简称引用对象
<template> <p>{{ a }}</p> <button @click="a+=1">add</button> </template> <script setup> import { ref } from 'vue' let a = ref(2) </script> <style scoped> </style>
每点击一次就会加1
vue2中定义数据如下:
data() { return { dialogVisible: false, desc: null }; },
vue3中定义数据如下:
const dialogVisible = ref(false) const desc = ref(null)
语法规则如下:
- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
- JS中操作数据:
xxx.value
- 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成的。 - 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数——
reactive
函数
ef和reactive一样,也是用来实现响应式数据的方法,由于reactive必须传递一个对象,所以在实际开发中如果只是想让某个变量实现响应式的时候回非常麻烦,所以Vue3提供了ref方法实现简单值得监听
ref的本质:ref底层其实还是reactive,所以当运行时系统会自动根据传入的ref转换成reactive.
ref的注意点:
- 在vue中使用ref的值不用通过value获取
- 在js中使用ref的值必须通过value获取
ref获取元素
在vue2中我们可以通过给元素添加ref=‘xxx’,然后在代码中通过refs.xxx的方式来获取元素,在vue3中也可以通过ref来获取元素.
但不是像以下这种熟悉的方式,因为在vue3中没有$和refs这些东西.
<template> <div> <div ref="box">我是div</div> </div> </template> <script> import { ref,onMounted } from "vue"; export default { setup() { let box = ref(null); //本质是reactive({value:null}) // 需要在生命周期获取 onMounted(()=>{ // 当界面挂载出来后就会自动执行 console.log(box.value); }) //接受的是null,原因是setup执行时机比mounted早,dom还没形成 console.log(box.value); return { box }; }, }; </script>
方法中改变ref数据,需要给引用对象加上.value
<script setup> import { ref } from 'vue' let a = ref(2) function gb() { a.value = 16 } </script>
ref改变对象
function gb() { a.value = 16 b.value.sex = '女' b.value.age = '22' }
五、watch
vue2中监听是这样子的:
watch: { page(newVal, oldVal) { this.lastPage = oldVal } },
监听基础类型
监听ref
<template> <p>{{ age }}</p> <button @click="age+=1">add</button> </template> <script setup> import {ref,watch} from 'vue' const age = ref(10) watch(age,(newval,oldval) => { console.log(newval); console.log(oldval); }) </script>
结果如下:
监视多个
<template> <p>{{ age }}</p> <p>{{ name }}</p> <p>{{ sex }}</p> <button @click="change()">改变</button> </template> <script setup> import {ref,watch} from 'vue' const age = ref(10) const name = ref('张三') const sex = ref('男') // watch(age,(newval,oldval) => { // console.log(newval); // console.log(oldval); // }) function change(){ age.value = 100 name.value = '李四' sex.value = '女' } // 添加属性 watch([age,name,sex],(newval,oldval) => { console.log(`新值${newval}`); console.log(`旧值${oldval}`); },{immediate:true, deep:true}) </script>
结果如下:
监听复杂类型
复杂类型的监听有很多种情况,具体的内容如下
1、监听整个对象
其第一个参数是直接传入要监听的对象。当监听整个对象时,只要这个对象有任何修改,那么就会触发 watch 方法。无论是其子属性变更(如 demo.name),还是孙属性变更(如 demo.soulmate.name)...,都是会触发 watch 方法的。
<template> <p>{{ demo.name }}</p> <button @click="change()">改变</button> </template> <script setup> import {ref,watch,reactive} from 'vue' function change(){ demo.name = '前端小九' } const demo = reactive({ name: '前端小玖', nickName: '小玖', soulmate: { name: '后台小王', nickName: '小王' } }) watch(demo, (newValue, oldValue) => { console.log('watch 已触发', newValue.name) }) </script>
结果:
2、监听对象中的某个属性
<template> <p>{{ demo.name }}</p> <button @click="change()">改变</button> </template> <script setup> import {ref,watch,reactive} from 'vue' function change(){ demo.name = '前端小九' } const demo = reactive({ name: '前端小玖', nickName: '小玖', soulmate: { name: '后台小王', nickName: '小王' } }) watch(() => demo.name, (newValue, oldValue) => { console.log('watch 已触发', newValue) }) </script>
结果:
如上代码,监听 demo 对象的 name 属性,那么只有当 demo 对象的 name 属性发生变更时,才会触发 watch 方法,其他属性变更不会触发 watch 方法。注意,此时的第一个参数是一个箭头函数。
3、只监听对象的子属性
这种情况,只有当 demo 的子属性发生变更时才会触发 watch 方法。孙属性,曾孙属性... 发生变更都不会触发 watch 方法。也就是说,当你修改 demo.soulmate.name 或者 demo.soulmate.nickName 时是不会触发 watch 方法的。
<template> <p>{{ demo.name }}</p> <button @click="change()">改变</button> </template> <script setup> import {ref,watch,reactive} from 'vue' function change(){ demo.name = '前端小九' } const demo = reactive({ name: '前端小玖', nickName: '小玖', soulmate: { name: '后台小王', nickName: '小王' } }) watch(() => ({ ...demo }), (newValue, oldValue) => { console.log('watch 已触发', newValue.name) }) </script>
结果:
4、监听对象的所有属性
这个相当于监听整个对象(效果与上面的第一种相同)。但是实现方式与上面第一种是不一样的,这里我们可以看到,第一个参数是箭头函数,并且还多了第三个参数 { deep: true }。当加上了第三个参数 { deep: true },那么就不仅仅是监听对象的子属性了,它还会监听 孙属性,曾孙属性 ...
通常要实现监听对象的所有属性,我们都会采用上面第一种方法,原因无他,第一种编码简单,第一个参数直接传入 demo 即可。
<template> <p>{{ demo.soulmate.name }}</p> <button @click="change()">改变</button> </template> <script setup> import {ref,watch,reactive} from 'vue' function change(){ demo.soulmate.name = '后台大神' } const demo = reactive({ name: '前端小玖', nickName: '小玖', soulmate: { name: '后台小王', nickName: '小王' } }) watch(() => demo, (newValue, oldValue) => { console.log('watch 已触发', newValue.soulmate.name) }, { deep: true }) </script>
结果:
组合监听
什么是组合监听呢?举个例子,比如我想同时监听 demo 对象的 name 属性,和基础类型 nums,只要他们其中任何一个发生变更,那么就触发 watch 方法。
<template> <p>{{ demo.name }}</p> <button @click="change()">改变</button> </template> <script setup> import {ref,watch,reactive} from 'vue' function change(){ demo.name = '后台大神' } const nums = ref(9) const demo = reactive({ name: '前端小玖', nickName: '小玖', soulmate: { name: '后台小王', nickName: '小王' } }) watch([() => demo.name, nums], ([newName, newNums], [oldName, oldNums]) => { console.log('watch 已触发: name', newName) console.log('watch 已触发: nums', newNums) }) </script>
结果:
注意,此时的第一个参数是一个数组,且第二参数箭头函数的参数也是数组的形式。
watch([()=>props.pageNum,()=>props.pageSize],([newNum, newSize], [oldNum, oldSize])=>{ pageParams.currentPage = newNum pageParams.pageSize = newSize },{deep: true, immediate: true})
六、组合API-钩子函数
1、setup 创建实例前
2、onBeforeMount 挂载DOM前
3、onMounted 挂载DOM后
4、onBeforeUpdate 更新组件前
5、onUpdated 更新组件后
6、onBeforeUnmount 卸载销毁前
7、onUnmounted 卸载销毁后
说明:
1.要先引入钩子函数
2.可以重复添加同一个钩子函数
七、vue-router4.x的基本使用
1、在项目中安装 vue-router
在 vue3 的项目中,只能安装并使用 vue-router 4.x。安装的命令如下:
参考文档:https://next.router.vuejs.org/zh/guide/
2、路由模块的创建
在 src 目录下创建router文件夹,并添加 router.js 路由模块文件,在其中按照如下 个步骤创建并得到路由的实例对象:
① 从 vue-router 中按需导入两个方法
② 导入需要使用路由控制的组件
③ 创建路由实例对象
④ 向外共享路由实例对象
在 main.js 中导入并挂载路由模块
路由结构设计
路由配置
添加整体布局组件
1.引入顶部通栏结构
2.引入轮播图结构
3.在主体结构中添加router-view
4.引入底部结构
八、axios的封装
1.src/utils/request文件的添加
2.下载,引入axios
3.配置基准路径(http://pcapi-xiaotuxian-front-devtest.itheima.net/)
4.暴露