如何爲React Redux應用程序,創建可編輯的數據網格(二)

近日,前端開發工具包 WijmoJS 發佈最新版本,將所有組件和功能模塊化,更加體現出 WijmoJS 的小巧、靈活和高效。這一版本的最大亮點,就是增加了“可在React Redux 應用程序中,編輯數據網格”。

上文中我們介紹了React Redux是什麼,以及前端UI組件庫WijmoJS 爲何要在本次新版本發佈中,增加在React Redux應用程序編輯數據網格的功能。

下面,我們將詳細介紹:如何創建一個使用DataGrid來顯示和編輯Redux Store中數組的簡單示例。

舉個例子

該示例遵循標準的React-Redux應用程序結構,但採用了扁平化的文件夾結構,以使其更適合WijmoJS的在線演示站點。另外,由於示例站點對演示站點的要求,因此它使用SystemJS運行時加載器加載模塊,而不是Webpack或類似的捆綁程序。

(React Redux應用程序中的可編輯DataGrid)

該應用程序具有一個包含兩個FlexGrid控件的單一視圖。

最上面的是由ImmutabilityProvider組件控制的可編輯DataGrid,該組件已從Redux Store綁定到陣列。這是用於檢查功能的DataGrid(我們在本文中討論的內容)。您可以通過從鍵盤上鍵入單元格值來編輯單元格值,使用網格行列表末尾的“新行”行來添加新項,或者通過選擇它們並按Delete鍵來刪除項。

您還可以粘貼剪貼板中的數據,或清除所選單元格區域中的多個單元格值。

值得一提的是,要使用Object.freeze() 函數凍結datagrid中顯示的數據數組中的所有項目,以確保執行編輯時datagrid不會讓數據改變。

除了編輯之外,您還可以根據需要轉換數據-單擊列標題進行排序,將列標題拖到datagrid上方的組面板中進行分組,然後單擊列標題中的過濾器圖標進行過濾。

第二個DataGrid是隻讀的。它不使用ImmutabilityProvider,而是使用其itemsSource屬性直接綁定到商店的數組。該DataGrid可以幫助您檢查通過頂部DataGrid所做的更改如何應用於Redux Store。

頂部DataGrid上方還有一個菜單,可用於更改數據陣列的大小。小型陣列可方便地檢查您的更改如何應用於商店。更大的數組可用於評估編輯過程的性能。您可以選擇一些與實際應用中期望的項目相似的項目,並評估其工作方式。

狀態定義 State

最初的應用程序全局狀態在reducers.jsx文件中定義如下:

const itemCount = 5000;
const initialState = {
    itemCount,
    items: getData(itemCount),
    idCounter: itemCount
}

它包含具有隨機生成的items數組,以及幾個輔助屬性-itemCount和idCounter,這些屬性定義數組中的項數,而idCounter存儲唯一的id值,該id值分配給新添加項的id屬性。

items數組是由示例的DataGrid表示的數組,使用Object.freeze()函數凍結數組中的每個項目,以確保真正滿足Redux提出的數據不變性要求。

事件 Actions

Redux操作創建者函數在actions.jsx文件中定義:

export const addItemAction = (item) => ({
    type: 'ADD_ITEM',
    item
});

export const removeItemAction = (item, index) => ({
    type: 'REMOVE_ITEM',
    item,
    index
});

export const changeItemAction = (item, index) => ({
    type: 'CHANGE_ITEM',
    item,
    index
});

export const changeCountAction = (count) => ({
    type: 'CHANGE_COUNT',
    count
});

有3個事件用於更改項目數組(ADD_ITEM,REMOVE_ITEM和CHANGE_ITEM)的操作,還有一個附加的CHANGE_COUNT事件,可使商店創建具有不同數量的全新項目數組。

每個事件都依賴於“action creator”事件。在ImmutabilityProvider.dataChanged事件處理程序(在GridView表示組件中)中調用這些函數,以通知商店有關datagrid中所做的數據更改。

對於項目更改操作,index屬性包含items數組中受影響的索引項目, item屬性保存了對item對象的引用。

Reducer

應用程序根據上述動作定義一個單例的Reducer,對執行器全局狀態進行更新。它在reducers.jsx文件中定義:

export const appReducer = (state = initialState, action) => {
    switch (action.type) {
        case 'ADD_ITEM':
            {
                // make a clone of the new item which will be added to the
                // items array, and assigns its 'id' property with a unique value.
                let newItem = Object.freeze(copyObject({}, action.item, 
                        { id: state.idCounter }));
                return copyObject({}, state, {
                    // items array clone with a new item added
                    items: state.items.concat([newItem]),
                    // increment 'id' counter
                    idCounter: state.idCounter + 1
                });
            }
        case 'REMOVE_ITEM':
            {
                let items = state.items,
                    index = action.index;
                return copyObject({}, state, {
                    // items array clone with the item removed
                    items: items.slice(0, index).concat(items.slice(index + 1))
                });
            }
        case 'CHANGE_ITEM':
            {
                let items = state.items,
                    index = action.index,
                    oldItem = items[index],
                    // create a cloned item with the property changes applied
                    clonedItem = Object.freeze(copyObject({}, oldItem, action.item));
                return copyObject({}, state, {
                    // items array clone with the updated item
                    items: items.slice(0, index).
                        concat([clonedItem]).
                        concat(items.slice(index + 1))
                });
            }
        case 'CHANGE_COUNT':
            {
                // create a brand new state with a new data
                let ret = copyObject({}, state, {
                    itemCount: action.count,
                    items: getData(action.count),
                    idCounter: action.count
                });
                return ret;
            }
        default:
            return state;
    }
}

根據Redux的要求,我們不會對現有項目數組及其項目的屬性進行修改。如果添加或刪除項目,會首先創建添加或刪除該項目的克隆。如果該操作要求我們更新現有項目,我們將創建一個新的數組,將更新後的項替換爲更改項的克隆。

我們使用 \@ grapecity / wijmo.grid.immutable模塊中的copyObject函數來克隆對象。如果它是由瀏覽器實現的,它將有效地使用Object.assign函數;如果沒有,則使用自定義實現(例如,在IE中)。

要處理REMOVE_ITEM和CHANGE_ITEM操作,我們需要知道items數組中受此更改影響的現有項目及其索引。在此示例中,我們使用最簡單,也是最快的方法來執行此操作:該項目的索引在操作數據的index屬性中傳遞(ImmutabilityProvider.dataChanged事件爲您帶來此信息!)。

如果上述方法不起作用,則可以在操作數據時傳遞將要更改的原始項目,並使用items.indexOf()方法找到其索引,或按商品ID搜索。

對於CHANGE_ITEM操作,您不僅需要知道將要更改的現有項目,還需要知道該項目的新屬性值。通過爲您提供包含新項目屬性值的克隆對象,ImmutabilityProvider.dataChanged事件的數據也帶來了此信息,此克隆對象在操作的item屬性中傳遞,並且由reducer用於創建具有新屬性值的新克隆項目,以在克隆items數組中使用它而不是舊的對象。

請注意,對於添加到克隆項數組中的任何克隆項,我們調用Object.freeze以保護該項免於意外情況。

數據預覽組件 :Presentational 和 Container

該示例的UI在GridView.jsx文件的單個GridView表示性組件中實現。

按照Redux的React綁定中的慣例,我們將其與容器組件(在GridViewContainer.jsx文件中實現的GridViewContainer)一起使用。後者只是前者的包裝,目的是向GridView提供來自Redux Store的必要數據。

數據是datagrid中表示的items數組,以及動作創建者函數(addItemAction,removeItemAction等),通過this.props對象,GridView可以將其作爲道具使用。

這是GridViewContainer的實現方式:

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { GridView } from './GridView';
import { addItemAction, removeItemAction, changeItemAction, changeCountAction } from './actions';


const mapStateToProps = state => ({
    items: state.items,
    itemCount: state.itemCount
})
const mapDispatchToProps = dispatch => {
    return bindActionCreators(
        { 
            addItemAction, removeItemAction, changeItemAction, changeCountAction 
        }, 
        dispatch
    );
};

export const GridViewContainer = connect(
    mapStateToProps,
    mapDispatchToProps
  )(GridView);

GridView演示性組件使用組件的render方法中的以下代碼添加了帶有關聯的ImmutabilityProvider的FlexGrid組件:

import * as wjFlexGrid from '@grapecity/wijmo.react.grid';
import * as wjGridFilter from '@grapecity/wijmo.react.grid.filter';
import { DataChangeEventArgs, DataChangeAction } from '@grapecity/wijmo.grid.immutable';
import { ImmutabilityProvider } from '@grapecity/wijmo.react.grid.immutable';
……
<wjFlexGrid.FlexGrid
        allowAddNew 
        allowDelete
        initialized={this.onGridInitialized}>
    <ImmutabilityProvider 
        itemsSource={this.props.items}
        dataChanged={this.onGridDataChanged} />
    <wjGridFilter.FlexGridFilter/>
    <wjFlexGrid.FlexGridColumn binding="id" header="ID" width={80} isReadOnly={true}></wjFlexGrid.FlexGridColumn>
    <wjFlexGrid.FlexGridColumn binding="start" header="Date" format="d"></wjFlexGrid.FlexGridColumn>
    <wjFlexGrid.FlexGridColumn binding="end" header="Time" format="t"></wjFlexGrid.FlexGridColumn>
    <wjFlexGrid.FlexGridColumn binding="country" header="Country"></wjFlexGrid.FlexGridColumn>
    <wjFlexGrid.FlexGridColumn binding="product" header="Product"></wjFlexGrid.FlexGridColumn>
    <wjFlexGrid.FlexGridColumn binding="sales" header="Sales" format="n2"></wjFlexGrid.FlexGridColumn>
    <wjFlexGrid.FlexGridColumn binding="downloads" header="Downloads" format="n0"></wjFlexGrid.FlexGridColumn>
    <wjFlexGrid.FlexGridColumn binding="active" header="Active" width={80}></wjFlexGrid.FlexGridColumn>
</wjFlexGrid.FlexGrid>

如您所見,ImmutabilityProvider的itemsSource屬性綁定到this.props.items屬性,該屬性包含來自全局應用程序狀態的items數組。

在每次Store reducer生成該數組的新克隆以應用用戶修改時,會使用新的數組實例自動更新this.props.items,並且ImmutabilityProvider將使FlexGrid更新其內容以映射修改。

每當用戶更改保存到datagrid中的數據時,都會調用ImmutabilityProvider的dataChanged事件。它綁定到onGridDataChanged處理函數,該函數實現如下:

onGridDataChanged(s: ImmutabilityProvider, e: DataChangeEventArgs) {
    switch (e.action) {
        case DataChangeAction.Add:
            this.props.addItemAction(e.newItem);
            break;
        case DataChangeAction.Remove:
            this.props.removeItemAction(e.oldItem, e.itemIndex);
            break;
        case DataChangeAction.Change:
            this.props.changeItemAction(e.newItem, e.itemIndex);
            break;
        default:
            throw 'Unknown data action'
    }
}

處理程序只調用一個適當的動作創建器函數,由於使用了GridViewContainer容器組件,該函數也可以通過this.props對象通過GridView組件使用。動作數據是從DataChangeEventArgs類型的事件參數中檢索的,它帶來有關已執行的更改操作(action屬性,可以採用“添加”、“刪除”或“更改”值)信息,源數組中受影響項目的索引,以及對受影響項目的引用操作)。

請注意:“更改”是一個特殊操作,它同時使用了oldItem和newItem屬性。 oldItem包含必須更改其屬性值的原始(未更改)項目,而newItem包含具有新屬性值的原始克隆項目。

因此,具有直接附加的ImmutabilityProvider的FlexGrid不會觸發直接改變源數組的操作,而是使用事件提供的數據觸發dataChanged事件,該事件調用適當的操作創建者函數,將操作分派到Redux商店,然後到達該商店的reducer。

示例程序將使用更改的數據創建該數組的克隆,並且該數組的新副本在綁定到ImmutabilityProvider.itemsSource屬性的this.props.items屬性中可用。 ImmutabilityProvider檢測到此新數組實例,並使FlexGrid刷新其內容。

該視圖包括一個Menu組件,該組件允許用戶更改在DataGrid中顯示數組的大小。更改其值會導致Redux Store創建指定長度的新項目數組。

以下代碼,可將菜單使用組件的render方法添加到視圖中:

import * as wjInput from '@grapecity/wijmo.react.input';
....


<wjInput.Menu header='Items number'
    value={this.props.itemCount}
    itemClicked={this.onCountChanged}>
    <wjInput.MenuItem value={5}>5</wjInput.MenuItem>
    <wjInput.MenuItem value={50}>50</wjInput.MenuItem>
    <wjInput.MenuItem value={100}>100</wjInput.MenuItem>
    <wjInput.MenuItem value={500}>500</wjInput.MenuItem>
    <wjInput.MenuItem value={5000}>5,000</wjInput.MenuItem>
    <wjInput.MenuItem value={10000}>10,000</wjInput.MenuItem>
    <wjInput.MenuItem value={50000}>50,000</wjInput.MenuItem>
    <wjInput.MenuItem value={100000}>100,000</wjInput.MenuItem>
</wjInput.Menu>

菜單的value屬性綁定到全局Redux狀態的itemCount屬性,該狀態包含當前items數組的長度。

當用戶在下拉列表中選擇另一個值時,將觸發itemClicked事件並調用onCountChanged事件處理函數,該函數如下:

onCountChanged(s: wjcInput.Menu) {
    this.props.changeCountAction(s.selectedValue);
}

處理程序僅調用changeCountAction操作創建者函數,將新的數組長度作爲操作數據傳遞。這迫使Store reducer創建一個指定長度的新items數組。視圖的另一個UI元素是隻讀datagrid,它僅顯示items數組的內容。

該DataGrid具有關聯的“顯示數據”複選框元素,該元素允許用戶臨時將DataGrid與數據陣列斷開連接。這是組件的render方法中的JSX,它添加了這些組件:

<input type="checkbox" 
    checked={this.state.showStoreData}
    onChange={ (e) => { 
        this.setState({ showStoreData: e.target.checked}); 
} } /> 
<b>Show data</b>
<wjFlexGrid.FlexGrid 
    itemsSource={this.state.showStoreData ? this.props.items : null} 
    isReadOnly/> 
</div>

“顯示數據”複選框是受控組件,它將值存儲在組件狀態的showStoreData屬性中。

我們在這裏使用本地組件來存儲此值,但是,如果您希望將所有內容存儲在全局Redux狀態中,沒有問題,可以輕鬆地將其移動到那裏。

請注意,FlexGrid.itemsSource屬性有條件地綁定到Store的items數組,或者綁定到null值,具體取決於showStoreData屬性值。

整合資源文件

應用程序的入口點是app.jsx文件,我們將所有應用程序片段放在一起並運行根App組件:

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
//Application
import { appReducer } from './reducers';
import { GridViewContainer } from './GridViewContainer';

// Create global Redux Store
const store = createStore(appReducer);

class App extends React.Component<any, any> {
    render() {
        return <Provider store={store}>
            <GridViewContainer />
          </Provider>;
    }
}

ReactDOM.render(<App />, document.getElementById('app'));

截至目前,我們已經創建了一個應用程序APP,並將其傳遞給我們的reducer。然後,使用GridViewContainer容器組件,依次呈現GridView組件,並將其作爲道具傳遞給全局Store數據。

我們用react-redux Provider組件包裝應用程序組件樹,這樣就可以從任何應用程序組件中輕鬆訪問存儲項目。

結論

FlexGrid DataGrid以及關聯的ImmutabilityProvider組件可以最大程度的滿足您的需求:創建基於Redux應用程序狀態管理和可編輯的DataGrid。

藉助 WijmoJS 前端開發工具包 ,您可以在應用程序UI中使用可編輯的DataGrid,而不會影響Redux對數據不變性的要求。即使在相當大的數據上,此解決方案也具備優秀的性能。

在Redux應用程序中將datagrid用作數據編輯控件幾乎與使用輸入控件一樣簡單,在輸入控件中,您只需將控件值綁定到全局狀態值,並在控件的“ value”中分配一個具有新值的動作已更改”事件即可。

文中的示例會加入 WijmoJS 的學習指南,請點擊此處查看。如果您也希望藉助WijmoJS的前端開發工具包,進一步提升企業IT部門的生產效率,歡迎訪問官網,下載試用。

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