构造React / Redux以进行有效更新的正确方法

时间:2017-03-06 14:21:21

标签: reactjs firebase firebase-realtime-database redux react-redux

我们的应用程序是使用React和Redux构建的,其中firebase充当我们的后端。

我们的应用程序有一个画布,上面有几种类型的“实体”。这些实体对于当前在画布上的任何用户都是难以处理的。一次可以在画布上有多达300个实体。

在加载时,应用程序会根据firebase中的节点熄灭并获取所需的所有实体。此节点只是画布所需的ID列表。然后,每次将id传递给应用程序时,应用程序都会退出并获取该ID的信息并将其存储在商店中。这是一个可以有子实体的堆栈实体的例子。

{
  "-KZ6ieI3kUyCjl_3d2jc" : true,
  "-KeKtL8HYhHsKmSLw-Lz" : true,
  "-KeKtLGQyMDLdlz-lmec" : true,
  "-KeKtLO_yBVHwle7QSQc" : true,
  "-KeKtLVvh8iLNfbdOdzV" : true,
  "-KeKtLofe8hLEZ0dfy8E" : true,
  "-KeLA446updVOw_a6EG5" : true,
  "-KeLAKJTTpImhBez5ivt" : true,
  "-KeYp76zg5Z-OJamIDqW" : true,
  "-KeYpmhno-ZAaztlxBtE" : true,
  "-KeYv8PTfMei0bWbllzg" : true,
  "-KeYv8Zo_DeyIlF3d_GA" : true
}

{
  "addNewItemPlaceholder" : false,
  "canvasID" : "-0testDevCanvas",
  "childEntities" : {
    "2" : [ "-KeKtLVvh8iLNfbdOdzV", "-KZ6ieI3kUyCjl_3d2jc", "-KeKtL8HYhHsKmSLw-Lz", "-KeLA446updVOw_a6EG5", "-KeLAKJTTpImhBez5ivt", "-KeKtLO_yBVHwle7QSQc", "-KeYp76zg5Z-OJamIDqW", "-KeYpmhno-ZAaztlxBtE" ]
  },
  "dataX" : 505,
  "dataY" : 570,
  "expanded" : true,
  "height" : "1200px",
  "location" : "-0testDevCanvas",
  "selected" : false,
  "subType" : 0,
  "type" : 1,
  "width" : "1176px"
}

以下是行动的示例。

export function fetchCanvasAndChildren(id, settings = {untrack: false}) {
  //if untrack is set to true then we are just removing all the data and removing the listeners
  if (!settings.untrack) {
    //track the canvas
    trackEntity(id);

    //get all the children and track them
    firebase.database().ref(`entitiesNode/groups/${id}`).on('child_added', child => {
      //track this entity
      trackEntity(child.key);
    });

    firebase.database().ref(`entitiesNode/groups/${id}`).on('child_removed', child => {
      //need to entrack the entity
      //remove the entity from store
      store.dispatch({
        type: 'REMOVE_ENTITY',
        id: child.key
      });
    });
  } else {
    //need to iterate through store and untrack all the entities and remove them from store
    let entities = store.getState().entities;
    for (var entity in entities) {
      untrackEntity(entity);
    }
  }//end untrack if
}

function trackEntity(id) {
  //track the entity and add it to the store
  firebase.database().ref(`entitiesNode/entities/${id}`).on('value', snapshot => {
    //TODO there are sometimes entities in the group that no longer exist. Ignore these but they will need to be cleaned up.
    if (snapshot.val() !== null) {
      let entityObj = snapshot.val();

      // update the store
      store.dispatch({
        type: 'ADD/UPDATE_ENTITY_FROM_FB',
        id,
        payload: entityObj
      });
    }
  });
}

function untrackEntity(id) {
  //remove the listener
  //get the entity types ONCE from firebase and remove listeners based on the entity types even though some may not be used
  firebase.database().ref(`entitiesNode/types`).once('value', typesSnapshot => {
    typesSnapshot.forEach(data => {
      //create a listener for this type
      firebase.database().ref(`entitiesNode/entities/${id}/children/${data.key}`).orderByValue().off()
    });
  });
  firebase.database().ref(`entitiesNode/entities/${id}`).off();
  //remove the entity from store
  store.dispatch({
    type: 'REMOVE_ENTITY',
    id: id
  });
}

这是我们的entites减速器的一个例子。

const defaultChildEntities = {
  0: [],
  1: [],
  2: [],
  3: [],
  4: [],
  5: [],
  6: []
}

export default function entities(state = {}, action) {
  switch(action.type) {
    case 'ADD/UPDATE_ENTITY_FROM_FB' :
    //need to make sure movingChildEntities does not come from store
    const entity = {childEntities: defaultChildEntities, ...state[action.id]};
    delete entity.childEntitiesMoving;
    delete entity.childEntitiesDocked;
    delete entity.newMoving;
    return {...state, [action.id]: {...entity, ...action.payload}}
    case 'UPDATE_ENTITY_LOCAL' :
    return {...state, [action.id]: {...state[action.id],...action.payload}}
    case 'REMOVE_ENTITY' :
    const newState = {...state}
    delete newState[action.id];
    return newState;
    case 'CLEAR_ENTITIES' :
    return {}
    default:
    return state;
  }
}

以下是我们用于canvas组件的连接器示例:

const mapStateToProps = (state, ownProps) => {
  const getAttributes = makeGetAttributes();
  let canvasID = ownProps.params.canvasID;
  //need to see if the object has entered store yet. If not then create a blank one for the app
  if (state.canvasPage.entities[canvasID] != undefined) {
    return {
      ...getAttributes(state, ownProps),
      canvasProps: state.canvasPage.canvasProps,
      dockedStacks: state.canvasPage.dockedEntities,
      editor: state.canvasPage.canvasEntityEditor,
      // stacks: entityObj.Stack,
      // uploads: entityObj.Upload,
      dragAndDrop: state.canvasPage.dragAndDrop,
      selections: state.canvasPage.selections,
      users: state.users
    }
  }

  return {
    canvasProps: state.canvasPage.canvasProps,
    dockedStacks: state.canvasPage.dockedEntities,
    editor: state.canvasPage.canvasEntityEditor,
    dragAndDrop: state.canvasPage.dragAndDrop,
    selections: state.canvasPage.selections,
    users: state.users
  }
}

然后我们使用选择器从商店获取信息

import { createSelector } from 'reselect';

const getAttributes = (state, props) => state.canvasPage.entities[props.params.canvasID];
const getCards = (state, props) => state.canvasPage.entities[props.params.canvasID].childEntities[2] || {};
const getStacks = (state, props) => state.canvasPage.entities[props.params.canvasID].childEntities[1] || {};
const getUploads = (state, props) => state.canvasPage.entities[props.params.canvasID].childEntities[6] || {};

export const makeGetAttributes = () => {
  return createSelector([getAttributes, getCards, getStacks, getUploads], (attributes, cards, stacks, uploads) => {
    return {
      ...attributes,
      cards,
      stacks,
      uploads
    }
  });
}

我们遇到的问题是让应用程序在正确的时间更新,而不是执行不必要的更新。我觉得我们正处于需要一双新鲜眼睛的地步,并建议是否正确设置。

特别是当实体添加到实体时,更改将显示在react和redux dev工具中,但ui未更新。以下是相关特定组件的示例。

class CanvasStack extends React.Component {
  //no need to update the component if the canvas moved.
  shouldComponentUpdate(nextProps){
    //here we are comparing the all the props before and after the update. If the canvas position is the only thing that has changed then there is no need to waste resources on rerendering the components.
    let currentProps = {...this.props},
    futureProps = {...nextProps};
    delete currentProps.canvasProps;
    delete futureProps.canvasProps;
    delete currentProps.dragAndDrop;
    delete futureProps.dragAndDrop;
    return JSON.stringify(currentProps) !== JSON.stringify(futureProps);
  }

  render() {
    let stackID = this.props.stackID,
    styles = {},
    dataX = this.props.dataX,
    dataY = this.props.dataY,
    expanded = this.props.expanded || false,
    placeholder = this.props.placeholder || false,
    docked = this.props.docked || false,
    selected = this.props.selected || false,
    isMoving = this.props.isMoving || false;
    // stackLength = this.props.cards.length;


    //return the correct position
    styles[`transform`] = `translate(${dataX}px, ${dataY}px)`;
    //set the height and width
    styles[`height`] = this.props.height;
    styles[`width`] = this.props.width;

    if (expanded && selected) {
      styles[`border`] = `solid 2px  rgb(${this.props.users[selected].color})`
    }

    return (
      <div
        ref="stack"
        id={stackID}
        className={this.getClassNames(expanded, docked, isMoving, placeholder)}
        data-type="stack"
        style={styles}
        draggable="true"
        //needed to target itmes moving from the new items sidebar
        data-newMoving={this.props.newMoving}
        data-selected={selected ? true : false}
      >
        <StackInnerContent
          stackLength={10}
          {...this.props}
          ></StackInnerContent>
        </div>
      )
    }
  }

const makeMapStateToProps = () => {
  const getAttributes = makeGetAttributes(),
  getCards = makeGetCards(),
  getStacks = makeGetStacks(),
  getUploads = makeGetUploads();
  const mapStateToProps = (state, ownProps) => {
    let stackID = ownProps.stackID;
    if (state.canvasPage.entities[stackID] != undefined) {
      return {
        ...getAttributes(state, ownProps),
        cards: getCards(state, ownProps),
        stacks: getStacks(state, ownProps),
        uploads: getUploads(state, ownProps),
        authenticated: state.authenticated,
        canvasProps: state.canvasPage.canvasProps,
        dragAndDrop: state.canvasPage.dragAndDrop,
        users: state.users
      }
    } else {
      return {
        authenticated: state.authenticated,
        canvasProps: state.canvasPage.canvasProps,
        dragAndDrop: state.canvasPage.dragAndDrop,
        users: state.users
      }
    }
  }
  return mapStateToProps
}

export default connect(makeMapStateToProps)(CanvasStack)

如果删除,UI将会更新:

    delete currentProps.dragAndDrop;
    delete futureProps.dragAndDrop;
来自shouldComponentUpdate的

但这不是正确的行为,它会导致组件更新次数过多。我知道这是一个非常具体的问题,如果没有足够的信息我会道歉。我试着尽可能详细。

0 个答案:

没有答案