一. 版本
當前還處於 beta版本, 想要正式使用在項目里還需要一段的時間, 但是結構與api變化應該不大了.
這里列出的並不全, 但是夠用了
1. alpha 內測版本 2. beta 公測版本 3. Gamma 正式發布的候選版本 4. Final 正式版 5. plus 增強版 6. full 完全版 7. Trial 試用版(一般有時間或者功能限制)
二. 介紹
1. 學一門新鮮的技術,就像練習王者榮耀新出的英雄一樣, 探索他的好玩之處可以給開發者帶來快樂, 使用新的好的技術會讓工作更愉悅
2. 這個版本的vue 類似"我的世界", 全部都是一個個方塊組成, 不要小看方塊, 方塊多了甚至可以組成圓形(量變引起質變), 新鮮的玩法才能激發我們的興趣
三. vue3.0的環境搭建
准備一套搭建好的環境防治到時候出現意外, 現場再按照步驟搭建一版, 每一步都不要落下能讓大家更好的理解.
1. npm install -g @vue/cli cli升級到4版本 2. 隨便創建個項目, vue create next-test 3. 選擇配置最好把router與vuex一起選上, 方便后續升級 4. vue add vue-next cli提供的進化到下一版本的命令, 執行后自動將router, vuex 升級到alpha版.
四. vue3.0重要的優化
1. 模板編譯速度的提升, 對靜態數據的跳過處理.
2. 對數組的監控
3. 對ts有了很好的支持 4. 對2.x版本的完全兼容 5. 可以有多個根節點 (也有bug, 比如外層開了display:flex 那么里面會收到影響, 也就是說布局更靈活但也更要小心, 總之請對自己與他人的代碼負責) 6. 支持Source map, 雖然沒演示但是這點真的重要
五. vuex, router, vue 初始化寫法的變化
vue:
import { createApp } from 'vue'; import App from './App.vue' import router from './router' import store from './store' // 方法一. 創建實例變成了鏈式, 直接寫下去感覺語義與結構有點模糊, 但是我們要理解vue這樣做的良苦用心, 前端趨近於函數化. // createApp(App).use(router).use(store).mount('#app') // 方法二. const app = createApp(App); app.use(router); app.use(store); app.mount('#app');
vuex:
import { createStore } from 'vuex' // 專門創建實例的一個方法 export default createStore({ state: { }, mutations: { }, actions: { }, modules: { } });
router
import { createRouter, createWebHistory } from 'vue-router'; import Home from '../views/Home.vue' const routes = [ { path: '/', name: 'Home', component: Home } ] const router = createRouter({ // 專門創建history的函數 history: createWebHistory(process.env.BASE_URL), routes }) export default router
六. 變量的定義
1: ref
import { ref } from "vue"; export default { // 1: 這個版本基本邏輯都在setup里面完成了, 有人說把他當成2.x的data. setup() { // 2: 定義一個追蹤的變量,也就是雙向綁定. const n = ref(1); // 生成的n是一個對象, 這樣方便vue去監控它 function addN() { n.value++; // 注意這里要使用.value的形式, 因為n是對象↑, value才是他的值 } return { n, // 返回出去頁面的template才可以使用它, {{n}} 不需要.value addN } } }
2: reactive
import { reactive, toRefs } from "vue"; export default { setup() { // 注意事項: reactive的對象不可以結構返回或導入, 會導致失去響應式 const obj = reactive({ name: "金毛", age: 4 }); function addObj() { obj.age++; } return { ...obj, // 這樣寫不好, 里面會失去響應式 obj, // 這樣寫那么外面就要都基於obj來調取, 類型{{obj.age}} ...toRefs(obj) // 必須是reactive生成的對象, 普通對象不可以, 他把每一項都拿出來包了一下, 我們可以這樣用了 {{age}}, 放心咱們多深的obj也可以響應 } } }
7. 之前的ref何去何從
這個老兄被別人搶了關鍵詞, 也只能自己改改寫法了
<div> <div ref="content">第一步, 在dom上面定義, 他會有一個回調</div> </div> <ul> <li>v-for 出來的ref</li> <li>可以寫為表達式的形式, 可以推導出vue是如何實現的</li> <li>vue2.x的時候v-for不用這么麻煩, 直接寫上去會被組裝成數組</li> <li :ref="el => { items[index] = el }" v-for="(item,index) in 6" :key="item">{{item}}</li> </ul>
import { ref, onMounted, onBeforeUpdate } from "vue";
export default { setup() { // 2: 定義一個變量接收dom, 名字無所謂, 但是與dom統一的話會有很好的語義化 const content = ref(null); const items = ref([]); // 4: 在生命周期下, 這個值已經完成了變化, 所以當然第一時間就拿到 onMounted(() => { console.log(content.value); console.log("li標簽組", items.value); }); // 5: 確保在每次變更之前重置引用 onBeforeUpdate(() => { items.value = []; }); // 3: 返出去的名稱要與dom的ref相同, 這樣就可以接收到dom的回調 return { content, items }; } };
8. 生命周期
<template> <div> <button @click="add">點擊增加, 觸發updata</button> <p>{{obj.count}}</p> </div> <p> 2.x與 3.0的對照 beforeCreate -> 使用 setup() created -> 使用 setup() beforeMount -> onBeforeMount mounted -> onMounted beforeUpdate -> onBeforeUpdate updated -> onUpdated beforeDestroy -> onBeforeUnmount destroyed -> onUnmounted errorCaptured -> onErrorCaptured </p> </template> <script> //這些生命周期注冊方法只能用在 setup 鈎子中 import { onMounted, onUpdated, onBeforeUnmount, reactive } from "vue"; export default { // 1: setup顯得冗長, 可以自己動手做一些插件來優化 // 2: 本身更加明確意圖 // 3: 需要樹立工程師的正確代碼意識 // 4: 能力不足可能會寫出更差的代碼 // 作者說: 提升上界的收益遠遠大於降低下界的損失。值得深思, 前端需要提高門檻 // 5: 調用時機: 創建組件實例,然后初始化 props ,緊接着就調用setup 函數。從生命周期鈎子的視角來看,它會在 beforeCreate 鈎子之前被調用 // 6: 這些生命周期鈎子注冊函數只能在 setup() 期間同步使用, 因為它們依賴於內部的全局狀態來定位當前組件實例(正在調用 setup() 的組件實例), 不在當前組件下調用這些函數會拋出一個錯誤。 // 7: 原則上生命周期里面不會放具體的邏輯,哪怕只有一句賦值一個三元都不可放, 這也正好符合當前的工程模式 // 討論: 有多少種方式, 可以判斷出某個函數 當前處於哪個函數? // 比如有多層嵌套的組件是否有影響 setup() { onMounted(() => { console.log("is mounted!"); }); onUpdated(() => { console.log("is onUpdated!"); }); onBeforeUnmount(() => { console.log("is onBeforeUnmount!"); }); const obj = reactive({ count: 1 }); function add() { obj.count++; } return { obj, add }; } }; </script>
9. 路由
<template> <div> {{id}} </div> </template> <script> import { getCurrentInstance, ref } from 'vue'; export default { setup(){ const { ctx } = getCurrentInstance() // 1. 這樣也是為了去掉this // 2. 方便類型推導 console.log(ctx.$router); // push等方法 console.log(ctx.$router.currentRoute.value); // 路由實例 // 這個其實沒有必要變成ref因為這個值沒必要動態 // 但是他太長了, 這個真的不能忍 const id = ref(ctx.$router.currentRoute.value.query.id) // 4: 頁面攔截器 ctx.$router.beforeEach((to, from,next)=>{ console.log('路由的生命周期') next() }) return { id } } } </script>
10. vuex
import { createStore } from 'vuex' // 難道前端趨勢只有函數這一種嗎 export default createStore({ state: { name:'牛逼, 你拿到我了', age: 24, a:'白', b:'黑' }, mutations: { updateName(state, n){ state.name += n } }, actions: { deferName(store) { setTimeout(()=>{ // 必須只有commit可以修改值, 這個設定我比較反對, 可以討論 // vuex本身結構就很拖沓, 定義域使用個人都不喜歡 store.state.name = '牛逼, 你改回來了' },1000) } }, getters: { fullName(state){ return `${state.a} - + -${state.b}` } }, modules: { } });
<template> <div> <p>{{name}}</p> <button @click="updateName('+')">點擊改變名字</button> <br /> <button @click="deferName('+')">改回來</button> <p>{{fullName}}</p> </div> </template> <script> import { useStore } from "vuex"; import { computed } from "vue"; export default { setup() { const store = useStore(); // 1: 單個引入 const name = computed(() => store.state.name); // 2: 引入整個state const state = computed(() => store.state); console.log("vuex的實例", state.value); // 別忘了.value // 3: 方法其實就直接從本體上取下來了 const updateName = newName => store.commit("updateName", newName); // 4: action一個意思 const deferName = () => store.dispatch("deferName"); // 5: getter 沒變化 const fullName = computed(() => store.getters.fullName); return { name, fullName, deferName, updateName, }; } }; </script>
11. composition(這個可能是最重要的改革了)
前端算是面向函數編程, 各種規范也都趨近於函數化
composition使得前端工程師的編程規范, 更接近於原生js, 三十年河東三十年河西, 幾年前前端需要模板來進行'規則化', 現在前端又想要更多的自由.
開發工程而不是插件的話, 還是不要使用mixin了, 這東西無法追溯來源, 搞得語義太差了, 我們要對它說'no'.
舉例子的變量命名有點low, 抱歉~~
<template> <div> <button @click="addN1">上面的增加</button>---> {{n1}} </div> <div> <button @click="addN2">下面的增加</button>---> {{n2}} <button @click="addN210">每次n2+10</button> </div> <div> <p>組件里面也可以如此引用, 這就可以代替mixin一部分功能了</p> <button @click="addN3">n3的增加</button>---> {{n3.value}} </div> <div> <com></com> </div> </template> <script> import { ref} from 'vue'; import n3Change from './mixin'; import com from '../components/composition.vue'; export default { components:{ com }, setup(){ // 1: setup只是一個整合函數 // 2: 甚至整個函數里面可能會沒有具體的邏輯 // 3: 以此推斷, ref等方式定義的變量, 會自動識別在哪個setup內部, 從而達到邏輯的復用 // 4: 由此方法可以很好的代替mixin了 // 5: 當然, 這里也可以截獲數據,來做一些事情 const {n2, addN2} = n2Change(); function addN210(){ n2.value += 10 } return { ...n1Change(), ...n3Change(), n2, addN2, addN210 } } } // 甚至已經可以寫在任何地方了, 響應式自由度大大提高 function n1Change(){ const n1 = ref(1); let addN1 = ()=>{ n1.value++ } return { n1, addN1 } } function n2Change(){ const n2 = ref(1); let addN2 = ()=>{ n2.value++ } return { n2, addN2 } } </script>
寫在任何地方, 然后導入就成了mixin
import { reactive } from 'vue'; export default ()=> { const n3 = reactive({ name: 'mixin', value: 1 }) const addN3=()=>{ n3.value++ } return { n3, addN3 } }
豌豆資源搜索網站https://55wd.com 電腦刺綉綉花廠 ttp://www.szhdn.com
12. 插件的新思路
// 開發插件並不一定要掛載到vue的原型上 // 導致vue原型臃腫, 命名沖突等等(比如兩個ui都叫 message) // 原理就是 provide 和 inject, 依賴注入. import {provide, inject} from 'vue'; // 這里使用symbol就不會造成變量名的沖突了, 這個命名權交給用戶才是真正合理的架構設計 const StoreSymbol = Symbol() export function provideString(store){ provide(StoreSymbol, store) } export function useString() { const store = inject(StoreSymbol) return store }
app.vue頁面統一的初始化一下
export default { setup(){ // 一些初始化'配置/操作'可以在這里進行 // 需要放在對應的根節點, 因為依賴provide 和 inject provideString({ a:'可能我是axios', b:'可能我是一個message彈框' }) } }
在需要使用的組件里面接收
<template> <div> 插件的演示 </div> </template> <script> import { useString } from '../插件'; export default { setup(){ const store = useString(); // 不光是拿到值, 可以由app定義什么可以被拿到 console.log('拿到值了',store) } } </script>
13. 新觀察者
<template> <div> <button @click="addn1">n1增加--{{n1}}</button> <button @click="addn2">n2增加--{{n2}}</button> <button @click="addn3">n3增加--{{n3}}</button> </div> </template> <script> import { watch, ref } from "vue"; export default { setup() { const n1 = ref(1); const n2 = ref(1); const n3 = ref(1); // 1: 監聽一個 // 第一個參數是函數返回值, 當然也可以 直接寫n1 // 如果監聽的是一個對象里面的某個屬性, 那就需要這種函數的寫法了, 比2.x的字符串寫法高明很多 watch( () => n1.value, (val, oldVal) => { console.log("新值", val); console.log("老值", oldVal); } ); // 2: 監聽多個 // 數組的形式定義多個, 這就出現問題了吧, 如果我觀察的對象就是個數組, 並且每一項都是一個返回值的函數, 豈不是會被他誤認為是多監控的結構, 苦惱 watch( [() => n2.value, ()=>n3.value], ([val, val3],[val2, val4]) => { // val 是 n2的新值 val2是 n2的老值 // val3 是 n3的新值 val4是 n3的老值 console.log("新值 與 老值 是這種對應關系", val, val2); console.log("新值 與 老值 是這種對應關系", val3, val4); } ); function addn1() { n1.value++; } function addn2() { n2.value++; } function addn3() { n3.value++; } return { addn1, addn2, addn3, n1, n2, n3 }; } }; </script>
13. 新計算屬性
別看watchEffect帶個'watch',但是他的功能可以歸為計算屬性里面
<template> <div> <button @click="addCount">點擊計算</button> <button @click="setCount(1)">點擊出發set</button> <p>count--{{count}}</p> <p>count2--{{count2}}</p> <p>count3--{{count3}}</p> </div> </template> <script> // 弄得類似react了 import { computed, ref, watchEffect } from "vue"; export default { setup() { const count = ref(1); // 1. 默認的定義方式 const count2 = computed(() => count.value * 2); console.log(count2.value); // 也要value因為可能是簡單類型 // 2. getter與setter當然可以定義 const count3 = computed({ get: () => count.value * 3, set: val => { // 這里重置了count count.value = val; } }); // 3. watchEffect 更像是計算函數 // 立即執行傳入的一個函數,並響應式追蹤其依賴,並在其依賴變更時重新運行該函數 // 偵聽器會被鏈接到該組件的生命周期,並在組件卸載時自動停止。 // Vue 的響應式系統會緩存副作用函數,並異步地刷新它, 比如同時改變了count與conut4此時watchEffect只是執行一次 // 初始化運行是在組件 mounted 之前執行的。因此,如果你希望在編寫副作用函數時訪問 DOM(或模板 ref),請在 onMounted 鈎子中進行 // 並不是返回值, 而是監聽里面所有的值, 任何有變化都會重新執行, 他應該可以玩出點東西。 const count4 = ref(1); const stop = watchEffect(() => { if (count4.value) { console.log("作為判斷條件也可以根據count4的變化而重新執行"); } console.log(count.value); }); setTimeout(() => { stop(); // 停止監聽 }, 10000); function addCount() { count.value++; setTimeout(() => { count4.value++; }, 1000); } // 觸發setter function setCount() { count3.value = 2; } return { count, count2, addCount, count3, setCount }; } }; </script>
14. customRef防抖
當然這里說的'防抖'不是重點, 重點是這種代碼的思維
<template> <div> <input type="text" v-model="text" /> </div> </template> <script> import { customRef, onUnmounted } from "vue"; export default { setup() { let timeout = null; // 並不需要響應式 const text = useDebouncedRef("hello", (time) => { // 畢竟是延時的不用擔心獲取不到這個值 console.log("延時執行回調", text.value); console.log('時間實例', time) timeout = time; }); // 好習慣也是成為合格工程師的必要條件 onUnmounted(()=>{ clearTimeout(timeout); }) return { text }; } }; // 並不用純粹的js編寫, 可以利用customRef來監控這個值的一舉一動 // 寫法一般, 但是思路又多了一條, 感謝 function useDebouncedRef(value, callback, delay = 200) { let timeout; // 兩個參數分別是用於追蹤的 track 與用於觸發響應的 trigger // 這兩個參數對 值的追蹤 在當前並沒有用,比如watchEffect的出發機制 // 不調用這兩個值沒問題, 但是如果寫成插件的話還是要調用的, 因為別人沒准在追蹤這個值, // 注意: 這個函數不可以有太大的delay, 如果超過500的話就需要考慮在組件銷毀時候的清除定時器, 反而邏輯加深了, 此時我們可以每次把演示器的實例拿到 return customRef((track,trigger) => { return { get() { track() return value; }, set(newValue) { clearTimeout(timeout); // callback接受的太晚了, 可以在這里用另一個函數或對象接收 timeout = setTimeout(() => { value = newValue; trigger() callback(timeout); }, delay); } }; }); } </script>
15. 組件與注入
父級
<template> <div> 組件: <zj :type="type" @ok="wancheng"></zj> </div> </template> <script> import zj from "../components/子組件.vue"; import { ref } from 'vue'; import { provide } from 'vue' export default { components: { zj }, setup() { provide('name','向下傳值'); // 基礎值 provide('name2', ref('向下傳值')); // 監控值 const type = ref('大多數'); function wancheng(msg){ console.log('子組件-->',msg) setTimeout(()=>{ type.value = 'xxxxxxx' },2000) } return { type, wancheng } } }; </script>
子組件
<template> <div>props的屬性不用setup去return --- {{type}}</div> </template> <script> import { inject, ref } from 'vue' // 為了讓 TypeScript 正確的推導類型,我們必須使用 createComponent 來定義組件: export default { props: { type: String }, // 1: props也是不可以解構的, 會失去響應式 // 2: context是上下文, 我們可以獲取到slots emit 等方法 // 3: props, context 分開也是為了ts更明確的類型推導 // setup({type}){ setup(props, context) { // 1: props console.log("props", props.type); console.log("上下文", context); context.emit('ok','傳遞完成') // 2: 注入 console.log('inject',inject('name')); console.log('inject',inject('xxxx','我是默認值')) inject('name1', ref('默認值')) // 接收方也可以這樣 } }; </script>
16. 總結
每次看到新技術都會感覺挺好玩的, 一成不變的生活會太無趣了, 在某些方面講vue失去了一些本來的優勢, 但是人家可以兼容vue2.x那就沒的說了, 作為分享會的稿子的話時間差不多一個小時, 最好每個點都現場手敲, 光讓大家看已經寫好的代碼會走神的, 我在學習視頻的時候最不喜歡的就是老師說"這個我就不演示了".
這次就這么多, 希望和你一起進步.