<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【Vuejs】1182- 歐耶!Pinia 正式成為 Vue.js 的一員

          共 11195字,需瀏覽 23分鐘

           ·

          2021-12-29 07:03

          Pinia?正式成為 vuejs 官方的狀態(tài)庫,意味著????就是?Vuex 5.x?。

          先來看早期?vue 上一個關(guān)于 Vuex 5.x 的 RFC :

          描述中可以看到,Vue 5.x 主要改善以下幾個特性:

          • 同時支持 composition apioptions api 的語法;
          • 去掉 mutations,只有 state、gettersactions;
          • 不支持嵌套的模塊,通過組合 store 來代替;
          • 更完善的 Typescript 支持;
          • 清晰、顯式的代碼拆分;

          Pinia 正是基于 RFC 所生成的一個玩物。

          它的定位和特點也很明確:

          • 直觀,像定義組件一樣地定義 store,并且能夠更好地組合它們;
          • 完整的 Typescript 支持;
          • 關(guān)聯(lián) Vue Devtools 鉤子,提供更好地開發(fā)體驗;
          • 模塊化設(shè)計,能夠構(gòu)建多個 stores 并實現(xiàn)自動地代碼拆分;
          • 極其輕量(1kb),甚至感覺不到它的存在 ;
          • 同時支持同步和異步 actions

          這么 niubility 特性,接下來就 show you the code 去逐一學(xué)習(xí)。

          接觸

          光說不練,等于白學(xué)。這一小節(jié)通過介紹 PiniaAPI,感受上一小節(jié)講到的特性。

          vite 快速起一個 vue 模板的項目:

          yarn?create?@vitejs/app?pinia-learning?--template?vue-ts

          cd?pinia-learning
          yarn
          yarn?dev

          項目運行起來后,安裝 pinia 并初始化一個 store

          yarn?add?pinia

          src/main.ts 下定義引用 pinia 插件:

          import?{?createApp?}?from?'vue'
          import?{?createPinia?}?from?'pinia'
          import?App?from?'./App.vue'

          createApp(App).use(createPinia()).mount('#app')

          了解(State)

          defineStore

          之后就可以定義我們的 store 并在組件中使用,我們新建 src/store/index.ts 文件并定義一個 store

          import?{?defineStore?}?from?'pinia'

          export?default?defineStore({
          ??id:?'app',

          ??state?()?{
          ????return?{
          ??????name:?'碼農(nóng)小余'
          ????}
          ??}
          })

          App.vue 引入上述文件就可以使用該 store

          <template>
          ??<div>{{?store.name?}}div>
          template>

          <script?setup?lang="ts">
          import?useAppStore?from?'./store/index'
          const?store?=?useAppStore()
          console.log(store)
          script>

          defineComponent <==> defineStore、id <==> namestate <==> setup,直觀,像定義組件一樣地定義 store 到這里是能夠體會到該特性的含義了。上述代碼是在 composition api 中 setup 的用法,在 options api 中使用跟 Vuex 類似,通過 mapState 或者 mapWritableState 輔助函數(shù)來讀寫 state

          <template>
          ??<div>{{?this.username?}}div>
          ??<div>{{?this.interests.join(',')?}}div>
          template>

          <script?lang="ts">
          import?{?mapState,?mapWritableState?}?from?'pinia'
          import?infoStore?from?'../store/info'

          export?default?{
          ??name:?'HelloWorld',

          ??computed:?{
          ????//?只讀計算屬性
          ????...mapState(infoStore,?['interests']),
          ????
          ????//?讀寫計算屬性
          ????...mapWritableState(infoStore,?{
          ??????username:?'name'
          ????})
          ??},

          ??mounted?()?{
          ????this.interests.splice(1,?1,?'足球')
          ????this.username?=?'Jouryjc'
          ??}
          }

          script>

          storeToRefs

          那么后半句是啥意思呢?并且能夠更好地組合它們 。舉個例子,馬上就 11.11 ,小余當(dāng)然得往購物車?yán)锩嫒麕妆尽翱▽毜洹?,于是乎就需要一個 cartstore

          import?{?defineStore?}?from?'pinia'

          export?default?defineStore('cart',?{
          ??state?()?{
          ????return?{
          ??????books:?[
          ????????{
          ??????????name:?'金瓶梅',
          ??????????price:?50
          ????????},
          ????????{
          ??????????name:?'微服務(wù)架構(gòu)設(shè)計模式',
          ??????????price:?139
          ????????},
          ????????{
          ??????????name:?'數(shù)據(jù)密集型應(yīng)用系統(tǒng)設(shè)計',
          ??????????price:?128
          ????????}
          ??????]
          ????}
          ??}
          })

          然后在 AppStore 組合 cartStore :

          //?store/index.ts
          import?{?defineStore?}?from?'pinia'
          import?useCartStore?from?'./cart'

          export?default?defineStore('app',?{
          ??state?()?{
          ????//?直接使用?cartStore
          ????const?cartStore?=?useCartStore()

          ????return?{
          ??????name:?'碼農(nóng)小余',
          ??????books:?cartStore.books
          ????}
          ??}
          })

          最終被 App 組件消費。?? 直接解構(gòu) store 會使其失去響應(yīng)式,為了在保持其響應(yīng)式的同時從 store 中提取屬性要使用 storeToRefs ,如下述代碼所示:

          <template>
          ??<div>{{?name?}}div>
          ??<p>購物車清單:p>
          ??<p?v-for="book?of?books"?:key="book.name">
          ????書名:{{ book.name }}?價格:{{ book.price }}
          ??p>
          template>

          <script?setup?lang="ts">
          import?{?storeToRefs?}?from?'pinia'
          import?useAppStore?from?'./store/index'

          //??????返回的是一個?reactive?對象,不能直接解構(gòu)哦,使用?pinia?提供的?storeToRefs?API
          const?{?name,?books?}?=?storeToRefs(useAppStore())
          script>

          此時的頁面效果如圖所示:

          $patch

          除了直接修改 store.xxx 的值,還可以通過 $patch 修改多個字段信息;下面在例子中添加購買數(shù)量、總價,并添加付款人:

          <template>
          ????<div>{{?name?}}div>
          ????<p>購物車清單:p>
          ????<p?v-for="book?of?books"?:key="book.name">
          ????????書名:{{ book.name }}?價格:{{ book.price }}?數(shù)量:?{{ book.count }}
          ????????<button?@click="add(book)">+button>
          ????????<button?@click="minus(book)">-button>
          ????????p>
          ????<button?@click="batchAdd">全部加到10本button>
          ????<p>總價:{{ price }}p>
          ????<button?@click="reset()">重置button>
          template>

          <script?setup?lang="ts">
          import?{?storeToRefs?}?from?"pinia";
          import?useAppStore?from?"./store/index";
          import?type?{?BookItem?}?from?"./store/cart";

          const?store?=?useAppStore();
          const?{?name,?books,?price?}?=?storeToRefs(store);

          const?reset?=?()?=>?{
          ??store.$reset();
          };

          const?add?=?(book:?BookItem)?=>?{
          ??//?直接修改?store.book
          ??book.count++;
          };

          const?minus?=?(book:?BookItem)?=>?{
          ??//?直接修改?store.book
          ??book.count--;
          };

          const?batchAdd?=?()?=>?{
          ??//?通過?$patch?方法修改?store?多個字段
          ??store.$patch({
          ????name:?'小I',
          ????books:?[
          ??????{
          ????????name:?"金瓶梅",
          ????????price:?50,
          ????????count:?10,
          ??????},
          ??????{
          ????????name:?"微服務(wù)架構(gòu)設(shè)計模式",
          ????????price:?139,
          ????????count:?10,
          ??????},
          ??????{
          ????????name:?"數(shù)據(jù)密集型應(yīng)用系統(tǒng)設(shè)計",
          ????????price:?128,
          ????????count:?10,
          ??????},
          ????],
          ??});
          };
          script>

          例子中添加了 “全部加到10本”和 “重置”按鈕,點擊前者會將全部書籍?dāng)?shù)量添加到 10 本,點擊后者會重置成 0,下面是執(zhí)行效果:

          假如你只想將《微服務(wù)架構(gòu)設(shè)計模式》的數(shù)量修改成10,通過 $patch 傳對象的方法需要這么操作:

          <template>
          ?<button?@click="batchAddMicroService">微服務(wù)加到10本button>
          template>

          <script>
          ?const?batchAddMicroService?=?()?=>?{
          ??????store.$patch({
          ????????name:?'小I',
          ????????books:?[
          ??????????{
          ????????????name:?"金瓶梅",
          ????????????price:?50,
          ????????????count:?0,
          ??????????},
          ??????????{
          ????????????name:?"微服務(wù)架構(gòu)設(shè)計模式",
          ????????????price:?139,
          ????????????count:?10,
          ??????????},
          ??????????{
          ????????????name:?"數(shù)據(jù)密集型應(yīng)用系統(tǒng)設(shè)計",
          ????????????price:?128,
          ????????????count:?0,
          ??????????},
          ????????],
          ??????});
          ????}
          script>

          可以看到,就算你只是修改數(shù)組(集合)的第二項,還是需要將整個 books 數(shù)組傳入,于是就產(chǎn)生了將函數(shù)作為 $patch 參數(shù)的寫法:

          <script>
          const?batchAddMicroService?=?()?=>?{
          ??store.$patch((state)?=>?{
          ????state.books[1].count?=?10;
          ??});
          }
          script>

          上述代碼重寫了 batchAddMicroService 方法。

          $subscribe

          該方法跟 vuex 的 subscribe 類似,用于監(jiān)聽 state 及其 mutation 動作。上述例子中我們訂閱 appStore 的狀態(tài):

          const?store?=?useAppStore();

          store.$subscribe((mutation,?state)?=>?{
          ??console.log(mutation);
          ??console.log(state);
          });

          當(dāng)我們添加一本《金瓶梅》時,會觸發(fā) $subscribelog 結(jié)果如下圖:

          Getters

          Getters 就是 store 的計算屬性(computed)。大部分時候,Getter 通過 state 值去做計算,這種情況下 TypeScript 能夠正確的推斷出類型。例如:

          export?default?defineStore('app',?{
          ??state:?()?=>?{
          ????const?userInfoStore?=?useUserInfoStore()
          ????const?cartStore?=?useCartStore()

          ????return?{
          ??????name:?userInfoStore.name,
          ??????books:?cartStore.books
          ????}
          ??},

          ??getters:?{
          ????price:?(state)?=>?{
          ??????return?state.books.reduce((init:?number,?curValue:?BookItem)?=>?{
          ????????return?init?+=?curValue.price?*?curValue.count
          ??????},?0)
          ????}
          ??}
          })

          我們將 stategetters 都改成箭頭函數(shù),這樣就能在 App.vue 中正確推斷出 price 的類型。我們來驗證一下:

          Bingo!如果在 getters 中使用 this 去訪問 state 的話,需要顯式聲明返回值才能正確標(biāo)記類型,我們來試試:

          export?default?defineStore('app',?{
          ??//?...
          ??getters:?{
          ????price?()?{
          ??????return?this.books.reduce((init:?number,?curValue:?BookItem)?=>?{
          ????????return?init?+=?curValue.price?*?curValue.count
          ??????},?0)
          ????}
          ??}
          })

          結(jié)果如下:

          我們給 price 顯示聲明返回類型:

          export?default?defineStore('app',?{
          ??//?...
          ??getters:?{
          ????price?():?number?{
          ??????return?this.books.reduce((init:?number,?curValue:?BookItem)?=>?{
          ????????return?init?+=?curValue.price?*?curValue.count
          ??????},?0)
          ????}
          ??}
          })

          此時又能正確地提示 price 的類型。

          Getters 其他用法比如組合 Getters、在 setupoptions api 中使用、傳參等等都跟 State 類似,本節(jié)就不展開細述。

          Actions

          Actions 相當(dāng)于組件里的 methods。雙 11 買東西當(dāng)然免不了折扣,商家也在折扣這環(huán)節(jié)上設(shè)計了活動,能夠讓顧客自己隨機一個折扣比率,于是在 store 中的 actions 下定義 changeDiscountRate 方法:

          export?default?defineStore('app',?{
          ??state:?()?=>?{
          ????const?userInfoStore?=?useUserInfoStore()
          ????const?cartStore?=?useCartStore()
          ????const?discountRate?=?1

          ????return?{
          ??????name:?userInfoStore.name,
          ??????books:?cartStore.books,
          ??????discountRate
          ????}
          ??},

          ??actions:?{
          ????changeDiscountRate?()?{
          ??????this.discountRate?=?Math.random()?*?this.discountRate
          ????}
          ??}
          })

          Getters 一樣,actions 中也通過 this 去獲取整個 store。我們通過異步 actions 讓修改折扣有一個延遲效果:

          function?getNewDiscountRate?(rate:?number):?Promise<number>?{
          ??return?new?Promise?((resolve)?=>?{
          ????setTimeout(()?=>?{
          ??????resolve(rate?*?Math.random())
          ????},?1000)
          ??})
          }

          export?default?defineStore('app',?{
          ??//?...
          ??actions:?{
          ????async?changeDiscountRate?()?{
          ??????this.discountRate?=?await?getNewDiscountRate(this.discountRate)
          ????}
          ??}
          })

          $onAction

          當(dāng)我們想統(tǒng)計 actions 的時間或者記錄折扣點擊總次數(shù)的時候,$onAction 訂閱器能夠很方便地實現(xiàn),下面是一個官方的示例:

          //?App.vue
          const?unsubscribe?=?store.$onAction(
          ?({
          ????name,
          ????store,
          ????args,
          ????after,
          ????onError,
          ??})?=>?{
          ????const?startTime?=?Date.now()
          ????console.log(`Start?"${name}"?with?params?[${args.join(',?')}].`)

          ????after((result)?=>?{
          ??????console.log(
          ????????`Finished?"${name}"?after?${
          ??????????Date.now()?-?startTime
          ????????}
          ms.\nResult:?${result}.`

          ??????)
          ????})

          ????onError((error)?=>?{
          ??????console.warn(
          ????????`Failed?"${name}"?after?${Date.now()?-?startTime}ms.\nError:?${error}.`
          ??????)
          ????})
          ?}
          )


          function?getNewDiscountRate?(rate:?number):?Promise<number>?{
          ??return?new?Promise?((resolve,?reject)?=>?{
          ????setTimeout(()?=>?{
          ??????//?這里通過reject結(jié)束promise
          ??????reject(rate?*?Math.random())
          ????},?1000)
          ??})
          }

          export?default?defineStore('app',?{
          ??//?...

          ??actions:?{
          ????async?changeDiscountRate?()?{
          ??????try?{
          ????????this.discountRate?=?await?getNewDiscountRate(this.discountRate)
          ??????}?catch?(e)?{
          ????????//?示例執(zhí)行這部分邏輯
          ????????throw?Error(e)
          ??????}
          ????}
          ??}
          })

          上述代碼會以 rejected 的狀態(tài)結(jié)束 promise,$onAction 能夠執(zhí)行到 onError 鉤子,這個鉤子是跟 vue3 的 errorHandler 掛鉤,想知道這部分怎么實現(xiàn)滴可以三連持續(xù)關(guān)注最新內(nèi)容哦,下篇文章將會從 pinia 源碼角度分析。最后執(zhí)行效果如下圖所示:

          從上圖可以看到,第一個 warn 是訂閱器 $onAction 返回的,第二個 warn 則是 errorHandler 中返回。

          最后,$onAction 一般是在組件的 setup 建立,它會隨著組件的 unmounted 而自動取消。如果你不想讓它取消訂閱,可以將第二個參數(shù)設(shè)置為 true

          store.$onAction(callback,?true)

          深入(Plugins)

          通過一些底層 API,我們能夠各種各樣的擴展:

          • store 添加新的屬性;
          • store 添加新的選項;
          • store 添加新的方法;
          • 包裝已經(jīng)存在的方法;
          • 修改或者刪除 actions;
          • 基于特定的 store 做擴展;

          光說不練,等于白學(xué)。下面就來實現(xiàn)一個 pinia-plugin,首先在 store 下建 pinia-plugin.ts

          import?{?PiniaPluginContext?}?from?'pinia'

          export?default?function?myPiniaPlugin(context:?PiniaPluginContext)?{
          ??console.log(context)
          }

          然后在 main.ts 中引入插件:

          import?{?createApp?}?from?'vue'
          import?{?createPinia?}?from?'pinia'
          import?piniaPlugin?from?'./store/pinia-plugin'
          import?App?from?'./App.vue'

          const?pinia?=?createPinia()
          pinia.use(piniaPlugin)

          createApp(App).use(pinia).mount('#app')

          appStore 打印出來的 context 如下:

          我們可以從 context 中拿到從 createApp() 中創(chuàng)建的 app 實例、defineStore 中的配置、從 createPinia() 中創(chuàng)建的 pinia 實例、當(dāng)前 store 對象。有了這些信息,你要完成上述那些擴展無非只是基于 context 去發(fā)揮。下面列對兩個比較常見的用法舉例說明。

          給 store 添加新的屬性

          我們實現(xiàn)一個添加 state 的插件:

          import?{?PiniaPluginContext?}?from?'pinia'

          export?default?function?myPiniaPlugin(context:?PiniaPluginContext)?{
          ??const?{?store?}?=?context

          ??store.pluginVar?=?'Hello?store?pluginVar'

          ??//?可以在?devtools?中使用?
          ??store.$state.pluginVar?=?'Hello?store?$state?pluginVar'

          ??if?(process.env.NODE_ENV?===?'development')?{
          ????//?加上自定義屬性可以被devtool捕獲到
          ????store._customProperties.add('pluginVar')
          ??}

          ??setTimeout(()?=>?{
          ????store.pluginVar?=?'Hello?store?pluginVar2'
          ????store.$state.pluginVar?=?'Hello?store?$state?pluginVar2'
          ??},?1000)
          }

          上述代碼中 store.pluginVar 的改變不會被 devtools 監(jiān)聽到,但是可以通過 store._customProperties.add('pluginVar') ?store.$state.pluginVar 的改變會被 devtools 監(jiān)聽到。

          基于特定的 store 做擴展

          action 支持異步,我們在 action 中發(fā)送請求的需求會比較多,我們就可以將 axios 作為擴展加到 store 中:

          import?{?PiniaPluginContext?}?from?'pinia'
          import?{?markRaw?}?from?'vue'
          import?axios?from?'axios'

          export?default?function?myPiniaPlugin(context:?PiniaPluginContext)?{
          ??const?{?store?}?=?context

          ??store.axios?=?markRaw(axios)
          }

          store.testAxios 調(diào)用的時候,我們就只需要直接通過 this 即可拿到 axios 對象:

          import?{?defineStore?}?from?'pinia'
          import?useCartStore,?{?BookItem?}?from?'./cart'
          import?useUserInfoStore?from?'./info'

          function?getNewDiscountRate?(rate:?number):?Promise<number>?{
          ??return?new?Promise?((resolve,?reject)?=>?{
          ????setTimeout(()?=>?{
          ??????reject(rate?*?Math.random())
          ????},?1000)
          ??})
          }

          export?default?defineStore('app',?{
          ??//?...
          ??actions:?{
          ????testAxios?()?{
          ??????//?this.testAxios.$get().then().catch(()?=>?{})
          ??????console.log(this.axios)
          ????}
          ??}
          })

          Pinia or Vuex

          Pinia 作為 Vue 狀態(tài)管理的后起之秀和官方團隊維護的 Vuex,什么時候該用哪個?這一小節(jié)來簡單地對比一下。

          首先下載量和社區(qū)方面:

          Pinia 作為后起之秀,不管是 Stack Overflow 上的問題解決方案還是下載量上,肯定都不如 Vue 核心團隊推薦的 Vuex。其他方面的對比可以直接閱讀 pinia-vs-vuex ,這篇文章非常詳細地比較了二者。言而總之:小項目可以試水 Pinia,大項目還是用成熟的 Vuex。(另外,外鏈的對比時間比較早,目前 Pinia 好像是支持了時間旅行功能)

          總結(jié)

          本文詳細地介紹了 Pinia 的基礎(chǔ)使用,從 state、Getters、ActionsPlugins,都有例子輔助學(xué)習(xí)。Pinia 的特性也都在文中有涉及,比如composable store、TypeScript 的支持,devtool 插件的集成如下圖所示:

          最后簡單地跟 Vuex 做了比較,詳細記得點擊鏈接去查閱引文哦。相信你讀完本文,對于 Pinia 的基礎(chǔ)一定是了如指掌。

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(上)||?Webpack4 入門(下)
          6.?MobX 入門(上)?||??MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點擊“閱讀原文”查看 130+ 篇原創(chuàng)文章

          瀏覽 35
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  最新亚洲视频在线观看 | 狠狠狠操 | 久久精品一二三视频 | 在线观看日视频国产 | 黄色小电影a |