我的 Redux 状态发生了变化,为啥 React 没有触发重新渲染?
Posted
技术标签:
【中文标题】我的 Redux 状态发生了变化,为啥 React 没有触发重新渲染?【英文标题】: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 组件(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>
)
;
【问题讨论】:
如果组件接收到新属性,它应该重新渲染自己。尝试在主组件的render
方法中记录this.props.notifications
,看看通知是否真的发生了变化。
【参考方案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;以上是关于我的 Redux 状态发生了变化,为啥 React 没有触发重新渲染?的主要内容,如果未能解决你的问题,请参考以下文章