筆者目前大四,在北京獲得App實習,因導師需我作一個技術分享,考慮再三,決定分享最近學習的vue3,又因分享形式不限,所以打算在掘金髮文。javascript
筆者目前接觸vue3已經差很少100天,對vue3的理解可能存在錯誤,若有錯誤的理解還請諒解。又因vue3對typeScript以及筆者更喜好使用typeScript,所以筆者下面所使用的是typeScript,不過就算讀者不會typeScript應該也不影響閱讀。css
考慮到篇幅長度,本文打算分上中下三部分,上篇主要講vue3的常規使用。中篇主要講vue3的一些小原理,後篇是筆者寫的一個使用vue3+deno/node(使用deno和node都寫了一份破破爛爛的接口,主要筆者node菜的摳腳,deno更菜)寫的小項目,估計就3000行代碼左右,本篇是上篇。html
但願你們能點個小贊,點個收藏,不要和我同樣養成白嫖的習慣。vue
新增 github demo地址 :github.com/1131446340a…java
gnode
新增 vue3從入門到實戰)(中)juejin.cn/post/687072…react
在vue3中,大部分使用都是先引入後函數調用,學習成本極低,若是有vue2開發經驗,應該很容易上手,下面先看一個小例子git
<script lang="ts">
import { onMounted, onBeforeMount, nextTick} from 'vue'
export default {
name: 'App',
setup() {
nextTick(() => {
console.log('nextTick');
})
onMounted(() => {
console.log('mounted');
})
onBeforeMount(() => {
console.log('beforeMounted');
})
console.log('hello vite Vue3')
}
}
</script>
複製代碼
在vue3中setup()是入口函數,至關於之前的created 和beforecreated生命週期。 你們能夠看到其餘生命週期在setup函數中調用便可,我相信這段代碼沒什麼好解釋的,輸出順序你們一看就懂。es6
你們先看代碼github
<template>
<div>
<h3>vue3響應式系統和methods</h3>
<div>年齡:{{ myAge }}</div>
<div>明年的年齡:{{ mylastAge }}</div>
<button @click="AgeAdd">年齡+1</button>
<div>姓名:{{ myName }}</div>
<div>
愛好:
<div v-for="(hoppy, index) in hoppys" :key="index">{{ hoppy }}</div>
</div>
<div>來自 {{ state1.from }}</div>
</div>
</template>
<script lang="ts">
import HelloWorld from './components/HelloWorld.vue'
import {
ref,
toRefs,
reactive,
watchEffect,
watch,
computed,
onMounted
} from 'vue'
export default {
name: 'App',
setup () {
let myAge = ref(23) //響應式數據
let myName = '黃力豪' //非響應式數據
const state = reactive({
//複雜數據響應式 相似data 基於proxy 操做數組也會觸發響應式
hoppys: ['中國象棋', 'javaScript']
})
const state1 = reactive({
// 能夠定義多個數據源
from: '江西撫州'
})
watchEffect(() => {
// watch 反作用函數 首次加載會觸發,當值發生變化也會觸發
console.log('年齡:' + myAge.value)
console.log('愛好:' + state.hoppys)
})
let mylastAge = computed(() => {
return myAge.value + 1
})
setTimeout(() => {
state.hoppys[1] = 'typeScript'
myAge.value += 1
myName = '力豪'
}, 1000)
watch([state.hoppys, myAge], newVal => {
//能夠監聽多個值
console.log('watch:' + newVal)
})
const methods = {
AgeAdd () {
myAge.value += 1
}
}
return {
myName,
myAge,
...toRefs(state), //將reactive轉化爲ref
state1,
mylastAge,
...methods
}
}
}
</script>
複製代碼
你們應該不難看出,在vue3中templeate模板的使用方式基本沒有發生任何變化,惟一要注意的就是在模板中使用到的任何響應式數據都要在setup函數中返回(包括方法)。
下面來看ts代碼,說是ts,其實目前這些數據均可以使用類型推斷出來,其實看起來和js同樣。
首先任何在setup函數中返回的數據均可以在模板中使用,只是否是響應式數據而已。
在vue3中筆者目前發現有兩種方法能夠將非響應式數據轉化成響應式數據,那就是ref函數和reactive函數,ref應該是基於Object.defineProperty實現的。而reactive是基於proxy實現的,所以建議普通數據類型使用ref,而複雜數據類型使用proxy。可是要注意對是,使用ref,是操做他的value屬性進行改值,可是模板中不須要加上value。其實筆者更傾向這種寫法:
const state = reactive({
name:"",
age:0,
Array:[]
})
複製代碼
這樣直接定義應該數據源就行,就和vue2中使用同樣了,同理methods也同樣。細心對讀者們應該發現了在setup函數中將methods解構以及將state解構,這樣在模板中就能夠方便的寫數據了,你們可能會問toRefs是什麼,見名知意,就是將state中的全部數據轉化爲ref數據。
在vue3中computed使用基本和之前同樣,只是表達形式改爲component API的形式,所以不過多言論。
在vue3中watch 有了一些變化,就是能夠同時監聽多個值,當其中一項發生變化後則會觸發watch。
這裏你們可能沒有見過的就是watchEffect了,有了解過react的朋友對這個應該很熟悉了。 watchEffct在一開始就會調用一次,當watchEffect中使用到的數據發生變化了就會從新執行一次。
<template>
<div>
<h3 ref="H3">ref,props 和一些小功能</h3>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup () {
let H3 = ref(null)
onMounted(() => {
H3.value.style.color = 'red'
})
//還有一些小功能
// readonly 數據只讀
// shallow -( reactive,ref,readonly) 只代理一層
// toRaw 將reactive或者readonly 的值還原
//markRow 永遠不會被代理
//isRef isReactive 等
//unref 若是參數是ref返回他的value不然返回參數
return { H3 }
}
}
</script>
複製代碼
首先用過vue2的小夥伴都知道,ref能夠用來獲取dom元素,在vue3中只需給ref傳如空值,隨後在don中綁定便可使用,注意的是,要在mounted中使用,除此以外必定要記得ref取值是經過其value屬性。 還有一些小功能你們看註釋便可明白。
在vue3中組件有多種定義方式,最大大區別就是在vue3中使用jsx/tsx定義組件比vue2中好用太多了,另外組件涉及到子傳父,props,attrs ,emit,slot等所以此次可能會說的比較詳細。
<template>
<div>
<h3>組件</h3>
<Hello name="黃力豪" @updateName="updateName"></Hello>
<Age :age="33"></Age>
<myInput>
<template v-slot:desc>
<div>
這是輸入框
</div>
</template>
</myInput>
</div>
</template>
<script lang="ts">
import Hello from './TSX/Hello'
import Age from './components/Age.vue'
export default {
components:{
Hello,Age
},
setup(){
const updateName =()=>{
console.log(1);
}
return {updateName}
}
}
</script>
複製代碼
你們能夠看到,我寫了hello,age,myInput三個組件,其實也是三種組件。
<template>
<div>
<div>{{ props.age }}</div>
</div>
</template>
<script lang="ts">
import { reactive, isReadonly, toRaw, inject, ref, readonly } from 'vue'
interface Props {
age: number
}
import vuex from '../shared/vuex'
export default {
props: {
age: {
type: Number,
default: 0
}
},
setup (props: Props) {
// props 是readonly
//不要嘗試去解構props,由於這樣會讓props失去響應式
// props.age = 23
// console.log(isReadonly(props))
props = reactive(toRaw(props))
return {
props
}
}
}
</script>
複製代碼
第一種組件和vue2使用基本相似,注意的是setup函數其實有兩個入參,第一個是props,值得注意的是不要嘗試去解構props,不然會讓props失去響應性,除此以外,props是上面提到的readonly,是隻讀屬性,若是嘗試修改props會報警告。關於readonly後我估計是作了以下操做
let proxy = new Proxy(obj, {
set(target, key, value) {
target[key] = value
},
get(target, key) {
return target[key]
}
})
function readonly(proxy) {
return new Proxy(proxy, {
get(target, key) {
return target[key]
},
set() {
throw Error()
}
})
}
複製代碼
若是執意要去修改props,能夠將props進行toRaw還原在reactive下。
export default {
setup (
props: object,
{ attrs, emit }: { attrs: { name: string }; emit: Function }
) {
return { attrs, emit }
},
render (props: { attrs: { name: string }; emit: Function }) {
return (
<div
onClick={() => {
props.emit('updateName')
}}
>
hello {props.attrs.name}
</div>
)
}
}
複製代碼
若是你喜歡使用tsx語法,那麼你直接導出一個ts對象便可,這個對象和使用.vue文件基本相似,只是多了一個render函數用來書寫html,另外我的感受vue3的tsx 比react要好用一些。
首先咱們來看setup函數,此次我給了第二個參數,並對其進行了解構,解構出attrs,和 emit。emit你們都很熟悉,不作多介紹。咱們來看attrs,從ts的接口咱們不難看出,有一個name屬性,沒錯,就是父組件傳過來的name屬性。這裏有朋友可能就會問了,父傳子不是要經過props嗎?是的,咱們像之前同樣使用props也能夠,可是使用props必須再上面聲明props對象,如今咱們能夠直接經過attrs取得,何樂而不爲。另外注意的是,attrs依舊是可讀不可寫的,可是不是readonly的,若是你嘗試去修改attrs則會報錯。若是執行修改,能夠嘗試根據數據和須要進行深淺拷貝。
下面來看render函數,render函數第一個參數,咱們通常也叫props,render的props是一個對象,裏面有不少屬性,如 slots,$attrs等,除此以外,還合併了setup函數等返回值。
attrs和$emit代替attrs和emit,而刪除setup函數。
咱們能夠看出,子傳父可使用emit函數,是否是比react的子傳父舒服不少?
使用tsx還有一個好處就是,若是使用template模板,咱們要求函數必須傳number類型的數據,可是在template中傳入其餘參數類型徹底檢查不到,而你使用tsx則能夠很容易檢查出來。
import {
reactive,
vShow,
vModelText,
withDirectives,
App,
isReadonly
} from 'vue'
interface Props {
number: number
$slots: {
desc: () => any[]
}
desc: () => {}
input: any
isShow: boolean
}
import { toRefs } from 'vue'
const install = (app: App) => {
app.component('myInput', {
props: {
number: {
type: String
}
},
setup (props: Props, { slots }) {
const state = reactive({ input: 0, isShow: false })
return { ...toRefs(slots), ...toRefs(state) }
},
data () {
return {
number: 0
}
},
render (props: Props) {
console.log(isReadonly(props))
return (
<div>
<div v-show={props.isShow}>你看不見我</div>
{props.desc()}
{props.$slots.desc()[0]}
{/* {withDirectives(<input type='text' />, [[vModelText, this.number]])} */}
<div>{this.number}</div>
{withDirectives(<h1>Count: 2</h1>, [[vShow, true]])}
</div>
)
}
})
}
export default {
install
}
複製代碼
全局組件和以往使用基本相似,傳入install 函數,注意的是,第一個參數不是Vue類,沒有prototype,使用app.component函數註冊插件便可,使用方式和以往基本相似,第一個是組件名,第二個則是一個組件。這裏我依舊選擇了tsx語法。能夠看出setup函數第二個參數解構出來了slots。
下面咱們看render函數,我嘗試使用了v-show,其實並無生效。我google了一些內容,發現要寫這樣書寫應該要使用babel。關於插槽,父組件傳了一個desc的插槽,在vue3中,插槽要經過函數調用的形式。上面兩種使用插槽的方法均可,你們注意下區別便可。在vue3中確實可使用指令,withDirectives()函數,經嘗試使用,vShow生效了,VModel只賦值了初始值,一旦輸入內容就會報錯,具體使用方法目前尚未查到。不過這些書寫仍是比較繁瑣的,但願官方原生支持和templete同樣的書寫指令。
<template>
<div>
css 屬性響應式與指令
<h1 v-highlight="紅色">這是一串被高亮爲紅色的字</h1>
</div>
</template>
<script>
export default {
setup(){
return {
"紅色": 'red',
"字體大小": '40px',
}
}
}
</script>
<style vars='{紅色, 字體大小}'>
div{
color: var(--紅色);
font-size: var(--字體大小);
}
</style>
複製代碼
不知道小夥伴有沒有想過在vue中使用響應式的css,反正我想過,如今vue3支持了。
使用方法也很簡單,在style 中加上vars ={} 而後使用逗號隔開變量便可,同時支持使用中文,而後就是正常的使用css變量了。
vue3中使用指令
const app = createApp(Demo)
app.directive('highlight', {
beforeMount(el, binding, vnode) {
el.style.color = binding.value;
},
pdated(){},
mounted(){},
created(){}
});
複製代碼
使用方法你們一看應該就知道,就不浪費過多口舌了。
在vue3中全局通訊我目前發現了三種,首先是使用provide和inject
<template>
<div>
<h3>全局通訊</h3>
{{myName}}
<Age></Age>
愛好:{{hoppy}}
</div>
</template>
<script lang="ts">
import {toRefs, provide,ref, inject} from 'vue'
import vuex from './shared/vuex'
import Age from './components/Age.vue'
export default {
components:{
Age
},
setup(){
const {myStore, updateName, updatedAge } = vuex
updateName('力豪')
updatedAge(18)
provide('hoppy',ref('javascript'))
let hoppy = ref(inject('hoppy') as string)
return {...toRefs(myStore),hoppy}
}
}
</script>
複製代碼
provide和inject從vue中導出,在某個組件中使用provide,則其子孫以及子子孫孫均可以拿到provide提供的值。第一個參數是提供的名字,使用字符串或者symbol便可。第二個是傳過去的數據, 不過注意要讓其成爲響應式的話則須要使用ref或者reactive包裝一下。子組件使用inject使用便可。 在vue3的vuex中,也是基於此api的。
直接建立一個文件,利用es6模塊特性,並導出一個對象便可,而後兩個組件都引入這個對象便可。我的仍是喜歡這種方法的,足夠簡單清晰並且目前沒有發現使用上的bug。
import { reactive } from 'vue';
const myStore = {
myName: '黃力豪',
myAge: 23
};
const updateName = (newName: string) => {
myStore.myName = newName;
};
const updatedAge = (newAge: number) => {
myStore.myAge = newAge;
};
export default { myStore, updateName, updatedAge };
複製代碼
另外這種方式還有一個好處,將導出的對象改爲爲函數,函數中return這個對象,使用時改成調用函數則這些屬性不會共享,則相似vue2中的mixins了。
第三種毫無疑問就是使用vuex了,不過我的感受在vue3中徹底能夠放棄vuex了,在下篇文章中,會簡單分析一下其原理,在這就不過多敘述了。
這個組件是antd design Vue 的第二版,9.1號發佈的,應該是測試版,你們根據官網說明使用便可可。筆者寫vue3小項目的時候,尚未第三方ui組件,筆者簡單修改了一點點elementUI,讓他能夠在vue3中使用某個組件,不過踩了一些坑,還不如本身重寫組件來的快。
除了vue-router和vuex和過濾器以外,基本已經講完vue3的常規操做。然而在vue3中沒有過濾器,直接使用函數調用便可,所以學完vue-router以後應該就能夠造輪子了。
下篇文章會簡單說一些我的已知的殘廢版vue的一些api原理。