重定向时的React导航组件在刷新时会丢失数据

时间:2018-09-20 22:46:19

标签: reactjs

我正在使用React 16.4.1和React-Router-Dom 4.3.1。我已经使用一个Web组件构建了一个侧边栏react组件,该组件将数据传递到该Web组件中,并呈现该组件。

这是侧边栏组件。

import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import { Redirect } from 'react-router-dom';

class Sidebar extends React.Component {
    constructor(props, context) {
      super(props, context);
      this.mySideBarRef = React.createRef();
      this.menuClick = this.menuClick.bind(this);

      this.state = {
        redirect: false,
        redirectUrl: '/',
        menuData: [
          {
            groupName: 'Group 1',
            icon: 'Home',
            children: [
              {
                title: 'Home',
                icon: 'favorite',
                url: '/',
              },
              {
                title: 'Grp 1 Item 2',
                children: [
                {
                title: 'Roles',
                icon: 'Person',
                url: '/roles',
              },
              {
                title: 'About',
                icon: 'gesture',
                url: '/about',
              },
                  {
                    title: 'Google',
                    icon: 'world',
                    url: 'http://www.google.co.uk',
                  },
                ],
              },
            ],
          },
          {
            groupName: 'Group 2',
            icon: 'search',
            children: [
              {
                title: 'Grp 2 Item 1',
                icon: 'microsoft',
                children: [
                  {
                    title: 'Grp 2 Item 1.1 Bing',
                    icon: '',
                    url: 'http://www.google.co.uk',
                  },
                  {
                    title: 'Grp 2 Item 1.2 Bing',
                    icon: '',
                    url: 'http://www.bing.com',
                  },
                ],
              },
            ],
          },
        ],
      };
    }

    componentDidMount() {
      let sideMenuContainer = this.mySideBarRef.current;
      sideMenuContainer.sideMenuData = this.state.menuData;
      sideMenuContainer.overrideCustomUrlEvent = true;
      window.addEventListener('customUrlEvent', this.menuClick)
    }

    menuClick(e) {
      console.log(e);
      this.setState({
          redirect: true,
          redirectUrl: e.url
      });
    }

    componentWillUnmount() {
      this.mySideBarRef.current.removeEventListener('customUrlEvent', this.menuClick);

    }

    render() {
      if(this.state.redirect) {
        this.state.redirect = false;
        return <Redirect to={this.state.redirectUrl} />;
      }


      if(this.state.menuData !== null) {
        return(
          <side-bar-component 
            ref={this.mySideBarRef}
            collapsable>
          </side-bar-component>
        );
      }
    }
  }

export default Sidebar;

在加载侧栏组件时,它会正确呈现数据,一旦单击链接之一,它就会重定向页面,但不会加载数据,因为未触发componentDidMount。这是app.js

// This component handles the App template used on every page.
import React from 'react';
import PropTypes from 'prop-types';
import { Route } from 'react-router-dom';
import Header from './common/header';
import {connect} from 'react-redux';
import HomePage from './home/home-page';
import RolesPage from './role/roles-page';
import AboutPage from './about/about-page';
import { ManageRolePage } from "./role/manage-role-page";
import Sidebar from "./common/sidebar"; //eslint-disable-line import/no-named-as-default

class App extends React.Component {
  render() {
    return (
      <div>
        <Header
          loading={this.props.loading}
        />
        <div>
          <div className="demo-sidebar-container">
              <Sidebar />
          </div>
          <div className="demo-content">
            <Route exact path="/" component={HomePage}/>
            <Route path="/roles" component={RolesPage}/>
            <Route path="/role/:id" component={ManageRolePage}/>
            <Route path="/role" component={ManageRolePage} exact />
            <Route path="/about" component={AboutPage}/>
          </div>
        </div>
      </div>
    );
  }
}

App.propTypes = {
  loading: PropTypes.bool.isRequired,
  match: PropTypes.object.isRequired
};

function mapStateToProps(state, ownProps) {
  return {
    loading: state.ajaxCallsInProgress > 0
  };
}

export default connect(mapStateToProps)(App);

对于新的反应,我认为我缺少一个基本步骤,我要实现的目标是进行重定向,还可以根据业务逻辑进行选择,以将数据重新加载到侧边菜单中或保留数据作为单击项将被展开,并希望保留侧边菜单数据。上面的示例中有一个硬编码的数据集,但最终我将获得一个可以参数化的服务。关于我要实现的目标的任何指导或示例将不胜感激。

**

  

阅读亚当斯的评论后,我进行了以下更改:   将所有代码放到sidebar-container.js和现在的sidebar.js中   看起来像这样;

**

import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import { Redirect } from 'react-router-dom';
const Sidebar = React.forwardRef((props, ref) => (
    <dijits-ui-dds-sidebar
      ref={ref}
      collapsable>
    </dijits-ui-dds-sidebar>
));
export default Sidebar;

**

  

我的sidebar-container.js文件现在包含用于   控制侧栏:

**

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as sideMenuActions from '../../actions/side-bar-actions';
import Sidebar from "./sidebar";
import sideMenu from "../../reducers/side-menu-reducer";
import {Redirect} from "react-router-dom";

class SidebarContainer extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.mySideBarRef = React.createRef();
    this.menuClick = this.menuClick.bind(this)

    this.state = {
      redirect: false,
      redirectUrl: '/',
      sideMenu: Object.assign({}, this.props.sideMenu),
    };
  }

  componentDidMount() {
    let sideMenuContainer = this.mySideBarRef.current;
    if(this.state.sideMenu.menuData !== undefined) {
      sideMenuContainer.sideMenuData = this.state.sideMenu.menuData;
    }
    sideMenuContainer.overrideCustomUrlEvent = true;
    window.addEventListener('customUrlEvent', this.menuClick);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.sideMenu !== this.state.sideMenu) {
      let sideMenuContainer = this.mySideBarRef.current;
      if(nextProps.sideMenu.menuData !== undefined) {
         sideMenuContainer.sideMenuData = nextProps.sideMenu.menuData;
      }
    }
  }

  componentWillUnmount() {
    this.mySideBarRef.current.addEventListener('customUrlEvent', this.menuClick);
  }

  menuClick(e) {
     console.log(e);
    this.setState({
      redirect: true,
      redirectUrl: e.url
    });
  }

  render() {
    if(this.state.redirect) {
      this.setState({ redirect: false} );
      return <Redirect to={this.state.redirectUrl} />;
    }

    return (
      <div>
        <Sidebar sideMenu={this.props.sideMenu} ref={this.mySideBarRef}/>
      </div>
    );
  }
}

SidebarContainer.propTypes = {
  sideMenu: PropTypes.object.isRequired
};

function mapStateToProps(state, ownProps) {
  debugger;
  return {
    sideMenu: state.sideMenu,
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(sideMenuActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps) (SidebarContainer);

**

  

侧边栏容器位于我的app.js中。

**

import React from 'react';
import PropTypes from 'prop-types';
import { Route } from 'react-router-dom';
import Header from './common/header';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import HomePage from './home/home-page';
import RolesPage from './role/roles-page';
import AboutPage from './about/about-page';
import { ManageRolePage } from "./role/manage-role-page";
import SidebarContainer from "./common/sidebar-container"; //eslint-disable-line import/no-named-as-default

class App extends React.Component {
  render() {
    return (
      <div>
        <Header
          loading={this.props.loading}
        />
        <div>
          <div className="sidebar-container">
              <SidebarContainer sideMenu={this.props.sideMenu} />
          </div>
          <div className="content">
            <Route exact path="/" component={HomePage}/>
            <Route path="/roles" component={RolesPage}/>
            <Route path="/role/:id" component={ManageRolePage}/>
            <Route path="/role" component={ManageRolePage} exact />
            <Route path="/about" component={AboutPage}/>
          </div>
        </div>
      </div>
    );
  }
}

App.propTypes = {

  loading: PropTypes.bool.isRequired,
  sideMenu: PropTypes.object.isRequired,
  match: PropTypes.object.isRequired
};

function mapStateToProps(state, ownProps) {
  debugger;
  return {
    loading: state.ajaxCallsInProgress > 0,
    sideMenu: state.sideMenu,
  };
}

export default connect(mapStateToProps)(App);

**

  

但是我仍然遇到与单击以下项目时相同的问题   侧边菜单栏,将我路由到其他路线,该菜单会丢失   数据。我知道原因,但尝试为此寻求解决方案。   容器中的menuClick(e)函数设置了redirect属性   和redirectUrl状态值刷新状态并强制   要重新加载的父容器,我也在渲染中需要设置   重定向回false的状态。这导致父母   刷新容器,然后强制边栏刷新。一世   感觉我在反应中缺少某些东西或误解了一些东西   做事的方式。我也有一个动作来执行   侧栏的初始负载,看起来像componentDidMount   没有为子组件设置propType,所以我添加了   选择了propType的componentWillReceiveProps生命周期方法   变化。这是否意味着我不需要以下内容   在componentDidMount方法中排成一行?

**

if(this.state.sideMenu.menuData !== undefined) {
  sideMenuContainer.sideMenuData = this.state.sideMenu.menuData;
}

**

  

对我的反应迟钝表示歉意,但我期待着您的光临   帮助。再次感谢社区的支持。

**

还包括我的route.js,以防可能需要解决此问题。

import React from 'react';
import { Route, IndexRoute, Switch } from 'react-router-dom';
// import { Route, IndexRoute  } from 'react-router';
import App from './components/app';
import HomePage from './components/home/home-page';
import AboutPage from './components/about/about-page';
import RolesPage from './components/role/roles-page';
import ManageRolePage from './components/role/manage-role-page'; //eslint-disable-line import/no-named-as-default

export default (
  <Switch>
    <Route path="/" components={App}>
      <IndexRoute component={HomePage} />
      <Route path="roles" component={RolesPage}/>
      <Route path="role" component={ManageRolePage}/>
      <Route path="role/:id" component={ManageRolePage}/>
      <Route path="about" component={AboutPage}/>
    </Route>
  </Switch>
);

再玩一会儿后,我将menuClick事件更改为使用私有变量,而不是将状态变量用于redirectUrl或重定向标志。

menuClick(e) {
    console.log(e);
    this.redirectUrl = e.url;
    this.redirect = true;
  }

这表示状态没有被更新,但是我可以保留菜单扩展,但是页面不会重定向,因为我没有点击渲染功能事件。

1 个答案:

答案 0 :(得分:2)

只要状态或道具发生变化,组件就会重新渲染,这意味着它们的render方法和相关的生命周期方法将再次运行。

关于您的代码,我最注意到的是您正在使用Route,但没有使用SwitchBrowserRouter。这可能是相关的。我不能肯定地说,但是由于您的<SideBar>组件位于Routes的上游,因此除非状态或道具发生变化,否则它不会重新渲染或更新。

我认为您的侧边栏不会更新,数据最初可能会加载,但会过时。

但是请别担心,请允许我给您链接一篇我认为将带您进入更高层次的文章:https://medium.com/@pshrmn/a-simple-react-router-v4-tutorial-7f23ff27adf(请注意未来的个人:如果该URL将来会中断,请告诉我)

我想您会在阅读完本书后弄清楚的,但是它将详细介绍BrowserRouterSwitchRoute。我认为您需要更改代码以使用所有这三个代码。

请记住,补充工具栏(及其所有子项)仅在状态或道具更改时才会更新。

您可以执行以下操作:

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      data: {},
    }
  }
  render() {
    return (
      <div>
        <Sidebar data={this.state.data} />
        <AnotherComponent onEventOccurred={newData => this.setState({ data: newData })} />
      </div>
    )
  }
}

在React中,我们将其称为提升状态,因此,补充工具栏的父级将是控制补充工具栏所依赖的状态的组件。在补充工具栏中,您可以将该状态称为this.props.data(或者如果它是无状态功能组件,则称为props.data)。每次道具更改时,侧边栏都会重新呈现自己。

其中的第二部分显示为:

<AnotherComponent onEventOccurred={newData => this.setState({ data: newData })} />

该组件具有一个名为onEventOccurred的道具,可以从该组件内部将其称为this.props.onEventOccurred(newData),或者将功能非类组件称为props.onEventOccurred()。它实际上只是一个回调,因此您将其调用并在其父级中触发this.setState()。这是一种非常强大的模式,因为它允许父级控制将对该事件执行的操作,因为操作是在父级中定义的。子级仅触发回调。

起初我很犹豫要发布答案,但是现在我觉得您可能会从中学到很多。让我知道是否还有任何具体的不清楚的地方。我希望您阅读我首先链接的整个中型文章。其中显示的模式非常普遍且惯用。

我刚刚注意到您已安装Redux。总体而言,这确实增加了复杂性,但对于跨领域的关注而言却是一件好事。有时我们称其为横向数据加载。通常,数据在React中是单向流动的,也就是说总是从父级到子级,从上游到下游。

使用Redux,您可以从任何组件中调用动作创建者,并且补充工具栏将监听状态Object上的更改。当您希望状态在不直接彼此连接的多个组件之间共享状态时,可以使用Redux。

使用Redux,连接的组件正在侦听“基本上”全局状态树上的更改。当状态更改时,订阅的组件将更新。

例如在补充工具栏中,您可以这样做:

function mapStateToProps(state, ownProps) {
  return {
    loading: state.ajaxCallsInProgress > 0,
    data: state.sidebar,
  };
}

,并且每次this.props.data更改时,它都会更新其state.sidebar。我正在尝试将其保持在最小限度,因为我可能会将您与额外的信息混淆,但这是最重要的部分。使用connect()的组件已订阅全局状态树上的更改。

Connect是一个高阶组件,使用它包装组件,因此,当应用程序状态更改时,它将导致该组件的道具发生更改,从而触发重新渲染。