vue3 从入门到实战(上)

10,489 阅读9分钟

前言

笔者目前大四,在北京得到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是一个对象,里面有很多属性,如emit,emit,slots,$attrs等,除此之外,还合并了setup函数等返回值。

attrs和我们的attrs是一样的,因此在这个例子中,我们完全可以使用attrs 和我们的attrs 是一样的,因此在这个例子中,我们完全可以使用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原理。