溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何寫出高質量react及vue組件

發(fā)布時間:2022-07-27 11:41:08 來源:億速云 閱讀:93 作者:iii 欄目:開發(fā)技術

這篇文章主要講解了“如何寫出高質量react及vue組件”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“如何寫出高質量react及vue組件”吧!

概述

vue和react都已經全面進入了hooks時代(在vue中也稱為組合式api,為了方便后面統(tǒng)一稱為hooks),然而受到以前react中類組件和vue2寫法的影響,很多開發(fā)者都不能及時轉換過來,以致于開發(fā)出一堆面條式代碼,整體的代碼質量反而不如改版以前了。

hooks組件到底應該如何寫,我也曾為此迷惘過一段時間。特別我以前以react開發(fā)居多,但在轉到新崗位后又變成了使用vue3開發(fā),對于兩個框架在思維方式和寫法的不同上,很是花了一段時間適應。好在幾個月下來,我發(fā)現(xiàn)二者雖然在寫法上有區(qū)別之處,但思想上卻大同小異。

所以在比較了兩個框架的異同后,我總結出了一套通用的hooks api的抽象方式,在這里分享給大家。如果您有不同意見歡迎在評論區(qū)指正。

一個組件內部的所有代碼——無論vue還是react——都可以抽象成以下幾個部分:

  • 組件視圖,組件中用來描述視覺效果的部分,如css和html、react的jsx或者vue的template代碼

  • 組件相關邏輯,如組件生命周期,按鈕交互,事件等

  • 業(yè)務相關邏輯,如登錄注冊,獲取用戶信息,獲取商品列表等與組件無關的業(yè)務抽象

單獨拆分這三塊并不難,難的是一個組件可能寫得特別復雜,里面可能包含了多個視圖,每個視圖相互之間又有交互;同時又可能包含多個業(yè)務邏輯,多個業(yè)務的函數(shù)和變量雜亂無章地隨意放置,導致后續(xù)維護的時候要在代碼之間反復橫跳。

要寫出高質量的組件,可以思考以下幾個問題:

1.組件什么時候拆?怎么拆?

一個常見的誤區(qū)是,只有需要復用的時候才去拆分組件,這種看法顯然過于片面了。你可以思考一下,自己是如何抽象一個函數(shù)的,你只會在代碼需要復用的時候才抽出一個函數(shù)嗎?顯然不是。因為函數(shù)不僅有代碼復用的功能,還具有一定的描述性質以及代碼封閉性。這種特性使得我們看到一個函數(shù)的時候,不必關注代碼細節(jié),就能大概知道這部分代碼是干啥的。

我們還可以再用函數(shù)將一部分函數(shù)組合起來,形成更高層級的抽象。按國內流行的說法,高層級的抽象被稱為粗粒度,低層級的抽象被稱為細粒度,不同粗細粒度的抽象可以稱它們?yōu)椴煌?strong>抽象層級。并且一個理想的函數(shù)內部,一般只會包含同一抽象層級的代碼。

組件的拆分也可以遵循同樣的道理。我們可以按照當前的結構或者功能、業(yè)務,將組件拆分為功能清晰且單一、與外部耦合程度低的組件(即所謂高內聚,低耦合)。如果一個組件里面干了太多事,或者依賴的外部狀態(tài)太多,那么就不是一個容易維護的組件了。

如何寫出高質量react及vue組件

然而,為了保持組件功能單一,我們是不是要將組件拆分得特別細才可以呢?事實并非如此。因為上面說過,抽象是有粗細粒度之分的,也許一個組件從較細的粒度來講功能并不單一,但是從較粗的粒度來說,可能他們的功能就是單一的了。例如登錄和注冊是兩個不同的功能,但是你從更高層級的抽象來看,它們都屬于用戶模塊的一部分。

所以是否要拆分組件,最關鍵還是得看復雜度。如果一個頁面特別簡單,那么不進行拆分也是可以,有時候拆分得過于細可能反而不利于維護。

如何判斷一個組件是否復雜?恐怕這里不能給出一個準確的答案,畢竟代碼的實現(xiàn)方式千奇百怪,很難有一個機械的標準評判。但是我們不妨站在第三方角度看看自己的代碼,如果你是一個工作一年的程序員,是否能比較容易地看懂這里的代碼?如果不能就要考慮進行拆分了。如果你非要一個機械的判斷標準,我建議是代碼控制在200行內。

總結一下,拆分組件的時候可以參考下面幾個原則:

  • 拆分的組件要保持功能單一。即組件內部代碼的代碼都只跟這個功能相關;

  • 組件要保持較低的耦合度,不要與組件外部產生過多的交互。如組件內部不要依賴過多的外部變量,父子組件的交互不要搞得太復雜等等。

  • 用組件名準確描述這個組件的功能。就像函數(shù)那樣,可以讓人不用關心組件細節(jié),就大概知道這個組件是干嘛的。如果起名比較困難,考慮下是不是這個組件的功能并不單一。

2.如何組織拆分出的組件文件?

拆分出來的組件應該放在哪里呢?一個常見的錯誤做法是一股腦放在一個名為components文件夾里,最后搞得這個文件夾特別臃腫。我的建議是相關聯(lián)的代碼最好盡量聚合在一起。

為了讓相關聯(lián)的代碼聚合到一起,我們可以把頁面搞成文件夾的形式,在文件夾內部存放與當前文件相關的組成部分,并將表示頁面的組件命名為index放在文件夾下。再在該文件夾下創(chuàng)建components目錄,將組成頁面的其他組件放在里面。

如果一個頁面的某個組成部分很復雜,內部還需要拆分成更細的多個組件,那么就把這個組成部分也做成文件夾,將拆分出的組件放在這個文件夾下。

最后就是組件復用的問題。如果一個組件被多個地方復用,就把它單獨提取出來,放到需要復用它的組件們共同的抽象層級上。 如下:

  • 如果只是被頁面內的組件復用,就放到頁面文件夾下。

  • 如果只是在當前業(yè)務場景下的不同頁面復用,就放到當前業(yè)務模塊的文件夾下。

  • 如果可以在不同業(yè)務場景間通用,就放到最頂層的公共文件夾,或者考慮做成組件庫。

關于項目文件的組織方式已經超過本文討論的范疇,我打算放到以后專門出一篇文章說下如何組織項目文件。這里只說下頁面級別的文件如何進行組織。下面是我常用的一種頁面級別的文件的組織方式:

homePage // 存放當前頁面的文件夾
    |-- components // 存放當前頁面組件的文件夾
        |-- componentA // 存放當前頁面的組成部分A的文件夾
            |-- index.(vue|tsx) // 組件A
            |-- AChild1.(vue|tsx) // 組件a的組成部分1
            |-- AChild2.(vue|tsx) // 組件a的組成部分2
            |-- ACommon.(vue|tsx) // 只在componentA內部復用的組件
        |-- ComponentB.(vue|tsx) // 當前頁面的組成部分B
        |-- Common.(vue|tsx) // 組件A和組件B里復用的組件
    |-- index.(vue|tsx) // 當前頁面

實際上這種組織方式,在抽象意義上并不完美,因為通用組件和頁面組成部分的組件并沒有區(qū)分開來。但是一般來說,一個頁面也不會抽出太多組件,為了方便放到一起也不會有太大問題。但是如果你的頁面實在復雜,那么再創(chuàng)建一個名為common的文件夾也未嘗不可。

3.如何用hooks抽離組件邏輯?

在hooks出現(xiàn)之前,曾流行過一個設計模式,這個模式將組件分為無狀態(tài)組件和有狀態(tài)組件(也稱為展示組件和容器組件),前者負責控制視覺,后者負責傳遞數(shù)據(jù)和處理邏輯。但有了hooks之后,我們完全可以將容器組件中的代碼放進hooks里面。后者不僅更容易維護,而且也更方便把業(yè)務邏輯與一般組件區(qū)分開來。

在抽離hooks的時候,我們不僅應該沿用一般函數(shù)的抽象思維,如功能單一,耦合度低等等,還應該注意組件中的邏輯可分為兩種:組件交互邏輯與業(yè)務邏輯。如何把文章開頭說的視圖、交互邏輯和業(yè)務邏輯區(qū)分開來,是衡量一個組件質量的重要標準。

以一個用戶模塊為例。一個包含查詢用戶信息,修改用戶信息,修改密碼等功能的hooks可以這樣寫:

// 用戶模塊hook
const useUser = () => {
    // react版本的用戶狀態(tài)
    const user = useState({});
    // vue版本的用戶狀態(tài)
    const userInfo = ref({});
    // 獲取用戶狀態(tài)
    const getUserInfo = () => {}
    // 修改用戶狀態(tài)
    const changeUserInfo = () => {};
    // 檢查兩次輸入的密碼是否相同
    const checkRepeatPass = (oldPass,newPass) => {}
    // 修改密碼
    const changePassword = () => {};
    return {
        userInfo,
        getUserInfo,
        changeUserInfo,
        checkRepeatPass,
        changePassword,
    }
}

交互邏輯的hook可以這么寫(為了方便只寫vue版本的,大家應該也都看得懂):

// 用戶模塊交互邏輯hooks
const useUserControl = () => {
    // 組合用戶hook
    const { userInfo, getUserInfo, changeUserInfo, checkRepeatPass, changePassword } = useUser();
    // 數(shù)據(jù)查詢loading狀態(tài)
    const loading = ref(false);
    // 錯誤提示彈窗的狀態(tài)
    const errorModalState = reactive({
        visible: false, // 彈窗顯示/隱藏
        errorText: '',  // 彈窗文案
    });
    // 初始化數(shù)據(jù)
    const initData = () => {
        getUserInfo();
    }
    // 修改密碼表單提交
    const onChangePassword = ({ oldPass, newPass ) => {
        // 判斷兩次密碼是否一致
        if (checkRepeatPass(oldPass, newPass)) {
            changePassword();
        } else {
            errorModalState.visible = true;
            errorModalState.text = '兩次輸入的密碼不一致,請修改'
        }
    };
    return {
        // 用戶數(shù)據(jù)
        userInfo,
        // 初始化數(shù)據(jù)
        initData: getUserInfo,
        // 修改密碼
        onChangePassword,
        // 修改用戶信息
        onChangeUserInfo: changeUserInfo,
    }
}

然后只要在組件里面引入交互邏輯的hook即可:

vue版本:

<template>
    <!-- 視圖部分省略,在對應btn處引用onChangePassword和onChangeUserInfo即可 -->
</template>
<script setup>
import useUserControl from './useUserControl';
import { onMounted } from 'vue';
const { userInfo, initData, onChangePassword, onChangeUserInfo } = useUserControl();
onMounted(initData);
<script>

react版本:

import useUserControl from './useUserControl';
import { useEffect } from 'react';
const UserModule = () => {
    const { userInfo, initData, onChangePassword, onChangeUserInfo } = useUserControl();
    useEffect(initData, []);
    return (
        // 視圖部分省略,在對應btn處引用onChangePassword和onChangeUserInfo即可
    )
}

而拆分出的三個文件放在組件同級目錄下即可;如果拆出的hooks較多,可以單獨開辟一個hooks文件夾。如果有可以復用的hooks,參考組件拆分里面分享的方法,放到需要復用它的組件們共同的抽象層級上即可

可以看到抽離出hooks邏輯后,組件變得十分簡單、容易理解,我們也實現(xiàn)了各個部分的分離。不過這里還有一個問題,那就是上面的業(yè)務場景實在太過簡單,有必要拆分得這么細,搞出三個文件這么復雜嗎?

針對邏輯并不復雜的組件,我個人覺得和組件放到一起也未嘗不可。為了簡便,我們可以只把業(yè)務邏輯封裝成hooks,而組件的交互邏輯就直接放在組件里面。如下:

<template>
    <!-- 視圖部分省略,在對應btn處引用changePassword和changeUserInfo即可 -->
</template>
<script setup>
import { onMounted } from 'vue';
// 用戶模塊hook
const useUser = () => { 
    // 代碼省略
}
const { userInfo, getUserInfo, changeUserInfo, checkRepeatPass, changePassword } = useUser();
// 數(shù)據(jù)查詢loading狀態(tài)
const loading = ref(false);
// 錯誤提示彈窗的狀態(tài)
const errorModalState = reactive({
    visible: false, // 彈窗顯示/隱藏
    errorText: '', // 彈窗文案
});
// 初始化數(shù)據(jù)
const initData = () => { getUserInfo(); }
// 修改密碼表單提交
const onChangePassword = ({ oldPass, newPass ) => {};
onMounted(initData);
<script>

但是如果邏輯比較復雜,或者一個組件里面包含多個復雜業(yè)務或者復雜交互,需要抽離出多個hooks的情況,還是單獨抽出一個個文件比較好。總而言之,依據(jù)代碼復雜度,選擇相對更容易理解的寫法。

也許單獨一個組件,你并不能體會出hooks寫法的優(yōu)越性。但當你封裝出更多的hooks之后,你會逐漸發(fā)現(xiàn)這樣寫的好處。正因為不同的業(yè)務和功能被封裝在一個個hooks里面,彼此互不干擾,業(yè)務才能更容易區(qū)分和理解。對于項目的可維護性和可讀性提升是非常之大的。

下圖展示了vue2寫法和vue3 hooks寫法的區(qū)別。圖中相同顏色的代碼塊代表這些代碼是屬于同一個功能的,但vue2的寫法導致本來是相同功能的代碼,卻被拆散到了不同地方(react其實也容易有相同的問題,例如當一個組件有多個功能時,不同功能的代碼也很容易混雜到一起)。而通過封裝成一個個hooks,相關聯(lián)的代碼就很容易被聚合到了一起,且和其他功能區(qū)分開了。

如何寫出高質量react及vue組件

感謝各位的閱讀,以上就是“如何寫出高質量react及vue組件”的內容了,經過本文的學習后,相信大家對如何寫出高質量react及vue組件這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI