前言
笔者目前大四,在北京得到App实习,因导师需我做一个技术分享,考虑再三,决定分享最近学习的vue3,又因分享形式不限,因此打算在掘金发文。
笔者目前接触vue3已经差不多100天,对vue3的理解可能存在错误,如有错误的理解还请谅解。又因vue3对typeScript以及笔者更喜爱使用typeScript,因此笔者下面所使用的是typeScript,不过就算读者不会typeScript应该也不影响阅读。
考虑到篇幅长度,本文打算分上中下三部分,上篇主要讲vue3的常规使用。中篇主要讲vue3的一些小原理,后篇是笔者写的一个使用vue3+deno/node(使用deno和node都写了一份破破烂烂的接口,主要笔者node菜的抠脚,deno更菜)写的小项目,估计就3000行代码左右,本篇是上篇。
希望大家能点个小赞,点个收藏,不要和我一样养成白嫖的习惯。
新增 github demo地址 :github.com/1131446340a…
g
新增 vue3从入门到实战)(中)juejin.cn/post/687072…
vue3 的生命周期及nextTick使用
在vue3中,大部分使用都是先引入后函数调用,学习成本极低,如果有vue2开发经验,应该很容易上手,下面先看一个小例子
<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函数中调用即可,我相信这段代码没什么好解释的,输出顺序大家一看就懂。
vue 响应式系统和methods
大家先看代码
<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中使用到的数据发生变化了就会重新执行一次。
props和 ref绑定dom
<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三个组件,其实也是三种组件。
首先大家来看age组件
<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下。
下面来看hello组件,hello组件是使用tsx写的
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则可以很容易检查出来。
最后一个是全局组件 myInput
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一样的书写指令。
指令与css属性响应式
<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了,在下篇文章中,会简单分析一下其原理,在这就不过多叙述了。
vue3的第一个UI组件
这个组件是antd design Vue 的第二版,9.1号发布的,应该是测试版,大家根据官网说明使用即可可。笔者写vue3小项目的时候,还没有第三方ui组件,笔者简单修改了一点点elementUI,让他可以在vue3中使用某个组件,不过踩了一些坑,还不如自己重写组件来的快。
结语
除了vue-router和vuex和过滤器之外,基本已经讲完vue3的常规操作。然而在vue3中没有过滤器,直接使用函数调用即可,因此学完vue-router之后应该就可以造轮子了。
下篇文章会简单说一些个人已知的残废版vue的一些api原理。