修改 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  
})

问题

活动活动下拉框的内容在页面加载时工作正常 - 并且状态树确实更新 - 但它不会在所选网站更改时更新。据我所知:

  1. 我正在正确调度动作
  2. 我正在更新状态,而不是改变它

我很失望 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()) - 顺便说一下,如果可能的话也应该避免。