Big list performance with React

后端 未结 9 791
甜味超标
甜味超标 2020-12-04 05:35

I am in the process of implementing a filterable list with React. The structure of the list is as shown in the image below.

PREMISE

9条回答
  •  有刺的猬
    2020-12-04 06:16

    First of all, the difference between the development and production version of React is huge because in production there are many bypassed sanity checks (such as prop types verification).

    Then, I think you should reconsider using Redux because it would be extremely helpful here for what you need (or any kind of flux implementation). You should definitively take a look at this presentation : Big List High Performance React & Redux.

    But before diving into redux, you need to made some ajustements to your React code by splitting your components into smaller components because shouldComponentUpdate will totally bypass the rendering of children, so it's a huge gain.

    When you have more granular components, you can handle the state with redux and react-redux to better organize the data flow.

    I was recently facing a similar issue when I needed to render one thousand rows and be able to modify each row by editing its content. This mini app displays a list of concerts with potential duplicates concerts and I need to chose for each potential duplicate if I want to mark the potential duplicate as an original concert (not a duplicate) by checking the checkbox, and, if necessary, edit the name of the concert. If I do nothing for a particular potential duplicate item, it will be considered duplicate and will be deleted.

    Here is what it looks like :

    There are basically 4 mains components (there is only one row here but it's for the sake of the example) :

    Here is the full code (working CodePen : Huge List with React & Redux) using redux, react-redux, immutable, reselect and recompose:

    const initialState = Immutable.fromJS({ /* See codepen, this is a HUGE list */ })
    
    const types = {
        CONCERTS_DEDUP_NAME_CHANGED: 'diggger/concertsDeduplication/CONCERTS_DEDUP_NAME_CHANGED',
        CONCERTS_DEDUP_CONCERT_TOGGLED: 'diggger/concertsDeduplication/CONCERTS_DEDUP_CONCERT_TOGGLED',
    };
    
    const changeName = (pk, name) => ({
        type: types.CONCERTS_DEDUP_NAME_CHANGED,
        pk,
        name
    });
    
    const toggleConcert = (pk, toggled) => ({
        type: types.CONCERTS_DEDUP_CONCERT_TOGGLED,
        pk,
        toggled
    });
    
    
    const reducer = (state = initialState, action = {}) => {
        switch (action.type) {
            case types.CONCERTS_DEDUP_NAME_CHANGED:
                return state
                    .updateIn(['names', String(action.pk)], () => action.name)
                    .set('_state', 'not_saved');
            case types.CONCERTS_DEDUP_CONCERT_TOGGLED:
                return state
                    .updateIn(['concerts', String(action.pk)], () => action.toggled)
                    .set('_state', 'not_saved');
            default:
                return state;
        }
    };
    
    /* configureStore */
    const store = Redux.createStore(
        reducer,
        initialState
    );
    
    /* SELECTORS */
    
    const getDuplicatesGroups = (state) => state.get('duplicatesGroups');
    
    const getDuplicateGroup = (state, name) => state.getIn(['duplicatesGroups', name]);
    
    const getConcerts = (state) => state.get('concerts');
    
    const getNames = (state) => state.get('names');
    
    const getConcertName = (state, pk) => getNames(state).get(String(pk));
    
    const isConcertOriginal = (state, pk) => getConcerts(state).get(String(pk));
    
    const getGroupNames = reselect.createSelector(
        getDuplicatesGroups,
        (duplicates) => duplicates.flip().toList()
    );
    
    const makeGetConcertName = () => reselect.createSelector(
        getConcertName,
        (name) => name
    );
    
    const makeIsConcertOriginal = () => reselect.createSelector(
        isConcertOriginal,
        (original) => original
    );
    
    const makeGetDuplicateGroup = () => reselect.createSelector(
        getDuplicateGroup,
        (duplicates) => duplicates
    );
    
    
    
    /* COMPONENTS */
    
    const DuplicatessTableRow = Recompose.onlyUpdateForKeys(['name'])(({ name }) => {
        return (
            
                {name}
                
            
        )
    });
    
    const PureToggle = Recompose.onlyUpdateForKeys(['toggled'])(({ toggled, ...otherProps }) => (
        
    ));
    
    
    /* CONTAINERS */
    
    let DuplicatesTable = ({ groups }) => {
    
        return (
            
    {groups.map(name => ( ))}
    {'Concert'} {'Duplicates'}
    ) }; DuplicatesTable.propTypes = { groups: React.PropTypes.instanceOf(Immutable.List), }; DuplicatesTable = ReactRedux.connect( (state) => ({ groups: getGroupNames(state), }) )(DuplicatesTable); let DuplicatesRowColumn = ({ duplicates }) => (
      {duplicates.map(d => ( ))}
    ); DuplicatessRowColumn.propTypes = { duplicates: React.PropTypes.arrayOf( React.PropTypes.string ) }; const makeMapStateToProps1 = (_, { name }) => { const getDuplicateGroup = makeGetDuplicateGroup(); return (state) => ({ duplicates: getDuplicateGroup(state, name) }); }; DuplicatesRowColumn = ReactRedux.connect(makeMapStateToProps1)(DuplicatesRowColumn); let DuplicateItem = ({ pk, name, toggled, onToggle, onNameChange }) => { return (
  • { toggled ? onNameChange(pk, e.target.value)}/> : name } onToggle(pk, e.target.checked)}/>
  • ) } const makeMapStateToProps2 = (_, { pk }) => { const getConcertName = makeGetConcertName(); const isConcertOriginal = makeIsConcertOriginal(); return (state) => ({ name: getConcertName(state, pk), toggled: isConcertOriginal(state, pk) }); }; DuplicateItem = ReactRedux.connect( makeMapStateToProps2, (dispatch) => ({ onNameChange(pk, name) { dispatch(changeName(pk, name)); }, onToggle(pk, toggled) { dispatch(toggleConcert(pk, toggled)); } }) )(DuplicateItem); const App = () => (
    ) ReactDOM.render( , document.getElementById('app') );

    Lessons learned by doing this mini app when working with huge dataset

    • React components work best when they are kept small
    • Reselect become very useful to avoid recomputation and keep the same reference object (when using immutable.js) given the same arguments.
    • Create connected component for component that are the closest of the data they need to avoid having component only passing down props that they do not use
    • Usage of fabric function to create mapDispatchToProps when you need only the initial prop given in ownProps is necessary to avoid useless re-rendering
    • React & redux definitively rock together !

提交回复
热议问题