前言
這幾天 Vue 3.0 Beta 版本發布了,本以為是皆大歡喜的一件事情,但是論壇里還是看到了很多反對的聲音。主流的反對論點大概有如下幾點:
- 意大利面代碼結構吐槽:
“太失望了。雜七雜八一堆丟在 setup 里,我還不如直接用 react”
我的天,3.0 這么搞的話,代碼結構不清晰,語義不明確,無異於把 vue 自身優點都扔了
怎么感覺代碼結構上沒有 2.0 清晰了呢 這要是代碼量上去了是不是不好維護啊
- 抄襲 React 吐槽:
抄來抄去沒自己的個性
有 react 香嗎?越來越像 react 了
在我看來,Vue 黑暗的一天還遠遠沒有過去,很多人其實並沒有認真的去看 Vue-Composition-Api
文檔中的 動機
章節,本文就以這個章節為線索,從 代碼結構
、底層原理
等方面來一一打消大家的一些顧慮。
在文章的開頭,首先要標明一下作者的立場,我對於 React 和 Vue 都非常的喜歡。他們都有着各自的優缺點,本文絕無引戰之意。兩個框架都很棒!只是各有優缺點而已。React 的 Immutable 其實也帶來了很多益處,並且 Hook 的思路還是 Facebook 團隊的大佬們首創的,真的是很讓人贊嘆的設計,我對 React 100% 致敬!
設計動機
大如 Vue3 這種全球熱門的框架,任何一個 breaking-change
的設計一定有它的深思熟慮和權衡,那么 composition-api
出現是為了解決什么問題呢?這是一個我們需要首先思考明白的問題。
首先拋出 Vue2 的代碼模式下存在的幾個問題。
- 隨着功能的增長,復雜組件的代碼變得越來越難以維護。 尤其發生你去新接手別人的代碼時。 根本原因是 Vue 的現有 API 通過「選項」組織代碼,但是在大部分情況下,通過邏輯考慮來組織代碼更有意義。
- 缺少一種比較「干凈」的在多個組件之間提取和復用邏輯的機制。
- 類型推斷不夠友好。
邏輯重用
相信很多接觸過 React Hook 的小伙伴已經對這種模式下組件間邏輯復用的簡單性有了一定的認知,自從 React 16.7 發布以來,社區涌現出了海量的 Hook 輪子,以及主流的生態庫 react-router
,react-redux
等等全部擁抱 Hook,都可以看出社區的同好們對於 Hook 開發機制的贊同。
其實組件邏輯復用在 React 中是經歷了很長的一段發展歷程的, mixin
-> HOC & render-props
-> Hook
,mixin
是 React 中最早啟用的一種邏輯復用方式,因為它的缺點實在是多到數不清,而后面的兩種也有着自己的問題,比如增加組件嵌套啊、props 來源不明確啊等等。可以說到目前為止,Hook 是相對完美的一種方案。
當然,我的一貫風格就是上代碼對比,我就拿 HOC 來說吧,Github 上的一個真實的開源項目里就出現了這樣的場景:
HOC 對比 Hook
class MenuBar extends React.Component { // props 里混合着來自各個HOC傳入的屬性,還有父組件傳入的屬性。 handleClickNew() { const readyToReplaceProject = this.props.confirmReadyToReplaceProject( this.props.intl.formatMessage(sharedMessages.replaceProjectWarning) ); this.props.onRequestCloseFile(); if (readyToReplaceProject) { this.props.onClickNew(this.props.canSave && this.props.canCreateNew); } this.props.onRequestCloseFile(); } handleClickRemix() { this.props.onClickRemix(); this.props.onRequestCloseFile(); } handleClickSave() { this.props.onClickSave(); this.props.onRequestCloseFile(); } handleClickSaveAsCopy() { this.props.onClickSaveAsCopy(); this.props.onRequestCloseFile(); } } export default compose( // 國際化 injectIntl, // 菜單 MenuBarHOC, // react-redux connect(mapStateToProps, mapDispatchToProps) )(MenuBar);
沒錯,這里用 compose 函數組合了好幾個 HOC,其中還有 connect 這種 接受幾個參數返回一個接受組件作為函數的函數
這種東西,如果你是新上手(或者哪怕是 React 老手)這套東西的人,你會在 「這個 props 是從哪個 HOC 里來的?」,「這個 props 是外部傳入的還是 HOC 里得到的?」這些問題中迷失了大腦,最終走向墮落(誤)。
不談 HOC,我的腦子已經快炸開來了,來看看用 Hook 的方式復用邏輯是怎么樣的場景吧?
function MenuBar(props) { // props 里只包含父組件傳入的屬性 const { show } = props; // 菜單 const { onClickRemix, onClickNew } = useMenuBar(); // 國際化 const { intl } = useIntl(); // react-redux const { user } = useSelector((store) => store.user); } export default MenuBar;
一切都變得很明朗,我可以非常清楚的知道這個方法的來源,intl
是哪里注入進來的,點擊了 useMenuBar
后,就自動跳轉到對應的邏輯,維護和可讀性都極大的提高了。
當然,這是一個比較「刻意」的例子,但是相信我,我在 React 開發中已經體驗過這種收益了。隨着組件的「職責」越來越多,只要你掌握了這種代碼組織的思路,那么你的組件並不會膨脹到不可讀。
常見的請求場景
再舉個非常常見的請求場景。
在 Vue2 中如果我需要請求一份數據,並且在loading
和error
時都展示對應的視圖,一般來說,我們會這樣寫:
<template> <div v-if="error">failed to load</div> <div v-else-if="loading">loading...</div> <div v-else>hello {{fullName}}!</div> </template> <script> import { createComponent, computed } from 'vue' export default { data() { // 集中式的data定義 如果有其他邏輯相關的數據就很容易混亂 return { data: { firstName: '', lastName: '' }, loading: false, error: false, }, }, async created() { try { // 管理loading this.loading = true // 取數據 const data = await this.$axios('/api/user') this.data = data } catch (e) { // 管理error this.error = true } finally { // 管理loading this.loading = false } }, computed() { // 沒人知道這個fullName和哪一部分的異步請求有關 和哪一部分的data有關 除非仔細閱讀 // 在組件大了以后更是如此 fullName() { return this.data.firstName + this.data.lastName } } } </script>
這段代碼,怎么樣都談不上優雅,湊合的把功能完成而已,並且對於loading
、error
等處理的可復用性為零。
數據和邏輯也被分散在了各個option
中,這還只是一個邏輯,如果又多了一些邏輯,多了data
、computed
、methods
?如果你是一個新接手這個文件的人,你如何迅速的分辨清楚這個method
是和某兩個data
中的字段關聯起來的?
讓我們把zeit/swr的邏輯照搬到 Vue3 中,
看一下swr
在 Vue3 中的表現:
<template> <div v-if="error">failed to load</div> <div v-else-if="loading">loading...</div> <div v-else>hello {{fullName}}!</div> </template> <script> import { createComponent, computed } from 'vue' import useSWR from 'vue-swr' export default createComponent({ setup() { // useSWR幫你管理好了取數、緩存、甚至標簽頁聚焦重新請求、甚至Suspense... const { data, loading, error } = useSWR('/api/user', fetcher) // 輕松的定義計算屬性 const fullName = computed(() => data.firstName + data.lastName) return { data, fullName, loading, error } } }) </script>
就是這么簡單,對嗎?邏輯更加聚合了。
對了,順嘴一提, use-swr
的威力可遠遠不止看到的這么簡單,隨便舉幾個它的能力:
- 間隔輪詢
- 請求重復數據刪除
- 對於同一個 key 的數據進行緩存
- 對數據進行樂觀更新
- 在標簽頁聚焦的時候重新發起請求
- 分頁支持
- 完備的 TypeScript 支持
等等等等……而這么多如此強大的能力,都在一個小小的 useSWR()
函數中,誰能說這不是魔法呢?
類似的例子還數不勝數。
代碼組織
上面說了那么多,還只是說了 Hook 的其中一個優勢。這其實並不能解決「意大利面條代碼」的問題。當邏輯多起來以后,組件的邏輯會糅合在一起變得一團亂麻嗎?
從獲取鼠標位置的需求講起
我們有這樣一個跨組件的需求,我想在組件里獲得一個響應式的變量,能實時的指向我鼠標所在的位置。
Vue 官方給出的自定義 Hook 的例子是這樣的:
import { ref, onMounted, onUnmounted } from "vue"; export function useMousePosition() { const x = ref(0); const y = ref(0); function update(e) { x.value = e.pageX; y.value = e.pageY; } onMounted(() => { window.addEventListener("mousemove", update); }); onUnmounted(() => { window.removeEventListener("mousemove", update); }); return { x, y }; }
在組件中使用:
import { useMousePosition } from "./mouse"; export default { setup() { const { x, y } = useMousePosition(); // other logic... return { x, y }; }, };
就這么簡單,無需多言。在任何組件中我們需要「獲取響應式的鼠標位置」,並且和我們的「視圖層」關聯起來的時候,僅僅需要簡單的一句話即可。並且這里返回的 x
、y
是由 ref
加工過的響應式變量,我們可以用 watch
監聽它們,可以把它們傳遞給其他的自定義 Hook 繼續使用。幾乎能做到你想要的一切,只需要發揮你的想象力。
從 Vue 官方的例子講起
上面的例子足夠入門和精簡,讓我們來到現實世界。舉一個 Vue CLI UI file explorer 官方吐槽的例子,這個組件是 Vue-CLI 的 gui 中(也就是平常我們命令行里輸入 vue ui
出來的那個圖形化控制台)的一個復雜的文件瀏覽器組件,這是 Vue 官方團隊的大佬寫的,相信是比較有說服力的一個案例了。
這個組件有以下的幾個功能:
- 跟蹤當前文件夾狀態並顯示其內容
- 處理文件夾導航(打開,關閉,刷新...)
- 處理新文件夾的創建
- 切換顯示收藏夾
- 切換顯示隱藏文件夾
- 處理當前工作目錄更改
文檔中提出了一個尖銳的靈魂之問,你作為一個新接手的開發人員,能夠在茫茫的 method
、data
、computed
等選項中一目了然的發現這個變量是屬於哪個功能嗎?比如「創建新文件夾」功能使用了兩個數據屬性,一個計算屬性和一個方法,其中該方法在距數據屬性「一百行以上」的位置定義。
當一個組價中,維護同一個邏輯需要跨越上百行的「空間距離」的時候,即使是讓我去維護 Vue 官方團隊的代碼,我也會暗搓搓的吐槽一句,「這寫的什么玩意,這變量干嘛用的!」
尤大很貼心的給出了一張圖,在這張圖中,不同的色塊代表着不同的功能點。

其實已經做的不錯了,但是在維護起來的時候還是挺災難的,比如淡藍色的那個色塊代表的功能。我想要完整的理清楚它的邏輯,需要「上下反復橫跳」,類似的事情我已經經歷過好多次了。
而使用 Hook 以后呢?我們可以把「新建文件夾」這個功能美美的抽到一個函數中去:
function useCreateFolder(openFolder) { // originally data properties const showNewFolder = ref(false); const newFolderName = ref(""); // originally computed property const newFolderValid = computed(() => isValidMultiName(newFolderName.value)); // originally a method async function createFolder() { if (!newFolderValid.value) return; const result = await mutate({ mutation: FOLDER_CREATE, variables: { name: newFolderName.value, }, }); openFolder(result.data.folderCreate.path); newFolderName.value = ""; showNewFolder.value = false; } return { showNewFolder, newFolderName, newFolderValid, createFolder, }; }
我們約定這些「自定義 Hook」以 use
作為前綴,和普通的函數加以區分。
右邊用了 Hook 以后的代碼組織色塊:

我們想要維護紫色部分功能的邏輯,那就在紫色的部分去找就好了,反正不會有其他「色塊」里的變量或者方法影響到它,很快咱就改好了需求,6 點准時下班!
這是 Hook 模式下的組件概覽,真的是一目了然。感覺我也可以去維護 @vue/ui
了呢(假的)。
export default { setup() { // ... }, }; function useCurrentFolderData(networkState) { // ... } function useFolderNavigation({ networkState, currentFolderData }) { // ... } function useFavoriteFolder(currentFolderData) { // ... } function useHiddenFolders() { // ... } function useCreateFolder(openFolder) { // ... }
再來看看被吐槽成「意大利面條代碼」的 setup
函數。
export default { setup() { // Network const { networkState } = useNetworkState(); // Folder const { folders, currentFolderData } = useCurrentFolderData(networkState); const folderNavigation = useFolderNavigation({ networkState, currentFolderData }); const { favoriteFolders, toggleFavorite } = useFavoriteFolders(currentFolderData); const { showHiddenFolders } = useHiddenFolders(); const createFolder = useCreateFolder(folderNavigation.openFolder); // Current working directory resetCwdOnLeave(); const { updateOnCwdChanged } = useCwdUtils(); // Utils const { slicePath } = usePathUtils(); return { networkState, folders, currentFolderData, folderNavigation, favoriteFolders, toggleFavorite, showHiddenFolders, createFolder, updateOnCwdChanged, slicePath, }; }, };
這是誰家的小仙女這么美啊!這邏輯也太清晰明了,和意大利面沒半毛錢關系啊!
對比
Hook 和 Mixin & HOC 對比
說到這里,還是不得不把官方對於「Mixin & HOC 模式」所帶來的缺點整理一下。
- 渲染上下文中公開的屬性的來源不清楚。 例如,當使用多個 mixin 讀取組件的模板時,可能很難確定從哪個 mixin 注入了特定的屬性。
- 命名空間沖突。 Mixins 可能會在屬性和方法名稱上發生沖突,而 HOC 可能會在預期的 prop 名稱上發生沖突。
- 性能問題,HOC 和無渲染組件需要額外的有狀態組件實例,這會降低性能。
而 「Hook」模式帶來的好處則是:
- 暴露給模板的屬性具有明確的來源,因為它們是從 Hook 函數返回的值。
- Hook 函數返回的值可以任意命名,因此不會發生名稱空間沖突。
- 沒有創建僅用於邏輯重用的不必要的組件實例。
當然,這種模式也存在一些缺點,比如 ref
帶來的心智負擔,詳見drawbacks。
React Hook 和 Vue Hook 對比
其實 React Hook 的限制非常多,比如官方文檔中就專門有一個章節介紹它的限制:
- 不要在循環,條件或嵌套函數中調用 Hook
- 確保總是在你的 React 函數的最頂層調用他們。
- 遵守這條規則,你就能確保 Hook 在每一次渲染中都按照同樣的順序被調用。這讓 React 能夠在多次的 useState 和 useEffect 調用之間保持 hook 狀態的正確。
而 Vue 帶來的不同在於:
- 與 React Hooks 相同級別的邏輯組合功能,但有一些重要的區別。 與 React Hook 不同,
setup
函數僅被調用一次,這在性能上比較占優。 - 對調用順序沒什么要求,每次渲染中不會反復調用 Hook 函數,產生的的 GC 壓力較小。
- 不必考慮幾乎總是需要 useCallback 的問題,以防止傳遞
函數prop
給子組件的引用變化,導致無必要的重新渲染。 - React Hook 有臭名昭著的閉包陷阱問題(甚至成了一道熱門面試題,omg),如果用戶忘記傳遞正確的依賴項數組,useEffect 和 useMemo 可能會捕獲過時的變量,這不受此問題的影響。 Vue 的自動依賴關系跟蹤確保觀察者和計算值始終正確無誤。
- 不得不提一句,React Hook 里的「依賴」是需要你去手動聲明的,而且官方提供了一個 eslint 插件,這個插件雖然大部分時候挺有用的,但是有時候也特別煩人,需要你手動加一行丑陋的注釋去關閉它。
我們認可 React Hooks 的創造力,這也是 Vue-Composition-Api 的主要靈感來源。上面提到的問題確實存在於 React Hook 的設計中,我們注意到 Vue 的響應式模型恰好完美的解決了這些問題。
順嘴一題,React Hook 的心智負擔是真的很嚴重,如果對此感興趣的話,請參考:
使用 react hooks 帶來的收益抵得過使用它的成本嗎? - 李元秋的回答 - 知乎 https://www.zhihu.com/question/350523308/answer/858145147
並且我自己在實際開發中,也遇到了很多問題,尤其是在我想對組件用 memo
進行一些性能優化的時候,閉包的問題爆炸式的暴露了出來。最后我用 useReducer
大法解決了其中很多問題,讓我不得不懷疑這從頭到尾會不會就是 Dan
的陰謀……(別想逃過 reducer
)
React Hook + TS 購物車實戰(性能優化、閉包陷阱、自定義 hook)
原理
既然有對比,那就從原理的角度來談一談兩者的區別,
在 Vue 中,之所以 setup
函數只執行一次,后續對於數據的更新也可以驅動視圖更新,歸根結底在於它的「響應式機制」,比如我們定義了這樣一個響應式的屬性:
Vue
<template> <div> <span>{{count}}</span> <button @click="add"> +1 </button> </div> </template> export default { setup() { const count = ref(0) const add = () => count.value++ return { count, add } } }
這里雖然只執行了一次 setup
但是 count
在原理上是個 「響應式對象」,對於其上 value
屬性的改動,
是會觸發「由 template 編譯而成的 render 函數」 的重新執行的。
如果需要在 count
發生變化的時候做某件事,我們只需要引入 effect
函數:
<template> <div> <span>{{count}}</span> <button @click="add"> +1 </button> </div> </template> export default { setup() { const count = ref(0) const add = () => count.value++ effect(function log(){ console.log('count changed!', count.value) }) return { count, add } } }
這個 log
函數只會產生一次,這個函數在讀取 count.value
的時候會收集它作為依賴,那么下次 count.value
更新后,自然而然的就能觸發 log
函數重新執行了。
仔細思考一下這之間的數據關系,相信你很快就可以理解為什么它可以只執行一次,但是卻威力無窮。
實際上 Vue3 的 Hook 只需要一個「初始化」的過程,也就是 setup
,命名很准確。它的關鍵字就是「只執行一次」。
React
同樣的邏輯在 React 中,則是這樣的寫法:
export default function Counter() { const [count, setCount] = useState(0); const add = () => setCount((prev) => prev + 1); // 下文講解用 const [count2, setCount2] = useState(0); return ( <div> <span>{count}</span> <button onClick={add}> +1 </button> </div> ); }
它是一個函數,而父組件引入它是通過 <Counter />
這種方式引入的,實際上它會被編譯成 React.createElement(Counter)
這樣的函數執行,也就是說每次渲染,這個函數都會被完整的執行一次。
而 useState
返回的 count
和 setCount
則會被保存在組件對應的 Fiber
節點上,每個 React 函數每次執行 Hook 的順序必須是相同的,舉例來說。 這個例子里的 useState
在初次執行的時候,由於執行了兩次 useState
,會在 Fiber
上保存一個 { value, setValue } -> { value2, setValue2 }
這樣的鏈表結構。
而下一次渲染又會執行 count 的 useState
、 count2 的 useState
,那么 React 如何從 Fiber
節點上找出上次渲染保留下來的值呢?當然是只能按順序找啦。
第一次執行的 useState 就拿到第一個 { value, setValue }
,第二個執行的就拿到第二個 { value2, setValue2 }
,
這也就是為什么 React 嚴格限制 Hook 的執行順序和禁止條件調用。
假如第一次渲染執行兩次 useState,而第二次渲染時第一個 useState 被 if 條件判斷給取消掉了,那么第二個 count2 的 useState
就會拿到鏈表中第一條的值,完全混亂了。
如果在 React 中,要監聽 count
的變化做某些事的話,會用到 useEffect
的話,那么下次 render
之后會把前后兩次 render
中拿到的 useEffect
的第二個參數 deps
依賴值進行一個逐項的淺對比(對前后每一項依次調用 Object.is),比如
export default function Counter() { const [count, setCount] = useState(0); const add = () => setCount((prev) => prev + 1); useEffect(() => { console.log("count updated!", count); }, [count]); return ( <div> <span>{count}</span> <button onClick={add}> +1 </button> </div> ); }
那么,當 React 在渲染后發現 count
發生了變化,會執行 useEffect
中的回調函數。(細心的你可以觀察出來,每次渲染都會重新產生一個函數引用,也就是 useEffect 的第一個參數)。
是的,React 還是不可避免的引入了 依賴
這個概念,但是這個 依賴
是需要我們去手動書寫的,實時上 React 社區所討論的「心智負擔」也基本上是由於這個 依賴
所引起的……
由於每次渲染都會不斷的執行並產生閉包,那么從性能上和 GC 壓力上都會稍遜於 Vue3。它的關鍵字是「每次渲染都重新執行」。
關於抄襲 React Hook
其實前端開源界談抄襲也不太好,一種新的模式的出現的值得框架之間相互借鑒和學習的,畢竟框架歸根結底的目的不是為了「標榜自己的特立獨行」,而是「方便廣大開發者」。這是值得思考的一點,很多人似乎覺得一個框架用了某種模式,另一個框架就不能用,其實這對於框架之間的進步和發展並沒有什么好處。
這里直接引用尤大在 17 年回應「Vue 借鑒虛擬 dom」的一段話吧:
再說 vdom。React 的 vdom 其實性能不怎么樣。Vue 2.0 引入 vdom 的主要原因是 vdom 把渲染過程抽象化了,從而使得組件的抽象能力也得到提升,並且可以適配 DOM 以外的渲染目標。這一點是借鑒 React 毫無爭議,因為我認為 vdom 確實是個好思想。但要分清楚的是 Vue 引入 vdom 不是因為『react 有所以我們也要有』,而是因為它確實有技術上的優越性。社區里基於 vdom 思想造的輪子海了去了,而 ng2 的渲染抽象層和 Ember Glimmer 2 的模板 -> opcode 編譯也跟 vdom 有很多思想上的相似性。
這段話如今用到 Hook 上還是一樣的適用,程序員都提倡開源精神,怎么到了 Vue 和 React 之間有些人又變得小氣起來了呢?說的難聽點,Vue 保持自己的特立獨行,那你假如換了一家新公司要你用 Vue,你不是又得從頭學一遍嘛。
更何況 React 社區也一樣有對 Vue 的借鑒,比如你看 react-router@6
的 api,你會發現很多地方和 vue-router
非常相似了。比如 useRoutes 的「配置式路由」,以及在組件中使子路由的代碼結構等等。當然這只是我淺顯的認知,不對的地方也歡迎指正。
擴展閱讀
對於兩種 Hook 之間的區別,想要進一步學習的同學還可以看黃子毅大大的好文:
尤小右在官方 issue 中對於 React Hook 詳細的對比看法:
Why remove time slicing from vue3?
總結
其實總結下來,社區中還是有一部分的反對觀點是由於「沒有好好看文檔」造成的,那本文中我就花費自己一些業余時間整理社區和官方的一些觀點作為一篇文章,至於看完文章以后你會不會對 Vue3 的看法有所改觀,這並不是我能決定的,只不過我很喜歡 Vue3,我也希望能夠盡自己的一點力量,讓大家能夠不要誤解它。
對於意大利面代碼:
- 提取共用的自定義 Hook(在寫 React 購物車組件的時候,我提取了 3 個以上可以全局復用的 Hook)。
- 基於「邏輯功能」去組織代碼,而不是
state
放在一塊,method
放在一塊,這樣和用 Vue2 沒什么本質上的區別(很多很多新人在用 React Hook 的時候犯這樣的錯誤,包括我自己)。
對於心智負擔:
- 更強大的能力意味着更多的學習成本,但是 Vue3 總體而言我覺得已經把心智負擔控制的很到位了。對於
ref
這個玩意,確實是需要仔細思考一下才能理解。 - React Hook 的心智負擔已經重的出名了,在我實際的開發過程中,有時候真的會被整到頭禿…… 尤其是抽了一些自定義 Hook,
deps
依賴會層層傳遞的情況下(隨便哪一層的依賴錯了,你的應用就爆炸了)。 - 不學習怎么能升職加薪,迎娶白富美,走向人生巔峰呢!(瞎扯)
Vue3 有多香呢?甚至《React 狀態管理與同構實戰》的作者、React 的忠實粉絲Lucas HC在這篇 Vue 和 React 的優點分別是什么? 中都說了這樣的一句話:
我不吐槽更多了:一個 React 粉絲向 Vue3.0 致敬!
Vue3 目前也已經有了 Hook 的一些嘗試:
https://github.com/u3u/vue-hooks
總之,希望看完這篇文章的你,能夠更加喜歡 Vue3,對於它的到來我已經是期待的不行了。
最后再次強調一下作者的立場,我對於 React 和 Vue 都非常的喜歡。他們都有着各自的優缺點,本文絕無引戰之意。兩個框架都很棒!只是各有優缺點而已。React 的 Immutable 其實也帶來了很多益處,並且 Hook 的思路還是 Facebook 團隊的大佬們首創的,真的是很讓人贊嘆的設計,我對 React 100% 致敬!
本文的唯一目的就是想消除一些朋友對於 Vue 3.0 的誤解,絕無他意,如有冒犯敬請諒解~