简体   繁体   English

如何在React中换出组件?

[英]How do you swap out a component in React?


I am trying to build a single-page application using React. 我正在尝试使用React构建一个单页应用程序。 Currently I have a component called App that is being rendered by ReactDOM , it contains navigation, then the component (or page) to be rendered after the navigation component. 当前,我有一个名为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;

I want to be able to select the "About" link and the Home component update to be About . 我希望能够选择“关于”链接,并在Home组件更新是About I am looking for solutions to this issue that will work with more than two elements (or pages). 我正在寻找针对此问题的解决方案,该解决方案可以使用两个以上的元素(或页面)。

This is my current code that works, though is a very poor solution. 尽管这是一个非常差的解决方案,但是这是我当前的有效代码。

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;

This is just a really simple example of switching out components, there are many many ways to do it, and I'm sure you will get better practice answers but hopefully, this gives you some ideas. 这只是切换组件的一个非常简单的例子,有很多方法可以实现,我相信您会获得更好的练习答案,但是希望,这会给您一些想法。 (in case it is not obvious, you would use this by calling setState({currentComponent: 'compX'})) (以防万一,可以通过调用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>
    );
}

TLDR: inside the render() method where the main body of your app is built, dynamically insert the React component that coincides with the current component/module/page that you are trying to display to the user. TLDR:在构建应用程序主体的render()方法内部,动态插入与您要向用户显示的当前组件/模块/页面一致的React组件。

You can absolutely use React Router. 您绝对可以使用React Router。 It's well-established and widely-used. 它是公认的并且被广泛使用。 But you can totally do this with without React Router if you wish. 但是,如果您愿意的话,完全可以在没有React Router的情况下完成此操作。 I'm also building a single-page app and I'm also swapping out components as you've described. 我也在构建一个单页应用程序,并且也在交换您所描述的组件。 Here are the two main files that accomplish that: 这是完成此任务的两个主要文件:

template.default.js: template.default.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);

And navigation.left.js: 和navigation.left.js:

// lots o' imports up here 

class LeftNavigation extends React.Component {
    listButtons = [];
    // this object controls the configuration of the nav links that show on the left side of the template
    navigation = {
        isLoggedIn : [
            {
                icon : the.icon.for.home,
                isFollowedByDivider : false,
                label : the.label.for.home,
                moduleId : the.module.id.for.home,
            },
            {
                icon : the.icon.for.powerOff,
                isFollowedByDivider : true,
                label : the.label.for.logOut,
                moduleId : the.module.id.for.logout,
            },
            {
                icon : the.icon.for.orderedList,
                isFollowedByDivider : false,
                label : the.label.for.lists,
                moduleId : the.module.id.for.lists,
            },
            {
                icon : the.icon.for.roles,
                isFollowedByDivider : false,
                label : the.label.for.roles,
                moduleId : the.module.id.for.roles,
            },
            {
                icon : the.icon.for.teams,
                isFollowedByDivider : false,
                label : the.label.for.teams,
                moduleId : the.module.id.for.teams,
            },
            {
                icon : the.icon.for.users,
                isFollowedByDivider : false,
                label : the.label.for.users,
                moduleId : the.module.id.for.users,
            },
        ],
        isLoggedOut : [
            {
                icon : the.icon.for.home,
                isFollowedByDivider : false,
                label : the.label.for.home,
                moduleId : the.module.id.for.home,
            },
            {
                icon : the.icon.for.powerOff,
                isFollowedByDivider : false,
                label : the.label.for.logIn,
                moduleId : the.module.id.for.login,
            },
            {
                icon : the.icon.for.registered,
                isFollowedByDivider : false,
                label : the.label.for.register,
                moduleId : the.module.id.for.register,
            },
        ],
    };

    populateListButtons() {
        // here we are generating an array of ListButtons that will comprise the left-hand navigation 
        this.listButtons = [];
        let buttonConfigs = [];
        switch (db.getItem(the.db.item.for.isLoggedIn)) {
            case true:
                buttonConfigs = this.navigation.isLoggedIn;
                break;
            case false:
                buttonConfigs = this.navigation.isLoggedOut;
                break;
            default:
                return;
        }
        buttonConfigs.forEach(buttonConfig => {
            let buttonIsEnabled = true;
            let fontAwesomeStyle = {fontSize : the.style.of.onePointFiveEms};
            let listItemStyle = {};
            let textStyle = {};
            switch (buttonConfig.label) {
                case the.label.for.logIn:
                    fontAwesomeStyle[the.style.property.name.of.color] = the.color.for.success;
                    break;
                case the.label.for.logOut:
                    fontAwesomeStyle[the.style.property.name.of.color] = the.color.for.error;
                    break;
                default:
                    if (session.DisplayLayer.state.moduleId === buttonConfig.moduleId) {
                        fontAwesomeStyle[the.style.property.name.of.color] = the.color.for.white.text;
                    } else {
                        fontAwesomeStyle[the.style.property.name.of.color] = the.color.for.headerBar;
                    }
                    break;
            }
            if (session.DisplayLayer.state.moduleId === buttonConfig.moduleId) {
                buttonIsEnabled = false;
                listItemStyle[the.style.property.name.of.backgroundColor] = the.color.for.selectedLeftNavButtonOrange;
                textStyle[the.style.property.name.of.color] = the.color.for.white.text;
            }
            this.listButtons.push(
                <ListItem
                    button={buttonIsEnabled}
                    key={`${buttonConfig.label}-listItem`}
                    // notice that when one of the left nav links is clicked, we are updating the moduleId value in session, 
                    // which dynamically determines which module shows up in the center panel
                    onClick={() => session.DisplayLayer.updateModuleId(buttonConfig.moduleId)}
                    style={listItemStyle}
                >
                    <ListItemIcon>
                        <FontAwesome name={buttonConfig.icon} style={fontAwesomeStyle}/>
                    </ListItemIcon>
                    <TranslatedText english={buttonConfig.label} style={textStyle}/>
                </ListItem>,
            );
            if (buttonConfig.isFollowedByDivider) {
                this.listButtons.push(<Divider key={`${buttonConfig.label}-divider`}/>);
            }
        });
    }

    render() {
        // dynamically generate the array of left nav buttons before rendering the links 
        this.populateListButtons();
        return <List style={{paddingTop : the.style.of.pixels.zero}}>{this.listButtons}</List>;
    }
}

export default LeftNavigation;

(Shameless plug coming) (无耻的插头来了)

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

Refer to the 1st case where you load a component dynamically using dynamic import() . 请参阅第一种情况,其中使用动态import()动态加载组件。

The gist is that you create a component as a state, and update it depending on the type (name of module in my blog) passed to addComponent . 要点是,您将component创建为状态,并根据传递给addComponent的类型(我的博客中模块的名称)进行更新。

The benefit of this approach is that, browser won't download component that's not needed. 这种方法的好处是,浏览器不会下载不需要的组件。

In your case, if nobody clicks on About page, that component is never downloaded. 对于您而言,如果没有人单击“ 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`);
    });
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM