处理 ag grid react 并渲染复选框网格

Posted

技术标签:

【中文标题】处理 ag grid react 并渲染复选框网格【英文标题】:Dealing with ag grid react and rendering a grid of checkboxes 【发布时间】:2018-12-31 14:44:07 【问题描述】:

我正在搞乱 ag-grid、react-apollo,而且一切似乎都运行良好。这里的目标是单击一个复选框并发生修改某些数据的突变/网络请求。我遇到的问题是它重绘了整个行,​​这可能真的很慢,但我真的只是想更新单元格本身,所以它很快并且用户体验更好。我的一个想法是进行乐观更新,然后更新我的缓存/利用我的缓存。你们采取了哪些方法。

列和行数据都是通过 apollo 查询获取的。

这里有一些代码:

复选框渲染器

import React,  Component  from "react";
import Checkbox from "@material-ui/core/Checkbox";
import _ from "lodash";

class CheckboxItem extends Component 
  constructor(props) 
    super(props);
    this.state = 
      value: false
    ;
    this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
  

  componentDidMount() 
    this.setDefaultState();
  

  setDefaultState() 
    const  data, colDef, api  = this.props;
    const  externalData  = api;
    if (externalData && externalData.length > 0) 
      if (_.find(data.roles, _.matchesProperty("name", colDef.headerName))) 
        this.setState(
          value: true
        );
      
    
  

  updateGridAssociation(checked) 
    const  data, colDef  = this.props;
    // const  externalData, entitySpec, fieldSpec  = this.props.api;
    // console.log(data);
    // console.log(colDef);
    if (checked) 
      this.props.api.assign(data.id, colDef.id);
      return;
    
    this.props.api.unassign(data.id, colDef.id);
    return;
  

  handleCheckboxChange(event) 
    const checked = !this.state.value;
    this.updateGridAssociation(checked);
    this.setState( value: checked );
  

  render() 
    return (
      <Checkbox
        checked=this.state.value
        onChange=this.handleCheckboxChange
      />
    );
  


export default CheckboxItem;

网格本身:

import React,  Component  from "react";
import  graphql, compose  from "react-apollo";
import gql from "graphql-tag";
import Grid from "@material-ui/core/Grid";
import _ from "lodash";
import  AgGridReact  from "ag-grid-react";
import  CheckboxItem  from "../Grid";
import "ag-grid/dist/styles/ag-grid.css";
import "ag-grid/dist/styles/ag-theme-material.css";

class UserRole extends Component 
  constructor(props) 
    super(props);
    this.api = null;
  

  generateColumns = roles => 
    const columns = [];
    const initialColumn = 
      headerName: "User Email",
      editable: false,
      field: "email"
    ;
    columns.push(initialColumn);
    _.forEach(roles, role => 
      const roleColumn = 
        headerName: role.name,
        editable: false,
        cellRendererFramework: CheckboxItem,
        id: role.id,
        suppressMenu: true,
        suppressSorting: true
      ;
      columns.push(roleColumn);
    );
    if (this.api.setColumnDefs && roles) 
      this.api.setColumnDefs(columns);
    
    return columns;
  ;

  onGridReady = params => 
    this.api = params.api;
    this.columnApi = params.columnApi;
    this.api.assign = (userId, roleId) => 
      this.props.assignRole(
        variables:  userId, roleId ,
        refetchQueries: () => ["allUserRoles", "isAuthenticated"]
      );
    ;

    this.api.unassign = (userId, roleId) => 
      this.props.unassignRole(
        variables:  userId, roleId ,
        refetchQueries: () => ["allUserRoles", "isAuthenticated"]
      );
    ;
    params.api.sizeColumnsToFit();
  ;

  onGridSizeChanged = params => 
    const gridWidth = document.getElementById("grid-wrapper").offsetWidth;
    const columnsToShow = [];
    const columnsToHide = [];
    let totalColsWidth = 0;
    const allColumns = params.columnApi.getAllColumns();
    for (let i = 0; i < allColumns.length; i++) 
      const column = allColumns[i];
      totalColsWidth += column.getMinWidth();
      if (totalColsWidth > gridWidth) 
        columnsToHide.push(column.colId);
       else 
        columnsToShow.push(column.colId);
      
    
    params.columnApi.setColumnsVisible(columnsToShow, true);
    params.columnApi.setColumnsVisible(columnsToHide, false);
    params.api.sizeColumnsToFit();
  ;

  onCellValueChanged = params => ;

  render() 
    console.log(this.props);
    const  users, roles  = this.props.userRoles;
    if (this.api) 
      this.api.setColumnDefs(this.generateColumns(roles));
      this.api.sizeColumnsToFit();
      this.api.externalData = roles;
      this.api.setRowData(_.cloneDeep(users));
    
    return (
      <Grid
        item
        xs=12
        sm=12
        className="ag-theme-material"
        style=
          height: "80vh",
          width: "100vh"
        
      >
        <AgGridReact
          onGridReady=this.onGridReady
          onGridSizeChanged=this.onGridSizeChanged
          columnDefs=[]
          enableSorting
          pagination
          paginationAutoPageSize
          enableFilter
          enableCellChangeFlash
          rowData=_.cloneDeep(users)
          deltaRowDataMode=true
          getRowNodeId=data => data.id
          onCellValueChanged=this.onCellValueChanged
        />
      </Grid>
    );
  


const userRolesQuery = gql`
  query allUserRoles 
    users 
      id
      email
      roles 
        id
        name
      
    

    roles 
      id
      name
    
  
`;

const unassignRole = gql`
  mutation($userId: String!, $roleId: String!) 
    unassignUserRole(userId: $userId, roleId: $roleId) 
      id
      email
      roles 
        id
        name
      
    
  
`;

const assignRole = gql`
  mutation($userId: String!, $roleId: String!) 
    assignUserRole(userId: $userId, roleId: $roleId) 
      id
      email
      roles 
        id
        name
      
    
  
`;

export default compose(
  graphql(userRolesQuery, 
    name: "userRoles",
    options:  fetchPolicy: "cache-and-network" 
  ),
  graphql(unassignRole, 
    name: "unassignRole"
  ),
  graphql(assignRole, 
    name: "assignRole"
  )
)(UserRole);

【问题讨论】:

初始渲染后,是否需要从外部事件同步网格状态,即有人从另一个地方更新这些值或从后端到达新值? 【参考方案1】:

我不知道 ag-grid 但是...在这种情况下发出请求会导致整个网格(UserRole 组件)重绘

当您传递影响整个父状态的操作(给子子)时,这是正常的(新数据到达道具 => 重绘)。

您可以通过 shouldComponentUpdate() 避免这种情况 - f.e.仅当行数量发生变化时才重绘。

但还有另一个问题 - 您正在做出乐观的更改(更改复选框状态) - 如果突变失败怎么办?您必须处理阿波罗错误并强制重绘整个网格 - 更改是本地的(单元格)。这可以做到通过在 shouldComponentUpdate 中设置标志(使用 setState)和附加条件。

【讨论】:

这是我最终要走的路线,我在错误时强制重新渲染,因为我想要准确性。【参考方案2】:

对我来说处理这个问题的最好方法是在 apollo 中使用网络状态进行 shouldComponentUpdate,这需要进行一些挖掘才能看到发生了什么:

 /**
   * Important to understand that we use network statuses given to us by apollo to take over, if either are 4 (refetch) we hack around it by not updating
   * IF the statuses are also equal it indicates some sort of refetching is trying to take place
   * @param  obj nextProps [Next props passed into react lifecycle]
   * @return [boolean]           [true if should update, else its false to not]
   */

  shouldComponentUpdate = nextProps => 
    const prevNetStatus = this.props.userRoles.networkStatus;
    const netStatus = nextProps.userRoles.networkStatus;
    const error = nextProps.userRoles.networkStatus === 8;
    if (error) 
      return true;
    
    return (
      prevNetStatus !== netStatus && prevNetStatus !== 4 && netStatus !== 4
    );
  ;

它基本上说如果有错误,只是重新呈现准确(我认为这可以假设错误不会发生太多但你永远不知道)然后我检查是否有任何网络状态不是 4(重新获取)如果他们是我不想要重新渲染,让我做我想做的事,而不会对那个级别的干扰做出反应。 (就像更新子组件一样)。

prevNetStatus !== netStatus

这部分代码只是说我希望初始加载只导致 UI 更新。我相信它可以从加载 -> 成功作为网络状态,然后如果你从成功重新获取 -> 重新获取 -> 成功或类似性质的东西。

基本上,我只是查看了查询的道具,看看我可以使用什么。

【讨论】:

以上是关于处理 ag grid react 并渲染复选框网格的主要内容,如果未能解决你的问题,请参考以下文章

React 中的 Ag-grid 纯 JS 单元格渲染器

AG-Grid + React - 添加新行时忽略网格排序

ag-grid-react 无法正确渲染

角度 ag-grid 单元格渲染器复选框不刷新值

Ag-grid-react 未正确刷新

ag-grid vs Kendo React 网格优缺点 [关闭]