一、创建Vue3项目的方法
1.使用vue-cli创建
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或升级vue-cli
npm i -g @vue/cli
## 创建vue3项目
vue create vue3_test
2.使用vite创建
## 创建工程
npm init vite-app <appname>
## 进入工程目录
cd <appname>
## 安装依赖
npm i
## 运行
npm run dev
二、模板语法
1.指令:
与vue2相同
2.给对象添加新的属性:
只需直接声明,无需Vue.set()
3.声明操作哪个html元素的方式与vue2不同
具体代码如下:
<body>
<div id="div1">
{{myname}}
<input type="text" v-model="mytext">
<button @click="handle">添加</button>
<ul>
<li v-for="(item, index) in classobj">
{{item}}
</li>
</ul>
<button @click="handleAdd">添加属性</button>
</div>
<script>
//与vue2有差别
var obj = {
data() {
return {
myname: "liumou",
mytext: "",
classobj: {
aa: true,
bb: true,
cc: false
}
}
},
methods: {
handle() {
console.log(this.mytext)
},
handleAdd() {
this.classobj.dd = true
}
},
}
Vue.createApp(obj).mount("#div1")
</script>
</body>
4.列表渲染:
vm.item[indexOfItem] = newValue → 可以被页面检测
三、组件写法
1.写法一:
<body>
<div id="div1">
<navbar myname="aaa"></navbar>
</div>
</body>
<script>
var obj = {
data() {
return {
myname: "kerwin"
}
},
methods: {
},
computed: {
}
}
Vue.createApp(obj)
.component("navbar", {
props: ["myname"],
template: `
<div>
navbar--{{myname}}
<slot></slot>
</div>
`
})
.mount("#div1")
</script>
2.写法2:(更整洁,建议用这种)
<body>
<div id="div1">
<navbar myname="aaa"></navbar>
<navbar-item></navbar-item>
</div>
</body>
<script>
var obj = {
data() {
return {
myname: "kerwin"
}
},
methods: {
},
computed: {
}
}
var app = Vue.createApp(obj)
app.component("navbar", {
props: ["myname"],
template: `
<div>
navbar--{{myname}}
<slot></slot>
</div>
`
})
app.component("navbar-item", {
template: `
<div>navbar-item</div>
`
})
app.mount("#div1")
</script>
四、生命周期
1.beforeDestroy改为beforeUnmount
2.destroyed改为unmounted
3.其他的和vue2一样
五、自定义指令
1.指令生命周期(≈组件的生命周期<少一个beforeCreate>):
①created
②beforeMount
③mounted
④beforeUpdate
⑤updated
⑥beforeUnmount
⑦unmounted
六、Composition API(重点)
- setup相当于是老式写法中的beforecreate和created
1.reactive(函数式写法--不用使用this)
(1)作用:
创建响应式对象,非包装对象,可以认为是模板中的状态
(2)注意事项:
-
vue3中 template可以放兄弟节点
<template> <div>12313333</div> <div>232323232</div> </template> -
reactive类似useState,如果参数是字符串、数字时,会报警告(value cannot be made reactive),所以应该设置对象,这样可以数据驱动页面
①会报错的情况
setup() {
const obj = reactive("");
return obj;
},
②不会报错的情况
setup() {
const obj = reactive({
mytext: "",
datalist: [],
});
const obj2 = reactive({
myage: "123",
});
const obj3 = reactive({
datalist2: [],
});
const obj4 = reactive([]);
const handleAdd = () => {
obj.datalist.push(obj.mytext);
obj4.push(obj.mytext);
};
return {
obj,
obj2,
obj3,
obj4,
handleAdd,
};
},
2.ref
(1)组件间的通信
- ref():创建ref对象
- .value获取dom节点
- .value.value获取dom节点的值
<template>
<div>
<input type="text" ref="mytextref" />
</div>
</template>
<script>
import { ref } from "@vue/reactivity";
export default {
setup() {
const mytextref = ref(); //创建ref对象
const handleAdd = () => {
console.log(mytextref.value); //必须是.value
};
return {
mytextref,
handleAdd,
};
},
};
</script>
(2)接收普通数据类型(字符串,数字)
①作用:
创建一个包装式对象,含有一个响应式属性value。它和reactive的差别,就是前者没有包装属性value
②注意事项:
- ref可以拦截字符串(其实还是对一个对象中的.value进行拦截,只不过.value可以省略)
- 使用ref对象中的值时,不需要使用.value
- 修改ref对象中的值时,必须使用.value
<template>
<div>
ref----新写法
<!-- 使用时不需要使用.value -->
{{ myname }}
<button @click="handleClick">change</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
//ref可以拦截字符串(其实还是对一个对象中的.value进行拦截,只不过.value可以省略)
const myname = ref("kerwin");
console.log(myname);
const handleClick = () => {
//修改myname中的值时,必须使用.value
myname.value = "xiaoming";
};
return {
myname,
handleClick,
};
},
};
</script>
③ref实现todo功能:
<template>
<div>
<input type="text" ref="mytext" />
<button @click="handleClick">add</button>
<ul>
<li v-for="data in datalist" :key="data">{{ data }}</li>
</ul>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const mytext = ref();
const datalist = ref([]);//此处也可以为datalist赋初始值,如: ref(['111','222'])
const handleClick = () => {
datalist.value.push(mytext.value.value);
};
return {
mytext,
datalist,
handleClick,
};
},
};
</script>
3.toRef - 只能导出单个属性,不能导出整个对象
(1)作用:
创建一个ref对象,其value值指向另一个对象中的某个属性
(2)格式:创建的变量name会指向person对象中的name属性(能够有响应式)
const name = toRef(person, 'name')
(3)实例:将person对象中的几个属性导出(只能分别导出)
import { toRef } from 'vue';
export default {
setup(){
let person = reactive({
name: '张三',
age: 18,
job: {
j1: {
salary: 20,
}
}
})
},
return {
name: toRef(person, 'name'),
age: toRef(person, 'age'),
salary: toRef(person.job.j1, 'salary')
}
}
4.toRefs
(1)作用:
默认直接展开state,那么此时reactive数据变成普通数据,通过toRefs,可以把reactive里的每个属性,转化为ref对象,这样展开后,就会变成多个ref对象,依然具有响应式特征
(2)注意事项:
- toRefs返回的是一个大对象,所以需要将大对象展开后再返回出去
(3)代码演示:
<template>
<div>
{{ myname }}--{{ myage }}
<button @click="handleClick">change</button>
</div>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
setup() {
//定义状态
const obj = reactive({
myname: "kerwin",
myage: 100,
});
const handleClick = () => {
obj.myname = "xiaoming";
obj.myage = 200;
};
return {
//toRefs返回的是一个大对象,所以需要将大对象展开来返回出去
...toRefs(obj),
handleClick,
};
},
};
</script>
5.父子组件间通信(props,emit)
(1)注意事项:
- props:父传子;emit:子传父
- setup中的参数名称可以随便定义,只不过为了区分就语义化写成了props与emit
- 子组件向父组件传递信息时,setup中的参数必须解构({emit})
- emit (hooks) === this.$emit (类)
(2)代码
<1>父组件
<template>
<div>
通信
<navbar myname="home" myid="111" @event="change"></navbar>
<sidebar v-show="obj.isShow"></sidebar>
</div>
</template>
<script>
import navbar from "./components/navbar";
import sidebar from "./components/sidebar";
import { reactive } from "vue";
export default {
components: {
navbar,
sidebar,
},
setup() {
const obj = reactive({
isShow: true,
});
const change = () => {
obj.isShow = !obj.isShow;
};
return {
obj,
change,
};
},
};
</script>
<2>子组件navbar----控制子组件sidebar的显示与隐藏
<template>
<div>
<button @click="handleShow">left</button>
{{ myname }}---{{ myid }}
<button>right</button>
</div>
</template>
<script>
export default {
props: ["myname", "myid"],
setup(props, { emit }) {
// console.log(props.myname,props.myid);
const handleShow = () => {
//emit (hooks) === this.$emit (类)
emit("event");
};
return {
handleShow,
};
},
};
</script>
<3>子组件sidebar
<template>
<div>
<ul>
<li>111111111111111</li>
<li>111111111111111</li>
<li>111111111111111</li>
<li>111111111111111</li>
<li>111111111111111</li>
<li>111111111111111</li>
<li>111111111111111</li>
<li>111111111111111</li>
<li>111111111111111</li>
<li>111111111111111</li>
<li>111111111111111</li>
<li>111111111111111</li>
<li>111111111111111</li>
<li>111111111111111</li>
</ul>
</div>
</template>
6.vue3新的生命周期
(1)生命周期
(2)代码演示
<template>
<div>
生命周期
<ul>
<li v-for="data in obj.list" :key="data">{{ data }}</li>
</ul>
</div>
</template>
<script>
import { reactive, onBeforeMount, onMounted } from "vue";
export default {
setup() {
const obj = reactive({
list: [],
});
onBeforeMount(() => {
console.log("onBeforeMount");
});
onMounted(() => {
console.log("dom上树", "axios事件监听");
setInterval(() => {
obj.list = ["aaa", "bbb", "ccc"];
}, 2000);
});
return {
obj,
};
},
};
</script>
7.计算属性computed
(1)计算方法(数据每次改变都要调用方法---效率低)
<template>
<div>
<input type="text" v-model="obj.mytext" />
<ul>
<li v-for="data in filterlist()" :key="data">{{ data }}</li>
</ul>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
setup() {
const obj = reactive({
mytext: "",
datalist: ["aaa", "bbb", "abc", "aac", "bbc", "cba"],
});
//计算方法
const filterlist = () => {
return obj.datalist.filter((item) => item.includes(obj.mytext));
};
return {
obj,
filterlist,
};
},
};
</script>
(2)计算属性(数据有缓存,省内存)
<template>
<div>
<input type="text" v-model="obj.mytext" />
<ul>
<li v-for="data in computedList" :key="data">{{ data }}</li>
</ul>
</div>
</template>
<script>
import { reactive, computed } from "vue";
export default {
setup() {
const obj = reactive({
mytext: "",
datalist: ["aaa", "bbb", "abc", "aac", "bbc", "cba"],
});
//计算属性
const computedList = computed(() => {
return obj.datalist.filter((item) => item.includes(obj.mytext));
});
return {
obj,
computedList,
};
},
};
</script>
8.watch
(1)格式:watch(监听的数据, 处理方法,监视的配置) → 第三个参数可以不写
immediate: true → 立即更新
import { watch } from 'vue';
watch(sum,(newValue,oldValue) => {
console.log(`sum的值变化了`, newValue, oldValue);
}, {immediate: true})
(2)代码:
<template>
<div>
<input type="text" v-model="obj.mytext" />
<ul>
<li v-for="data in obj.datalist" :key="data">{{ data }}</li>
</ul>
</div>
</template>
<script>
import { reactive, watch } from "vue";
export default {
setup() {
const obj = reactive({
mytext: "",
datalist: ["aaa", "bbb", "abc", "aac", "bbc", "cba"],
oldlist: ["aaa", "bbb", "abc", "aac", "bbc", "cba"],
});
watch(
() => obj.mytext,
() => {
obj.datalist = obj.oldlist.filter((item) => item.includes(obj.mytext));
}
);
return {
obj,
};
},
};
</script>
9.watchEffect
(1)与watch的区别
watch 既要指明监视的属性,也要指明监视的回调
watchEffect 不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性
(2)watchEffect与computed的区别
①相同点
watchEffect 在监视数据改变时,监视的回调中用到哪个属性就监视哪个属性,这一点与computed相似
②不同点
computed 注重计算出来的值(回调函数的返回值),所以必须要写返回值;
watchEffect 更注重的是过程(回调函数的函数体),所以不用写返回值。
(3)实例
// watchEffect所指定的回调函数中用到的数据只要发生变化,则直接重新执行回调
watchEffect(() => {
const x1 = sum.value
const x2 = person.age
console.log(`watchEffect配置的回调执行了`)
})
10.setup语法糖
(1)props(子组件接收父组件传来的msg属性)
<script setup>
const props = defineProps({
msg: string,
})
</script>
(2)动态组件写法component
<template>
<component :is='num2%2==0?HelloWord:About'></component>
</template>
<script setup>
import HelloWord from './components/HelloWord.vue';
import About from './components/About.vue';
import {ref} from 'vue';
var num2 = ref(456123);
</script>
(3)emit实现父子通信
父组件中
<template>
<HelloWord msg='hello' @sendParent='receiveEvent'></HelloWord>
<h1>{{message}}</h1>
</template>
<script setup>
import HelloWord from './components/HelloWord.vue';
import About from './components/About.vue';
import {ref} from 'vue';
let num2 = ref(456123);
let message = ref('');
let receiveEvent = (event) => {
message.value = event;
}
</script>
子组件HelloWord中
<template>
<h1 @click='clickEvent'>{{msg}}</h1>
</template>
<script setup>
const props = defineProps({
msg: string,
})
let clickEvent = () => {
emit('sendParent', '这是子组件发给父组件的信息');
}
const emit = defineEmits(['sendParent']);
</script>
(4)defineExpose(子传父时使用)
从HelloWord中导出
<template>
<div>helloword</div>
</template>
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
从App中引入
<template>
<HelloWord ref='hello'></HelloWord>
</template>
<script setup>
import HelloWord from './components/HelloWord.vue';
import {ref} from 'vue';
let hello = ref(null);
const showRef = () => {
console.log(hello.value);
}
</script>
(5)getCurrentInstance
①作用
由于 setup 在生命周期 beforecreate 和 created 前执行,此时 vue 对象还未创建,因无法使用我们在 vue2.x 常用的 this。那就用getCurrentInstance代替this。
②使用方式(引入全局定义的axios)
main.js中通过app.config.globalProperties来挂载全局axios
import axios from 'axios';
const app = createApp(App);
app.config.globalProperties.$axios = axios;
app.use(store).mount('#app')
vue文件中通过getCurrentInstance来调用全局axios
<script setup>
import {ref,getCurrentInstance} from 'vue';
let hello = ref(null);
const {proxy} = getCurrentInstance();
proxy.$axios({
method:'get',
url:'xxxx',
})
</script>
七、自定义hooks
- !作用:将vue文件里的js操作提取出来,提高代码复用率,降低页面负担
1.app.js
import { reactive, onMounted } from 'vue'
import axios from 'axios'
function getData1() {
const obj1 = reactive({
list: []
})
onMounted(() => {
axios.get('/test1.json').then(res => {
obj1.list = res.data.list
})
})
return obj1
}
function getData2() {
const obj2 = reactive({
list: []
})
onMounted(() => {
axios.get('/test2.json').then(res => {
obj2.list = res.data.list
})
})
return obj2
}
export { getData1, getData2 }
2.10-app.vue
<template>
<div>
<ul>
<li v-for="data in obj1.list" :key="data">{{ data.name }}</li>
</ul>
<ul>
<li v-for="data in obj2.list" :key="data">{{ data.title }}</li>
</ul>
</div>
</template>
<script>
import { getData1, getData2 } from "./module/app";
export default {
setup() {
const obj1 = getData1();
const obj2 = getData2();
return {
obj1,
obj2,
};
},
};
</script>
八、vue-router的使用方法
1.页面跳转时的路由写法
- router === this.$router
import { useRouter } from 'vue-router'
setup(){
const router = useRouter()
const handleChangePage = (id) => {
router.push(`/detail/${id}`)
}
return {
handleChangePage
}
}
2.页面跳转后接收跳转数据
- route === this.$route
import { useRoute } from 'vue-router'
import { onMounted } from 'vue'
setup(){
const route = useRoute()
onMounted(() => {
console.log(route.params.id)
})
}
3.router.js中创建路由
(1)路由模式
- history模式:history: createWebHistory()
- hash模式:history: createWebHashHistory()
const router = createRouter({
history: createWebHistory(), //history模式
history: createWebHashHistory(), //hash模式
routes
})
(2)* 对于路由重定向--不起作用
- 除了*,其他重定向都不变
①错误写法
{
path: '*',
redirect: '/films'
}
②正确写法('*' === '/' + '/:xxx' )
{
path: '/films',
components: Film,
name: 'film',
children:[]
},
//路由重定向
{
path: '/',
redirect: '/films'
},
{
path: '/:kerwin',
redirect: {
name: '/film' //命名路由写法
}
}
九、Vuex的使用方法
- store === this.$store
import { useStore } from 'vuex'
setup(){
const store = useStore()
onMounted(() => {
store.commit('hide')
})
}
十、provide,inject管理公共状态(代替vuex)
- 方法:在App.vue里将需要共享的状态用provide导出,子组件中使用inject引入共享的状态
1.代码
(1)App.vue(项目根组件)
import { provide, ref } from 'vue'
setup(){
const isShow = ref(true)
provide('liumoushow',isShow)
return isShow
}
(2)子组件中
import { inject } from 'vue'
setup(){
const isShow = inject('liumoushow')
isShow.value = false //isShow是通过ref定义的,所以需要使用.value修改值
}
十一、其他Composition API(不常用)
1.shallowReactive与shallowRef
(1)shallowReactive:
只处理对象最外层(第一层) 属性的响应式(浅响应式)
(2)shallowRef:
只处理基本数据类型的响应式,不进行对象的响应式处理
(3)使用场景:
如果有一个对象数据,结构比较深,但变化时只是外层属性变化 → 使用 shallowReactive
如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换 → shallowRef
2.readonly与shallowReadonly
(1)readonly
让一个响应式数据变为只读的(深只读 → 整个数据都是只读)
(2)shallowReadonly
让一个响应式数据变为只读的(浅只读 → 数据的第一层为只读,更深层的可修改)
(3)应用场景
不希望数据被修改时。如:数据不是在自己组件中定义的,是其他组件给自己发来了,其他组件要求在使用时不能修改该数据,此时拿到数据是就需要在数据外套一层readonly
3.toRow与markRow
(1)toRow
①作用
将一个由reactive生成的响应式对象转为普通对象
②使用场景
用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
(2)markRow
①作用
标记一个对象,使其永远不会再成为响应式对象
②使用场景
<1>有些值不应被设置为响应式的,例如复杂的第三方类库等
<2>当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
4.customRef
(1)作用
创建一个自定义的ref,并对其依赖项进行跟踪和更新触发进行显示控制(多用于防抖效果的制作)
(2)实现防抖效果代码
<template>
<input type='text' v-model='keyword' />
<h3>{{keyword}}</h3>
</template>
<script>
import { customRef } from 'vue';
export default {
name: 'Demo',
setup(){
// 自定义一个myRef
function myRef(value, delay){
let timer
// 通过customRef 去实现自定义
return customRef((track, trigger) => {
return {
get(){
console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
// 通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
track()
return value
},
set(newValue){
console.log(`有人把myRef这个容器中数据改为了:${newValue}`)
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
// 通知Vue去重新解析模板
trigger()
}, delay)
},
}
})
}
let keyword = myRef('hello',500)
return {keyword}
}
}
</script>
5.响应式数据的判断
(1)isRef
检查一个值是否为一个ref对象
(2)isReactive
检查一个对象是否是由 reactive 创建的响应式代理
(3)isReadonly
检查一个对象是否是由 readonly 创建的只读代理
(4)isProxy
检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
十二、Vue3新的组件
1.Fragment - 减少标签层级,减少内存占用
在vue2中:组件必须有一个根标签
在vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中(vue3自动完成,没有代码)
2.Teleport
视频链接(可能会失效):www.bilibili.com/video/BV1Zy…
(1)作用
能够将组件的html结构移动到指定位置的技术。例如:弹窗
(2)格式
- 移动位置可以是一个标签也可以是css的选择器(id选择器,类选择器等)
<teleport to='移动位置'>
<div v-if='isShow' class='mask'>
<div class='dialog'>
<h3>我是一个弹窗</h3>
<button onClick='isShow=false'>关闭弹窗</button>
</div>
</div>
</teleport>
3.Suspense
视频链接(可能会失效):www.bilibili.com/video/BV1Zy…
(1)作用:
等待异步组件(异步组件:可以将组件分离开。正常情况下,页面会等待所有组件加载完毕才会渲染,在网速较差时,会影响用户体验;采用异步组件后,就会分别渲染组件,但是如果不适用Suspense标签,会导致页面闪动)时渲染一些额外内容,让应用有更好的用户体验
(2)使用步骤
①异步引入组件
import { defineAsyncComponent } from 'vue';
const Child = defineAsyncComponent(() => import('./components/Child.vue'))
②使用 Suspense 包裹组件,并配置好 default 与 fallback
- default:放要渲染的异步组件;
- fallback:放异步组件渲染过程中显示的内容
<template>
<div class='app'>
<h3>我是App组件</h3>
<Suspense>
<template v-slot:default>
<Child />
</template>
<template v-slot:fallback>
<h3>加载中......</h3>
</template>
</Suspense>
</div>
</template>
十三、pinia
官方网址:pinia.web3doc.top/
1.与Vuex的区别
①pinia 没有mutations,只有:state、getters、actions
②pinia 分模块不需要modules(之前vuex分模块需要modules)
③pinia 体积更小(性能更好)
④pinia 可以直接修改state的数据
2.pinia的配置
(1)安装下载
yarn add pinia
# or with npm
npm install pinia
(2)main.js中引入
import { createPinia } from 'pinia'
app.use(createPinia())
(3)根目录新建store/index.js中写入
import { defineStore } from 'pinia'
export const useStore = defineStore('storeId', {
state: () => {
return {
counter: 0,
}
},
getters:{},
actions:{}
})
(4)组件使用
<script setup>
import { useStore } from '../store'
const store = useStore();
</script>
3.pinia的使用
(1)state
(2)getters
(3)actions
十四、总结(vue2和vue3区别)
1.vue3中取消了过滤器用法
2.vue3取消了中央事件bus
3.vue3底层将Object.defineProperty换成了Proxy(可以对对象中属性的添加/数组改变进行拦截)
4.vue3修改了生命周期(包括指令的生命周期)
5.vue3新增composition API,提供函数式写法