vue3.0新特性

性能提升

  • 打包大小減少41%
  • 初次渲染快55%,更新快133%
  • 內存使用減少54%
  • 原因:重寫虛擬dom的優(yōu)化和treeshaking的優(yōu)化

Componsition API(API組合)

  • ref 和 reactive
  • computed 和watch
  • 新的生命周期函數(shù)
  • 自定義函數(shù)--hooks函數(shù)
  • Teleport - 瞬移組件的位置
  • Suspense - 異步加載組件的新福音
  • 全局API的修改和優(yōu)化
  • 更多的誓言性特性

更好的Typescript 支持

  • vue3.0源碼就是ts寫的

為什么要有vue3.0?

vue2 遇到的難題

  • 隨著功能的增長,復雜組件的代碼變得難以維護

Mixin 的缺點

  • 命名沖突
  • 不清楚暴露吃了變量的作用
  • 重用到其他component經(jīng)常會遇到問題

對typeScript 的支持非常有限

  • 原因是:依賴this指向上下文油额,主要是沒考慮的ts的集成

總結:vue3 完美解決上訴2.0的問題

ts+ vue Cli 項目搭建環(huán)境

image.png
  • 選擇多項選擇可以更好的支持ts
  • 之后選擇版本3.0就行
安裝好用的vscode插件
  • eslint 語法檢查

3 vue3.0新特性介紹

3.1 ref的妙用

通過vue 3.0實現(xiàn)一個計數(shù)器

  • 使用ref一般傳入的是原始值谆焊,比如count初始化為0
  • setup函數(shù)中定義的對象及方法都是響應式的
<template>
  <h1>{{ count }}</h1>
  <h1>{{ double }}</h1>
  <button @click="increase">點擊+1</button>
</template>

<script lang="ts">
import { defineComponent, ref, computed } from "vue";

// 實現(xiàn)計數(shù)器
export default defineComponent({
  name: "App",
  setup() {
    // 方法和ref,computed的api的使用
    // 無法使用this
    // 創(chuàng)建響應式對象 count茸歧,但是模板上通過ref會直接將里面的value展示出來
    const count = ref(0);
    // computed是一個函數(shù),參數(shù)是回調
    const double = computed(() => {
      return count.value * 2;
    });
    // 定義一個函數(shù)暖夭,count是對象睦授,所以要寫value++
    const increase = () => {
      count.value++;
    };

    // 導出count
    return {
      count,
      increase,
      double,
    };
  },
});
</script>
  • 注意:setup方法中無法使用this
  • 在setup 方法中定義的函數(shù)两芳,變量需要使用return導出才能在template中使用
  • 通過refAPI定義的變量,是一個對象去枷,操作值得時候需要操作value

3.2 reactive函數(shù)的用法

  • 將上面的計算器代碼優(yōu)化下
<template>
  <h1>{{ count }}</h1>
  <h1>{{ double }}</h1>
  <button @click="increase">點擊+1</button>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";
// 定義data的數(shù)據(jù)類型
interface DataProps {
  count: number;
  double: number;
  increase: () => void;
}
// reactive 也是一個函數(shù)盗扇,可以批量歸類變量
// 實現(xiàn)計數(shù)器
export default defineComponent({
  name: "App",
  setup() {
      定義一個reactive對象
      const data: DataProps = reactive({
      count: 0,
      increase: () => {
        data.count++;
      },
      double: computed(() => data.count * 2),
    });
    // 導出data,正常展示count
    // return {
    //   data,
    // };
    // 使用toRefs將數(shù)據(jù)變成響應式對象
    const refData = toRefs(data);
    // 優(yōu)化展示,取出來就不是響應式 使用toRefs函數(shù)將數(shù)據(jù)變成響應式對象,數(shù)據(jù)改變模板更新
    // 點擊無反應
    // return {
    //   ...data,
    // };
    // 
    return {
      ...refData,
    };
  },
});
  • 使用reactive可以將變量及函數(shù)整理在一個對象中
  • 然后在setup中導出data,zai template中使用data.count調用
  • 當使用es6的結構方法優(yōu)化時祷肯,會出現(xiàn)失去響應式的情況,模板無法更新
  • 結合toRefs將數(shù)據(jù)變成響應式對象
  • 使用reactive 的同時記得使用toRefs函數(shù)變成響應式屬性

3.3 vue3響應式對象的新花樣

使用es6 proxy 實現(xiàn)響應式疗隶,完美支持數(shù)組和對象的改變響應式

// vue2.0
Object.defineProperty(data,'count',{
get(){},
set(){}
})
// vue3
new proxy(data,{
    get(key){},
    set(key,value){}
}
)

   const data: DataProps = reactive({
      count: 0,
      increase: () => {
        data.count++;
      },
      double: computed(() => data.count * 2),
      numbers: [1, 2, 3],
      person: {},
    });
        // vue 2.0 直接修改值不更新 3.0 直接修改會是響應式
    data.person.name = "violet";
    data.numbers[0] = 8;

3.3 生命周期

生命周期名字替換: 更好的語義化
beforeDestory => beforeUnmount
destroyed => unmounted :銷毀完畢


image2.png
  • 這些生命周期在setup中使用佑笋,需要加'on'關鍵字
  • setup函數(shù)是跟create的一起運行的,只會執(zhí)行一次
setup() {
    onMounted(() => {
      console.log("mounted");
    });
    // 點擊事件更新數(shù)據(jù)
    onUpdated(() => {
      console.log("update");
    });
    // 調式
    onRenderTracked(() => {
      console.log("onRenderTracked");
    }),
      // 更新時斑鼻,才會觸發(fā)蒋纬,記錄了那些值發(fā)生了變化
      onRenderTriggered((event) => {
        console.log(event, "onRenderTriggered");
      });
}

3.4 watch 偵測變化

  • watch 函數(shù),前面是監(jiān)聽的值坚弱,后面是執(zhí)行的方法
  • setup跟created只執(zhí)行一次蜀备,所以,點擊值發(fā)生變化荒叶,不會去更新title
<template>
  <el-button @click="upDatedGreeting">點擊</el-button>
  </template>
  <script lang="ts">
 setup() {
const greetings = ref("");
    const upDatedGreeting = () => {
      greetings.value += "violet";
    };
    // watch 回調函數(shù)里面接入兩個參數(shù)碾阁,變化值及舊的值
    watch(greetings, (newval, oldavl) => {
      console.log("new", newval, "old", oldavl); // new violet old
      document.title = "1" + greetings.value;
    });
    
     // watch 監(jiān)聽多個值,第一個參數(shù)可以是數(shù)組
    watch([greetings, data], (newval, oldavl) => {
      console.log("new", newval, "old", oldavl); // new violet old
      document.title = "1" + greetings.value;
    });  //["violet", Proxy]0: "violet"1: Proxy {count: 1, double: ComputedRefImpl, numbers: Array(3), person: {…}, increase: ?}length: 2__proto__: Array(0) 
 }
 
 </script>
  • 也可以監(jiān)聽data里面的某個變量

注意直接監(jiān)聽 data.count會失去響應式
at; 可以將數(shù)組里面的第二個參數(shù)變成getter函數(shù)

// 會報警告Invalid watch source:  0 A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.
 watch([greetings, data.count], (newval, oldavl) => {
       console.log("new", newval, "old", oldavl);
       document.title = "1" + greetings.value;
     });
  • 此時根據(jù)警告提示些楣,可以使用getter函數(shù)
watch([greetings, () => data.count], (newval, oldavl) => {
      console.log("new", newval, "old", oldavl); // 0: "violetviolet" 1:0
      document.title = "1" + greetings.value;
    });

3.5 使用模塊化

  • 實現(xiàn)鼠標追蹤器
  • 效果脂凶,點擊鼠標更新鼠標位置
  1. 使用ref實現(xiàn)
// helloword
<template>
  <div class="hello">
    <h1>X:{{ x }} Y:{{ y }}</h1>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  name: "HelloWorld",
  setup() {
    const x = ref(0);
    const y = ref(1);
    const updateMouse = (e: MouseEvent) => {
      x.value = e.pageX;
      y.value = e.pageY;
    };
    onMounted(() => {
      document.addEventListener("click", updateMouse);
    });
    onUnmounted(() => {
      document.removeEventListener("click", updateMouse);
    });
    return {
      x,
      y,
    };
  },
});
</script>
  1. 將這個功能模塊化使用
  • 在src下新建hooks文件夾
// 新建userMousePosition.ts
import { onMounted, onUnmounted, ref } from 'vue'

function useMousePosition() {
   const x = ref(0);
     const y = ref(0);
     const updateMouse = (e: MouseEvent) => {
       x.value = e.pageX;
       y.value = e.pageY;
     };
     onMounted(() => {
       document.addEventListener("click", updateMouse);
     });
     onUnmounted(() => {
       document.removeEventListener("click", updateMouse);
     });
     // 導出x,y
     return {
       x,
       y,
     };
}

export default useMousePosition
  • 在helloword中引用
<template>
  <div class="hello">
    <h1>X:{{ x }} Y:{{ y }}</h1>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
// 引入函數(shù)
 import useMousePosition from "../hooks/userMousePosition";
export default defineComponent({
  name: "HelloWorld",
  setup() {
    //  得函數(shù)返回x,y
     const { x, y } = useMousePosition();
    // 導出使用
     return {
       x,
       y,
     };
  },
});
</script>
  1. 使用reactive改造這個模塊
// userMousePosition.ts
import { onMounted, onUnmounted, reactive, toRefs } from 'vue'

function useMousePosition() {
  const data = reactive({
    x: 0,
    y: 0,
  })
  const updateMouse = (e: MouseEvent) => {
    data.x = e.pageX
    data.y = e.pageY
  }
  onMounted(() => {
    document.addEventListener('click', updateMouse)
  })
  onUnmounted(() => {
    document.removeEventListener('click', updateMouse)
  })
  // 注意使用toRefs將data轉換成響應式數(shù)據(jù)
  const refsData = toRefs(data)
  return {
    ...refsData,
  }
}

export default useMousePosition

3.6 hooks模塊化理解

  1. 異步請求模塊
  • 在src/hooks中新建useUrlLoader.ts文件
import { reactive, toRefs } from 'vue'
import axios from 'axios'
function useURLloader(url: string) {
  const data = reactive({
    result: null,
    loading: true,
    loaded: false,
    error: null,
  })

  axios
    .get(url)
    .then(res => {
      data.loading = false
      data.result = res.data
      data.loaded = true
    })
    .catch(err => {
      data.error = err
      data.loading = false
    })
  const refsData = toRefs(data)
  return {
    ...refsData,
  }
}
export default useURLloader
  • 在loading.vue中使用
<template>
  <div class="box">
    <h1 v-if="loading">Loading!...</h1>
    <img v-if="loaded" :src="result.message" alt="" />
    <p v-if="error">{{ error }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import useURLloader from '../hooks/useUrlLoader'
export default defineComponent({
  name: 'isLoading',
  setup() {
    const { result, loading, loaded, error } = useURLloader(
      'https://dog.ceo/api/breeds/image/random'
    )
    return {
      loading,
      loaded,
      result,
      error,
    }
  },
})
</script>
  • 結果


    image3.png

2. 模塊化結合ts --- 泛型改造

報錯(待解決): result: <T | null>null ==

error  Use 'as T | null' instead of '<T | null>'
// useUrlLoader.ts
import { reactive, toRefs } from 'vue';
import axios from 'axios';

function useURLloader<T>(url: string) {
  const data = reactive({
    result: <T | null>null,  
    loading: true,
    loaded: false,
    error: null,
  });

  axios
    .get(url)
    .then(res => {
      data.loading = false;
      data.result = res.data;
      data.loaded = true;
    })
    .catch(err => {
      data.error = err;
      data.loading = false;
    });
  const refsData = toRefs(data);
  return {
    ...refsData,
  };
}
export default useURLloader;
  • 在頁面中使用
<template>
  <div class="box">
    <h1 v-if="loading">Loading!...</h1>
    <img v-if="loaded" :src="result.message" alt="" />
    <p v-if="error">{{ error }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, watch } from 'vue';
import useURLloader from '../hooks/useUrlLoader';
// 提前將接口返回的參數(shù)類型定義好
interface UrlProps {
  message: string;
  state: string;
}
export default defineComponent({
  name: 'isLoading',
  setup() {
    const { result, loading, loaded, error } = useURLloader<UrlProps>(
      'https://dog.ceo/api/breeds/image/random'
    );
    watch(result, () => {
      if (result.value) {
        console.log(result.value.message, 'value');
      }
    });
    return {
      loading,
      loaded,
      result,
      error,
    };
  },
});
</script>

3.7 ts對vue3的加持

  • defineComponent服務ts 而存在的
  • 兼容vue2.0的3.0的語法提示

3.8 Teleport - 瞬間移動(新增加標簽)

  • 場景:全局彈窗的使用,使用v-if
  • 問題:dialog 被包裹在其他組件之中愁茁,容易被干擾
    -樣式問題邊的容易混亂
  • 使用<Teleport></Teleport>
// 在index.html 中新建一個id為model的根節(jié)點與app層級一樣
// index.html
<div id="model"></div>
// model 中掛載到model節(jié)點上
<teleport to="#model"></teleport>
  • 新建model
// model.vue
<template>
  <teleport to="#model">
    <div class="center" v-if="isOpen">
      <h2><slot>this is a modal</slot></h2>
      <button @click="buttonClick">Close</button>
    </div>
  </teleport>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  props: {
    isOpen: Boolean,
  },
  emits: {
    // 向外發(fā)送的事件名稱蚕钦,并且驗證函數(shù)參數(shù)
    // 'close-modal': (payload: any) => {
    //   return payload.type === 'close';
    // },
    'close-modal': null, // 這個事件不需要驗證
  },
  setup(props, context) {
    // 驗證發(fā)送參數(shù)是否正確
    const buttonClick = () => {
      context.emit('close-modal', false);
      // context.emit('close-modal', { type: 'close' });
    };
    return {
      buttonClick,
    };
  },
});
</script>
<style>
.center {
  width: 200px;
  height: 200px;
  border: 2px solid #eee;
  background: white;
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
</style>
  • 子父組件調用
<template>
  <model :isOpen="modelIsOpen" @close-modal="openModel">我的彈窗</model>
  <el-button @click="openModel(true)">打開彈窗</el-button>
</template>
<script lang="ts">
import Model from './components/Model.vue';
import { defineComponent,ref} from 'vue';
export default defineComponent({
  name: 'App',
  components: { HelloWorld, Loading, Model },
  setup() {
    const modelIsOpen = ref(false);
    const openModel = (val: boolean) => {
      modelIsOpen.value = val;
    };
     return {
      openModel,
      modelIsOpen,
    };
    })
</script>

3.9 Suspense - 異步請求

語法:

<Suspense>
      <template #default>
        <!-- <async-show /> -->
        <dog-show />
      </template>
      <template #fallback> <h1>加載中.....</h1></template>
    </Suspense>
  • 異步組件的困境
  • Suspense 是Vue3推出的一個內置的特殊組件
  • 如果使用Suspense,要返回一個promise
  1. 使用Suspense實現(xiàn)一個簡單異步請求
//AsyncShow.vue
<template>
  <div>
    <h1>{{ result }}</h1>
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  setup() {
    return new Promise(resolve => {
      setTimeout(() => {
        return resolve({
          result: 42,
        });
      }, 3000);
    });
  },
});
</script>


  • 父組件調用
import AsyncShow from './components/AsyncShow.vue';
 
 // 支持具名插槽,第一個default是異步請求data鹅很,#fallback是數(shù)據(jù)會返回前展示的data
 
<Suspense>
      <template #default>
         <async-show /> 
      </template>
      <template #fallback> <h1>加載中.....</h1></template>
    </Suspense>
  • 結果如下:先出現(xiàn)加載中.....,3s之后出現(xiàn)42

2.Suspense支持多個請求

  • 新建一個DogShow.vue 組件
// DogShow.vue
<template>
  <div>
    <img :src="result && result.message" alt="" />
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import axios from 'axios';
export default defineComponent({
  // 里面可以采用await
  async setup() {
    const res = await axios.get('https://dog.ceo/api/breeds/image/random');
    return {
      result: res.data,
    };
  },
});
</script>
  • 在父組件中引用

import DogShow from './components/DogShow.vue';
import AsyncShow from './components/AsyncShow.vue';
<Suspense>
      <template #default>
        <div>
          <async-show />
          <dog-show />
        </div>
      </template>
      <template #fallback> <h1>加載中.....</h1></template>
    </Suspense>
    // 展示錯誤
    <p>{{ error }}</p>
  • 結果如下嘶居,先出現(xiàn)加載中....,等兩個異步組件成功返回展示對應的數(shù)據(jù)
image4.png
  • 如果有一個異步請求失敗,可以使用onErrorCaptured去監(jiān)聽錯誤
import { defineComponent, onErrorCaptured, ref } from 'vue';
export default defineComponent({
  name: 'App',
  components: { HelloWorld, Model, AsyncShow, DogShow },
  setup() {
    const error = ref(null);
    // 通過這個生命周期函數(shù)可以監(jiān)聽Suspense里面的錯誤
    onErrorCaptured((err: any) => {
      error.value = err;
      return true;
    });
    
      error,
    };
  • 結果展示如下:

AsyncShow展示42促煮,DogShow接口失敗邮屁,報錯

image5.png

3.10 Global API Change(全局API修改)

Vue2全局API遇到的問題
  1. 在單元測試中,全局配置非常容易污染全局環(huán)境
  2. 在不同的apps中菠齿,共享一份有不同配置的Vue對象佑吝,也變的非常困難

vue3.0 解決了直接修改vue,防止污染

  1. 全局配置修改:
  • Vue.config => app.config
  • config.productionTip 被刪除
  • config.ignoredElements 改名為 config.isCustomElement
  • config.keyCodes被刪除
  1. 全局注冊類API
  • Vue.component -> app.component
  • Vue.directtive -> app.directtive
  1. 行為類API
  • Vue.mixins -> app.mixins
  • Vue.use -> app.use
  1. Global API Treeshaking
// vue2 全局導入,API掛載在Vue原型上
import Vue from 'vue'
Vue.nextTick(()=> {})
const obj = Vue.observable()

// vue3  實名導出泞当,方便Treeshaking
import Vue,{nextTick,observable} from 'vue'
Vue.nextTick // undefined

nextTick(()=> {})
const obj = observable()
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市民珍,隨后出現(xiàn)的幾起案子襟士,更是在濱河造成了極大的恐慌,老刑警劉巖嚷量,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陋桂,死亡現(xiàn)場離奇詭異,居然都是意外死亡蝶溶,警方通過查閱死者的電腦和手機嗜历,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門宣渗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人梨州,你說我怎么就攤上這事痕囱。” “怎么了暴匠?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵鞍恢,是天一觀的道長。 經(jīng)常有香客問我每窖,道長帮掉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任窒典,我火速辦了婚禮蟆炊,結果婚禮上,老公的妹妹穿的比我還像新娘瀑志。我一直安慰自己涩搓,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布后室。 她就那樣靜靜地躺著缩膝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪岸霹。 梳的紋絲不亂的頭發(fā)上疾层,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音贡避,去河邊找鬼痛黎。 笑死,一個胖子當著我的面吹牛刮吧,可吹牛的內容都是我干的湖饱。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼杀捻,長吁一口氣:“原來是場噩夢啊……” “哼井厌!你這毒婦竟也來了?” 一聲冷哼從身側響起致讥,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤仅仆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后垢袱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體墓拜,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年请契,在試婚紗的時候發(fā)現(xiàn)自己被綠了咳榜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夏醉。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖涌韩,靈堂內的尸體忽然破棺而出畔柔,到底是詐尸還是另有隱情,我是刑警寧澤贸辈,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布释树,位于F島的核電站,受9級特大地震影響擎淤,放射性物質發(fā)生泄漏奢啥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一嘴拢、第九天 我趴在偏房一處隱蔽的房頂上張望桩盲。 院中可真熱鬧,春花似錦席吴、人聲如沸赌结。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柬姚。三九已至,卻和暖如春庄涡,著一層夾襖步出監(jiān)牢的瞬間量承,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工穴店, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留撕捍,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓泣洞,卻偏偏與公主長得像忧风,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子球凰,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內容