<dfn id="hx5t3"><strike id="hx5t3"><em id="hx5t3"></em></strike></dfn>

    <thead id="hx5t3"></thead><nobr id="hx5t3"><font id="hx5t3"><rp id="hx5t3"></rp></font></nobr>

    <listing id="hx5t3"></listing>

    <var id="hx5t3"></var>
    <big id="hx5t3"></big>

      
      

      <output id="hx5t3"><ruby id="hx5t3"></ruby></output>
      <menuitem id="hx5t3"><dfn id="hx5t3"></dfn></menuitem>

      <big id="hx5t3"></big>

        帶你入門前端工程(三):前端組件化

        譚光志

        在了解模塊化、組件化之前,最好先了解一下什么是高內聚,低耦合。它能更好的幫助你理解模塊化、組件化。

        高內聚,低耦合

        高內聚,低耦合是軟件工程中的概念,它是判斷代碼好壞的一個重要指標。高內聚,就是指一個函數盡量只做一件事。低耦合,就是兩個模塊之間的關聯程度低。

        僅看文字可能不太好理解,下面來看一個簡單的示例。

        // math.js
        export function add(a, b) {
            return a + b
        }
        
        export function mul(a, b) {
            return a * b
        }
        // test.js
        import { add, mul } from 'math'
        add(1, 2)
        mul(1, 2)
        mul(add(1, 2), add(1, 2))

        上面的 math.js 就是高內聚,低耦合的典型示例。add()、mul() 一個函數只做一件事,它們之間也沒有直接聯系。如果要將這兩個函數聯系在一起,也只能通過傳參和返回值來實現。

        既然有好的示例,那就有壞的示例,下面再看一個不好的示例。

        // 母公司
        class Parent {
            getProfit(...subs) {
                let profit = 0
                subs.forEach(sub => {
                    profit += sub.revenue - sub.cost
                })
        
                return profit
            }
        }
        
        // 子公司
        class Sub {
            constructor(revenue, cost) {
                this.revenue = revenue
                this.cost = cost
            }
        }
        
        const p = new Parent()
        const s1 = new Sub(100, 10)
        const s2 = new Sub(200, 150)
        console.log(p.getProfit(s1, s2)) // 140

        上面的代碼是一個不太好的示例,因為母公司在計算利潤時,直接操作了子公司的數據。更好的做法是,子公司直接將利潤返回給母公司,然后母公司做一個匯總。

        class Parent {
            getProfit(...subs) {
                let profit = 0
                subs.forEach(sub => {
                    profit += sub.getProfit()
                })
        
                return profit
            }
        }
        
        class Sub {
            constructor(revenue, cost) {
                this.revenue = revenue
                this.cost = cost
            }
        
            getProfit() {
                return this.revenue - this.cost
            }
        }
        
        const p = new Parent()
        const s1 = new Sub(100, 10)
        const s2 = new Sub(200, 150)
        console.log(p.getProfit(s1, s2)) // 140

        這樣改就好多了,子公司增加了一個 getProfit() 方法,母公司在做匯總時直接調用這個方法。

        高內聚,低耦合在業務場景中的運用

        理想很美好,現實很殘酷。剛才的示例是高內聚、低耦合比較經典的例子。但在業務場景中寫代碼不可能做到這么完美,很多時候會出現一個函數要處理多個邏輯的情況。

        舉個例子,用戶注冊。一般注冊會在按鈕上綁定一個點擊事件回調函數 register(),用于處理注冊邏輯。

        function register(data) {
            // 1. 驗證用戶數據是否合法
            /**
            * 驗證賬號
            * 驗證密碼
            * 驗證短信驗證碼
            * 驗證身份證
            * 驗證郵箱
            */
            // 省略一大堆串 if 判斷語句...
        
            // 2. 如果用戶上傳了頭像,則將用戶頭像轉成 base64 碼保存
            /**
            * 新建 FileReader 對象
            * 將圖片轉換成 base64 碼
            */
            // 省略轉換代碼...
        
            // 3. 調用注冊接口
            // 省略注冊代碼...
        }

        這個示例屬于很常見的需求,點擊一個按鈕處理多個邏輯。從代碼中也可以發現,這樣寫的結果就是三個功能耦合在一起。

        按照高內聚、低耦合的要求,一個函數應該盡量只做一件事。所以我們可以將函數中的另外兩個功能:驗證和轉換單獨提取出來,封裝成一個函數。

        function register(data) {
            // 1. 驗證用戶數據是否合法
            verifyUserData()
        
            // 2. 如果用戶上傳了頭像,則將用戶頭像轉成 base64 碼保存
            toBase64()
        
            // 3. 調用注冊接口
            // 省略注冊代碼...
        }
        
        function verifyUserData() {
            /**
            * 驗證賬號
            * 驗證密碼
            * 驗證短信驗證碼
            * 驗證身份證
            * 驗證郵箱
            */
            // 省略一大堆串 if 判斷語句...
        }
        
        function toBase64() {
            /**
            * 新建 FileReader 對象
            * 將圖片轉換成 base64 碼
            */
            // 省略轉換代碼...
        }

        這樣修改以后,就比較符合高內聚、低耦合的要求了。以后即使要修改或移除、新增功能,也非常方便。

        模塊化、組件化

        模塊化

        模塊化,就是把一個個文件看成一個模塊,它們之間作用域相互隔離,互不干擾。一個模塊就是一個功能,它們可以被多次復用。另外,模塊化的設計也體現了分治的思想。什么是分治?維基百科的定義如下:

        字面上的解釋是“分而治之”,就是把一個復雜的問題分成兩個或更多的相同或相似的子問題,直到最后子問題可以簡單的直接求解,原問題的解即子問題的解的合并。

        從前端方面來看,單獨的 JavaScript 文件、CSS 文件都算是一個模塊。

        例如一個 math.js 文件,它就是一個數學模塊,包含了和數學運算相關的函數:

        // math.js
        export function add(a, b) {
            return a + b
        }
        
        export function mul(a, b) {
            return a * b
        }
        
        export function abs() { ... }
        ...

        一個 button.css 文件,包含了按鈕相關的樣式:

        /* 按鈕樣式 */
        button {
            ...
        }

        組件化

        那什么是組件化呢?我們可以認為組件就是頁面里的 UI 組件,一個頁面可以由很多組件構成。例如一個后臺管理系統頁面,可能包含了 Header、Sidebar、Main 等各種組件。

        一個組件又包含了 template(html)、script、style 三部分,其中 script、style 可以由一個或多個模塊組成。

        從上圖可以看到,一個頁面可以分解成一個個組件,每個組件又可以分解成一個個模塊,充分體現了分治的思想(如果忘了分治的定義,請回頭再看一遍)。

        由此可見,頁面成為了一個容器,組件是這個容器的基本元素。組件與組件之間可以自由切換、多次復用,修改頁面只需修改對應的組件即可,大大的提升了開發效率。

        最理想的情況就是一個頁面元素全部由組件構成,這樣前端只需要寫一些交互邏輯代碼。雖然這種情況很難完全實現,但我們要盡量往這個方向上去做,爭取實現全面組件化。

        Web Components

        得益于技術的發展,目前三大框架在構建工具(例如 webpack、vite...)的配合下都可以很好的實現組件化。例如 Vue,使用 *.vue 文件就可以把 template、script、style 寫在一起,一個 *.vue 文件就是一個組件。

        <template>
            <div>
                {{ msg }}
            </div>
        </template>
        
        <script>
        export default {
            data() {
                return {
                    msg: 'Hello World!'
                }
            }
        }
        </script>
        
        <style>
        body {
            font-size: 14px;
        }
        </style>

        如果不使用框架和構建工具,還能實現組件化嗎?

        答案是可以的,組件化是前端未來的發展方向,Web Components 就是瀏覽器原生支持的組件化標準。使用 Web Components API,瀏覽器可以在不引入第三方代碼的情況下實現組件化。

        實戰

        現在我們來創建一個 Web Components 按鈕組件,點擊它將會彈出一個消息 Hello World!。點擊這可以看到 DEMO 效果。

        Custom elements(自定義元素)

        瀏覽器提供了一個 customElements.define() 方法,允許我們定義一個自定義元素和它的行為,然后在頁面中使用。

        class CustomButton extends HTMLElement {
            constructor() {
                // 必須首先調用 super方法 
                super()
        
                // 元素的功能代碼寫在這里
                const templateContent = document.getElementById('custom-button').content
                const shadowRoot = this.attachShadow({ mode: 'open' })
        
                shadowRoot.appendChild(templateContent.cloneNode(true))
        
                shadowRoot.querySelector('button').onclick = () => {
                    alert('Hello World!')
                }
            }
        
            connectedCallback() {
                console.log('connected')
            }
        }
        
        customElements.define('custom-button', CustomButton)

        上面的代碼使用 customElements.define() 方法注冊了一個新的元素,并向其傳遞了元素的名稱 custom-button、指定元素功能的類 CustomButton。然后我們可以在頁面中這樣使用:

        <custom-button></custom-button>

        這個自定義元素繼承自 HTMLElement(HTMLElement 接口表示所有的 HTML 元素),表明這個自定義元素具有 HTML 元素的特性。

        使用 <template> 設置自定義元素內容

        <template id="custom-button">
            <button>自定義按鈕</button>
            <style>
                button {
                    display: inline-block;
                    line-height: 1;
                    white-space: nowrap;
                    cursor: pointer;
                    text-align: center;
                    box-sizing: border-box;
                    outline: none;
                    margin: 0;
                    transition: .1s;
                    font-weight: 500;
                    padding: 12px 20px;
                    font-size: 14px;
                    border-radius: 4px;
                    color: #fff;
                    background-color: #409eff;
                    border-color: #409eff;
                    border: 0;
                }
        
                button:active {
                    background: #3a8ee6;
                    border-color: #3a8ee6;
                    color: #fff;
                }
              </style>
        </template>

        從上面的代碼可以發現,我們為這個自定義元素設置了內容 <button>自定義按鈕</button> 以及樣式,樣式放在 <style> 標簽里??梢哉f <template> 其實就是一個 HTML 模板。

        Shadow DOM(影子DOM)

        設置了自定義元素的名稱、內容以及樣式,現在就差最后一步了:將內容、樣式掛載到自定義元素上。

        // 元素的功能代碼寫在這里
        const templateContent = document.getElementById('custom-button').content
        const shadowRoot = this.attachShadow({ mode: 'open' })
        
        shadowRoot.appendChild(templateContent.cloneNode(true))
        
        shadowRoot.querySelector('button').onclick = () => {
            alert('Hello World!')
        }

        元素的功能代碼中有一個 attachShadow() 方法,它的作用是將影子 DOM 掛到自定義元素上。DOM 我們知道是什么意思,就是指頁面元素。那“影子”是什么意思呢?“影子”的意思就是附加到自定義元素上的 DOM 功能是私有的,不會與頁面其他元素發生沖突。

        attachShadow() 方法還有一個參數 mode,它有兩個值:

        1. open 代表可以從外部訪問影子 DOM。
        2. closed 代表不可以從外部訪問影子 DOM。
        // open,返回 shadowRoot
        document.querySelector('custom-button').shadowRoot
        // closed,返回 null
        document.querySelector('custom-button').shadowRoot

        生命周期

        自定義元素有四個生命周期:

        1. connectedCallback: 當自定義元素第一次被連接到文檔 DOM 時被調用。
        2. disconnectedCallback: 當自定義元素與文檔 DOM 斷開連接時被調用。
        3. adoptedCallback: 當自定義元素被移動到新文檔時被調用。
        4. attributeChangedCallback: 當自定義元素的一個屬性被增加、移除或更改時被調用。

        生命周期在觸發時會自動調用對應的回調函數,例如本次示例中就設置了 connectedCallback() 鉤子。

        最后附上完整代碼:

        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="utf-8">
            <title>Web Components</title>
        </head>
        <body>
            <custom-button></custom-button>
        
            <template id="custom-button">
                <button>自定義按鈕</button>
                <style>
                    button {
                        display: inline-block;
                        line-height: 1;
                        white-space: nowrap;
                        cursor: pointer;
                        text-align: center;
                        box-sizing: border-box;
                        outline: none;
                        margin: 0;
                        transition: .1s;
                        font-weight: 500;
                        padding: 12px 20px;
                        font-size: 14px;
                        border-radius: 4px;
                        color: #fff;
                        background-color: #409eff;
                        border-color: #409eff;
                        border: 0;
                    }
        
                    button:active {
                        background: #3a8ee6;
                        border-color: #3a8ee6;
                        color: #fff;
                    }
                  </style>
            </template>
        
            <script>
                class CustomButton extends HTMLElement {
                    constructor() {
                        // 必須首先調用 super方法 
                        super()
        
                        // 元素的功能代碼寫在這里
                        const templateContent = document.getElementById('custom-button').content
                        const shadowRoot = this.attachShadow({ mode: 'open' })
        
                        shadowRoot.appendChild(templateContent.cloneNode(true))
        
                        shadowRoot.querySelector('button').onclick = () => {
                            alert('Hello World!')
                        }
                    }
        
                    connectedCallback() {
                        console.log('connected')
                    }
                }
        
                customElements.define('custom-button', CustomButton)
            </script>
        </body>
        </html>

        小結

        用過 Vue 的同學可能會發現,Web Components 標準和 Vue 非常像。我估計 Vue 在設計時有參考過 Web Components(個人猜想,未考證)。

        如果你想了解更多 Web Components 的信息,請參考 MDN 文檔。

        參考資料

        帶你入門前端工程 全文目錄:

        1. 技術選型:如何進行技術選型?
        2. 統一規范:如何制訂規范并利用工具保證規范被嚴格執行?
        3. 前端組件化:什么是模塊化、組件化?
        4. 測試:如何寫單元測試和 E2E(端到端) 測試?
        5. 構建工具:構建工具有哪些?都有哪些功能和優勢?
        6. 自動化部署:如何利用 Jenkins、Github Actions 自動化部署項目?
        7. 前端監控:講解前端監控原理及如何利用 sentry 對項目實行監控。
        8. 性能優化(一):如何檢測網站性能?有哪些實用的性能優化規則?
        9. 性能優化(二):如何檢測網站性能?有哪些實用的性能優化規則?
        10. 重構:為什么做重構?重構有哪些手法?
        11. 微服務:微服務是什么?如何搭建微服務項目?
        12. Severless:Severless 是什么?如何使用 Severless?
        閱讀 1.2k
        5.6k 聲望
        10.3k 粉絲
        0 條評論
        你知道嗎?

        5.6k 聲望
        10.3k 粉絲
        宣傳欄
        一本到在线是免费观看_亚洲2020天天堂在线观看_国产欧美亚洲精品第一页_最好看的2018中文字幕