【问题标题】:My Redux state has changed, why doesn't React trigger a re-render?我的 Redux 状态发生了变化,为什么 React 没有触发重新渲染?
【发布时间】:2017-01-23 15:15:48
【问题描述】:

我正在尝试设计一个通知组件,通知将在某些情况下出现(如连接问题、成功修改等)。

我需要通知在几秒钟后消失,因此我正在触发状态更改以从通知的setTimeout 内的 setTimeout 删除 Redux 状态的通知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 组件(MainNotification):

/* 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>
        )
    }

};

【问题讨论】:

  • 如果组件接收到新属性,它应该重新渲染自己。尝试在主组件的render 方法中记录this.props.notifications,看看通知是否真的发生了变化。

标签: reactjs redux react-redux


【解决方案1】:

您已经正确连接了所有内容,但是您缺少 Redux 的一个关键概念:

使用 Redux,您永远不会改变 state 的任何部分。

来自Redux guide

你不应该在 reducer 中做的事情:

  • 改变其参数;
  • 执行 API 调用和路由转换等副作用;
  • 调用非纯函数,例如Date.now() 或 Math.random()。

deleteSingleNotification 中,您使用 .splice 将旧通知从您的数组中删除。相反,您需要返回一个全新的数组,其中缺少不需要的通知。最简单的方法是使用 .filter 函数:

function deleteSingleNotification(notifications, notificationId){
    return notifications.filter (notification => {
        return notification.id !== notificationId
    }
}
Here is a JSBin with your working notification system!

这就是为什么它起作用的原因: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 出于性能原因使用这种方法。遍历一个大型状态树以查看一切是否相同需要很长时间。当你保持你的树不可变时,只需要一个=== 测试并且这个过程变得更加容易。

【讨论】:

  • 谢谢,这就是问题所在!我意识到需要避免突变,但我认为我实现它的方式是一种不可变的方式。使用 Object.assign 不会将新项目返回到状态?对我来说,过去状态的拼接并不重要,因为该函数只是返回一个新状态,尽管发生了变异,但它将使用 Object.assign 方法返回一个新状态。我错过了什么吗?
  • @Nicolas Object.assign () 只做浅拷贝。所以状态被复制了,但是通知数组被改变了。
  • 我也遇到过类似的情况。我在哪里使用这个 let newState = state;新状态.值 = 新值;在我的情况下,副本是通过引用,因此我实际上是在改变父语句。为了做深拷贝,我做了以下,终于解决了我的问题——let newState = {...state};
猜你喜欢
  • 2022-01-13
  • 1970-01-01
  • 1970-01-01
  • 2020-07-26
  • 1970-01-01
  • 2021-06-12
  • 2020-11-05
  • 2023-03-20
  • 2022-11-12
相关资源
最近更新 更多