笔者目前大四,在北京获得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原理。