React/Redux rendering a list that's updating every second

后端 未结 5 871
生来不讨喜
生来不讨喜 2021-02-01 06:44

I have a react component that receives props from the redux store every second. The new state has an array that\'s different than the last array. To be specific, every second an

相关标签:
5条回答
  • 2021-02-01 06:58

    There is quite an easy solution for this. React VDOM is just a diffing algorithm. The only piece missing with your JSX is something called key which is like an id that the diffing algo uses and renders the particular element. Just tag the element with a KEY something like this https://reactjs.org/docs/lists-and-keys.html#keys

    <li key={number.toString()}>
        {number}   </li>
    
    0 讨论(0)
  • 2021-02-01 07:00

    it looks like you are creating a new array each time in the reducer in which all array indices need to be re-calculated. have you tried appending the new node to the end of the list instead of prepending?

    0 讨论(0)
  • 2021-02-01 07:01

    You're using PureComponent, that do shallow comparison, then your component MyRow should not be rerendered on each new item being added (Please follow my code example below).

    Is there any way to only add the newly added item to the list and not re-render the whole list?

    According to your question - Yes, using PureComponent should render only 1 time the new item:

    Here's what the React's docs says:

    If your React component’s render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.

    Code example of PureComponent:

    You can check out the code sample, that I did for you.

    You will see that the Item component is always rendered only 1 time, because we use React.PureComponent. To prove my statement, each time the Item is rendered, I added current time of rendering. From the example you will see that the Item Rendered at: time is always the same, because it's rendered only 1 time.

    const itemsReducer = (state = [], action) => {
      if (action.type === 'ADD_ITEM') return [ ...state, action.payload]
    
      return state
    }
    
    const addItem = item => ({
      type: 'ADD_ITEM',
      payload: item
    })
    
    class Item extends React.PureComponent {
      render () {
        // As you can see here, the `Item` is always rendered only 1 time,
        // because we use `React.PureComponent`.
        // You can check that the `Item` `Rendered at:` time is always the same.
        // If we do it with `React.Component`,
        // then the `Item` will be rerendered on each List update.
        return <div>{ this.props.name }, Rendered at: { Date.now() }</div>
      }
    }
    
    class List extends React.Component {
      constructor (props) {
        super(props)
        this.state = { intervalId: null }
        this.addItem = this.addItem.bind(this)
      }
    
      componentDidMount () {
        // Add new item on each 1 second,
        // and keep its `id`, in order to clear the interval later
        const intervalId = setInterval(this.addItem, 1000)
        this.setState({ intervalId })
      }
    
      componentWillUnmount () {
        // Use intervalId from the state to clear the interval
        clearInterval(this.state.intervalId)
      }
    
      addItem () {
        const id = Date.now()
        this.props.addItem({ id, name: `Item - ${id}` })
      }
    
      renderItems () {
        return this.props.items.map(item => <Item key={item.id} {...item} />)
      }
    
      render () {
        return <div>{this.renderItems()}</div>
      }
    }
    
    const mapDispatchToProps = { addItem }
    const mapStateToProps = state => ({ items: state })
    const ListContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(List)
    
    const Store = Redux.createStore(itemsReducer)
    const Provider = ReactRedux.Provider
    
    ReactDOM.render(
      <Provider store={Store}>
        <ListContainer />
      </Provider>,
      document.getElementById('container')
    )
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.0/redux.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.7/react-redux.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script>
    
    <div id="container">
        <!-- This element's contents will be replaced with your component. -->
    </div>

    Solutions:

    1. If the performance problem is caused by MyRow rerending, please find out what's the reason of rerending, because it should not happen, because of PureComponent usage.
      • You can try to simplify your reducer, in order to check / debug, is the reducer causing the problem. For instance, just add the new item to the list (without doing anything else as filtrations, slice, etc): myList: [ ...state.myList, payload ]
      • Please make sure you always pass the same key to your item component <MyRow key={list.id} data={list} />. If the key or data props are changed, then the component will be rerendered.

    1. Here are some other libraries, these stand for efficient rendering of lists. I'm sure they will give us some alternatives or insights:

      • react-virtualized - React components for efficiently rendering large lists and tabular data
      • react-infinite - A browser-ready efficient scrolling container based on UITableView

    0 讨论(0)
  • 2021-02-01 07:09

    PureComponent will shallowly compare the props and state. So my guess here is that the items are somehow new objects than the previous passed props, thus the rerendering.

    I would advice, in general, to only pass primitive values in pure components :

    class MyList extends Component {
        render() {
            return (
                <div>
                    {this.props.myList.map((item, index) => (
                        <MyRow key={item.id} id={item.id} name={data.name} />
                        //or it's alternative 
                        <MyRow key={item.id} {...item} />
                    ))}
                </div>
            );
        }
    }
    
    //...
    
    class MyRow extends PureComponent {
        render() {
            const {id, name} = this.props;
            return (
                <div>
                    {id} - {name}
                </div>
            );
        }
    }
    
    0 讨论(0)
  • 2021-02-01 07:13

    The problem really exists in the reducer.

    myList: [ payload, ...state.myList.filter(item => payload.id !== item.id).slice(0, -1) ]
    

    What is the logic implemented using slice(0,-1)?

    It is the culprit here.

    From your question I understood the next state after [1,2,3] will be [1,2,3,4].

    But your code will be giving [4,1,2], then [5,4,1] then [6,5,4].

    Now all the elements in the state are new, not in the initial state. See state is not just getting appended it is completely changing.

    Please see if you are getting the desired result by avoiding slice.

     myList: [ payload, ...state.myList.filter(item => payload.id !== item.id)]
    
    0 讨论(0)
提交回复
热议问题