【问题标题】:How do you swap out a component in React?如何在 React 中更换组件?
【发布时间】:2018-12-26 23:18:37
【问题描述】:


我正在尝试使用 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;

【问题讨论】:

  • 我不太明白你想做什么。是否要将单击的链接移动到链接列表的开头?你能包括你迄今为止尝试过的代码吗?
  • @Tholle 当然,它已被添加。
  • 谢谢。你有没有研究过类似 React Router 的东西?我认为您的解决方案很好,但可能很难扩展到更多页面和嵌套页面。
  • 不,我明天去看看。谢谢。

标签: javascript reactjs navigation


【解决方案1】:

这只是一个非常简单的切换组件的示例,有很多方法可以做到这一点,我相信你会得到更好的练习答案,但希望这能给你一些想法。 (如果不明显,您可以通过调用 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>
    );
}

【讨论】:

  • 会保留组件的状态吗?我不认为 :/
  • @AkashGorai 我无法想象它会,尽管现在使用我不知道的反应已经很长时间了。如果需要维护更复杂的状态,我会处理父级中的状态或使用 redux 之类的东西。
【解决方案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`);
    });
};

【讨论】:

    【解决方案3】:

    TLDR:在构建应用主体的 render() 方法中,动态插入与您尝试向用户显示的当前组件/模块/页面一致的 React 组件。

    你绝对可以使用 React Router。它已经成熟并被广泛使用。但是如果你愿意,你完全可以在没有 React Router 的情况下做到这一点。我还在构建一个单页应用程序,并且我还按照您的描述替换了组件。以下是完成此任务的两个主要文件:

    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);
    

    还有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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-08-08
      • 2018-02-15
      • 1970-01-01
      • 1970-01-01
      • 2017-07-23
      • 1970-01-01
      • 2019-09-05
      相关资源
      最近更新 更多