每次使用 React Router 导航到 Redux 组件时,如何阻止状态存储数据在 Redux 组件中累积

问题描述 投票:0回答:3

好吧,需要注意的是我对 redux 非常陌生。我正在做一门关于 atm 的课程,我正在尝试跳出一些框框,使用 wordpress API 和 Redux 生成一个相当标准的网站。我很欣赏 redux 通常适用于更大的事情,但这似乎是学习过程中有用的第一步。

我有一系列组件,列出了从 WordPress API 获取的帖子、页面和不同类型的自定义帖子,我使用 React-router-dom 在这些组件之间导航。

问题是,每次我返回组件/查看时,帖子或页面列表都会再次呈现,因此,例如,我第一次去那里时,列表可能是:测试帖子 1、测试帖子 2、第二个时间将是:测试帖子1,测试帖子2,测试帖子1,测试帖子2,第三次:测试帖子1,测试帖子2,测试帖子1,测试帖子2,测试帖子1,测试帖子2等等等

原因很明显,每次渲染组件时,都会从存储中提取数据并进行渲染,但是,由于整个应用程序不会像使用普通旧的 ReactJS 那样重新渲染,因此不会被清除。

我的问题当然是解决这个问题的最佳方法是什么。我读过一些相关的帖子,建议在组件上附加某种条件来检查数据是否已经存在,但我不知道如何做到这一点,也不知道如何做。我的尝试没有成功,因为似乎在 render 方法中看不到从 componentDidMount 返回的任何 var。

提前致谢。

代码如下:

src/index.js

import React from "react";
import { BrowserRouter as Router } from 'react-router-dom';
import { render } from "react-dom";
import { Provider } from "react-redux";
import store from "./js/store/index";
import App from "./js/components/App";
render(
  <Router>
    <Provider store={store}>
      <App />
    </Provider>
  </Router>,
  document.getElementById("root")
);

src/js/index.js

import store from "../js/store/index";
window.store = store;

src/js/store/index.js

import { createStore, applyMiddleware, compose } from "redux";
import rootReducer from "../reducers/index";
import thunk from "redux-thunk";
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  rootReducer,
  storeEnhancers(applyMiddleware(thunk))
);
export default store;

src/js/reducers/index.js

import { POSTS_LOADED } from "../constants/action-types";
import { PAGES_LOADED } from "../constants/action-types";

const initialState = {
  posts: [],
  pages: [],
  banner_slides: [],
  portfolio_items: []
};

function rootReducer(state = initialState, action) {

    switch (action.type) {
      case 'POSTS_LOADED':
          return Object.assign({}, state, {
            posts: state.posts.concat(action.payload)
          });

      case 'PAGES_LOADED':
          return Object.assign({}, state, {
            pages: state.pages.concat(action.payload)
          });

      default: 
          return state;
    }
}

export default rootReducer;

src/js/actions/index.js

export function getWordpress(endpoint) {
    return function(dispatch) {
        return fetch("http://localhost/all_projects/react-wpapi/my_portfolio_site/wordpress/wp-json/wp/v2/" + endpoint )
            .then(response => response.json())
            .then(json => { 
            dispatch({ type: endpoint.toUpperCase() + "_LOADED", payload: json });
        });
    };
}

src/js/constants/action-types.js

export const ADD_ARTICLE = "ADD_ARTICLE";
export const POSTS_LOADED = "POSTS_LOADED";
export const PAGES_LOADED = "PAGES_LOADED";

src/js/components/app.js

import React from "react";
import { Route, Switch, Redirect } from 'react-router-dom';

import Header from "./Header/Header";
import Posts from "./Posts";
import Pages from "./Pages";
import BannerSlides from "./BannerSlides";
import PortfolioItems from "./PortfolioItems";

const App = () => (
    <div>
        <Header />
        <Route render = {({ location }) => (
            <Switch location={location}>
            <Route 
                exact path="/posts"
                component={Posts} 
            />   
            <Route 
                exact path="/pages"
                component={Pages} 
            />   
            </Switch>
        )} />
    </div>
);
export default App;

src/js/components/Posts.js

import React, { Component } from "react";
import { connect } from "react-redux";
import { getWordpress } from "../actions/index";
export class Posts extends Component {

  componentDidMount() {
      this.props.getWordpress('posts');
      let test = 1;
      return test;
  }

  render() {
    console.log("test: ", test); // not defined
      if (test !== 1) { 
          return (
            <ul>
              {this.props.posts.map(item => ( 
                <li key={item.id}>{item.title.rendered}</li>
              ))}
            </ul>
          );
      }
  }
}
function mapStateToProps(state) {
    return {
        posts: state.posts.slice(0, 10)
    };
}
export default connect(
  mapStateToProps,
  { getWordpress }
)(Posts);
reactjs redux react-redux react-router navigation
3个回答
1
投票

问题是,每次获取数据时,都会将其添加到数组中以前的数据中。这就是为什么它随着时间的推移而重复。只需分配而不是在你的减速器中添加

function rootReducer(state = initialState, action) {
switch (action.type) {
  case 'POSTS_LOADED':
      return {
        ...state,
        posts: action.payload
      };

  case 'PAGES_LOADED':
      return {
        ...state,
        pages: action.payload
      };

  default: 
      return state;
 }
}

1
投票

如果我理解的话,您只想在第一次安装时获取初始帖子,而不是每次安装组件时获取?

src/js/components/Posts.js
中,您可以在 CDM 生命周期方法中获取之前检查是否有任何帖子存储在 Redux 中。例如。

componentDidMount() {
  // access props.posts which you set inside mapDispatchToProps
  if (this.props.posts.length === 0) {
    this.props.getWordpress('posts');
  }
}

如果您可以接受每次挂载时重复的 API 调用,并且可以一次获取所有帖子,则可以调整您的减速器以覆盖帖子数组而不是连接。但覆盖它假设您想要在 1 个 API 调用中加载所有帖子,而不是加载每页 25 个帖子或使用“加载更多帖子”按钮。


1
投票

在调用 fetch 之前,您需要检查您的状态。我喜欢将大部分逻辑放在应用程序的 redux 部分(胖动作创建者)中,并仅使用我的 React 组件来渲染当前状态。我会推荐这样的东西:

    export function getWordpress(endpoint) {
        return function(dispatch, getState) {
            const currentState = getState();
            if (currentState.posts && currentState.posts.length) {
            // already initialized, can just return current state
                return currentState.posts;
            }
            return fetch("http://localhost/all_projects/react-wpapi/my_portfolio_site/wordpress/wp-json/wp/v2/" + endpoint )
                .then(response => response.json())
                .then(json => { 
                dispatch({ type: endpoint.toUpperCase() + "_LOADED", payload: json });
            });
        };
    }

稍后,如果帖子被初始化到选择器中,您可以分离逻辑并添加一些额外的层(例如帖子是否过时)。这样您的“业务”逻辑就可以轻松测试并与您的 UI 分离。

希望这有帮助:)

© www.soinside.com 2019 - 2024. All rights reserved.