# "可插拔式"組件設計,領略組件開發的奧祕

從一個 Confirm 組件開始,一步步寫一個可插拔式的組件。

處理一個正常的支付流程(比如支付寶購買基金)

  1. 點擊購買按鈕
  2. 如果風險等級不匹配則:彈確認框(Confirm)
  3. 用戶確認風險後:彈出支付方式選擇彈窗(Dialog)
  4. 選擇好支付方式後:彈窗調用指紋驗證(Dialog)
  5. 如果關閉指紋驗證:提示是否輸入密碼(Dialog)
  6. 彈出輸入密碼的鍵盤(自定義鍵盤)
  7. 當然還有密碼加班
  8. 如果密碼輸入錯誤則彈出修改/重試提示(Confirm)
  9. ...再次彈出鍵盤

大約6個彈窗...

地攤貨(精簡版)

首先嚐試以一個平常的註冊組件實現

Confirm 通過 v-model="isShow" 切換展示,通過 @onConfirmonCancel 接收點擊事件。

組件代碼

    <template>
        <div v-if="value">
            <slot></slot>
            <div>
                <div @click="cancelHandler">{{cancelTxt}}</div>
                <div @click="confirmHandler">{{confirmTxt}}</div>
            </div>
        </div>
    </template>

    <script>
    export default {
        props: {
            value: {
                type: Boolean,
                default: false,
            }
        },
        data() {
            return {
                content: '',
                confirmTxt: '',
                cancelTxt: '',
            }
        },
        methods: {
            close() {
                this.$emit('input');
            },
            cancelHandler() {
                this.$emit('onCancel');
            },
            confirmHandler() {
                this.$emit('onConfirm');
            }
        }
    }
    </script>

使用代碼

    <confirm
        v-model="isConfirmShow"
        @onCancel="onCancel"
        @onConfirm="onConfirm"
    >內容部分</confirm>

那麼用它來完成上面的需求吧。

    openRiskConfirm() {
        this.isRiskConfirmShow = true;
    },
    onRiskCancel() {
        this.isRiskConfirmShow = false;
    },
    onRiskConfirm() {
        // something
        this.openPaymeList();
    },
    openPaymeList() {
        this.isPaymentListShow = ture;
    }
    // ... 巴拉巴拉
    // ... 大約需要 3*6 = 18 個方法才能完成需求(其他請求類的還不算)

如果你能接受,但是:

那麼萬一監管放鬆了,不需要校驗風險了呢?或者一開始沒有校驗風險,監管突然要校驗風險了呢?又或者不在 app 上使用,不用調用指紋呢?又或者要添加一個 人臉識別功能了呢?

代碼改起來會是一個災難,因爲就算業務代碼是你寫的,你一段時間後也不一定能記得流程,而且,代碼看起來沒有任何的連續性,只能一個個方法看。

流行款(精簡版)

針對上面的業務流程,嘗試使用現在比較流行的的彈窗。

組件:更改接收方法位置,從 props 放到 $data

    <template>
        <div>
            <div>{{content}}</div>
            <div>
                <div @click="cancelHandler">{{cancelTxt}}</div>
                <div @click="confirmHandler">{{confirmTxt}}</div>
            </div>
        </div>
    </template>

    <script>
    export default {
        data() {
            return {
                content: '',
                confirmTxt: '',
                cancelTxt: '',
                onConfirm: function() {},
                onCancel: function() {},
            }
        },
        methods: {
            uninstall() {
                this.$destroy(true);
                this.$el.parentNode.removeChild(this.$el);
            },
            cancelHandler() {
                (typeof this.onCancel === 'function') && this.onCancel()
                this.uninstall();
            },
            confirmHandler() {
                (typeof this.onConfirm === 'function') && this.onConfirm()
                this.uninstall();
            }
        }
    }
    </script>

註冊到全局

    import confirm from './confirm.vue'

    export default {
        install: function(Vue) {
            const Profile = Vue.extend(confirm);
            
            const PortfolioMsg = (options) => {
                let $ele = document.createElement("div");
                document.body.appendChild($ele);
                new Profile({
                    data() {
                        return options;
                    }
                }).$mount($ele);
            };

            Vue.prototype.$confirm = PortfolioMsg;
        }
    }

調用

    this.$confirm({
        content: '內容',
        confirmTxt: '確定',
        cancelTxt: '取消',
        onConfirm: () => {
            console.log('確定')
        },
        onCancel: () => {
            console.log('取消')
        }
    })

哪啊麼用它完成上面的需求會如何?

    this.$confirm({
        content: '風險認證',
        cancelTxt: '再看看',
        confirmTxt: '同意',
        onConfirm: () => {
            // something
            this.$dialog({
                content: '指紋認證',
                slot: `<div>指紋認證</div>`,
                onFinish: () => {
                    // 支付 成功? 失敗?
                    // something
                },
                onCancel: () => {
                    // something
                    this.$confirm({
                        content: '密碼認證',
                        cancelTxt: '取消',
                        confirmTxt: '確定',
                        onConfirm: () => {
                            // something
                            this.$keyboard({
                                // 略
                                onFinish: (password) => {
                                    // 密碼加密
                                    // something
                                    if (/* 密碼錯誤? */) {
                                        // 重複了
                                        // 這個代碼就可以抽象成一個方法
                                        this.$confirm({
                                            content: '密碼認證',
                                            cancelTxt: '取消',
                                            confirmTxt: '確定',
                                            // 略
                                        })
                                    }
                                }
                            })
                        },
                        onCancel: () => {
                            // 取消
                        }
                    })
                }
            })
        },
        onCancel: () => {
            // 取消
        }
    })

這樣看起來確實清晰了很多,代碼量也少了很多,不需要註冊全局的組件可以通過在 methods 中封裝一個方法實現,維護起來也方便了很多。但是:回調地獄有木有?也只是稍微輕鬆一點,可不可以再優化一下呢?

抽象版

ajax 的回調地獄是通過 Promise 實現的,那麼上面的組件回調地獄是不是也可以通過 Promise 實現呢?

import confirm from './confirm.vue'

export default {
    install: function(Vue) {
        const Profile = Vue.extend(confirm);
        
        const PortfolioMsg = (options) => {
            let $ele = document.createElement("div");
            document.body.appendChild($ele);
            const profile = new Profile({
                data() {
                    return options;
                }
            }).$mount($ele);
            
            return new Promise((resolve, reject) => {
                profile.$on('onConfirm', resolve)
                profile.$on('onCancel', reject)
            })
        };

        Vue.prototype.$confirm = PortfolioMsg;
    }
}

使用一下

    this.$confirm({
        confirmTxt: '確定'
    }).then(res => {
        console.log('點擊了確定')
    }).catch(res => {
        console.log('點擊了取消')
    })

那麼回調地獄的問題很輕鬆的就解決了,可讀性很高,中間添加刪除邏輯也變的特別方便,維護起來成本大大的降低了。具體代碼自己抽象一遍或許更好哦。

大家其他的封裝方法嗎?請留言哈

最後

譯者寫了一個 React + Hooks 的 UI 庫,方便大家學習和使用,

React + Hooks 項目實戰

歡迎關注公衆號「前端進階課」認真學前端,一起進階。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章