如何为 React Redux 应用程序,创建可编辑的数据网格(二) - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
GrapeCityChina
V2EX    React

如何为 React Redux 应用程序,创建可编辑的数据网格(二)

  •  
  •   GrapeCityChina
    GrapeCity 2020-04-08 17:09:16 +08:00 1625 次点击
    这是一个创建于 2064 天前的主题,其中的信息可能已经有所发展或是发生改变。

    近日,前端开发工具包 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 inding="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 部门的生产效率,欢迎访问官网,下载试用。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Soana     997 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 18:38 PVG 02:38 LAX 10:38 JFK 13:38
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86