用vite创建工程
npm create vue@latest
vue2和vue3的区别
vue2的Api是配置式风格的,vue2的Api是组合式风格的
vue2中若想修改一个需求,就要修改与其相关的data,methods,computed等,vue3将这些数据都组合起来,作为一个函数
createApp工厂函数
创建应用实例对象app(类似vm)
app比vm更轻量化,app上的属性很少
import { createApp } from 'vue'
const app = createApp(App);
app.mount('#app');
setup函数
setup是一个函数,跟methods类似,在setup中可以写数据,方法,最后要return一个对象,则对象中的属性,方法在模板中可以直接使用
setup中的this是undefined
setup中的数据不是响应式的,数据会改变,但页面不发生变化
vue2中data可以读取vue3中setup的值,setup中不能读data中数据,vue2和vue3中属性名一致时,模板读取的值是vue3的值,但不建议vue2和vue3混用
setup语法糖:在script标签是加setup,会将里面的数据自动return
<script lang="ts" setup>
let name='xx';
let n=0;
function add() {
n++
}
</script>
emits:['hello']
setup(props,context){
console.log(context.attrs)
console.log(context.slots)
console.log(context.emit)
function test(){
context.emit('hello',666)
}
return {test}
}
ref函数
ref可以实现数据的响应式
ref用来定义响应式的基本类型数据,靠getter,setter实现的
ref定义响应式的对象类型是靠reactive函数实现的
ref()返回的是一个引用实现的实例对象
- 将要实现响应式的数据,用ref( )包裹
(1)首先引入ref import { ref } from 'vue';
(2) 将ref标记值
let n = ref(0);
function add(){
console.log(n);
n.value++; //在方法中使用时要加.value(n.value),在模板中直接使用即可(n)
}
reactive
只能定义响应式的对象,数组类型
-
引入reactive
import { reactive } from 'vue';
-
使用reactive()
let personObj = reactive({
name:'zs',
age:18
})
function add2(){
personObj.age++ //在方法中和在模板中直接使用即可(person.age)
}
ref和reactive区别
- 若直接修改一个对象
由reactive定义的,必须使用 Object.assign(personObj,{name:'lisi',age:20})
才能改变
//定义数据
let personObj = reactive({
name:'zs',
age:18
})
//定义方法
function changeObj(){
Object.assign(personObj,{name:'lisi',age:20})
}
由ref定义的,直接修改即可
//定义数据
let personObj = ref({
name:'zs',
age:18
})
//定义方法
function changeObj(){
personObj.value = {name:'lisi',age:30}
}
##toRefs
方便在模板中直接使用对象的属性名
将一个对象的属性直接拎出来,且是响应式的,跟原对象的属性值是绑定的
toRefs(person)会返回一个ref对象,其value值指向的是person对象中的所有属性。(name:person.name)
let person = reactive({
name:'张三',
age:19
})
const {name,age} = toRefs(person) //{name:person.name},并为name配置了get,set。在模板中读取name,在函数中使用name.value
let n = toRef(person,'name')
shallowReactive,shallowRef
readonly shallowReadonly
toRaw markRaw
provide与inject
实现祖组件与后代组件之间的通信
祖组件
let car = '奔驰';
provide('car',car);
后代组件
let car = inject('car')
判断响应式数据的类型
fragment
在vue3中可以不用写根标签,会自动将多个标签包含在一个fragment标签中
teleport传送门
将teleport标签内html结构传送到页面其他位置
<teleport to='body'>
<p>hello!!!!!!!!</p>
</teleport>
##vue2和vue3响应式的区别
- vue2的响应式
(1) vue2中对对象类型数据是通过Object.defineProperty()
,给对象上的属性配置get,set来实现数据的响应式
let number = 0;
Object.defineProperty(person, 'age', {
//当有人读取person.age时,函数被调用,就会返回一个值
get() {
console.log("有人读取了age的值");
return number;
},
//当有人修改person.age时,函数被调用,且会收到修改的最新值(value)
set(value) {
console.log("有人修改了age的值");
number = value;
}
});
缺点: vue2中给对象新添加,删除一个属性,vue都不能检测到,所以就会出现数据已经发生变化了,但页面不能改变
只能用$set(this.person,'sex','女'),$delete(his.person,'sex)
来实现页面的更新
(2) vue2中对数组类型的数据通过对数组的方法进行封装,实现响应式
(push,pop,shift,unshift,splice,sort,reverse),当程序员对数组调用以上的方法时,页面会自动更新
缺点:通过数组下标对元素进行修改,页面不会发生变化。
只能this.$set(this.arr,0,'xx')
进行修改或用以上的数组方法进行修改
- vue3的响应式
通过Proxy(代理)实现对源对象的属性进行增删改查(get,set,deleteProperty)
get(target,propertyName),set(target,propertyName,value)
target:源数据,propertyName:属性名,value:修改的值
通过Reflect对源数据进行操作
##computed
计算属性 computed(() =>{return })
计算属性结果也是也ref定义的响应式数据
在模板中直接读取计算属性,在方法中要.value
//先引入computed
import { computed } from 'vue';
//函数写法
let full = computed(()=>{
console.log(full.value);
return first.value +''+ last.value;
})
//对象写法
let fullName = computed({
get() {
return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + lastName.value
},
set(newVal) { //只有当fullName整体被修改时才会调用该方法,收到的newVal就是修改之后的值
let str1 = newVal.slice(0,2)
let str2 = newVal.slice(2)
firstName.value = str1
lastName.value = str2
}
})
function changeName() {
fullName.value = 'lisi'
}
watch
- 监视ref定义的基本类型数据
首先要引入watch,ref
watch(‘监视的数据’,函数)
import { ref,watch } from 'vue';
let sum = ref(0);
watch(sum,(newValue,oldValue)=>{
console.log(newValue,oldValue);
})
- 监视ref定义的对象类型数据
若要监视对象内属性,要配置deep。不加deep只有当整个对象发生变化时才会被监听
watch(‘监视的数据’,函数,{配置}) 配置:{deep:rue,immediate:true}
watch监听对象属性时,返回的newValue和oldValue都是一样的,因为监视的一直都是person对象,内存地址没变
watch(person,(newValue,oldValue)=>{
console.log(newValue,oldValue);
},{deep:true,immediate:true})
-
监视reactive定义的对象类型数据
默认开启深度监视,且无法关闭深度监视
watch(person,(newValue,oldValue)=>{
console.log(newValue,oldValue);
})
- 只监视对象中某个属性的变化
若该属性是基本数据类型,则需要写成函数形式()=>{}
watch(()=>{return person.name},(newValue,oldValue)=>{
console.log(newValue,oldValue);
})
若该属性是对象数据类型,则需要写成函数形式()=>{},且手动开启深度监视
watch(()=>{return person.cars},(newValue,oldValue)=>{
console.log(newValue,oldValue);
},{deep:true})
- 监视多个属性
将要监视的属性写在一个数组中
watch([()=>{return person.cars.c1},()=>person.name],(newValue,oldValue)=>{
console.log(newValue,oldValue);
},{deep:true})
watchEffect
自动化的watch,不用明确指出监视的数据,函数中用到哪些属性,就监视哪些属性
引入 import {watchEffect} from 'vue'
watchEffect(()=>{})
里面直接传一个函数
标签的ref属性
用在普通的DOM标签上,获取的是DOM节点
用在组件标签上,获取的是组件实例
-
在DOM组件中使用
<h2 ref="title"> hello world </h2> let title = ref() //创建一个变量来接收
-
当父组件获取到子组件实例时,默认是看不到子组件的数据,使用defineExpose将子组件数据暴露给父组件
import {defineExpose} from 'vue'; //可以不用引入 defineExpose(person) // 写在script标签的最下面
props
接受父组件传过来的值
引入 import {defineProps} from 'vue'
可以不用引入
接收 defineProps(['a'])
const props = defineProps(['title'])
接收 + 限制类型 defineProps<list:Person>()
限制类型 withDefaults()
生命周期
-
创建 beforeCreate created setup
-
挂载 beforeMount mounted onBeforeMount onMounted
onMounted(()=>{ console.log('hhh'); })
-
更新 beforeUpdate updated onBeforeUpdate onUpdated
-
销毁 beforeDestory destoryed onBeforeUnmount onUnmounted
自定义Hooks
每一个hook就是一个函数,跟mixin类似
就是一个一个的ts文件,每一个ts文件实现一个功能,都是一个模块,里面包含数据,方法等等
ts文件内的数据方法要写在一个函数里,函数的数据和方法要return出去,并且将函数默认暴露
import { ref } from 'vue';
export default function(){
let sum = ref(0);
function add(){
sum.value++
}
return{sum,add}
}
在使用该功能的组件里引用即可
import useSum from '@/hooks/useSum'
const sumData = useSum() //useSum()返回的是一个普通的对象,里面的数据是ref类型的,sumData.sum.value
const {sum,add} = useSum(); //可以使用结构赋值
路由
路由中对应的组件在切换时会销毁
- 安装路由器
npm i vue-router
- 创建router文件夹
import { createRouter,createWebHistory } from "vue-router";
import Home from '@/pages/Home.vue'
const router = createRouter({
history:createWebHistory(),
routes:[
{
path:'/home',
component:Home
}
]
})
export default router;
- 在main.ts中引入并使用router
import { createApp } from 'vue'
import App from './App.vue'
import router from '@/router' // 引入
const app = createApp(App);
app.use(router); // 使用
app.mount('#app');
- 在组件中使用
//先引入
import { RouterLink,RouterView } from 'vue-router';
<router-link to="/home">首页</router-link>
<router-view></router-view>
路由器工作模式
hash模式 createWebHashHistory
history模式 createWebHistory
to的两种写法
<router-link to="/home">首页</router-link>
<router-link :to="{path:'/home'}">首页</router-link> //对象写法
路由的name属性
<router-link :to="{name:'home'}">首页</router-link>
routes:[
{
name:'home', //name属性
path:'/home',
component:Home
}
]
路由传参
- 传递参数
<RouterLink :to="{
path:'/home/news',
query:{
id:person.id,
name:person.name
}
}">
</RouterLink>
- 接收参数
//先引入
import {useRoute} from 'vue-router'
let route = useRoute()
模板中route.query.id
即可
编程式导航
import {useRouter} from 'vue-router'
const router = useRouter()
router.push('/home')
重定向
在route中配置,当路径为/时跳转为/home路径
{path:'/',
redirect:'/home'
},
pinia(与Vuex类似)
集中式数据管理,管理共享的数据
- 在main.js中完成以下配置
-
安装pinia
npm i pinia
-
引入pinia
import {createPinia} from 'pinia'
-
创建pinia
const pinia = createPinia()
-
使用pinia
app.use(pinia);
- 创建store文件夹,里面配置不同部分要共享数据的ts文件
import { defineStore } from "pinia"; //defineStore会返回一个proxy对象,将state中数据都包装成响应式
export const useCountStore = defineStore('count',{
//数据
state() {
return {
sum:0
}
},
//各种方法
actions:{
increment(value:number){
this.sum += value //this就是当前这个store
}
},
//类似与computed,就是将state中数据进行操作
getters:{
bigSum(state){ //会接受一个参数,就是store中的state
return state.sum*10
}
}
})
- 在组件中使用仓库中数据
import {useCountStore} from '@/store/count'
const countStore = useCountStore(); //useCountStore()返回的是一个proxy对象,sum是由ref封装的
<h2>{{ countStore.sum }}</h2>
修改store中数据
1.直接修改,在组件中可以拿到store中state的数据,并且可以直接进行修改
function add(){
countStore.sum++
}
2.同时修改store中多个数据
function add(){
countStore.$patch({
sum:999,
age:90
})
}
3.通过actions修改(ations中写各种方法,在组件中直接调用即可,vuex中还要commit一下给mutations去处理)
在组件中直接调用store中actions中定义的方法
//store.ts
actions:{
increment(value:number){
this.sum += value
}
}
//组件
function add(){
countStore.increment(1)
}
storeToRefs
简化模板中使用store中的数据
将store中的state数据结构出来
//先引入
import { storeToRefs } from 'pinia';
const {sum} = storeToRefs(countStore) //这样在模板中直接{{sum}}即可,以前要{{countStore.sum}}
$subscribe
监听pinia state中数据的变化
store.$subscribe((mutate,state)=>{})
store的组合式写法
import { defineStore } from "pinia";
import { ref } from 'vue';
export const useCountStore = defineStore('count',()=>{
let number = ref(0);
return {number}
})
组件之间通信
- 自定义事件
父组件给子组件绑定一个事件,子组件触发事件
//子组件
const emit = defineEmits(['getX'])
function sendX(){
emit('getX',x)
}
自定义事件推荐写成 @my-send=sendToy 这种形式
-
mitt (跟$bus类似)
先安装mitt
npm i mitt
- src下创建utils/emitter.ts文件
import mitt from "mitt";
const emitter = mitt();
export default emitter;
- 绑定事件
emitter.on('asd',(value)=>{
console.log(value)
})
- 触发事件
emitter.emit('asd',666)
//666是要传的参数
- 解绑事件
emitter.off('asd')
emitter.all.clear() //清空全部事件
- 在绑定和触发的组件中引入emitter
import emitter from '@/utils/emitter';
- $attrs(父孙之间传值)
如果父给子传的值,子组件不接收,则数据会在子组件$attrs中保存
-
$refs $parent
都需要将组件内的数据defineExpose()
r e f s 是父操作子组件数据, refs是父操作子组件数据, refs是父操作子组件数据,parent子操作父组件数据
<son1 ref="c"></son1>
<son2 ref="c"></son2>
<button @click="changeName($refs)">+1</button>
function changeName(refs){
for(let key in ) //c是组件实例对象,由ref封装的,要通过.value来访问
}
$parent
<button @click="add($parent)">+1</button>
function add(parent:any){
parent.n++; //parent是父组件实例对象(用proxy封装的),可以直接访问属性
}
- provide和inject
实现祖组件与后代组件之间的通信
子组件接收