如何在React中换出组件?

时间:2018-07-18 23:03:59

标签: javascript reactjs navigation


我正在尝试使用React构建一个单页应用程序。当前,我有一个名为App的组件,它由ReactDOM渲染,它包含导航,然后是要在导航组件之后渲染的组件(或页面)。

import React from 'react';
import Navigation from './Navigation';
import NavigationLink from './NavigationLink';
import Home from './Home';
import About from './About';

const App = () => (
  <div>
    <Navigation>
      <NavigationLink onClick={ ... }>Home</NavigationLink>
      <NavigationLink onClick={ ... }>About</NavigationLink>
    </Navigation>
    <Home />
  </div>
);

export default App;

我希望能够选择“关于”链接,并将Home组件更新为About。我正在寻找解决此问题的解决方案,以解决两个以上的要素(或页面)的问题。

这是我当前的有效代码,尽管解决方案很差。

import React, { Component } from 'react';
import {
  NavItem,
  NavLink,
  UncontrolledDropdown,
  DropdownToggle,
  DropdownMenu,
  DropdownItem,
} from 'reactstrap';
import firebase from '../../utils/firebase';
import Navigation from '../../components/Navigation/Navigation';
import Login from '../Login/Login';
import Profile from '../Profile/Profile';
import Home from '../Home/Home';

class App extends Component {
  state = {
    currentShowingPage: 0,
    currentlyLoggedInUser: null,
    isLoginModalOpen: false,
  };

  componentDidMount = () => {
    firebase.auth().onAuthStateChanged((currentUser) => {
      if (currentUser) {
        this.setState({
          currentlyLoggedInUser: currentUser,
          isLoginModalOpen: false,
        });
      } else {
        this.setState({
          currentlyLoggedInUser: null,
        });
      }
    });
  };

  handleLoginModalToggle = () => {
    this.setState((previousState) => ({
      isLoginModalOpen: !previousState.isLoginModalOpen,
    }));
  };

  render = () => {
    let currentShowingPageComponent;

    const {
      isLoginModalOpen,
      currentlyLoggedInUser,
      currentShowingPage,
    } = this.state;

    if (currentShowingPage === 0) {
      currentShowingPageComponent = <Home />;
    } else if (currentShowingPage === 1) {
      currentShowingPageComponent = <Profile />;
    }

    return (
      <div>
        <Login
          isModalOpen={isLoginModalOpen}
          modalToggler={this.handleLoginModalToggle}
        />
        <Navigation>
          {currentlyLoggedInUser ? (
            <UncontrolledDropdown nav>
              <DropdownToggle nav caret>
                {currentlyLoggedInUser.email}
              </DropdownToggle>
              <DropdownMenu right>
                <DropdownItem
                  onClick={() =>
                    this.setState({
                      currentShowingPage: 1,
                    })
                  }
                >
                  Profile
                </DropdownItem>
                <DropdownItem disabled>Expeditions</DropdownItem>
                <DropdownItem divider />
                <DropdownItem onClick={() => firebase.auth().signOut()}>
                  Sign Out
                </DropdownItem>
              </DropdownMenu>
            </UncontrolledDropdown>
          ) : (
            <NavItem>
              <NavLink onClick={this.handleLoginModalToggle}>Login</NavLink>
            </NavItem>
          )}
        </Navigation>
        {currentShowingPageComponent}
      </div>
    );
  };
}

export default App;

3 个答案:

答案 0 :(得分:3)

这只是切换组件的一个非常简单的例子,有很多方法可以实现,我相信您会获得更好的实践答案,但希望可以给您一些想法。 (以防不明显,您可以通过调用setState({currentComponent:'compX'})来使用它)

getComponent(){
    let component;
    switch (this.state.currentComponent){
        case 'compA' :
            component = <CompA/>;
            break;
        case 'compB' :
            component = <CompB/>;
            break;
        case 'compC' :
            component = <CompC/>;
            break;
        case 'compD' :
            component = <CompD/>;
            break;
    }
    return component;
}

render(){
    return(
        <div>
            {this.getComponent()}
        </div>
    );
}

答案 1 :(得分:2)

TLDR:在构建应用程序主体的render()方法内部,动态插入与您要向用户显示的当前组件/模块/页面一致的React组件。 / strong>

您绝对可以使用React Router。它是公认的并且被广泛使用。但是,如果您愿意的话,完全可以在没有React Router的情况下完成此操作。我也在构建一个单页应用程序,并且也在交换您所描述的组件。这是完成此任务的两个主要文件:

template.default.js:

INSERT INTO X (output) VALUES ('awesomeness');

and navigation.left.js:

// lots o' imports up here...

// styles/themes that are needed to support the drawer template

class DefaultTemplate extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            mainHeight : 200,
            mobileOpen : false,
            toolbarSpacerHeight : 100,
        };
        session[the.session.key.for.DefaultTemplate] = this;
        session.browser = detect();
    }

    getModule() {
        // here I'm switching on a session variable (which is available throughout the entire app to determine which module ID the user has currently chosen
        // notice that the return values are the dynamic React components that coincide with the currently-chosen module ID 
        switch (session.DisplayLayer.state.moduleId) {
            case the.module.id.for.home:
                return <HomeModule/>;
            case the.module.id.for.lists:
                return <ListsModule/>;
            case the.module.id.for.login:
                return <LogInModule/>;
            case the.module.id.for.logout:
                return <LogOutModule/>;
            case the.module.id.for.register:
                return <RegisterModule/>;
            case the.module.id.for.roles:
                return <RolesModule/>;
            case the.module.id.for.teams:
                return <TeamsModule/>;
            case the.module.id.for.users:
                return <UsersModule/>;
            default:
                return null;
        }
    }

    handleDrawerToggle = () => {
        this.setState({mobileOpen : !this.state.mobileOpen});
    };

    render() {
        // the module is dynamically generated every time a render() is invoked on this template module 
        const module = this.getModule();
        return (
            <div className={classes.root} style={{height : the.style.of.percent.hundred}}>
                <AppBar className={classes.appBar} style={{backgroundColor : the.color.for.appBar}}>
                    <Toolbar>
                        <IconButton
                            aria-label={the.ariaLabel.openDrawer}
                            className={classes.navIconHide}
                            color={the.style.of.inherit}
                            onClick={this.handleDrawerToggle}
                        >
                            <MenuIcon/>
                        </IconButton>
                        <FontAwesome name={the.icon.for.palette} style={{marginRight : '10px', fontSize : the.style.of.onePointFiveEms}}/>
                        <Typography variant={the.variant.of.title} color={the.style.of.inherit} noWrap>
                            <TranslatedText english={'Groupware'}/>.<TranslatedText english={'Studio'}/>
                        </Typography>
                        <LanguageMenu
                            containerStyle={{marginLeft : the.style.of.margin.auto}}
                            onClose={event => {this.updateLanguage(event)}}
                            selectedLanguageId={db.getItem(the.db.item.for.languageId)}
                        />
                    </Toolbar>
                </AppBar>
                <Hidden mdUp>
                    <Drawer
                        anchor={theme.direction === the.direction.of.rightToLeft ? the.direction.of.right : the.direction.of.left}
                        classes={{paper : classes.drawerPaper}}
                        ModalProps={{keepMounted : true}}
                        onClose={this.handleDrawerToggle}
                        open={this.state.mobileOpen}
                        variant={the.variant.of.temporary}
                    >
                        {drawer}
                    </Drawer>
                </Hidden>
                <Hidden smDown implementation={the.implementation.of.css}>
                    <Drawer
                        classes={{paper : classes.drawerPaper}}
                        open
                        variant={the.variant.of.permanent}
                    >
                        {drawer}
                    </Drawer>
                </Hidden>
                <main
                    className={classes.content}
                    ref={main => this.main = main}
                    style={{backgroundColor : the.color.for.module.background}}
                >
                    <div
                        className={classes.toolbar}
                        ref={toolbarSpacer => this.toolbarSpacer = toolbarSpacer}
                    />
                    {/*
                        here is where that dynamically-generated module is rendered inside the template 
                    */}
                    {module}
                </main>
            </div>
        );
    }
}

export default withStyles(styles, {withTheme : true})(DefaultTemplate);

答案 2 :(得分:2)

(即将提供无耻的插头)

我写了一篇关于动态加载React组件的博客文章。
https://www.slightedgecoder.com/2017/12/03/loading-react-components-dynamically-demand/#case1

请参阅第一种情况,即使用动态import()动态加载组件。

要点是,您创建一个component作为状态,并根据传递给addComponent的类型(我博客中模块的名称)对其进行更新。

这种方法的好处是,浏览器不会下载不需要的组件。

对于您而言,如果没有人点击About页,则该组件将永远不会下载。

addComponent = async type => {
  console.log(`Loading ${type} component...`);

  import(`./components/${type}.js`)
    .then(component =>
      this.setState({
        components: this.state.components.concat(component.default)
      })
    )
    .catch(error => {
      console.error(`"${type}" not yet supported`);
    });
};