我的 Redux 状态发生了变化,为什么 React 没有触发重新渲染? [英] My Redux state has changed, why doesn't React trigger a re-render?

查看:113
本文介绍了我的 Redux 状态发生了变化,为什么 React 没有触发重新渲染?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我需要通知在几秒钟后消失,因此我触发了状态更改,以从通知的 componentDidMount 内的 setTimeout 中删除 Redux 状态中的通知.

我可以看到状态确实发生了变化,但是 React-Redux 没有重新渲染父组件,因此通知仍然出现在 DOM 上.

这是我的 Redux 减速器:

const initialState = {通知:[]}导出默认函数(状态 = 初始状态,动作){开关(动作.类型){案例 CLEAR_SINGLE_NOTIFICATION:返回 Object.assign ({}, state, {通知:deleteSingleNotification(state.notifications, action.payload)})案例 CLEAR_ALL_NOTIFICATIONS:返回 Object.assign ({}, state, {通知:[]})默认:返回状态}}函数 deleteSingleNotification(通知,notificationId){通知.一些(功能(通知,索引){return (notifications [index] ['id'] === notificationId) ?!!(notifications.splice(index, 1)) :错误的;})返回通知;}

和我的 React 组件(MainNotification):

/* MAIN.JS */类 Main 扩展组件 {renderDeletedVideoNotifications() {console.log('渲染通知');const clearNotification = this.props.clearNotification;返回 this.props.notifications.map((notification)=> {返回<通知键={notification.id}消息={通知.消息}style={notification.style}clearNotification={clearNotification}NotificationId={notification.id}/>});}使成为() {console.log('重新渲染');返回 (<div className="_main"><导航栏位置={this.props.location} logStatus={this.props.logStatus}logOut={this.logout.bind(this)}/><div className="_separator"></div>{this.props.children}<底层堆栈>{this.renderDeletedVideoNotifications()}</BottomStack>

);}}函数 mapStateToProps(state) {返回 {logStatus:state.logStatus,通知:state.notifications.notifications};}函数 mapDispatchToProps(dispatch) {return bindActionCreators({checkLogStatus, logOut, clearNotification, clearAllNotifications}, dispatch);}导出默认连接(mapStateToProps,mapDispatchToProps)(主);/* 通知.JS */导出默认类通知扩展组件{构造函数(道具){超级(道具);this.state = {显示:真}}componentWillReceiveProps(nextProps){如果(nextProps.message){this.setState({show: true});}}清除通知(通知 ID){this.props.clearNotifications(notificationId);}componentDidMount(){console.log('通知挂载');setTimeout(()=>{console.log('超时');this.props.clearNotification(this.props.notificationId);}, 1000);}关闭通知(){this.props.clearNotification(this.props.notificationId);this.setState({show: false});}使成为(){const 通知样式 = () =>{if (this.props.style === "错误"){返回 {backgroundColor: 'rgba(152, 5, 19, 0.8)'}}返回 {backgroundColor: 'rgba(8, 130, 101, 0.8)'}};如果(!this.state.show){返回空;}返回 (<div className="notification" style={notificationStyles()}><div className="notificationCloseButton" onClick={this.closeNotification.bind(this)}><i className="material-icons">关闭</i>

{this.props.message}

)}};

解决方案

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

使用 Redux,你永远改变state的任何部分.

来自 Redux 指南:

<块引用>

在减速器中你永远不应该做的事情:

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

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

function deleteSingleNotification(notifications, notificationId){返回notifications.filter (notification => {返回notification.id !== notificationId}}

这是一个 JSBin 和您的工作通知系统!


这就是为什么这样做的原因:React-Redux 的工作是在 Redux 存储的特定部分发生更改时更新您的组件.它对状态树的每个部分使用 === 测试来了解是否有任何更改.

当您使用 .splice 之类的内容更改状态时,它会检查并认为没有任何不同.

以下是演示问题的示例:

var array = [ 'a', 'b', 'c' ]var oldArray = 数组array.splice (1, 1)//切出 'b'oldArray === 数组//=>真的!使用 .splice 更改了两个数组,//所以 React-Redux *不* 更新任何东西

相反,React-Redux 需要我们这样做:

var array = [ 'a', 'b', 'c' ]var oldArray = 数组array = array.filter (item, index => index !== 1)//没有 'b' 的新数组oldArray === 数组//假.你状态的那部分发生了变化,所以你的//组件重新渲染

Redux 出于性能原因使用这种方法.遍历一个大的状态树以查看是否一切都相同需要很长时间.当你保持你的树不可变时,只需要一个 === 测试并且这个过程变得更容易.

I am trying to design a notification component where notifications will appear on certain occasions (like connections problems, successful modifications, etc.).

I need the notifications to disappear after a couple of seconds, so I am triggering a state change to delete the notification from Redux state from setTimeout inside the notification's componentDidMount.

I can see that the state does change, but React-Redux is not re-rendering the parent component so the notification still appears on the DOM.

Here is my Redux reducer:

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;
}

and my React components (Main and 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>
        )
    }

};

解决方案

You've got everything hooked up correctly, but you're missing one key concept for Redux:

With Redux, you never mutate any part of state.

From the Redux guide:

Things you should never do inside a reducer:

  • Mutate its arguments;
  • Perform side effects like API calls and routing transitions;
  • Call non-pure functions, e.g. Date.now() or Math.random().

In deleteSingleNotification, you're using .splice to cut the old notification out of your array. Instead, you need to return a brand new array with the unwanted notification missing from it. The easiest way to do this is with the .filter function:

function deleteSingleNotification(notifications, notificationId){
    return notifications.filter (notification => {
        return notification.id !== notificationId
    }
}

Here is a JSBin with your working notification system!


So here is why this works: React-Redux's job is to update your components whenever a specific part of your Redux store is changed. It uses a === test on every part of the state tree to know if anything changed.

When you go and change the state with something like .splice, it checks and thinks nothing is different.

Here's an example to demonstrate the problem:

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

Instead, React-Redux needs us to do this:

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 uses this approach for performance reasons. It takes a really long time to loop through a big state tree looking to see if everything is the same. When you keep your tree immutable, only a === test is needed and the process gets much easier.

这篇关于我的 Redux 状态发生了变化,为什么 React 没有触发重新渲染?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆