修改 redux store 时组件不更新
Component not updating when redux store modified
我正在使用 redux thunk 来 return 一个 API 调用一个动作:
export const getActiveCampaigns = () => {
return (dispatch, getState) => {
const bearer = 'Bearer ' + getState().login.bearer
return axios.get(API.path + 'campaign/active/?website_id=' + getState().selectedWebsite.selectedWebsite + '&' + API.beaconAPI_client, { headers: { 'Authorization': bearer } })
.then(function (response) {
dispatch({
type: GET_ACTIVE_CAMPAIGNS,
activeCampaigns: response.data.response
})
})
}
}
它成功地工作 returns 一个活动列表,我正在使用以下方法将其呈现到另一个组件中:
class ActiveCampaignsDropdown extends Component {
// usual stuff
componentDidMount(){
this.props.dispatch(getActiveCampaigns())
}
// render function displays campaigns using this.props.activeCampaigns
}
const mapStateToProps = (state) => {
return {
activeCampaigns: state.activeCampaigns.activeCampaigns
}
}
但是,请注意 getState.selectedWebsite.selectedWebsite
操作。这是通过应用程序其他地方的操作设置的,用户从下拉列表中选择一个网站。我的减速器看起来像这样:
export default function (state = {}, action) {
switch(action.type){
case SET_SELECTED_WEBSITE:
return {
...state,
selectedWebsite: action.websiteId
}
default:
return state;
}
}
export default function (state = {}, action) {
switch(action.type){
case GET_ACTIVE_CAMPAIGNS:
return {
...state,
activeCampaigns: action.activeCampaigns
}
default:
return state;
}
}
我设置所选网站的操作:
export const setSelectedWebsite = (websiteId) => {
return {
type: SET_SELECTED_WEBSITE,
websiteId
}
}
这与其他减速器结合在一起,如下所示:
export default combineReducers({
login,
activeWebsites,
activeCampaigns,
selectedWebsite
})
问题
活动活动下拉框的内容在页面加载时工作正常 - 并且状态树确实更新 - 但它不会在所选网站更改时更新。据我所知:
- 我正在正确调度动作
- 我正在更新状态,而不是改变它
我很失望 Redux 在这种情况下不是 "just working",尽管我可能只睡了几个小时就忽略了一些愚蠢的事情!任何帮助表示赞赏。
在 React 中,组件会在以下三种情况之一发生时更新:
- 道具已更改
- 状态改变
- forceUpdate() 被调用
根据您的情况,您希望在商店 state.activeCampaigns
发生变化时更新 ActiveCampaignsDropdown
。为此,您必须连接您的组件,以便它接收此值作为道具(从而在它更改时强制更新)。
这可以按如下方式完成:
import {connect} from 'react-redux'
class ActiveCampaignsDropdown extends React.Component { ... }
const mapStateToProps = (state) => ({activeCampaigns: state.activeCampaigns});
const Container = connect(mapStateToProps)(ActiveCampaignsDropdown);
export default Container;
最终的 Container
组件将通过其属性完成将 ActiveCampaignsDropdown
与所需商店状态连接起来的所有工作。
Redux 的 connect()
还允许我们连接调度函数以修改商店中的数据。例如:
// ... component declaration
// ... mapStateToProps
const mapDispatchToProps = (dispatch) =>
{
return {
getActiveCampaigns: () => dispatch(getActiveCampaigns())
};
}
const Container = connect(mapStateToProps, mapDispatchToProps)(ActiveCampaignsDropdown);
定义映射函数、创建容器组件并渲染容器后,ActiveCampaignsDropdown
将正确连接。在我的示例中,它将接收 'activeCampaigns' 和 'getActiveCampaigns' 作为道具,并在它们的值发生变化时相应地更新。
编辑:
再次查看您的代码后,我认为您的问题是由于当网站更改时没有满足更新 ActiveCampaignsDropdown
的条件。通过从您的 WebsiteDropdown 调用 getActiveCampaigns()
(根据您的评论),这会强制 state.activeCampaigns
进行更改,从而成功更新 ActiveCampaignsDropdown
。正如我在其中一条评论中提到的,'forcing' 从一个不负责这样做的组件进行的这种更改将被视为不良做法。
一个完全合理的解决方案是 ActiveCampaignsDropdown
到 'listen' 更改当前网站并相应地更新自身。为此,您需要做两件事:
(1) 将网站状态映射到组件
const mapStateToProps = (state) => {
return {
activeCampaigns: state.activeCampaigns.activeCampaigns, // unsure why structured like this
selectedWebsite: state.selectedWebsite.selectedWebsite
}
}
(2) 将您的调度调用移至 componentWillReceiveProps
class ActiveCampaignsDropdown extends React.Component
{
// ...
componentWillReceiveProps(nextProps)
{
if (this.props.selectedWebsite !== nextProps.selectedWebsite)
{
this.props.getActiveCampaigns();
}
}
}
现在每次选定的网站发生变化时,都会进行刷新并调用 componentWillReceiveProps()
(导致 activeCampaigns
也进行更新)。应用此更新后,将进行另一次刷新,呈现的下拉列表将包含新更新的广告系列。
一些小改进:
如果你的一些组件依赖于当前网站的状态(我想是很多),那么你可以考虑通过 context
.[=31 来提供它们。 =]
现在你的 ActiveCampaignsDropdown
收到 'selectedWebsite' 作为道具,你可以将它直接传递给你的动作函数,而不是让它从状态中获取它(使用 getState()
) - 顺便说一下,如果可能的话也应该避免。
我正在使用 redux thunk 来 return 一个 API 调用一个动作:
export const getActiveCampaigns = () => {
return (dispatch, getState) => {
const bearer = 'Bearer ' + getState().login.bearer
return axios.get(API.path + 'campaign/active/?website_id=' + getState().selectedWebsite.selectedWebsite + '&' + API.beaconAPI_client, { headers: { 'Authorization': bearer } })
.then(function (response) {
dispatch({
type: GET_ACTIVE_CAMPAIGNS,
activeCampaigns: response.data.response
})
})
}
}
它成功地工作 returns 一个活动列表,我正在使用以下方法将其呈现到另一个组件中:
class ActiveCampaignsDropdown extends Component {
// usual stuff
componentDidMount(){
this.props.dispatch(getActiveCampaigns())
}
// render function displays campaigns using this.props.activeCampaigns
}
const mapStateToProps = (state) => {
return {
activeCampaigns: state.activeCampaigns.activeCampaigns
}
}
但是,请注意 getState.selectedWebsite.selectedWebsite
操作。这是通过应用程序其他地方的操作设置的,用户从下拉列表中选择一个网站。我的减速器看起来像这样:
export default function (state = {}, action) {
switch(action.type){
case SET_SELECTED_WEBSITE:
return {
...state,
selectedWebsite: action.websiteId
}
default:
return state;
}
}
export default function (state = {}, action) {
switch(action.type){
case GET_ACTIVE_CAMPAIGNS:
return {
...state,
activeCampaigns: action.activeCampaigns
}
default:
return state;
}
}
我设置所选网站的操作:
export const setSelectedWebsite = (websiteId) => {
return {
type: SET_SELECTED_WEBSITE,
websiteId
}
}
这与其他减速器结合在一起,如下所示:
export default combineReducers({
login,
activeWebsites,
activeCampaigns,
selectedWebsite
})
问题
活动活动下拉框的内容在页面加载时工作正常 - 并且状态树确实更新 - 但它不会在所选网站更改时更新。据我所知:
- 我正在正确调度动作
- 我正在更新状态,而不是改变它
我很失望 Redux 在这种情况下不是 "just working",尽管我可能只睡了几个小时就忽略了一些愚蠢的事情!任何帮助表示赞赏。
在 React 中,组件会在以下三种情况之一发生时更新:
- 道具已更改
- 状态改变
- forceUpdate() 被调用
根据您的情况,您希望在商店 state.activeCampaigns
发生变化时更新 ActiveCampaignsDropdown
。为此,您必须连接您的组件,以便它接收此值作为道具(从而在它更改时强制更新)。
这可以按如下方式完成:
import {connect} from 'react-redux'
class ActiveCampaignsDropdown extends React.Component { ... }
const mapStateToProps = (state) => ({activeCampaigns: state.activeCampaigns});
const Container = connect(mapStateToProps)(ActiveCampaignsDropdown);
export default Container;
最终的 Container
组件将通过其属性完成将 ActiveCampaignsDropdown
与所需商店状态连接起来的所有工作。
Redux 的 connect()
还允许我们连接调度函数以修改商店中的数据。例如:
// ... component declaration
// ... mapStateToProps
const mapDispatchToProps = (dispatch) =>
{
return {
getActiveCampaigns: () => dispatch(getActiveCampaigns())
};
}
const Container = connect(mapStateToProps, mapDispatchToProps)(ActiveCampaignsDropdown);
定义映射函数、创建容器组件并渲染容器后,ActiveCampaignsDropdown
将正确连接。在我的示例中,它将接收 'activeCampaigns' 和 'getActiveCampaigns' 作为道具,并在它们的值发生变化时相应地更新。
编辑:
再次查看您的代码后,我认为您的问题是由于当网站更改时没有满足更新 ActiveCampaignsDropdown
的条件。通过从您的 WebsiteDropdown 调用 getActiveCampaigns()
(根据您的评论),这会强制 state.activeCampaigns
进行更改,从而成功更新 ActiveCampaignsDropdown
。正如我在其中一条评论中提到的,'forcing' 从一个不负责这样做的组件进行的这种更改将被视为不良做法。
一个完全合理的解决方案是 ActiveCampaignsDropdown
到 'listen' 更改当前网站并相应地更新自身。为此,您需要做两件事:
(1) 将网站状态映射到组件
const mapStateToProps = (state) => {
return {
activeCampaigns: state.activeCampaigns.activeCampaigns, // unsure why structured like this
selectedWebsite: state.selectedWebsite.selectedWebsite
}
}
(2) 将您的调度调用移至 componentWillReceiveProps
class ActiveCampaignsDropdown extends React.Component
{
// ...
componentWillReceiveProps(nextProps)
{
if (this.props.selectedWebsite !== nextProps.selectedWebsite)
{
this.props.getActiveCampaigns();
}
}
}
现在每次选定的网站发生变化时,都会进行刷新并调用 componentWillReceiveProps()
(导致 activeCampaigns
也进行更新)。应用此更新后,将进行另一次刷新,呈现的下拉列表将包含新更新的广告系列。
一些小改进:
如果你的一些组件依赖于当前网站的状态(我想是很多),那么你可以考虑通过
context
.[=31 来提供它们。 =]现在你的
ActiveCampaignsDropdown
收到 'selectedWebsite' 作为道具,你可以将它直接传递给你的动作函数,而不是让它从状态中获取它(使用getState()
) - 顺便说一下,如果可能的话也应该避免。