Подтвердить что ты не робот

Мое состояние Redux изменилось, почему React не вызывает повторную визуализацию?

Я пытаюсь создать компонент уведомления, в котором уведомления появятся в определенных случаях (например, проблемы с соединениями, успешные изменения и т.д.).

Мне нужно, чтобы уведомления исчезли через пару секунд, поэтому я запускаю изменение состояния для удаления уведомления из состояния Redux из setTimeout внутри уведомления componentDidMount.

Я вижу, что состояние действительно изменяется, но React-Redux не перерисовывает родительский компонент, поэтому уведомление все еще появляется в DOM.

Вот мой редуктор Redux:

const initialState = {
    notifications: []
}

export default function (state = initialState, action) {
  switch(action.type) {
    case CLEAR_SINGLE_NOTIFICATION:
      return Object.assign ({}, state, {
        notifications: deleteSingleNotification(state.notifications, action.payload)
      })
      case CLEAR_ALL_NOTIFICATIONS:
        return Object.assign ({}, state, {
          notifications: []
        })
      default:
        return state
    }
}

function deleteSingleNotification (notifications, notificationId) {
  notifications.some (function (notification, index) {
    return (notifications [index] ['id'] === notificationId) ?
           !!(notifications.splice(index, 1)) :
           false;
  })

  return notifications;
}

и мои компоненты React (Main и Notification):

/* MAIN.JS */
class Main extends Component {

    renderDeletedVideoNotifications() {
        console.log('rendering notifications');
        const clearNotification = this.props.clearNotification;
        return this.props.notifications.map((notification)=> {
            return <Notification
                key={notification.id}
                message={notification.message}
                style={notification.style}
                clearNotification={clearNotification}
                notificationId={notification.id}
            />
        });
    }

    render() {
        console.log('rerendering');
        return (
            <div className="_main">
                <Navbar location={this.props.location} logStatus={this.props.logStatus}
                        logOut={this.logout.bind(this)}/>
                <div className="_separator"></div>
                {this.props.children}
                <BottomStack>
                    {this.renderDeletedVideoNotifications()}
                </BottomStack>
            </div>
        );
    }

}

function mapStateToProps(state) {
    return {logStatus: state.logStatus, notifications: state.notifications.notifications};
}

function mapDispatchToProps(dispatch) {
    return bindActionCreators({checkLogStatus, logOut, clearNotification, clearAllNotifications}, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(Main);

/* NOTIFICATION.JS */

export default class Notification extends Component{
    constructor(props){
        super(props);
        this.state = {show: true}
    }

    componentWillReceiveProps(nextProps){
        if(nextProps.message){
            this.setState({show: true});
        }
    }

    clearNotification(notificationId){
        this.props.clearNotifications(notificationId);
    }

    componentDidMount(){
        console.log('notification  mount');
        setTimeout(()=>{
            console.log('timed out');
            this.props.clearNotification(this.props.notificationId);
        }, 1000);
    }

    closeNotification(){
        this.props.clearNotification(this.props.notificationId);
        this.setState({show: false});
    }

    render(){
        const notificationStyles = () =>{
            if (this.props.style === "error"){
                return {backgroundColor: 'rgba(152, 5, 19, 0.8)'}
            }
            return {backgroundColor: 'rgba(8, 130, 101, 0.8)'}
        };

        if(!this.state.show){
            return null;
        }
        return (
            <div className="notification" style={notificationStyles()}>
                <div className="notificationCloseButton" onClick={this.closeNotification.bind(this)}>
                    <i className="material-icons">close</i>
                </div>
                {this.props.message}
            </div>
        )
    }

};
4b9b3361

Ответ 1

У вас все подключено правильно, но вам не хватает одной ключевой концепции для Redux:

С Redux вы никогда не мутируете какую-либо часть state.

Из руководства Redux:

Вещи, которые вы никогда не должны делать внутри редуктора:

  • Мутировать свои аргументы
  • Выполнять побочные эффекты, такие как вызовы API и переходы маршрутизации;
  • Вызов нечистых функций, например. Date.now() или Math.random().

В deleteSingleNotification вы используете .splice, чтобы вырезать старое уведомление из вашего массива. Вместо этого вам нужно вернуть новый массив с отсутствием нежелательного уведомления. Самый простой способ сделать это - с помощью функции .filter:

function deleteSingleNotification(notifications, notificationId){
    return notifications.filter (notification => {
        return notification.id !== notificationId
    }
}
Вот JSBin с вашей рабочей системой уведомлений!

Итак, вот почему это работает: Задача React-Redux - обновлять ваши компоненты всякий раз, когда изменяется определенная часть вашего хранилища Redux. Он использует тест === для каждой части дерева состояний, чтобы узнать, что-либо изменилось.

Когда вы идете и меняете состояние с чем-то вроде .splice, оно проверяет и думает, что ничего не изменилось.

Вот пример демонстрации проблемы:

var array = [ 'a', 'b', 'c' ]

var oldArray = array

array.splice (1, 1) // cut out 'b'

oldArray === array // => true!  Both arrays were changed by using .splice,
                   // so React-Redux *doesn't* update anything

Вместо этого React-Redux нуждается в этом:

var array = [ 'a', 'b', 'c' ]

var oldArray = array

array = array.filter (item, index => index !== 1) // new array without 'b'

oldArray === array // false.  That part of your state has changed, so your
                   // componenet is re-rendered

Redux использует этот подход по соображениям производительности. Потребовалось очень много времени, чтобы пройти через большое дерево состояний, чтобы посмотреть, все ли одинаково. Когда вы держите дерево неизменным, требуется только тест ===, и процесс становится намного проще.