vue3 從入門到實戰(上)

前言

筆者目前大四,在北京獲得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 的生命週期及nextTick使用

在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

vue 響應式系統和methods

你們先看代碼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中使用到的數據發生變化了就會從新執行一次。

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是一個對象,裏面有不少屬性,如 e m i t , emit, slots,$attrs等,除此以外,還合併了setup函數等返回值。

a t t r s 和咱們的 a t t r s 是同樣的,所以在這個例子中,咱們徹底可使用 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原理。

相關文章
相關標籤/搜索