一: Vue3.0六大亮点
1. 性能比Vue2.x快1.2-2倍
1.1、diff方法优化
- vue2中的虚拟dom是进行全量的对比
- vue3新增了静态标记(PathchFlag),在与上次虚拟节点进行对比的时候,只对比带有patch flag的节点。并且可以通过flag的信息得知当前节点要对比的具体内容。
1.2、静态提升
- Vue2中无论元素是否参与更新,每次都会重新创建,然后在渲染
- Vue3中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
1.3、事件监听器缓存
- 默认情况下Onclick会被视为动态绑定,所以每次都会去追踪它的变化。
- 但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。
1.4、ssr渲染
2. 按需编译,体积比Vue2.x更小
3. Composition API:组合API
4. 更好的TS支持
5. 暴露了自定义渲染API
6. 更先进的组件(Fragment、Teleport、Suspense)
二: 安装vue官方脚手架以及创建项目
1. 通过Vue-cli创建项目
1.1 安装vue-cli(只需安装一次)
yarn global add @vue/cli
npm install -g @vue/cli
cnpm install -g @vue/cli
1.2 通过vue-cli创建项目
vue create <project-name>
cd <project-name>
npm run serve 或者 yarn serve
会报错❌ ERROR Error: Cannot find module 'vue-loader-v16/package.json'
解决办法:更新node版本 或者 cnpm i vue-loader-v16之后在npm run serve
2. 通过Vite脚手架创建项目
1. 使用npm创建
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
2. 使用yarn创建
yarn create vite-app <project-name>
cd <project-name>
yarn
yarn dev
3. 通过webpack创建
git clone https://github.com/vuejs/vue-next-webpack-preview.git <project-name>
cd <project-name>
npm install
npm run dev
三:绑定数据、循环
1. 绑定
1.1 {{}}绑定数据
<template>
<div>绑定普通数据:{{msg}}</div>
<div>绑定对象中的数据:{{userInfo.name}} --- {{userInfo.age}}</div>
</template>
<script>
export default{
data(){
return{
msg: '你好vue',
userInfo:{
name: '张三',
age: 20
}
}
}
}
</script>
//绑定普通数据:你好vue
//绑定对象中的数据:张三 --- 20
1.2 v-html绑定html
<template>
<p>绑定html: <span v-html="htmlText"></span></p>
</template>
<script>
export default{
data(){
return{
htmlText: '<h2>我是一个h2标签</h2>'
}
}
}
</script>
1.3 v-bind绑定属性
<template>
<img v-bind:src="logoSrc" alt="itying">
//v-bind:src的缩写
<img :src="logoSrc" alt="itying"> 、
<div v-bind:title="title">鼠标放上去试试</div>
<div :title="title">鼠标放上去试试</div>
</template>
<script>
export default{
data(){
return{
logoSrc: 'https://www.itying.com/themes/itying/images/logo.gif',
title: '自定义属性值'
}
}
}
</script>
1.4 ⚠️v-bind动态参数
<template>
<a v-bind:[myHref]="'http://www.baidu.com'">跳转到百度</a>
<br>
<a v-bind:[myHref]="myLink">跳转到itying</a>
</template>
<script>
export default{
data(){
return{
myHref: 'href',
myLink: 'http://www.itying.com'
}
}
}
</script>
// <a href="'http://www.baidu.com'">跳转到百度</a>
// <a href="http://www.itying.com">跳转到itying</a>
2. 循环(vue3中必须动态绑定key值,否则报错)
2.1 循环数组
<template>
<ul>
<li v-for="(item,index) in list" :key="index">{{item}} --- {{index}}</li>
</ul>
<ul>
<li v-for="(item,index) in list1" :key="index">{{item.title}} --- {{index}}</li>
</ul>
<ul>
<li v-for="(item,index) in list2" :key="index">
{{item.cate}}
<ol>
<li v-for="(i,k) in item.list" :key="k">
{{i.title}}
</li>
</ol>
</li>
</ul>
</template>
<script>
export default{
data(){
return{
list: ['李总','刘总','王总'],
list1: [
{
title: '标题1'
},
{
title: '标题2'
},
{
title: '标题3'
},
],
list2: [
{
cate: "国内新闻",
list: [
{
title: '国内新闻1'
},
{
title: '国内新闻2'
}
]
},
{
cate: "国际新闻",
list: [
{
title: '国际新闻1'
},
{
title: '国际新闻2'
}
]
}
]
}
}
}
</script>
2.2 循环对象
<template>
<ul>
<li v-for="(value,key,index) in myObject" :key="key">
{{key}}:{{value}}--{{index}}
</li>
</ul>
</template>
<script>
export default{
data(){
return{
myObject:{
title: '这是标题',
author:'我是作者',
publishAt: '2020-02-03'
}
}
}
}
</script>
title:这是标题--0
author:我是作者--1
publishAt:2020-02-03--2
四:模版中类、样式的绑定
1. 类的绑定
1.1 v-bind:class绑定字符串
<template>
<div :class="myClass">绑定单个类名</div>
</template>
<script>
export default{
data(){
return{
myClass: "red"
}
}
}
</script>
<style>
.red{
color: red;
}
</style>
1.2 v-bind:class绑定对象
<template>
<div :class="{'active': isActive,'red': isRed}">使用对象方式绑定多个动态类名</div>
<div class="blue" :class="{'active': isActive}">使用对象方式绑定多个动态类名</div>
</template>
<script>
export default{
data(){
return{
isActive: true,
isRed: true
}
}
}
</script>
<style>
.active{
font-size: 30px;
background: purple;
}
.red{
color: red;
}
.blue{
color: blue;
}
</style>
1.3 v-bind:class绑定数组
<template>
<div :class=[activeClass,baseClass]>使用数组方式绑定多个类名</div>
</template>
<script>
export default{
data(){
return{
activeClass: 'active',
baseClass: 'base'
}
}
}
</script>
<style>
.base{
width: 300px;
height: 300px;
}
.active{
font-size: 30px;
background: purple;
}
</style>
1.4 数组语法 结合三目运算
<template>
<div :class=[flag?activeClass:errorClass]>三目运算绑定class</div>
</template>
<script>
export default{
data(){
return{
flag: true,
activeClass: 'active',
errorClass: 'error'
}
}
}
</script>
<style>
.active{
font-size: 30px;
background: purple;
}
.error{
font-size: 16px;
color: red;
}
</style>
2. 样式的绑定
2.1 使用变量绑定
<template>
<div :style="{'color': activeColor,'fontSize': fontSize}">绑定多个style</div>
</template>
<script>
export default{
data(){
return{
activeColor:'blue',
fontSize: '20px'
}
}
}
</script>
2.2 使用对象绑定
<template>
<div :style="styleObject">绑定多个style</div>
</template>
<script>
export default{
data(){
return{
styleObject: {
color:'blue',
fontSize: '40px',
}
}
}
}
</script>
2.3 使用数组方式绑定
<template>
<div :style="[baseStyle,blueStyle]">使用数组方式绑定多个style</div>
</template>
<script>
export default{
data(){
return{
baseStyle:{
width: '200px',
height: '200px',
fontSize: '20px'
},
blueStyle:{
background: "blue"
}
}
}
}
</script>
五:事件方法详解
1. 监听事件:v-on:click或者@click
2. 方法传值
<template>
<h1>{{msg}}</h1>
<button @click="setMsg('我是设置的msg')">设置msg</button>
</template>
<script>
export default{
data(){
return{
msg: '你好vue'
}
},
methods:{
setMsg(data){
this.msg = data;
}
}
}
</script>
3. 事件对象
$event传参数。
e.srcElement:当前dom节点。
e.srcElement.dataset.xxx:当前dom节点的自定义属性;
3.1 单个参数
<template>
<button data-id="123" @click="eventFn($event)">事件对象</button>
</template>
<script>
export default{
data(){
return{}
},
methods:{
eventFn(e){
e.srcElement.style.background = 'red';
console.log(e.srcElement.dataset.id); //123
}
}
}
</script>
3.2 多个参数
<template>
<button @click="warn('传入的参数',$event)">事件对象多个参数</button>
</template>
<script>
export default{
data(){
return{}
},
methods:{
warn(msg, event){
if(event){
event.preventDefault()
}
alert(msg); //传入的参数
}
}
}
</script>
4. 多事件处理程序
<template>
<button @click="one(),two()">多事件处理</button>
</template>
<script>
export default{
data(){
return{}
},
methods:{
one(){
console.log('one')
},
two(){
console.log('two')
}
}
}
</script>
5. 事件修饰符
- .stop
- .prevent
- .capture
- .self
- .once
- .passiv
6. 按键修饰符
- .enter
- .tab
- .delete(捕获“删除”和“退格”键)
- .esc
- .space
- .up
- .down
- .left
- .right
六:模版中使用js表达式、条件判断、计算属性、watch监听
1. 模版中使用js表达式
- 加减乘除
{{number + 1}}
{{number - 1}}
{{number * 2}}
{{number / 2}}
- 三目运算符
{{flag?" this is true ":" this is false "}}
- 调用方法
{{message.split("").reverse().join("")}}
2. 条件判断
v-if(dom操作)
v-else
v-else-if(必须紧随v-if或v-else-if元素)
v-show(css的显示隐藏、适合频繁操作切换)
3. 计算属性
<div>{{reverseMsg}}</div>
computed:{
reverseMsg(){
return this.message.split("").reverse().join("")
}
}
计算属性实现筛选功能
4. watch监听数据变化
watch:{
fristName: function(value){
this.fullName = value + " " + this.lastName
},
lastName: function(value){
this.fullName = this.fristName + " " + value
},
}
⚠️:v-for跟v-if无法一起使用,可以使用template进行包装
<template v-for="(item,index) in list" :key="index">
<div v-if="item.isShow">内容</div>
</template>
七:父子组件传参数、自定义组件
1. 把整个父实例传给子组件(:属性名="this"),子组件可以获取到父组件所有的数据跟方法;
父组件的代码:
<template>
<Header :title="title" :home="this"/>
<button @click="setTitle">修改title的值</button>
</template>
<script>
import Header from './Header'
export default{
data(){
return{
title: '首页',
list: [1,2,3,4,5]
}
},
components: {
Header
},
methods:{
setTitle(){
this.title = '我是修改后的标题'
},
run(){
console.log('我是首页的run方法')
}
}
}
</script>
子组件代码:
<template>
<header>{{title}}</header>
<ul>
<li v-for="(item,index) in home.list" :key="index">{{item}}</li>
</ul>
<button @click="getParentFn">获取父组件的run方法</button>
</template>
<script>
export default{
props: ["title","home"],
methods: {
getParentFn(){
this.home.run(); //'我是首页的run方法'
}
}
}
</script>
<style>
header{
text-align: center;
font-size: 40px;
}
</style>
2. 单向数据流
所有的prop都使得父子prop之间形成了一个单向下行绑定;父级prop的更新会向下流动到子组件中,但反过来不行(vue会在浏览器控制台发出警告)。这样会防止子组件意外变更父组件的状态,导致应用数据流向难以理解。
3. 父组件主动获取子组件的数据和执行子组件的方法
(可以在父组件中修改子组件的值)
3.1、调用子组件的时候定义一个ref
<v-header ref="header"></v-header>
3.2、父组件主动获取子组件数据
this.$refs.header.属性
3.3、父组件主动执行子组件方法
this.$refs.header.方法
4. 子组件主动获取父组件的数据和执行父组件方法
(可以在子组件中修改父组件的值)
4.1、子组件主动获取父组件的数据
this.$parent.数据
4.2、子组件主动执行父组件方法
this.$parent.方法
5. 自定义组件事件以及验证传入参数
@emit;
emits进行传值验证。
//父组件代码:
<template>
<Login @submit="doLogin"/>
</template>
<script>
import Login from './Login'
export default{
components:{
Login
},
methods: {
doLogin(data){
console.log('接受来自子组件的数据', data.username, data.password)
// 打印出来的是输入的用户名,输入的密码
}
}
}
</script>
//子组件代码:
<template>
<input type="text" v-model="userInfo.username" placeholder="用户名">
<input type="text" v-model="userInfo.password" placeholder="密码">
<button @click="submit">提交用户数据到父组件</button>
</template>
<script>
export default{
emits:{
submit:(userInfo)=>{
if(userInfo.username!='' && userInfo.password!=''){
return true
}else{
console.error('用户名和密码不能为空');
return false
}
}
},
data(){
return{
userInfo:{
username: '',
password: ''
}
}
},
methods:{
submit(){
this.$emit('submit', this.userInfo)
}
}
}
</script>
6. vue3.x第三方插件mitt实现非父子组件传值
父组件广播事件使用emit、子组件监听事件on。
6.1、安装mitt模块
npm install --save mitt
6.2、新建model/event.js
import mitt from 'mitt'
const VueEvent = mitt();
export default VueEvent;
6.3、Header组件
<template>
<button @click="sendLogin">触发登录组件的事件并传值</button>
</template>
<script>
import VueEvent from '../model/event'
export default{
data(){
return{
msg: '我是header组件里面的msg'
}
},
methods: {
//广播组件
sendLogin(){
VueEvent.emit('toLogin', this.msg)
}
}
}
</script>
6.4、Login组件
<script>
import VueEvent from '../model/event'
export default{
mounted() {
//监听事件
VueEvent.on("toLogin", function(data){
console.log(data) //我是header组件里面的msg
})
}
}
</script>
7. 自定义组件通过v-model实现双向数据绑定
父组件:v-model:自定义属性 = "xxx"
子组件:props接受 @emit触发事件
//父组件代码:
<template>
<user-name v-model:fristName="fristName" v-model:lastName="lastName"></user-name>
<br>
{{fristName}} -- {{lastName}} //可以根据子组件输入的名字动态变更
</template>
<script>
import UserName from './UserName'
export default{
data(){
return{
fristName: '',
lastName: ''
}
},
components: {
UserName
}
}
</script>
//子组件代码:
<template>
<input type="text" :value="fristName" placeholder="fristName" @input="$emit('update:fristName', $event.target.value)">
<input type="text" :value="lastName" placeholder="lastName" @input="$emit('update:lastName', $event.target.value)">
</template>
<script>
export default{
props: ["fristName", "lastName"]
}
</script>
8. 非Prop的Attribute继承
一个非prop的attribute是指传向一个组件,但是该组件并没有相应的props或emits定义的attribute,常见的示例包括class、style、id属性。
8.1、当组件返回单个根节点时,非Prop Attribute将自动添加到根节点的attribute中。
如:
父组件代码:
<template>
<v-button class="primary" data-id="1">
确定
</v-button>
<v-button class="black" data-id="2">
取消
</v-button>
<v-button></v-button>
</template>
<script>
import Button from './Button'
export default{
components: {
"v-button": Button
}
}
</script>
子组件代码:
<template>
<button>
<slot>Default</slot>
</button>
</template>
<style scoped>
.primary{
width: 100px;
height: 40px;
line-height: 40px;
background: orange;
color: #fff;
border: none;
}
.black{
width: 100px;
height: 40px;
line-height: 40px;
background: #000;
color: #fff;
border: none;
}
</style>
虽然子组件中样式写了scoped,但是在父组件中绑定样式跟非Prop的Attribute(data-id)都可自动添加到根节点(button)身上。
渲染完成的结果:
<button class="primary" data-id="1"> 确定 </button>
<button class="black" data-id="2"> 取消 </button>
8.2、自定义Attribute继承
如果你不希望组件的根元素继承attribute,你可以在组件的选项中设置inheritAttrs:false。禁用attribute继承的常见情况是需要将attribute应用于根节点之外的其他元素。然后通过访问组件的$attrs来获取。
父组件代码:
<template>
<date-picker data-time="2021-02-05"></date-picker>
</template>
<script>
import DatePicker from './DatePicker'
export default{
components: {
DatePicker
}
}
</script>
子组件代码:
<template>
<div class="date-picker">
<input type="date" v-bind="$attrs">
</div>
</template>
<script>
export default{
// 禁用默认继承
inheritAttrs: false
}
</script>
渲染完成的结果:
<div class="date-picker">
<input type="date" data-time="2021-02-05">
</div>
八:vue3.x全局绑定属性
import { createApp } from 'vue'
import App from './App.vue'
import Axios from 'axios'
import baseMixin from './mixin/baseMixin'
const app = createApp(App);
app.config.globalProperties.Axios = Axios; //其他页面用this.Axios
//全局配置mixin
app.mixin(baseMixin);
app.mount('#app');
九:vue3.x Teleport
vue3.x中的组件模版属于该组件,有时候我们想把模版的内容移动到当前组件之外的DOM中,这个时候就可以使用Teleport。 表示Teleport内包含的内容显示到body中
<teleport to="body">
内容
</teleport>
表示Teleport内包含的内容显示到app标签中
<teleport to="#app">
内容
</teleport>
十:Composition API(组合式API)
1. ref reactive - 定义响应式数据
ref注意点:
ref 定义字符串、Number、Boolean、数组。
ref的本质其实还是reactive,系统会自动根据我们给ref传入的值转换成ref(xx) -> reactive({value:xx})
在vue中使用ref的值不用通过value获取,会自动添加.value;但在js中使用ref的值必须通过value获取;
reactive注意点:
vue2中响应式数据是通过definePropery实现的,在vue3中是通过es6的Proxy实现的;
reactive 定义对象。
如果给reactive传递了其它对象,默认情况下修改对象,界面不会自动更新。如果想更新,可以通过重新赋值的方式;
<template>
<div>{{state.time}}</div>
<button @click="myFn">修改</button>
</template>
<script>
import {reactive} from 'vue';
export default {
setup(){
let state = reactive({
time: new Date()
});
function myFn() {
//直接修改以前的,界面不会更新
// state.time.setDate(state.time.getDate() + 1);
//重新赋值,界面才会更新
const newTime = new Date(state.time.getTime());
newTime.setDate(state.time.getDate() + 1);
state.time = newTime;
console.log(state.time);
}
return{
state,
myFn
}
}
}
</script>
两者区别:
如果在template里使用的是ref类型的数据,那么vue会自动添加.value;反之不会自动添加;
vue通过当前数据的__v_isRef来判断是不是ref类型;如果值为true,代表是ref类型;
自己判断的话使用isRef、isReactive方法;isRef(xxx);
两者共同点:
默认情况下,无论是通过ref还是reactive都是递归监听;(所有的变化都会封装成proxy对象)如果数据量比较大,非常消耗性能;
通过shallowReactive跟shallowRef创建非递归监听;只封装第一层为proxy对象;
2. shallowReactive、shallowRef、triggerRef
如果是通过shallowRef创建数据,那么vue监听的是.value的变化;因为shallowRef的本质是shallowRef(xx)-> shallowReactive({value:xx})第一层是value;
triggerRef可以主动触发页面更新;没有triggerReactive方法,所以如果是reactive类型的数据,是没办法主动触发页面更新的;
//shallowReactive
<template>
<div>{{state.a}}</div>
<div>{{state.gf.b}}</div>
<div>{{state.gf.f.c}}</div>
<div>{{state.gf.f.s.d}}</div>
<button @click="myFn">修改</button>
</template>
<script>
import {shallowReactive} from 'vue';
export default {
setup() {
let state = shallowReactive({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
});
function myFn() {
// state.a = '1'; //如果把这行注释掉,页面不会更新,因为第一层数据没修改,不会触发更新ui,如果不注释,整个页面都会修改成1,2,3,4;
state.gf.b = '2';
state.gf.f.c = '3';
state.gf.f.s.d = '4';
console.log(state); //只有这一层是proxy对象
console.log(state.gf);
console.log(state.gf.f);
console.log(state.gf.f.s);
}
return {
state,
myFn
}
}
}
</script>
//shallowRef、triggerRef
<template>
<div>{{state.a}}</div>
<div>{{state.gf.b}}</div>
<div>{{state.gf.f.c}}</div>
<div>{{state.gf.f.s.d}}</div>
<button @click="myFn">修改</button>
</template>
<script>
import {shallowRef, triggerRef} from 'vue';
export default {
setup() {
let state = shallowRef({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
});
function myFn() {
// 以下修改方式不会更新ui。因为shallowRef的第一层数据是value
/*state.value.a = '1';
state.value.gf.b = '2';
state.value.gf.f.c = '3';
state.value.gf.f.s.d = '4';*/
//修改value的值才会触发更新ui
/*state.value = {
a: '1',
gf: {
b: '2',
f: {
c: '3',
s: {
d: '4'
}
}
}
};*/
//只更新第四个的值触发ui 使用triggerRef主动更新
state.value.gf.f.s.d = 4;
triggerRef(state);
console.log(state.value);
console.log(state.value.gf);
console.log(state.value.gf.f);
console.log(state.value.gf.f.s);
}
return {
state,
myFn
}
}
}
</script>
3. toRaw、markRaw
toRaw注意点:
toRaw的作用是从reactive或ref中得到原始数据;做一些不想被监听的事情(提升性能); reactive或ref每次修改都会被追踪,都会更新UI界面。但是这样非常消耗性能,所以如果我们有一些操作不需要追踪,不需要更新Ui界面,就可以通过toRaw方法拿到它的原始数据,在进行修改,这样就不会被追踪,就不会更新UI界面,性能就好了;
如果想通过toRaw拿到ref类型的原始数据,那么就要通过toRaw获取的是.value的值;
markRaw注意点:
添加不可转为响应式数据的标记;如果修改响应式数据,数据改变,视图不会发生变化;
<template>
reactive: {{state}}
ref: {{state2}}
<button @click="myFn">修改</button>
</template>
<script>
import {reactive, ref, toRaw, markRaw} from 'vue';
export default {
setup() {
let obj = {name: '张三', age: 18};
//永远不会被追踪
obj = markRaw(obj);
let state = reactive(obj);
let obj2 = toRaw(state);
console.log(obj === state);//false
//state和obj的关系:引用关系,state的本质是一个Proxy对象,在这个Proxy对象中引用了obj;
console.log(obj === obj2); //true
//如果想通过toRaw拿到ref类型的原始数据,那么就要通过toRaw获取的是.value的值
let state2 = ref(obj);
let obj3 = toRaw(state2.value);
console.log(obj3); //{name: "张三", age: 18}
console.log(obj === obj3);//true
function myFn() {
// 如果直接修改obj,那么是无法触发界面更新的,只有通过包装之后的对象来修改,才会触发界面的更新。
// obj.name = '李四';
// console.log(obj); //{name: "李四", age: 18}
//如果指定了markRaw数据可以修改但不更新ui界面,正常可以更新UI界面
state.name = '李四';
//修改toRaw的原始值,值可以修改,但不会更新ui,提高性能
// obj2.name = '李四';
// console.log(obj2);//{name: "李四", age: 18}
console.log(state);//Proxy{name: "李四", age: 18}
}
return {
state,
state2,
myFn
}
}
}
</script>
4. toRef、toRefs - 解构响应式对象数据、customRef
toRef注意点:
toRef 是将某个对象中的某个值转化为响应式数据,其接收两个参数,第一个参数为 obj 对象;第二个参数为对象中的属性名;
应用场景:如果想让响应式数据和之前的数据关联起来,并且更新响应式数据还不想更新ui,就可以使用toRef;
跟ref的区别:
ref:复制;修改响应式数据不会影响引用的数据;数据发生改变,界面会自动更新; toRef:引用;修改响应式数据会影响引用的数据;数据发生改变,界面不会自动更新;
<template>
{{state}}
<button @click="myFn">修改</button>
</template>
<script>
import {ref, toRef, toRefs} from 'vue';
export default {
setup() {
let obj = {name: '张三', age: 18};
// ref
// let state = ref(obj.name);
// toRef
// let state = toRef(obj, 'name');
// toRefs
// toRefs(obj)->toRefs({name: '张三', age: 18})->toRef('张三')&toRef('18')
let state = toRefs(obj);
function myFn() {
//通过ref创建的,不会修改原始数据,可以更新ui
// state.value = '李四';
// console.log(state);//RefImpl {_rawValue: "李四", _shallow: false, __v_isRef: true, _value: "李四"}
// console.log(obj); //{name: '张三', age: 18}
//通过toRef创建的,会修改原始数据,不会更新ui
// state.value = '李四';
// console.log(state);//RefImpl {_rawValue: "李四", _shallow: false, __v_isRef: true, _value: "李四"}
// console.log(obj); //{name: '李四', age: 18}
//通过toRefs创建 相当于创建了两次toRef
state.name.value = '李四';
state.age.value = 20;
console.log(state);//{name: ObjectRefImpl, age: ObjectRefImpl}
console.log(obj);//{name: '李四', age: 20}
}
return {
state,
myFn
}
}
}
</script>
toRefs注意点:
通过...userName解构的话就失去了响应式;
使用toRefs之后可以在模版中直接使用对象的属性名,也可以相应式;
<template>
<div>标题:{{title}}</div>
<div>用户信息:{{userInfo.name}} --- {{userInfo.age}}</div>
<button @click="getUserName">获取username</button>
<button @click="setUserName">设置username</button>
<button @click="getTitle">获取标题</button>
<button @click="setTitle">设置标题</button>
//可以双向绑定动态更改
<input type="text" v-model="title" placeholder="标题" />
<input type="text" v-model="userInfo.name" placeholder="用户名称" />
// 使用toRefs之后可以直接用属性名
<div>fullName: {{firstName}} --- {{lastName}}</div>
</template>
<script>
import { ref, reactive, toRefs } from 'vue'
export default {
setup() {
//ref 定义字符串、Number、Boolean、数组
//reactive 定义对象
var title = ref('我是home组件的标题');
var userInfo = reactive({
name: '张三',
age: 20
})
var userName = reactive({
firstName: '张',
lastName: '三'
})
//获取reactive里面定义的数据
var getUserName = () => {
alert(userInfo.name)
}
var setUserName = () => {
userInfo.name = '李四'
}
//获取ref里面定义的数据
var getTitle = () => {
alert(title.value)
}
var setTitle = () => {
title.value = '修改后的标题'
}
return {
title,
userInfo,
getUserName,
setUserName,
getTitle,
setTitle,
...toRefs(userName)
}
}
}
</script>
customRef注意点:
自定义返回一个ref对象,可以显式地控制依赖追踪和触发响应;
<template>
{{age}}
<button @click="myFn">修改</button>
</template>
<script>
import {customRef} from 'vue';
function myRef(value) {
return customRef((track, trigger) => {
return {
get() {
track();//告诉vue这个数据是需要追踪变化的
console.log('get', value);
return value;
},
set(newValue) {
console.log('set', newValue);
value = newValue;
trigger();//告诉vue触发界面更新
}
}
})
}
export default {
setup() {
let age = myRef(18);
function myFn() {
age.value += 1;
}
return {
age,
myFn
}
}
}
</script>
5. ref - 获取元素
在vue2.x中我们可以通过给元素添加ref='xxx', 然后通过refs.xxx的方式来获取元素;
在vue3.0中我们可以通过ref来获取元素;但要注意生命周期;
<template>
<div ref="box">我是div</div>
</template>
<script>
import {ref, onMounted} from 'vue';
export default {
setup() {
// 命名需要跟ref中定义的值一样
let box = ref(null);
onMounted(() => {
console.log('onMounted', box.value);//onMounted 元素
});
console.log(box.value);//null
return {
box
}
}
}
</script>
6. computed - 计算属性
<template>
<input type="text" v-model="firstName" placeholder="firstName">
<input type="text" v-model="lastName" placeholder="lastName">
<div>fullName: {{fullName}}</div>
</template>
<script>
import { reactive, toRefs, computed } from 'vue'
export default {
setup() {
var userName = reactive({
firstName: '',
lastName: ''
})
var fullName = computed(() => {
return userName.firstName + '---' + userName.lastName
})
return {
...toRefs(userName),
fullName
}
}
}
</script>
7. readonly、shallowReadonly、isReadonly
readonly: 创建一个只读数据,并且是递归只读;
跟const区别:
const:赋值保护,不能给变量重新赋值;可以修改对象中的属性;
readonly:属性保护,不能给属性重新赋值;
shallowReadonly:创建一个只读数据,但不是递归只读
<template>
<input type="text" v-model="firstName" placeholder="firstName">
<input type="text" v-model="lastName" placeholder="lastName">
<div>fullName: {{fullName}}</div>
</template>
<script>
import { reactive, toRefs, computed, readonly } from 'vue'
export default {
setup() {
var userName = reactive({
firstName: '',
lastName: ''
})
userName = readonly(userName);
var fullName = computed(() => {
return userName.firstName + '---' + userName.lastName
})
return {
...toRefs(userName),
fullName
}
}
}
</script>
8. watchEffect
9. watch
区别:
-
- watchEffect无论如何都会执行一次;watch只有值改变的时候才会执行;
-
- watchEffect可以详细监听对象中具体变化; watch不能详细监听某一个变化 只能监听reactive的返回的对象;
-
- watch可以监听变化前后的值
<template>
<div>num: {{num}}</div>
<div>count: {{count}}</div>
<input type="text" v-model="count" placeholder="count">
</template>
<script>
import { ref, reactive, toRefs, watchEffect, watch } from 'vue'
export default {
setup() {
let data = reactive({
num: 1
})
let count = ref('');
//无论如何都会执行一次 num=1 可以详细监听对象中具体变化
watchEffect(() => {
console.log(`num=${data.num}`)
})
// watch不能详细监听某一个变化 只能监听reactive的返回的对象
watch(data, () => {
console.log(`data.num=${data.num}`);
})
watch(count, (newVal, oldVal) => {
console.log(newVal, oldVal)
})
setInterval(() => {
data.num++;
}, 1000);
return {
count,
...toRefs(data)
}
}
}
</script>
10. 生命周期钩子
setup注意点:
setup是在beforeCreate之前运行的,所以不需要定义他们。除此之外,其他的都可以在setup中定义,定义时需要前面加on;
setup还没有执行created方法,所以在setup中,无法使用data跟methods。
setup中this是undefined。
setup函数只能是同步的,不能是异步的。
<template></template>
<script>
import { onMounted } from 'vue'
export default {
setup() {
console.log('setup');
onMounted(()=>{
console.log('onMounted')
})
},
beforeCreate(){
console.log('beforeCreate');
}
}
</script>
//setup beforeCreate onMounted
11. props
props: ["xxx"],
setup(props) {}
父组件代码:
<template>
<count :countNum="countNum"></count>
</template>
<script>
import count from './components/count'
export default{
data(){
return {
countNum: 10
}
},
components: {
count
}
}
</script>
子组件代码:
<template></template>
<script>
export default {
props: ["countNum"],
setup(props) {
console.log('props', props.countNum); //10
}
}
</script>
12. Provider Inject
解决多层嵌套父子组件传值问题;
父组件provide提供数据;子组件inject注入数据;
App组件 - Home组件 - Header组件
12.1、非组合式api中的写法
⚠️:父组件修改值的时候子组件没法修改;
// App.vue中的代码:
<template>
<Header/>
<button @click="updateTitle">改变父组件的title</button>
{{title}}
</template>
<script>
import Header from './components/Header'
export default{
data(){
return {
title: 'app根组件的标题'
}
},
components: {
Header
},
methods: {
updateTitle(){
this.title = '改变后的标题'
}
},
provide(){
return{
title: this.title
}
}
}
</script>
// header.vue里面的代码:
<template>
<h1>header组件---{{title}}</h1>
</template>
<script>
export default{
inject: ["title"]
}
</script>
//点击父组件中的button时触发事件子组件中的title值没法修改 还是之前的app根组件的标题
12.2、组合式api中的写法
子组件改变数据也会影响父组件;
// App.vue里面的代码:
<template>
<Header/>
<button @click="updateTitle">改变父组件的title</button>
{{title}}
</template>
<script>
import Header from './components/Header'
import { ref, provide } from 'vue'
export default {
setup() {
let title = ref('app中的标题');
let updateTitle = () => {
title.value = '修改后的title'
}
provide('title', title);
return {
title,
updateTitle
}
},
components: {
Header
}
}
</script>
// header.vue里面的代码:
<template>
<h1>header组件---{{title}}</h1>
// 子组件改变数据也会影响父组件;
<input type="text" v-model="title" placeholder="title">
</template>
<script>
import {inject} from 'vue'
export default{
setup(){
let title = inject('title');
return {
title
}
}
}
</script>
十:vue3.x集合ts
1. 创建项目
npm install -g @vue/cli(只需要执行一次)
vue create <project-name>
cd <project-name>
vue add typescript
2. 定义组件
2.1 将语言设置为Typescript
<script lang="ts">
........
</script>
2.2 需要使用 defineComponent 全局方法定义组件
import { defineComponent } from 'vue';
export default defineComponent({
//已启用类型推断
});
示例代码:
<template>
<div>
Home组件 - {{title}} //这是文章标题
</div>
</template>
<script lang="ts">
interface Article{
title: string,
desc: string,
count?:number
}
let article: Article = {
title: '这是文章标题',
desc: '这是文章描述'
};
import {defineComponent} from 'vue';
export default defineComponent({
name: 'Home',
data() {
return article
}
});
</script>
3. 与组合式API一起使用
reactive数据三种定义接口的办法:
let user:User = reactive({});
let user = reactive<User>({});
let user = reactive({}) as User;
ref数据只能用ref<T>来定义泛型;
<template>
<div>
User组件
userName = {{username}}
age = {{age}}
<button @click="setUserName('李四')">设置name</button>
获取name:{{getUserName()}}
count={{count}}
计算属性: {{reserveUserName}}
<!-- num={{num}} -->
</div>
</template>
<script lang="ts">
interface User {
username: string,
age: number,
setUserName(username: string): void,
getUserName(): string
}
import { defineComponent, reactive, toRefs , ref, computed} from 'vue';
export default defineComponent({
setup() {
//1. 实现接口的第一种写法
let user: User = reactive({
username: '张三',
age: 20,
setUserName(username: string): void {
this.username = username
},
getUserName(): string {
return this.username
}
})
//2. 实现接口的第二种写法
// let user = reactive<User>({
// username: '张三',
// age: 20,
// setUserName(username: string):void{
// this.username = username
// },
// getUserName():string{
// return this.username
// }
// })
//3. 实现接口的第三种写法
// let user = reactive({
// username: '张三',
// age: 20,
// setUserName(username: string):void{
// this.username = username
// },
// getUserName():string{
// return this.username
// }
// }) as User;
let count = ref<string | number>(111);
// let num:string = ref('111');//错误写法
let reserveUserName = computed(():string=>{
return user.username.split('').reverse().join('')
})
return {
...toRefs(user),
count,
reserveUserName
// num
}
}
});
</script>
十一:vue3.x路由
一: 路由基本配置
1. 安装路由模块
npm install vue-router@next --save
2. 新建所需组件
3. 配置路由
新建src/routes.ts配置路由
import { createRouter, createWebHashHistory } from 'vue-router'
// 引入组件
import Home from './components/Home.vue'
import News from './components/News.vue'
import User from './components/User.vue'
// 配置路由
const router = createRouter({
// hash 模式
history: createWebHashHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/news', component: News },
{ path: '/user', component: User },
]
})
export default router;
4. 挂载路由
在main.ts中挂载路由
import { createApp } from 'vue'
import App from './App.vue'
import route from './routes'
let app = createApp(App);
//挂载路由
app.use(route);
app.mount('#app')
5. 渲染组件
通过router-view渲染组件
<template>
<ul>
<li>
<router-link to="/">首页</router-link>
</li>
<li>
<router-link to="/news">新闻</router-link>
</li>
<li>
<router-link to="/user">用户</router-link>
</li>
</ul>
<router-view></router-view>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({});
</script>
二:vue3.x 动态路由
1. 配置路由
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/news', component: News },
//动态路由
{ path: '/NewsContent/:id', component: NewsContent }
]
})
2. 路由跳转
<ul>
<li v-for="(item,index) in list" :key="index">
<router-link :to="`/NewsContent/${index}`">{{item}}</router-link>
</li>
</ul>
3. 获取路由
this.$route.params
三:vue3.x Get传值
<router-link to="/NewsContent?id=1">get传值</router-link>
this.$route.query
四:vue3.x路由编程式导航(JS跳转路由)
1. 静态路由
this.$router.push('/news')
this.$router.push({
path: '/news'
})
2. 动态路由
this.$router.push('/NewsContent/1')
this.$router.push({
path: '/NewsContent/1'
})
错误写法❌:
this.$router.push({
path: '/NewsContent',
params: {
id: 1
}
})
3. Get传值
this.$router.push('/NewsContent?id=1')
this.$router.push({
path: '/NewsContent',
query: {
id: 1
}
})
错误写法❌:
this.$router.push({
path: '/NewsContent?id=1'
})
五:vue3.x路由模式
1. hash模式(#)
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
// hash 模式
history: createWebHashHistory(),
routes: [
......
]
})
http://0.0.0.0:8080/#/news
http://0.0.0.0:8080/#/user
如果想把上面的路由改成下面的方式:
http://0.0.0.0:8080/news
http://0.0.0.0:8080/user
我们就可以使用HTML5 history 模式;
2. history模式
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
// history 模式
history: createWebHistory(),
routes: [
.......
]
})
注意⚠️:开启history模式后,发布到服务器需要配置伪静态;
六:vue3.x命名路由
1. 配置命名路由(name)
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/NewsContent/:id', component: NewsContent, name: 'NewsContent' },
]
})
2. 跳转方式
2.1 router-link跳转
<router-link :to="{name:'NewsContent', params:{id: 1}}">router-link跳转</router-link>
2.2 动态路由JS跳转
this.$router.push({
name: 'NewsContent',
params: {
id: 1
}
})
2.3 Get方式JS跳转
this.$router.push({
name: 'NewsContent',
query: {
id: 1
}
})
七:路由重定向(redirect)
重定向配置要在routes.ts中完成
routes: [
{ path: '', redirect: '/home' },
{ path: '/home', component: Home }
]
也可以针对命名路由:
routes: [
{ path: '', redirect: {name: 'news'} },
{ path: '/news', component: News, name: 'news' },
]
八:路由别名(alias)
设置别名跟设置path路径的方式相同,可以设置一个,也可以多个,也可以设置动态传参数的形式。 别名的路径跟path的路径加载相同的组件。
routes: [
{ path: '/news', component: News, name: 'news', alias:['/n','/xw'] },
{ path: '/NewsContent/:id', component: NewsContent, name: 'NewsContent', alias:'/nc/:id' },
{ path: '/user', component: User, alias: '/person'},
]
九:嵌套路由
import { createRouter, createWebHistory } from 'vue-router'
// 引入组件
import User from './components/User.vue'
import UserAdd from './components/User/userAdd.vue'
import UserList from './components/User/userList.vue'
// 配置路由
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/user', component: User, redirect: '/user/list',
children: [
{ path: '/user/list', component: UserList },
{ path: '/user/add', component: UserAdd },
]
},
]
})
export default router;