1. Vue2到Vue3
vue3项目搭建
1.安装vue3的cli
cnpm i -g @vue/cli@next
@next 是因为没有正式发布,后面正式发布可能会去掉
2.创建vue3项目
vue create <name>
3.vue3项目
vue3跟vue2差不多 多了一个
composition API
vue3和ts的配合更好了
vue3与vue2的区别
Vue3与Vue2的不同:
1.vue实例化方式变了
//vue2
import Vue from 'vue';
import App from './App.vue';
new Vue({
render: h => h(App)
}).mount('#app');
//vue3
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
2.Vue全局方法变了
//vue2
import Vue from 'vue';
Vue.component('name', {});
//vue3
import {createApp} from 'vue';
let app=createApp(App);
app.component('name', {});
app.mount('#app');
3.vue3取消了filter 用computed、函数替代
4.v-model
//vue2
v-model = :value + @input
//vue3
[html组件]
v-model = :value + @input
[自定义组件]
v-model = :modelValue + @update:modelValue
5.函数组件的写法变了
//vue2
render(h){
return h(...);
}
//vue3
import {h} from 'vue';
render(props, context){
return h(...);
}
6.data变了
//vue的data统一了,只有函数一种
data(){
return {};
}
7.异步组件(分包加载)
//vue2
const cmp1=()=>import('./cmp1');
//vue3
import {defineAsyncComponent} from 'vue';
const cmp1=defineAsyncComponent(()=>import('./cmp1'));
8.vue3事件简化了
//vue2
this.$emit('name', ...);
this.$on();
this.$off();
this.$once();
//vue3
this.$emit('name', ...);
9.其他
自定义directive变了——beforeMount、mounted、beforeUpdate、updated、...
template不需要根元素
2.详解composition API
composition API究竟是什么
1.数据、方法、computed注入到一个组件当中
2.重用组件的代码---vue2.x种组件里面东西特别多、特别乱(data、method、computed、...)
composition API——注入
把东西(数据、方法、computed、生存周期函数、...)注入到组件当中——集中到一个地方写
//普通方式
export default {
data(){ a: 12 },
methods: {
fn(){
....
}
},
computed: {}
}
//composition API
export default {
setup(){
return {
a: 12,
fn(){
...
},
}
}
}
setup、可响应数据—reactive
- setup
beforeCreate
setup
created
setup执行顺序是在
beforeCreate
之后created
之前,这个时候组件刚刚被new出来,组件里面什么也没有,所以无法使用this
及其他的东西setup无法使用组件的其他东西(data、methods、computed、...)、也不要使用this
setup执行的过程中,组件中其他东西都没有创建;不能用this
setup是同步的(不能用async) 可响应数据
普通的变量,无法完成响应操作(检测到数据变化,重新渲染组件)使用可响应数据(reactive)
import { reactive, isReactive } from "vue"; const state = reactive({ xxx:xxx, xxx:xxx }) // isReactive 可以判断一个值是不是reactive console.log(isReactive(state)); // true
reactive
的参数必须是一个objectreactive(number|string|boolean) ×
reactive({...}|[...]) √
必须是对象(json、数组)如果是其他对象(Date、RegExp、...),修改不会被监听(proxy的原因),如果非要使用可以创建一个新的来覆盖。例如:
<template> <div>a={{a.date}}</div> <button type="button" @click="fn">++</button> </template> <script> import { reactive } from "vue"; export default { setup() { const a = reactive({ date: new Date() }); return { a, fn() { //这样修改数据改变页面不会刷新 // a.date.setDate(a.date.getDate() + 1); //需要直接覆盖 const newDate = new Date(a.date.getTime() + 86400000); a.date = newDate; console.log(a.date); }, }; }, }; </script>
ref、readonly
ref
使用基本类型
使用
isRef
判断一个值是不是refimport { ref, isRef } from "vue"; let a = ref(12); //基本等价于 reactive({ value: 12 }),只是会自动加value console.log(isRef(a)); //true - 判断是否ref
在
template
中ref会自动加.value
,例如使用a<template>{{a}}</template>
在js中需要自己加
.value
例如自己修改<script>fn(){ a.value++ }</script>
引用节点(html原生、自定义组件)
<template> <div ref="a">dsfasdfdas</div> </template> <script> import { ref, onMounted } from "vue"; export default { setup() { const a = ref(null); //需要在onMounted中使用,不然在setup执行过程中 a是不存在 onMounted(() => { a.value.style.border = "1px solid black"; }); return { a, }; }, }; </script>
readonly
类似于
const
,不同的是:readonly
保护所有操作、递归保护;const
----仅保护赋值、非递归isReadonly
判断一个值是不是readonly<template> {{a.count}} <button type="button" @click="a.count++">++</button> </template> <script> import { readonly, isReadonly } from "vue"; export default { setup() { const a = readonly({ count: 12 }); // a.count++的时候const会报警告,readonly是递归保护多层 //判断一个值是不是readonly console.log(isReadonly(a)); //true // const a = { count: 5 }; // a.count++的时候const会增加,const只保护赋值操作,只保护一层 return { a }; }, }; </script>
递归监听、shallow、trigger
- 递归监听(深度监听)
在vue2.x中修改对象属性页面是无法完成刷新的,例如
this.data.obj.a = 12
页面并不会刷新(需要使用$set
),vue3.x中实现了递归监听,修改data.obj.a = 12
页面也会刷新 shallow
&trigger
vue3递归监听的实现原理是把多层数据全部改为
Proxy
,例如:import { reactive, shallowRef, triggerRef } from "vue"; // reactive、ref、readonly 都是递归监听,这里使用ref举例 // 非递归版本 // shallowReactive // shallowRef // shallowReadonly export default { setup(){ const json = reactive({ arr:[ { a:12 } ] }); console.log(json); // Proxy console.log(json.arr); // Proxy console.log(json.arr[0]); // Proxy return {json} } }
当数据特别庞大时,例如几万、几十万条数据时,性能可能会有影响(普通对象转成proxy),这个需要使用非递归版本,例如:
import { shallowRef, triggerRef } from "vue"; export default { setup(){ const json = shallowRef({ arr: [ { a:12 } ] }) console.log(json); // Proxy console.log(json.arr); // [...] console.log(json.arr[0]); // {...} setTimeout(()=>{ json.value.arr[0].a++; console.log(json.value.arr[0].a); // 如果没有这一行,数据变,页面不会刷新 triggerRef(json); //通知页面刷新 }, 500) return {json} } }
raw原始数据
toRaw
如果数据有大量的操作,一直被监听可能会影响性能,可以先把数据转换成原始数据,改完之后再变回去
//reactive、ref、readonly -> 原始数据 let json = reactive({ ... }; //proxy let obj = toRow(json); // {...} //原始数据 -> reactive、ref、readonly reactive(obj); //proxy
把创建reactive、ref的参数返回来 对原始数据进行操作,不会被监听的(性能高)
markRaw 保持一个数据永远是原始的
可能写库的时候会用,数据不会被转成proxy,比如返回一个数据,不希望这个数据被监听
let json = markRaw({xxx:xxx}); reactive(json); // object 不会被转成proxy
深入ref操作
- unRef 或如ref的原始数据
例如
let res = ref(12);
使用toRaw获取 => { value:12 }
使用unRef获取 => 12
toRaw
是连着value一块获取的,unRef是ref专用的 toRef
const json = { a:12 }; let res = ref(json.a); res.value = 13; //这个时候res的value会变为13,但是json的内容不会变,相当于 ref和原始数据没有关系, // let res = ref(json.a) 等价于 let res = ref(12); //toRef let res = toRef(json, "a"); res.value = 13; // 这个时候res的value和json的a都会变为13,但是页面不会刷新 // ref - 普通ref对象 // 1. 不会跟原始数据挂钩 // 2. 会触发页面渲染 // toRef - 特殊的ref对象 // 1. 创建的ref对象,跟原始数据对象挂钩 // 2. 不会触发渲染
ref toRef 跟原始数据无关(相当于复制) 会引用原始数据(改的话都会变) 会触发页面渲染 不会触发页面渲染 toRefs
相当于toRef的批量操作
const json = { a:12, b:5 } // toRef let res1 = toRef(json, "a"); let res2 = toRef(json, "b"); // toRefs let res = toRefs(json) //相当于: //toRefs({a: xx, b: xxx}) => {a: toRef(json, 'a'), b: toRef(json, 'b')}
customRef - 自定义ref
function myRef(...){ //code... return customRef((track, trigger)=>{ // track == 整个ref开始 // trigger == 流程结束,通知vue渲染 return { get(){ track(); return xxxx; }, set(newVal){ // code.. trigger(); } } }) } setup(){ //返回的内容就是普通的ref对象 let data = myRef(...); }
可响应数据的本质
vue2.x是基于Object.defineProprty()
,vue3.x是基于Proxy
shallowReactive
基本实现(非深度监听)function shallowReactive(data) { if (typeof data == 'object') { return new Proxy(data,{ get(data, key){ return data[key] }, set(data, key, val){ console.log('重新渲染'); data[key] = val; // 这里必须return true,因为如果是数组修改会进行两个操作,操作数组,修改长度 // 这里return true 表示第一次成功 return true; } }) } else { console.warn('args is not object:' + data); } } let arr = shallowReactive([1,2,3]); arr.push(4) console.log(arr); // 会刷新页面,并且是两次(1.数组元素修改,2.数组length修改) let d = shallowReactive({ a:12 }); d.a++; console.log(d); // 会刷新页面 let obj = shallowReactive({ a: { b:12 } }); obj.a.b++; console.log(obj); // 不会刷新页面
reactive
基本实现(深度监听)function reactive(data) { if (typeof data == 'object') { if( Array.isArray(data) ){ data.forEach((item, idx)=>{ if(typeof item == 'object'){ data[idx] = reactive(item) } }) }else{ for(item in data){ if(typeof data[item] == 'object'){ data[item] = reactive(data[item]); } } } return new Proxy(data,{ get(data, key){ return data[key] }, set(data, key, val){ console.log('重新渲染'); data[key] = val; // 这里必须return true,因为如果是数组修改会进行两个操作,操作数组,修改长度 // 这里return true 表示第一次成功 return true; } }) } else { console.warn('args is not object:' + data); } } let d = reactive({ a:{ b:12 } }); d.a.b++; console.log(d.a); // 可以监听到
setup与函数组件
setup
setup
有两种用法普通用法
setup(){ // code... return { //.... } }
函数组件用法
import {h} from "vue"; // h('type', props, children) // type -> 标签名称(可以是组件) // props -> 属性内容(json格式) // children -> 标签子元素(数组格式),标签里面的东西 // 下面的代码等价于 // <div title="aaaa" id="div1"> <a href="xxx">aaaaa</s> </div> setup(){ return ()=>{ return h('div', { title:'aaaa', id:'div1' }, [ h('a', { href:'xxx' }, ['aaaaa']) ]) } }
组件
组件分为单文件组件(用的最多),和函数组件。每种组件又分为局部组件和全局组件
单文件组件+局部
import xxx from "..."; export default { components:{xxx} }
单文件组件+全局
// main.ts import xxx from 'xxx.vue'; // app为createApp(App)的返回内容 app.component("xxx", xxx);
函数组件+局部
import {h} from "vue"; const xxx = { setup(){ return ()=>{ return h('type', {}, []) } } } export default { components:{xxx} }
函数组件+全局
const xxx={ setup(){ return ()=>{ return h('type', {}, []); }; } }; // app为createApp(App)的返回内容 app.component('name', xxx);
computed
computed——虚拟数据,自身只是一个转换——对变量的操作,转换为调用;本身不是响应数据
普通用法
在vue3.x中仍然可以使用vue2,x的用法
data(){ return { a:12 } }, computed:{ b(){ // code... return "xxx"; } }
setup中的用法
import { computed, ref } from "vue"; setup(){ // 只读的,只有get,无法修改 const a = computed(()=>{ // code... return "xxx"; }) // 完整版 // computed本身不是可响应数据(修改不会触发页面刷新),所以需要使用ref const _b = ref(11); const b = computed({ get(){ return _b.value; }, set(newVal){ _b.value = newVal; } }) //setup中必须把数据返回出去 return { a,b } }
watch和watchEffect
watchEffect
自动监听
watchEffect
中使用到的数据import {watchEffect, reactive} from "vue"; setup(){ const a = reactive({ count:5 }); const b = reactive({ count:8 }); const c = reactive({ count:12 }); // watchEffect返回一个函数,用来停止监听 const stop = watchEffect(()=>{ // 这里使用了a和b,让a和b改变时会触发这个watch,c改变不会触发 console.log('变了',a,b); }); stop(); // 停止监听 }
清理
import {watchEffect, ref} from "vue"; setup(){ const count = ref(0); // 当监听失效时(调用返回方法、组件销毁)会调用invalidate watchEffect((invalidate)=>{ const timer = setInterval(()=>{ count.value ++; console.log(count.value); }, 500); // 当监听失效时,清除定时器 invalidate(()=>{ clearInterval(timer); }) }) }
watch
手动指定监听的数据,watch同样会返回 停止的方法
import { watch, ref } from "vue"; setup(){ const a = ref(12); // 用法1 - 监听单个数据 const stop = watch(a, ()=>{ // 当a改变时触发回调 // code... }) stop(); //停止 // 用法2 - 监听多个数据 watch([a,b], ()=>{}) // 用法3 - 自定义 // 在函数中返回true就会执行后面的回调,返回false就不会执行,自定义是否监听 watch(()=>{ // code... return true|false },()=>{ /* code.. */ }) }
inject
注入类似于函数的传参,这边声明,那边使用,和传参不同的是provide
和inject
可以跨组件层级传参
注入 | 传参 |
---|---|
不同都写出来 | 需要按顺序写出完整的参数列表 |
容易修改 | 修改费劲 |
穿透(父组件声明,里面的组件都可以使用) | 一层一层传 |
用法
//父组件 import { provide } from "vue"; setup(){ // provide(名字,值); provide("name", "xxx"); } //子组件 import { inject } from "vue"; setup(){ // inject(名字, 默认值); const name = inject("name", "defaultName"); return {name} }
inject尽量少用,用的多了会导致程序逻辑混乱(比如看代码突然出来一个inject,根本不知道在哪个组件声明的),可以用来存储一些配置类的东西,比如主题、权限
3.Vue3生态圈
router路由
// 使用 router/index.ts
import { createRouter, createWebHistory } from "vue-router";
import Home from '@/views/home.vue';
import News from '@/views/news.vue';
// 创建路由实例 ------------ createRouter
// 创建history模式路由 ----- createWebHistory √
// 创建hash模式路由--------- createWebHashHistory
// 创建内存模式路由--------- createMemoryHistory
export default createRouter({
// 指定路由模式,参数:指定网站根路径
// process.env.NODE_ENV == 'devlopment' ? '/' : 'http://aaa.com/news/'
history: createWebHistory("/"),
routes: [
{ path: '/', component: Home },
{ path: '/news', component: News },
],
})
//挂载 main.ts
import router from './router';
createApp(App).use(router).mount('#app');
vuex数据管理
// 使用 store/index.ts
import { createStore } from "vuex";
export default createStore({
state:{ a:12 },
momutaions:{
addA(state, payload){
state.a += playload;
}
}
})
// 挂载 main.ts
import router from './router';
import store from './store';
createApp(App).use(router).use(store).mount('#app');
// 修改
<template>
{{$store.state.a}}
<button @click="fn">+5</button>
</template>
setup(){
return {
fn(){
this.$store.commit("addA", 5)
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。