亚洲综合图片区自拍_思思91精品国产综合在线观看_一区二区三区欧美_欧美黑人又粗又大_亚洲人成精品久久久久桥本

熱議:感受Vue3的魔法力量

2023-01-29 13:16:48 來(lái)源:51CTO博客

作者:京東科技 牛至偉

近半年有幸參與了一個(gè)創(chuàng)新項(xiàng)目,由于沒(méi)有任何歷史包袱,所以選擇了Vue3技術(shù)棧,總體來(lái)說(shuō)感受如下:

? setup語(yǔ)法糖<script setup lang="ts">擺脫了書寫聲明式的代碼,用起來(lái)很流暢,提升不少效率


(資料圖片僅供參考)

? 可以通過(guò)Composition API(組合式API)封裝可復(fù)用邏輯,將UI和邏輯分離,提高復(fù)用性,view層代碼展示更清晰

? 和Vue3更搭配的狀態(tài)管理庫(kù)Pinia,少去了很多配置,使用起來(lái)更便捷

? 構(gòu)建工具Vite,基于ESM和Rollup,省去本地開發(fā)時(shí)的編譯步驟,但是build打包時(shí)還是會(huì)編譯(考慮到兼容性)

? 必備VSCode插件Volar,支持Vue3內(nèi)置API的TS類型推斷,但是不兼容Vue2,如果需要在Vue2和Vue3項(xiàng)目中切換,比較麻煩

當(dāng)然也遇到一些問(wèn)題,最典型的就是響應(yīng)式相關(guān)的問(wèn)題

響應(yīng)式篇

本篇主要借助watch函數(shù),理解ref、reactive等響應(yīng)式數(shù)據(jù)/狀態(tài),有興趣的同學(xué)可以查看Vue3源代碼部分加深理解,

watch數(shù)據(jù)源可以是ref (包括計(jì)算屬性)、響應(yīng)式對(duì)象、getter 函數(shù)、或多個(gè)數(shù)據(jù)源組成的數(shù)組

import { ref, reactive, watch, nextTick } from "vue"http://定義4種響應(yīng)式數(shù)據(jù)/狀態(tài)//1、ref值為基本類型const simplePerson = ref("張三") //2、ref值為引用類型,等價(jià)于:person.value = reactive({ name: "張三" })const person = ref({    name: "張三"})//3、ref值包含嵌套的引用類型,等價(jià)于:complexPerson.value = reactive({ name: "張三", info: { age: 18 } })const complexPerson = ref({ name: "張三", info: { age: 18 } })//4、reactiveconst reactivePerson = reactive({ name: "張三", info: { age: 18 } })//改變屬性,觀察以下不同情景下的監(jiān)聽結(jié)果nextTick(() => {     simplePerson.value = "李四"     person.value.name = "李四"     complexPerson.value.info.age = 20    reactivePerson.info.age = 22})//情景一:數(shù)據(jù)源為RefImplwatch(simplePerson, (newVal) => {    console.log(newVal) //輸出:李四})//情景二:數(shù)據(jù)源為"張三"watch(simplePerson.value, (newVal) => {     console.log(newVal) //非法數(shù)據(jù)源,監(jiān)聽不到且控制臺(tái)告警 })//情景三:數(shù)據(jù)源為RefImpl,但是.value才是響應(yīng)式對(duì)象,所以要加deepwatch(person, (newVal) => {     console.log(newVal) //輸出:{name: "李四"}},{    deep: true //必須設(shè)置,否則監(jiān)聽不到內(nèi)部變化}) //情景四:數(shù)據(jù)源為響應(yīng)式對(duì)象watch(person.value, (newVal) => {     console.log(newVal) //輸出:{name: "李四"}})//情景五:數(shù)據(jù)源為"張三"watch(person.value.name, (newVal) => {     console.log(newVal) //非法數(shù)據(jù)源,監(jiān)聽不到且控制臺(tái)告警 })//情景六:數(shù)據(jù)源為getter函數(shù),返回基本類型watch(    () => person.value.name,     (newVal) => {         console.log(newVal) //輸出:李四    })//情景七:數(shù)據(jù)源為響應(yīng)式對(duì)象(在Vue3中狀態(tài)都是默認(rèn)深層響應(yīng)式的)watch(complexPerson.value.info, (newVal, oldVal) => {     console.log(newVal) //輸出:Proxy {age: 20}     console.log(newVal === oldVal) //輸出:true}) //情景八:數(shù)據(jù)源為getter函數(shù),返回響應(yīng)式對(duì)象watch(     () => complexPerson.value.info,     (newVal) => {         console.log(newVal) //除非設(shè)置deep: true或info屬性被整體替換,否則監(jiān)聽不到    })//情景九:數(shù)據(jù)源為響應(yīng)式對(duì)象watch(reactivePerson, (newVal) => {     console.log(newVal) //不設(shè)置deep: true也可以監(jiān)聽到 })

總結(jié):

在Vue3中狀態(tài)都是默認(rèn)深層響應(yīng)式的(情景七),嵌套的引用類型在取值(get)時(shí)一定是返回Proxy響應(yīng)式對(duì)象watch數(shù)據(jù)源為響應(yīng)式對(duì)象時(shí)(情景四、七、九),會(huì)隱式的創(chuàng)建一個(gè)深層偵聽器,不需要再顯示設(shè)置deep: true情景三和情景八兩種情況下,必須顯示設(shè)置deep: true,強(qiáng)制轉(zhuǎn)換為深層偵聽器情景五和情景七對(duì)比下,雖然寫法完全相同,但是如果屬性值為基本類型時(shí)是監(jiān)聽不到的,尤其是ts類型聲明為any時(shí),ide也不會(huì)提示告警,導(dǎo)致排查問(wèn)題比較費(fèi)力所以精確的ts類型聲明很重要,否則經(jīng)常會(huì)出現(xiàn)莫名其妙的watch不生效的問(wèn)題ref值為基本類型時(shí)通過(guò)get\\set攔截實(shí)現(xiàn)響應(yīng)式;ref值為引用類型時(shí)通過(guò)將.value屬性轉(zhuǎn)換為reactive響應(yīng)式對(duì)象實(shí)現(xiàn);deep會(huì)影響性能,而reactive會(huì)隱式的設(shè)置deep: true,所以只有明確狀態(tài)數(shù)據(jù)結(jié)構(gòu)比較簡(jiǎn)單且數(shù)據(jù)量不大時(shí)使用reactive,其他一律使用ref

Props篇

設(shè)置默認(rèn)值

type Props = {  placeholder?: string  modelValue: string  multiple?: boolean}const props = withDefaults(defineProps(), {  placeholder: "請(qǐng)選擇",  multiple: false,})

雙向綁定(多個(gè)值)

? 自定義組件

//FieldSelector.vuetype Props = { businessTableUuid: string businessTableFieldUuid?: string}const props = defineProps()const emits = defineEmits([ "update:businessTableUuid", "update:businessTableFieldUuid",])const businessTableUuid = ref("")const businessTableFieldUuid = ref("")// props.businessTableUuid、props.businessTableFieldUuid轉(zhuǎn)為本地狀態(tài),此處省略//表切換const tableChange = (businessTableUuid: string) => { emits("update:businessTableUuid", businessTableUuid) emits("update:businessTableFieldUuid", "") businessTableFieldUuid.value = ""}//字段切換const fieldChange = (businessTableFieldUuid: string) => { emits("update:businessTableFieldUuid", businessTableFieldUuid)}

? 使用組件

<script setup lang="ts">import { reactive } from "vue"const stringFilter = reactive({  businessTableUuid: "",  businessTableFieldUuid: ""})</script>

單向數(shù)據(jù)流

大部分情況下應(yīng)該遵循【單向數(shù)據(jù)流】原則,禁止子組件直接修改props,否則復(fù)雜應(yīng)用下的數(shù)據(jù)流將變得混亂,極易出現(xiàn)bug且難排查直接修改props會(huì)有告警,但是如果props是引用類型,修改props內(nèi)部值將不會(huì)有告警提示,因此應(yīng)該有團(tuán)隊(duì)約定(第5條除外)如果props為引用類型,賦值到子組件狀態(tài)時(shí),需要解除引用(第5條除外)復(fù)雜的邏輯,可以將狀態(tài)以及修改狀態(tài)的方法,封裝成自定義hooks或者提升到store內(nèi)部,避免props的層層傳遞與修改一些父子組件本就緊密耦合的場(chǎng)景下,可以允許修改props內(nèi)部的值,可以減少很多復(fù)雜度和工作量(需要團(tuán)隊(duì)約定固定場(chǎng)景)

邏輯/UI解耦篇

利用Vue3的Composition/組合式API,將某種邏輯涉及到的狀態(tài),以及修改狀態(tài)的方法封裝成一個(gè)自定義hook,將組件中的邏輯解耦,這樣即使UI有不同的形態(tài)或者調(diào)整,只要邏輯不變,就可以復(fù)用邏輯。下面是本項(xiàng)目中涉及的一個(gè)真實(shí)案例-邏輯樹組件,UI有2種形態(tài)且可以相互轉(zhuǎn)化。

? hooks部分的代碼:useDynamicTree.ts

import { ref } from "vue"import { nanoid } from "nanoid"export type TreeNode = { id?: string pid: string nodeUuid?: string partentUuid?: string nodeType: string nodeValue?: any logicValue?: any children: TreeNode[] level?: number}export const useDynamicTree = (root?: TreeNode) => {  const tree = ref(root ? [root] : [])  const level = ref(0)  //添加節(jié)點(diǎn)  const add = (node: TreeNode, pid: string = "root"): boolean => {    //添加根節(jié)點(diǎn)    if (pid === "") {      tree.value = [node]      return true    }    level.value = 0    const pNode = find(tree.value, pid)    if (!pNode) return false    //嵌套關(guān)系不能超過(guò)3層    if (pNode.level && pNode.level > 2) return false    if (!node.id) {      node.id = nanoid()    }    if (pNode.nodeType === "operator") {      pNode.children.push(node)    } else {      //如果父節(jié)點(diǎn)不是關(guān)系節(jié)點(diǎn),則構(gòu)建新的關(guān)系節(jié)點(diǎn)      const current = JSON.parse(JSON.stringify(pNode))      current.pid = pid      current.id = nanoid()      Object.assign(pNode, {        nodeType: "operator",        nodeValue: "and",        // 重置回顯信息        logicValue: undefined,        nodeUuid: undefined,        parentUuid: undefined,        children: [current, node],      })    }    return true  }  //刪除節(jié)點(diǎn)  const remove = (id: string) => {    const node = find(tree.value, id)    if (!node) return    //根節(jié)點(diǎn)處理    if (node.pid === "") {      tree.value = []      return    }    const pNode = find(tree.value, node.pid)    if (!pNode) return    const index = pNode.children.findIndex((item) => item.id === id)    if (index === -1) return    pNode.children.splice(index, 1)    if (pNode.children.length === 1) {      //如果只剩下一個(gè)節(jié)點(diǎn),則替換父節(jié)點(diǎn)(關(guān)系節(jié)點(diǎn))      const [one] = pNode.children      Object.assign(        pNode,        {          ...one,        },        {          pid: pNode.pid,        },      )      if (pNode.pid === "") {        pNode.id = "root"      }    }  }  //切換邏輯關(guān)系:且/或  const toggleOperator = (id: string) => {    const node = find(tree.value, id)    if (!node) return    if (node.nodeType !== "operator") return    node.nodeValue = node.nodeValue === "and" ? "or" : "and"  }  //查找節(jié)點(diǎn)  const find = (node: TreeNode[], id: string): TreeNode | undefined => {    // console.log(node, id)    for (let i = 0; i < node.length; i++) {      if (node[i].id === id) {        Object.assign(node[i], {          level: level.value,        })        return node[i]      }      if (node[i].children?.length > 0) {        level.value += 1        const result = find(node[i].children, id)        if (result) {          return result        }        level.value -= 1      }    }    return undefined  }  //提供遍歷節(jié)點(diǎn)方法,支持回調(diào)  const dfs = (node: TreeNode[], callback: (node: TreeNode) => void) => {    for (let i = 0; i < node.length; i++) {      callback(node[i])      if (node[i].children?.length > 0) {        dfs(node[i].children, callback)      }    }  }  return {    tree,    add,    remove,    toggleOperator,    dfs,  }}

? 在不同組件中使用(UI1/UI2組件為遞歸組件,內(nèi)部實(shí)現(xiàn)不再展開)

//組件1<script setup lang="ts">  import { useDynamicTree } from "@/hooks/useDynamicTree"  const { add, remove, toggleOperator, tree: logic, dfs } = useDynamicTree()  const handleAdd = () => {    //添加條件  }  const handleRemove = () => {     //刪除條件   }  const toggleOperator = () => {     //切換邏輯關(guān)系:且、或     }</script>
//組件2  <script setup lang="ts">   import { useDynamicTree } from "@/hooks/useDynamicTree"   const { add, remove, toggleOperator, tree: logic, dfs } = useDynamicTree()   const handleAdd = () => { //添加條件 }   const handleRemove = () => { //刪除條件  }   const toggleOperator = () => { //切換邏輯關(guān)系:且、或  } </script>

Pinia狀態(tài)管理篇

將復(fù)雜邏輯的狀態(tài)以及修改狀態(tài)的方法提升到store內(nèi)部管理,可以避免props的層層傳遞,減少props復(fù)雜度,狀態(tài)管理更清晰

? 定義一個(gè)store(非聲明式):User.ts

import { computed, reactive } from "vue"import { defineStore } from "pinia"type UserInfo = {  userName: string  realName: string  headImg: string  organizationFullName: string}export const useUserStore = defineStore("user", () => {  const userInfo = reactive({    userName: "",    realName: "",    headImg: "",    organizationFullName: ""  })  const fullName = computed(() => {    return `${userInfo.userName}[${userInfo.realName}]`  })  const setUserInfo = (info: UserInfo) => {    Object.assgin(userInfo, {...info})  }  return {    userInfo,    fullName,    setUserInfo  }})

? 在組件中使用

<script setup lang="ts">  import { useUserStore } from "@/stores/user"  import avatar from "@/assets/avatar.png"  const { userInfo } = useUserStore()</script>

標(biāo)簽: 引用類型 基本類型 邏輯關(guān)系

上一篇:
下一篇: