国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

從零開(kāi)始的 React 組件開(kāi)發(fā)之路 (一):表格篇

DesGemini / 3422人閱讀

摘要:下的表格狂想曲前言歡迎大家閱讀從零開(kāi)始的組件開(kāi)發(fā)之路系列第一篇,表格篇。北京小李中的每一個(gè)元素是一列的配置,也是一個(gè)對(duì)象,至少應(yīng)該包括如下幾部分表頭該列使用行中的哪個(gè)進(jìn)行顯示易用性與通用性的平衡易用性與通用性互相制衡,但并不是絕對(duì)矛盾。

React 下的表格狂想曲 0. 前言

歡迎大家閱讀「從零開(kāi)始的 React 組件開(kāi)發(fā)之路」系列第一篇,表格篇。本系列的特色是從 需求分析、API 設(shè)計(jì)和代碼設(shè)計(jì) 三個(gè)遞進(jìn)的過(guò)程中,由簡(jiǎn)到繁地開(kāi)發(fā)一個(gè) React 組件,并在講解過(guò)程中穿插一些 React 組件開(kāi)發(fā)的技巧和心得。

為什么從表格開(kāi)始呢?在企業(yè)系統(tǒng)中,表格是最常見(jiàn)但功能需求最豐富的組件之一,同時(shí)也是基于 React 數(shù)據(jù)驅(qū)動(dòng)的思想受益最多的組件之一,十分具有代表性。這篇文章也是近期南京谷歌開(kāi)發(fā)者大會(huì)前端專(zhuān)場(chǎng)的分享總結(jié)。UXCore table 組件 Demo 也可以和本文行文思路相契合,可做參考。

1. 一個(gè)簡(jiǎn)單 React 表格的構(gòu)造 1.1 需求分析

有表頭,每行的展示方式相同,只是數(shù)據(jù)上有所不同

每一列可能有不同的對(duì)齊方式,可能有不同的展示類(lèi)型,比如金額,比如手機(jī)號(hào)碼等

1.2 API 設(shè)計(jì)

因?yàn)槊恳涣械恼故绢?lèi)型不同,因此列配置應(yīng)該作為一個(gè) Prop,由于有多列應(yīng)該是一個(gè)數(shù)組

數(shù)據(jù)源應(yīng)該作為基礎(chǔ)配置之一,應(yīng)該作為一個(gè) prop,由于有多行也應(yīng)該是一個(gè)數(shù)組

現(xiàn)在的樣子:

基本思路是通過(guò)遍歷列配置來(lái)生成每一行

data 中的每一個(gè)元素應(yīng)該是一行的數(shù)據(jù),是一個(gè) hash 對(duì)象。

{
    city: "北京",
    name: "小李"
}

columns 中的每一個(gè)元素是一列的配置,也是一個(gè) hash 對(duì)象,至少應(yīng)該包括如下幾部分:

{
    title: "表頭",
    dataKey: "city", // 該列使用行中的哪個(gè) key 進(jìn)行顯示
}

易用性與通用性的平衡

易用性與通用性互相制衡,但并不是絕對(duì)矛盾。

何為易用?使用盡量少的配置來(lái)完成最典型的場(chǎng)景。

何為通用?提供盡量多的定制接口已適應(yīng)各種不同場(chǎng)景。

在 API 設(shè)計(jì)上盡量開(kāi)放保證通用性

在默認(rèn)值上提煉最典型的場(chǎng)景提高易用性。

從易用性角度出發(fā)

{
    align: "left", // 默認(rèn)左對(duì)齊
    type: "money/action", // 提供 "money", "card", "cnmobile" 等常用格式化形式
    delimiter: ",", // 格式化時(shí)的分隔符,默認(rèn)是空格
    actions: { // 表格中常見(jiàn)的操作列,不以數(shù)據(jù)進(jìn)行渲染,只包含動(dòng)作,hash 對(duì)象使配置最簡(jiǎn)化
      "編輯": function() {doEdit();}
    }, 
}

從通用性角度出發(fā)

{
    actions: [ // 相對(duì)繁瑣,但定制能力更強(qiáng)
      {
          title: "編輯",
          callback: function() {doEdit();},
          render: function(rowData) {
              // 根據(jù)當(dāng)前行數(shù)據(jù),決定是否渲染,及渲染成定制的樣子
          }
      }
    ],
    render: function(cellData, rowData) {
        // 根據(jù)當(dāng)前行數(shù)據(jù),完全由用戶(hù)決定如何渲染
        return {`${rowData.city} - ${rowData.name}`}
    }
}

提供定制化渲染的兩種方式:

渲染函數(shù) (更推薦)

{
    render: function(rowData) {
        return 
    },
}

渲染組件

{
    renderComp: , // 內(nèi)部接收 rowData 作為參數(shù)
}

推薦渲染函數(shù)的原因:

函數(shù)在做屬性比較時(shí),更簡(jiǎn)單

約定更少,渲染組件的方式需要配合 Table 預(yù)留比如 rowData 一類(lèi)的接口,不夠靈活。

1.3 代碼設(shè)計(jì)

圖:Table 的分層設(shè)計(jì)

圖:最初的 Table 結(jié)構(gòu),詳細(xì)的分層為后續(xù)的功能擴(kuò)展做好準(zhǔn)備。

2. 加入更多的內(nèi)置功能

目前的表格可以滿(mǎn)足我們的最簡(jiǎn)單常用的場(chǎng)景,但仍然有很多經(jīng)常需要使用的功能沒(méi)有支持,如列排序,分頁(yè),搜索過(guò)濾、常用動(dòng)作條、行選擇和行篩選等。

2.1 需求分析

列排序:升序/降序/默認(rèn)順序 Head/Cell 相關(guān)

分頁(yè):當(dāng)表格需要展示的條數(shù)很多時(shí),分頁(yè)展示固定的條數(shù) Table/Pagination 相關(guān),這里假設(shè)已有 Pagination 組件

搜索過(guò)濾:Table 相關(guān)

常用操作:Table 相關(guān)

行選擇:選中某些行,Row/Cell 相關(guān)

行篩選:手動(dòng)展示或者隱藏一些行,不屬于任何一列,因此是 Table 級(jí)

2.2 API 設(shè)計(jì)

根據(jù)上面對(duì)于功能的需求分析,我們很容易定位 API 的位置,完成相應(yīng)的擴(kuò)展。

// table 配置,需求對(duì)應(yīng)的模塊對(duì)應(yīng)了他的配置在整個(gè)配置中的位置
{
    columns: [ // HEAD/ROW 相關(guān)
        {
            order: true, // 是否展示排序按鈕
            hidden: false, // 是否隱藏,行篩選需要
        }
    ],
    onOrder: function (activeColumn, order) { // 排序時(shí)的回調(diào)
        doOrder(activeColumn, order)
    }, 
    actionBar: { // 常用操作條
        "打印": function() {doPrint()}, 
    },
    showSeach: true, // 是否顯示搜索過(guò)濾,為什么不直接用下面的,這里也是設(shè)計(jì)上的一個(gè)優(yōu)化點(diǎn)
    onSearch: function(keyword) { doSearch(keyword) }, // 搜索時(shí)的回調(diào)
    showPager: true, // 是否顯示分頁(yè)
    onPagerChange: function(current, pageSize) {}, // 分頁(yè)改變時(shí)的回調(diào)
    rowSelection: { // 行選擇相關(guān)
        onSelect: function(isSelected, currentRow, selectedRows) { 
            doSelect() 
        }
    }
}
// data 結(jié)構(gòu)
{
    data: [{
        city: "xxx",
        name: "xxx",
        __selected__: true, // 行選擇相關(guān),用以標(biāo)記該行是否被選中,用前后的 __ 來(lái)做特殊標(biāo)記,另一方面也盡可能避免與用戶(hù)的字段重復(fù)
    }],
    currentPage: 1, // 當(dāng)前頁(yè)數(shù)
    totalCount: 50, // 總條數(shù)
}
2.3 代碼設(shè)計(jì)結(jié)構(gòu)圖

圖:擴(kuò)展后的 Table 結(jié)構(gòu)

內(nèi)部數(shù)據(jù)的處理

目前組件的數(shù)據(jù)流向還比較簡(jiǎn)單,我們似乎可以全部通過(guò) props 來(lái)控制狀態(tài),制作一個(gè) stateless 的組件。

何時(shí)該用 state?何時(shí)該用 props?

UI=fn(state, props), 人們常說(shuō) React 組件是一個(gè)狀態(tài)機(jī),但我們應(yīng)該清楚的是他是由 state 和 props 構(gòu)成的雙狀態(tài)機(jī);

props 和 state 的改變都會(huì)觸發(fā)組件的重新渲染,那么我們使用它們的時(shí)機(jī)分別是什么呢?由于 state 是組件自身維護(hù)的,并不與他的父級(jí)組件進(jìn)行溝通,進(jìn)而也無(wú)法與他的兄弟組件進(jìn)行溝通,因此我們應(yīng)該盡量只在頁(yè)面的根節(jié)點(diǎn)組件或者復(fù)雜組件的根節(jié)點(diǎn)組件使用 state,而在其他情況下盡量只使用 props,這可以增強(qiáng)整個(gè) React 項(xiàng)目的可預(yù)知性和可控性。

但凡事不是絕對(duì)的,全都使用 Props 固然可以使組件可維護(hù)性變強(qiáng),但全部交給用戶(hù)來(lái)操作會(huì)使用戶(hù)的使用成本大大提高,利用 state,我們可以讓組件自己維護(hù)一些狀態(tài),從而減輕用戶(hù)使用的負(fù)擔(dān)。

我們舉個(gè)簡(jiǎn)單的例子

{/* 受控模式 */}

{/* 非受控模式 */}

value 配置時(shí),input 的值由 value 控制,value 沒(méi)有配置時(shí),input 的值由自己控制,如果把 看做一個(gè)組件,那么此時(shí)可以認(rèn)為 input 此時(shí)有一個(gè) state 是 value。顯然,無(wú) value 狀態(tài)下的配置更少,降低了使用的成本,我們?cè)谧鼋M件時(shí)也可以參考這種模式。

例如在我們希望為用戶(hù)提供 行選擇 的功能時(shí),用戶(hù)通常是不希望自己去控制行的變化的,而只是關(guān)心行的變化時(shí)應(yīng)該拿取的數(shù)據(jù),此時(shí)我們就可以將 data 這個(gè) prop 變成 state。有一點(diǎn)需要注意的是,用戶(hù)的 prop

class Table extends React.Component {
    constructor(props) {
        super(props);
        this.data = deepcopy(props.data);
        this.state = {
            data: this.data,
        };
    }
    
    /**
     * 在 data 發(fā)生改變時(shí),更改對(duì)應(yīng)的 state 值。
     */
    componentWillReceiveProps(nextProps, nextState) {
        if (!deepEqual(nextProps.data, this.data) {
            this.data = deepcopy(nextProps.data);
            this.setState({
                data: this.data,
            });
        }
    }
}

這里涉及的一個(gè)很重要的點(diǎn),就是如何處理一個(gè)復(fù)雜類(lèi)型數(shù)據(jù)的 prop 作為 state。因?yàn)?JS 對(duì)象傳地址的特性,如果我們直接對(duì)比 nextProps.datathis.props.data 有些情況下會(huì)永遠(yuǎn)相等(當(dāng)用戶(hù)直接修改 data 的情況下),所以我們需要對(duì)這個(gè) prop 做一個(gè)備份。

生命周期的使用時(shí)機(jī)

圖:React 的生命周期

constructor: 盡量簡(jiǎn)潔,只做最基本的 state 初始化

willMount: 一些內(nèi)部使用變量的初始化

render: 觸發(fā)非常頻繁,盡量只做渲染相關(guān)的事情。

didMount: 一些不影響初始化的操作應(yīng)該在這里完成,比如根據(jù)瀏覽器不同進(jìn)行操作,獲取數(shù)據(jù),監(jiān)聽(tīng) document 事件等(server render)。

willUnmount: 銷(xiāo)毀操作,銷(xiāo)毀計(jì)時(shí)器,銷(xiāo)毀自己的事件監(jiān)聽(tīng)等。

willReceiveProps: 當(dāng)有 prop 做 state 時(shí),監(jiān)聽(tīng) prop 的變化去改變 state,在這個(gè)生命周期里 setState 不會(huì)觸發(fā)兩次渲染。

shouldComponentUpdate: 手動(dòng)判斷組件是否應(yīng)該更新,避免因?yàn)轫?yè)面更新造成的無(wú)謂更新,組件的重要優(yōu)化點(diǎn)之一。

willUpdate: 在 state 變化后如果需要修改一些變量,可以在這里執(zhí)行。

didUpdate: 與 didMount 類(lèi)似,進(jìn)行一些不影響到 render 的操作,update 相關(guān)的生命周期里最好不要做 setState 操作,否則容易造成死循環(huán)。

父子級(jí)組件間的通信

父級(jí)向子級(jí)通信不用多說(shuō),使用 prop 進(jìn)行傳遞,那么子級(jí)向父級(jí)通信呢?有人會(huì)說(shuō),靠回調(diào)啊~ onChange等等,本質(zhì)上是沒(méi)有錯(cuò)誤的,但當(dāng)組件比較復(fù)雜,存在多級(jí)結(jié)構(gòu)時(shí),如果每一級(jí)都去處理他的子級(jí)的回調(diào)的話(huà),不僅寫(xiě)起來(lái)非常麻煩,而且很多時(shí)候是沒(méi)有意義的。

我們采取的辦法是,只在頂級(jí)組件也就是 Table 這一層控制所有的 state,其他的各個(gè)子層都是完全由 prop 來(lái)控制,這樣一來(lái),我們只需要 Table 去操作數(shù)據(jù),那么我們逐級(jí)向下傳遞一個(gè)屬于 Table 的回調(diào)函數(shù),完成所有子級(jí)都只向 Table 做“匯報(bào)”,進(jìn)行跨級(jí)通信。

圖:父子級(jí)間的通信

3. 自行獲取數(shù)據(jù)3.1 需求分析

作為一個(gè)盡可能為用戶(hù)提高效率的組件,除了手動(dòng)傳入 data 外,我們也應(yīng)該有自行獲取數(shù)據(jù)的能力,用戶(hù)只需要配置 url 和相應(yīng)的參數(shù)就可以完成表格的配置,為此我們可能需要以下參數(shù):

數(shù)據(jù)源,返回的數(shù)據(jù)格式應(yīng)和我們之前定義的 data 數(shù)據(jù)結(jié)構(gòu)一致。 (易用)

隨請(qǐng)求一起發(fā)出去的參數(shù)。(通用)

在發(fā)請(qǐng)求前的回調(diào),可以在這里調(diào)整發(fā)送的參數(shù)。(通用)

請(qǐng)求回來(lái)后的回調(diào),可以在這里調(diào)整數(shù)據(jù)結(jié)構(gòu)以滿(mǎn)足對(duì) data 的要求。(通用)

同時(shí)要考慮到內(nèi)置功能的適配。(易用)

3.2 API 設(shè)計(jì)
// table 配置,需求對(duì)應(yīng)的模塊對(duì)應(yīng)了他的配置在整個(gè)配置中的位置
{
    url: "http://fetchurl.com/data", // 數(shù)據(jù)源,只支持 json 和 jsonp
    fetchParams: { // 額外的一些參數(shù)
        token: "xxxabxc_sa"
    },
    beforeFetch: function(data, from) { // data 為要發(fā)送的參數(shù),from 參數(shù)用來(lái)區(qū)分發(fā)起 fetch 的來(lái)源(分頁(yè),排序,搜索還是其他位置)
        return data; // 返回值為真正發(fā)送的參數(shù)
    },
    afterFetch: function(result) { // result 為請(qǐng)求回來(lái)的數(shù)據(jù)
        return process(result); // 返回值為真正交給 table 進(jìn)行展示的數(shù)據(jù)。
    },
}
3.3 代碼設(shè)計(jì)

基于前面良好的通信模式,url 的擴(kuò)展變得非常簡(jiǎn)單,只需要在所有的回調(diào)中加入是否配置 url 的判斷即可。

class Table extends React.Component {
    constructor(props) {
        super(props);
        this.data = deepcopy(props.data);
        this.fetchParams = deepcopy(props.fetchParams);
        this.state = {
            data: this.data,
        };
    }
    
    /**
     * 獲取數(shù)據(jù)的方法
     */
    fetchData(props, from) {
        props = props || this.props;
        const otherParams = process(this.state);
        ajax(props.url, this.fetchParams, otherParams, from);
    }
    
    /**
     * 搜索時(shí)的回調(diào)
     */
    handleSearch(key) {
        if (this.props.url) {
            this.setState({
                searchKey: key,
            }, () => {
                this.fetchData();
            });
        } else {
            this.props.onSearch(key);
        }
        
    }
    
    componentDidMount() {
        if (this.props.url) {
            this.fetchData();
        }
    }

    componentWillReceiveProps(nextProps, nextState) {
        let newState = {};
        if (!deepEqual(nextProps.data, this.data) {
            this.data = deepcopy(nextProps.data);
            newState["data"] = this.data; 
        }
        if (!deepEqual(nextProps.fetchParams, this.fetchParams)) {
            this.fetchParams = deepcopy(nextProps.fetchParams);
            this.fetchData();
        }
        if (nextProps.url !== this.props.url) {
            this.fetchData(nextProps);
        }
        if (Object.keys(newState) !== 0) {
            this.setState(newState);
        }
    }
}
4. 行內(nèi)編輯4.1 需求分析

通過(guò)雙擊或者點(diǎn)擊編輯按鈕,實(shí)現(xiàn)行內(nèi)可編輯狀態(tài)的切換。如果只是變成普通的文本框那就太 low 了,有追求的我們希望每個(gè)列根據(jù)數(shù)據(jù)類(lèi)型可以有不同的編輯形式。既然是可編輯的,那么關(guān)于表單的一套東西都適用,他要可以驗(yàn)證,可以重置,也可以聯(lián)動(dòng)。

4.2 API 設(shè)計(jì)
// table 配置,需求對(duì)應(yīng)的模塊對(duì)應(yīng)了他的配置在整個(gè)配置中的位置,顯然行內(nèi)編輯是和列相關(guān)的
{
    columns: [ // HEAD/ROW 相關(guān)
        {   
            dataKey: "cityName", // 展示時(shí)操作的變量
            editKey: "cityValue", // 編輯時(shí)操作的變量
            customField: SelectField, // 編輯狀態(tài)的類(lèi)型
            config: {}, // 編輯狀態(tài)的一些配置
            renderChildren: function() {
                return [
                {id: "bj", name: "北京"},
                {id: "hz", name: "杭州"}].map((item) => {
                    return 
                });
            },
            rules: function(value) { // 校驗(yàn)相關(guān)
                return true;
            }
        }
    ],
    onChange: function(result) {
        doSth(result); // result 包括 {data: 表格的所有數(shù)據(jù), changedData: 變動(dòng)行的數(shù)據(jù), dataKey: xxx, editKey: xxx, pass: 正在編輯的域是否通過(guò)校驗(yàn)}
    }
}
// data 結(jié)構(gòu)
{
    data: [{
        cityName: "xxx",
        cityValue: "yyy",
        name: "xxx",
        __selected__: true, 
        __mode__: "edit", // 用來(lái)區(qū)分當(dāng)前行的狀態(tài)
    }],
    currentPage: 1, // 當(dāng)前頁(yè)數(shù)
    totalCount: 50, // 總條數(shù)
}
4.3 代碼設(shè)計(jì)

圖:行內(nèi)編輯模式下的表格架構(gòu)

所有的 CellField 繼承一個(gè)基類(lèi) Field,這個(gè)基類(lèi)提供通用的與 Table 通信,校驗(yàn)的方式,具體的 Field 只負(fù)責(zé)交互部分的實(shí)現(xiàn)。

下面是這部分設(shè)計(jì)的具體代碼實(shí)現(xiàn),礙于篇幅,不在文章中直接貼出。

https://github.com/uxcore/uxc...

https://github.com/uxcore/uxc...

5. 總結(jié)

這篇文章以復(fù)雜表格組件的開(kāi)發(fā)為切入點(diǎn),討論了以下內(nèi)容:

組件設(shè)計(jì)的通用流程

組件分層架構(gòu)與 API 的對(duì)應(yīng)設(shè)計(jì)

組件設(shè)計(jì)中易用性與通用性的權(quán)衡

State 和 Props 的正確使用

生命周期的實(shí)戰(zhàn)應(yīng)用

父子級(jí)間組件通信

礙于整體篇幅,有一些和這個(gè)組件相關(guān)的點(diǎn)未詳細(xì)討論,我們會(huì)在本系列的后續(xù)文章中詳細(xì)說(shuō)明。

數(shù)據(jù)的 不可變性(immutability)

shouldComponentUpdate 和 pure render

樹(shù)形表格 和 數(shù)據(jù)的遞歸處理

在目前架構(gòu)上進(jìn)行折疊面板的擴(kuò)展

最后

慣例地來(lái)宣傳一下團(tuán)隊(duì)開(kāi)源的 React PC 組件庫(kù) UXCore ,上面提到的點(diǎn),在我們的組件開(kāi)發(fā)工具中都有體現(xiàn),歡迎大家一起討論,也歡迎在我們的 SegmentFault 專(zhuān)題下進(jìn)行提問(wèn)討論。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/86391.html

相關(guān)文章

  • 某熊技術(shù)之路指北 ?

    某熊的技術(shù)之路指北 ? 當(dāng)我們站在技術(shù)之路的原點(diǎn),未來(lái)可能充滿(mǎn)了迷茫,也存在著很多不同的可能;我們可能成為 Web/(大)前端/終端工程師、服務(wù)端架構(gòu)工程師、測(cè)試/運(yùn)維/安全工程師等質(zhì)量保障、可用性保障相關(guān)的工程師、大數(shù)據(jù)/云計(jì)算/虛擬化工程師、算法工程師、產(chǎn)品經(jīng)理等等某個(gè)或者某幾個(gè)角色。某熊的技術(shù)之路系列文章/書(shū)籍/視頻/代碼即是筆者蹣跚行進(jìn)于這條路上的點(diǎn)滴印記,包含了筆者作為程序員的技術(shù)視野、...

    shadowbook 評(píng)論0 收藏0
  • 精華閱讀第 9 期 |滴滴出行 iOS 客戶(hù)端架構(gòu)演進(jìn)之路

    摘要:架構(gòu)都是演變出來(lái)的,沒(méi)有最好的架構(gòu),只有最合適的架構(gòu)最近,滴滴出行平臺(tái)產(chǎn)品中心技術(shù)負(fù)責(zé)人李賢輝接受了的采訪(fǎng),闡述了滴滴的客戶(hù)端架構(gòu)模式與演變過(guò)程。李賢輝也是移動(dòng)開(kāi)發(fā)精英俱樂(lè)部中的一員,所以本期重點(diǎn)推薦了這篇文章。 「架構(gòu)都是演變出來(lái)的,沒(méi)有最好的架構(gòu),只有最合適的架構(gòu)!」最近,滴滴出行平臺(tái)產(chǎn)品中心 iOS 技術(shù)負(fù)責(zé)人李賢輝接受了 infoQ 的采訪(fǎng),闡述了滴滴的 iOS 客戶(hù)端架構(gòu)模式...

    Eirunye 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<abbr id="e0kye"></abbr>
<fieldset id="e0kye"><menu id="e0kye"></menu></fieldset>
  • <abbr id="e0kye"></abbr>
    <