日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

基于項目時間闡述vue3.0新型狀態管理和邏輯復用方式

 

作者:Mingle

轉發鏈接:https://mp.weixin.qq.com/s/iOq-eeyToDXJ6lvwnC12DQ

前言

背景:2019年2月6號,React 發布 「16.8.0」 版本,vue緊隨其后,發布了「vue3.0 RFC」

Vue3.0受React16.0 推出的hook抄襲啟發(咳咳...),提供了一個全新的邏輯復用方案。使用基于函數的 API,我們可以將相關聯的代碼抽取到一個 "composition function"(組合函數)中 —— 該函數封裝了相關聯的邏輯,并將需要暴露給組件的狀態以相應式的數據源的方式返回出來。

本文目的

本文會介紹Vue3.0「組合api的用法和注意點」。最后會用一個 Todolist 的項目實戰,向大家介紹「Vue3.0的邏輯復用寫法以及借用provide和inject的新型狀態管理方式」

本文提綱:

  • 如何新建一個使用vue3.0的項目
  • conposition api
  • 邏輯復用(hook)和狀態管理(provide+inject)
    • 結合項目實戰,做一個todo list

正文

如何新建一個使用vue3.0的項目

接下來向大家簡單介紹下如何嘗鮮 -- 自己創建一個vue3.0的項目。

  1. 安裝vue0-cli

我這邊使用的是最新版本的vue-cli 4.4.0

npm install -g @vue/cli
# OR
yarn global add @vue/cli
  1. 將vue升級到bata版本
vue add vue-next
基于項目時間闡述vue3.0新型狀態管理和邏輯復用方式

 

ok了。就這么簡單!

conposition api

#### 目錄

  • 基本例子
  • setup()
  • reactive
  • ref
  • computed
  • watchEffect
  • watch
  • 生命周期
  • 依賴注入

基本例子

<template>
  <div>
    <div>count is {{ count.count }}</div>
    <div>plusOne is {{ plusOne }}</div>
    <button @click="increment">count++</button>
  </div>
</template>

<script>
// eslint-disable-next-line no-unused-vars
import { reactive, computed, watch, onMounted } from 'vue'
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  setup () {
    // reactive state
    const count = reactive({ count: 0 })
    console.log("setup -> count", count.count)
    // computed state
    const plusOne = computed(() => count.count + 1)
    // method
    const increment = () => { count.count++ }
    // watch
    watch(() => count.count * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // lifecycle
    onMounted(() => {
      console.log(`mounted`)
    })
    // expose bindings on render context
    return {
      count,
      plusOne,
      increment
    }
  }
}
</script>

setup

?

該setup功能是新的組件選項。它是組件內部暴露出所有的屬性和方法的統一API。

?

調用時機

創建組件實例,然后初始化 props ,緊接著就調用setup 函數。從生命周期鉤子的視角來看,它會在 beforeCreate 鉤子之前被調用

模板中使用

如果 setup 返回一個對象,則對象的屬性將會被合并到組件模板的渲染上下文

<template>
  <div>{{ count }} {{ object.foo }}</div>
</template>
<script>
  import { ref, reactive } from 'vue'
  export default {
    setup() {
      const count = ref(0)
      const object = reactive({ foo: 'bar' })
      // 暴露給模板
      return {
        count,
        object,
      }
    },
  }
</script>

setup 參數

  1. 「props」第一個參數接受一個響應式的props,這個props指向的是外部的props。如果你沒有定義props選項,setup中的第一個參數將為undifined。props和vue2.x并無什么不同,仍然遵循以前的原則;
  • 不要在子組件中修改props;如果你嘗試修改,將會給你警告甚至報錯。
基于項目時間闡述vue3.0新型狀態管理和邏輯復用方式

 

  • 不要結構props。結構的props會失去響應性。
基于項目時間闡述vue3.0新型狀態管理和邏輯復用方式

 

2.「上下文對象」第二個參數提供了一個上下文對象,從原來 2.x 中 this 選擇性地暴露了一些 property。

const MyComponent = {
  setup(props, context) {
    context.attrs
    context.slots
    context.emit
  },
}

Tip:

由于vue3.x向下兼容vue2.x,所以我在嘗試之后發現,一個vue文件中你可以同時寫兩個版本的東西。

import { reactive, computed, watch, onMounted } from 'vue'
export default {
  name: 'HelloWorld',
  props: {
    count: Number,
  },
  data () {
    return {
      msg: "我是vue2.x中的this"
    }
  },
  methods: {
    test () {
      console.log(this.msg)
    }
  },
  mounted () {
    console.log('vue2.x mounted')
  },
  // eslint-disable-next-line no-unused-vars
  setup (props, val) {
    console.log(this, 'this') // undefined
    onMounted(() => {
      console.log('vue3.x mounted')
    })
    return {
      ...props
    }
  }
}

當然這邊不推薦你在項目中這么用,但是抱著嘗鮮和探究的態度,我們勢必要弄清如果這么寫要注意哪些?

  1. 如果我寫了mounted(2.x),在setup函數中又寫了onMounted(3.x),誰先執行?

setup中的先執行。因為setup() 在解析 2.x 選項前被調用;

  1. 我在vue2.x選項中中定義在this上的變量,在setup上可以通過this訪問嗎?可以重復定義嗎?可以return嗎?

首先在setup中的this將不再指向vue,而是undefined;所以在setup函數內部自然無法訪問到vue實例上的this。

setup內部定義的變量和外表的變量并無沖突;

但是如果你要將其return 暴露給template,那么就會產生沖突。

基于項目時間闡述vue3.0新型狀態管理和邏輯復用方式

 

reactive

?

接收一個普通對象然后返回該普通對象的響應式代理。等同于 2.x 的 Vue.observable()

?

const obj = reactive({ count: 0 })

ref

?

接受一個參數值并返回一個響應式且可改變的 ref 對象。ref 對象擁有一個指向內部值的單一屬性 value。

?

const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1

tip:

  1. ref常用于基本類型,reactive用于引用類型。如果ref傳入對象,其實內部會自動變為reactive.
  2. 當 ref 作為渲染上下文的屬性返回(即在setup() 返回的對象中)并在模板中使用時,它會自動解套,無需在模板內額外書寫 .value;
<template>
  <div>{{ count }}</div>
</template>
<script>
  export default {
    setup() {
      return {
        const count = ref(0)
        count: count, // 而不是 count.value
      }
    },
  }
</script>
  1. 當 ref 作為 reactive 對象的 property 被訪問或修改時,也將自動解套 value 值,其行為類似普通屬性。
const count = ref(0)
const state = reactive({
  count,
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
  1. 注意當嵌套在 reactive Object 中時,ref 才會解套。從 Array 或者 Map 等原生集合類中訪問 ref 時,不會自動解套:
const arr = reactive([ref(0)])
// 這里需要 .value
console.log(arr[0].value)
const map = reactive(new Map([['foo', ref(0)]]))
// 這里需要 .value
console.log(map.get('foo').value)

computed

computed和vue2.x版本保持一致,支持getter和setter

  • 傳入一個 getter 函數,返回一個默認不可手動修改的 ref 對象。
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 錯誤!
  • 或者傳入一個擁有 get 和 set 函數的對象,創建一個可手動修改的計算狀態。
const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  },
})
plusOne.value = 1
console.log(count.value) // 0

watchEffect

?

傳入的一個函數,并且立即執行,響應式追蹤其依賴,并在其依賴變更時重新運行該函數。

?

注冊監聽

import {watchEffect}from 'vue' // 導入api
const count = ref(0) // 定義響應數據
watchEffect(() => console.log(count.value)) // 注冊監聽函數
// -> 打印出 0
setTimeout(() => {
  count.value++
  // -> 打印出 1
}, 100)

注銷監聽

- 默認情況下是在**組件卸載**的時候停止監聽;- 也可以顯示**調用返回值**以停止偵聽;

const stop = watchEffect(() => {
  /* ... */
})
// 之后
stop()

清除副作用

> 有時副作用函數會執行一些異步的副作用, 這些響應需要在其失效時清除(即完成之前狀態已改變了)。所以偵聽副作用傳入的函數可以接收一個 onInvalidate 函數作入參, 用來注冊清理失效時的回調。

當以下情況發生時,這個失效回調會被觸發:

  • 副作用即將重新執行時
  • 偵聽器被停止
const count = ref(0)
watchEffect(
  (onInvalidate) => {
    console.log(count.value, '副作用')
   const token =  setTimeout(() => {
      console.log(count.value, '副作用')
    }, 4000)
    onInvalidate(() => {
    // id 改變時 或 停止偵聽時
    // 取消之前的異步操作
    token.cancel()
  })
  }
)

副作用刷新時機

> Vue 的響應式系統會緩存副作用函數,并異步地刷新它們,這樣可以避免同一個 tick 中多個狀態改變導致的不必要的重復調用。在核心的具體實現中, 組件的更新函數也是一個被偵聽的副作用。當一個用戶定義的副作用函數進入隊列時, 會在所有的組件更新后執行:

<template>
  <div>{{ count }}</div>
</template>
<script>
  export default {
    setup() {
      const count = ref(0)
      watchEffect(() => {
        console.log(count.value)
      })
      return {
        count,
      }
    },
  }
</script>

在這個例子中:

  • count 會在初始運行時同步打印出來
  • 更改 count 時,將在組件更新后執行副作用。

如果副作用需要同步或在組件更新之前重新運行,我們可以傳遞一個擁有 flush 屬性的對象作為選項(默認為 'post'):

// 同步運行
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'sync',
  }
)
// 組件更新前執行
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'pre',
  }
)

watch

> watch API 完全等效于 2.x this.$watch (以及 watch 中相應的選項)。watch 需要偵聽特定的數據源,并在回調函數中執行副作用。默認情況是懶執行的,也就是說僅在偵聽的源變更時才執行回調。

  • 對比 watchEffect,watch 允許我們:
    • 懶執行副作用;
    • 更明確哪些狀態的改變會觸發偵聽器重新運行副作用;
    • 訪問偵聽狀態變化前后的值。
  • 偵聽單個數據源

偵聽器的數據源可以是一個擁有返回值的 getter 函數,也可以是 ref:

// 偵聽一個 getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)
// 直接偵聽一個 ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})
  • 偵聽多個數據源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})
  • 與 watchEffect 共享的行為

watch 和 watchEffect 在停止偵聽, 清除副作用 (相應地 onInvalidate 會作為回調的第三個參數傳入),副作用刷新時機 和 助聽器調試 等方面行為一致.

生命周期鉤子函數

?

可以直接導入 onXXX 一組的函數來注冊生命周期鉤子,這些生命周期鉤子注冊函數只能在 setup() 期間同步使用,在卸載組件時,在生命周期鉤子內部同步創建的偵聽器和計算狀態也將自動刪除。

?

  • 「與 2.x 版本生命周期相對應的組合式 API」
    • beforeCreate -> 使用 setup()
    • created -> 使用 setup()
    • beforeMount -> onBeforeMount
    • mounted -> onMounted
    • beforeUpdate -> onBeforeUpdate
    • updated -> onUpdated
    • beforeDestroy -> onBeforeUnmount
    • destroyed -> onUnmounted
    • errorCaptured -> onErrorCaptured
  • 新增的鉤子函數
    • onRenderTracked
    • onRenderTriggered

兩個鉤子函數都接收一個DebuggerEvent,與 watchEffect 參數選項中的 onTrack 和 onTrigger 類似:

export default {
  onRenderTriggered(e) {
    debugger
    // 檢查哪個依賴性導致組件重新渲染
  },
}

依賴注入

?

provide 和 inject 提供依賴注入,功能類似 2.x 的 provide/inject。兩者都只能在當前活動組件實例的 setup() 中調用。

?

這是本篇文章的重點。結合項目實戰以此來探索一下未來的 Vue 狀態管理模式和邏輯復用模式。

「用法」

?

provide 和 inject 提供依賴注入,功能類似 2.x 的 provide/inject。兩者都只能在當前活動組件實例的 setup() 中調用。

?

import { provide, inject } from 'vue'
const ThemeSymbol = Symbol()
const Ancestor = {
  setup() {
    provide(ThemeSymbol, 'dark')
  },
}
const Descendent = {
  setup() {
    const theme = inject(ThemeSymbol, 'light' /* optional default value */)
    return {
      theme,
    }
  },
}

inject 接受一個可選的的默認值作為第二個參數。如果未提供默認值,并且在 provide 上下文中未找到該屬性,則 inject 返回 undefined。

  • 「注入的響應性」

可以使用 ref 來保證 provided 和 injected 時間值的響應:

// 提供者:
const themeRef = ref('dark')
provide(ThemeSymbol, themeRef)
// 使用者:
const theme = inject(ThemeSymbol, ref('light'))
watchEffect(() => {
  console.log(`theme set to: ${theme.value}`)
})

如果注入一個響應式對象,則它的狀態變化也可以被偵聽。

邏輯組合與復用

引出問題:

我們通常會基于一堆相同的數據進行花樣呈現,有列表展示、有餅圖占比、有折線圖趨勢、有熱力圖說明頻次等等,這些組件使用的是相同的一些數據和數據處理邏輯。對于數據處理邏輯,目前vue有

  • Mixins
  • 高階組件 (Higher-order Components, aka HOCs)
  • Renderless Components (基于 scoped slots / 作用于插槽封裝邏輯的組件)

但是上面的方案是存在一些弊端:

  1. 模版中的數據來源不清晰
  2. 命名空間沖突。
  3. 需要額外的組件實例嵌套來封裝邏輯(性能問題);
##### 基于組合api 的解決方案
function useMouse() {
  const x = ref(0)
  const y = ref(0)
  const update = e => {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })
  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })
  return { x, y }
}
// 在組件中使用該函數
const Component = {
  setup() {
    const { x, y } = useMouse()
    // 與其它函數配合使用
    const { z } = useotherLogic()
    return { x, y, z }
  },
  template: `<div>{{ x }} {{ y }} {{ z }}</div>`
}

項目預覽

源碼:https://github.com/961998264/todolist-vue-3.0

項目介紹

  1. 已完成事件列表
  2. 未完成事件列表
  3. 查看事件詳情
  4. 修改事件完成狀態和事件詳情

項目src目錄

基于項目時間闡述vue3.0新型狀態管理和邏輯復用方式

 

hooks文件夾是專門放hook的

基于項目時間闡述vue3.0新型狀態管理和邏輯復用方式

 

context文件夾以模塊劃分

基于項目時間闡述vue3.0新型狀態管理和邏輯復用方式

 

先來看下context編寫(我這邊是用的ts)

import { provide, ref, Ref, inject, computed, } from 'vue' //vue api
import { getListApi } from 'api/home' // mock的api
// 以下為定義的ts類型,你也可以單獨建一個專門定義類型的文件。
type list = listItem[]
interface listItem {
  title: string,
  context: string,
  id: number,
  status: number,
}
interface ListContext {
  list: Ref<list>,
  getList: () => {},
  changeStatus: (id: number, status: number) => void,
  addList: (item: listItem) => void,
  delList: (id: number) => void,
  finished: Ref<list>,
  unFinish: Ref<list>,
  setContext: (id: number, context: string) => void,
  setActiveItem: () => void,
}

provide名稱,推薦用Symbol

const listymbol = Symbol()

提供provide的函數

export const useListProvide = () => {
  // 全部事件 
  const list = ref<list>([]);
  // 當前查看的事件id
  const activeId = ref<number | null>(null)
  // 當前查看的事件
  const activeItem = computed(() => {
    if (activeId.value || activeId.value === 0) {
      const item = list.value.filter((item: listItem) => item.id === activeId.value)
      return item[0]
    } else {
      return null
    }
  })
  // 獲取list
  const getList = async function () {
    const res: any = await getListApi()
    console.log("useListProvide -> res", res)
    if (res.code === 0) {
      list.value = res.data
    }
  }
  // 新增list
  const addList = (item: listItem) => {
    list.value.push(item)
  }
  //修改狀態
  const changeStatus = (id: number, status: number) => {
    console.log('status', status)
    const removeIndex = list.value.findIndex((listItem: listItem) => listItem.id === id)
    if (removeIndex !== -1) {
      list.value[removeIndex].status = status
    }
  };
  // 修改事件信息
  const setContext = (id: number, context: string) => {
    const Index = list.value.findIndex((listItem: listItem) => listItem.id === id)
    if (Index !== -1) {
      list.value[Index].context = context
    }
  }
  // 刪除事件
  const delList = (id: number) => {
    console.log("delList -> id", id)
    for (let i = 0; i < list.value.length; i++) {
      if (list.value[i].id === id) {
        list.value.splice(i, 1)
        break
      }
    }
  }
  // 未完成事件列表
  const unFinish = computed(() => {
    return list.value.filter(item => item.status === 0)
  })
  // 已完成事件列表
  const finished = computed(() => {
    return list.value.filter(item => item.status === 1)
  })
  
  provide(listymbol, {
    list,
    unFinish,
    finished,
    changeStatus,
    getList,
    addList,
    delList,
    setContext,
    activeItem,
    activeId
  })
}

在這個函數中定義 待辦事件,并且定義一系列增刪改查函數,通過provide暴露出去。

提供inject的函數

export const useListInject = () => {
  const listContext = inject<ListContext>(listymbol);
  if (!listContext) {
    throw new Error(`useListInject must be used after useListProvide`);
  }
  return listContext
};

全局狀態肯定不止一個模塊,所以在 context/index.ts 下做統一的導出

import { useListProvide, useListInject } from './home/index'
console.log("useListInject", useListInject)
export { useListInject }
export const useProvider = () => {
  useListProvide()
}

然后在 App.vue 的根組件里使用 provide,在最上層的組件中注入全局狀態。

import { useProvider } from './context/index'
export default {
  name: 'App',
  setup () {
    useProvider()
    return {
    }
  }
}

在組件中獲取數據:

import { useListInject } from '../../context/home/index'
setup () {
  const { list, changeStatus, getList, unFinish, finished, addList, a   ctiveItem, setContext } = useListInject()
}

不管是父子組件還是兄弟組件,或者是比關系套更深的組件,我們都可以通過useListInject來獲取到相應式的數據。

  1. 「邏輯聚合」 同一份數據的相關邏輯我們可以寫在一個usexxxx的函數中,不再像以前,按照選擇將邏輯分開。在methods,computed,watch,created,mounted中來回跳轉。
  2. 「取代vuex」 在比較小的項目中,你可以用這種狀態管理的方式取代vuex。(反正我用react基本不用redux,不管項目大小)。

作者:Mingle

轉發鏈接:https://mp.weixin.qq.com/s/iOq-eeyToDXJ6lvwnC12DQ


 

分享到:
標簽:狀態 管理 vue3
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定