【问题标题】:How would I restructure my React App, so that I can pass state down to a component on a different route我将如何重组我的 React 应用程序,以便我可以将状态传递给不同路由上的组件
【发布时间】:2018-10-07 05:18:57
【问题描述】:

我将三个组件路由到不同的路径。我想重组我的应用程序,以便我可以通过道具将状态从我的SubmitProject 组件传递到我的Portfolio 组件我仍然希望它们具有单独的路径,即; /portfolio/SubmitProject 我计划打开两个浏览器窗口来测试当我在SubmitProject 上提交表单时,它会显示在Portfolio 上,然后我将使用firebase 将我的状态保存到数据库中。

我是否需要让我的状态处于像App.js 这样的顶级组件,然后在其中包含BrowserRouter?如果是这样,我如何重新创建从 <SubmitProject/> -> <PortfolioForm/> -> <FormAdd/> 建立的连接

我的理想目标是,当我在 /submit 路由上从 FormAdd 组件提交表单时,它将通过Portfolio 组件上的状态在/Portfolio 路由上输出。

建议使用上下文 api 之类的状态管理器或其他东西,但我想知道是否有办法重组我的应用程序并能够从每个组件和路由的顶级组件传递状态分享。

这是我的相关代码

components/Router.js

import React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import Portfolio from './Portfolio';
import SubmitProject from './SubmitProject';
import App from './App';

const Router = () => (
    <BrowserRouter>
        <Switch>
            <Route exact path="/" component={App}/>
            <Route exact path="/portfolio" component={Portfolio}/>
            <Route exact path="/submit" component={SubmitProject}/>
        </Switch>
    </BrowserRouter>
);

export default Router;

components/App.js // 我的路由器应该在这里吗?

import React from 'react';

class App extends React.Component {

  render() {
      return <div>Test</div>
  }

}

export default App;

/components/SubmitProject.js

import React from 'react';
import PortfolioForm from './PortfolioForm';
import Section from './Section';

class SubmitProject extends React.Component {
    state = {
        sections:{}
    };
    addSection = section =>{
        const sections = {...this.state.sections};
        sections[`section${Date.now()}`] = section;
        this.setState({
            sections: sections
        });
    }
    render() {
        return(
            <React.Fragment>
                <h1>Submit Project</h1>
                <h2>Enter Project Data</h2>
                <ul className="section">
                    {Object.keys(this.state.sections).map(key => <Section key={key} details={this.state.sections[key]}/>)}
                </ul>
                <PortfolioForm addSection={this.addSection} />
            </React.Fragment>
        )
    }
}

export default SubmitProject;

/components/PortfolioForm.js

import React from 'react';
import FormAdd from './FormAdd';

class Portfolio extends React.Component {
    render() {
        return(
            <React.Fragment>
                <h1>Submit Form</h1>
                <FormAdd addSection={this.props.addSection}/>
            </React.Fragment>
        )
    }
}

export default Portfolio;

/components/FormAdd.js

import React from 'react';

class FormAdd extends React.Component {
    nameRef = React.createRef();

    createSection = (event) =>{
        event.preventDefault();
        const section = {
            name: this.nameRef.current.value
        };
        this.props.addSection(section);
    };  
    render() {
        return(
            <React.Fragment>
                <form onSubmit={this.createSection}>
                    <input type="text" ref={this.nameRef} name="name" placeholder="Name"/>
                    <button type="submit">+ Add Section</button>
                </form>
            </React.Fragment>
        )
    }
}

export default FormAdd;

/components/Portfolio.js

import React from 'react';

class Portfolio extends React.Component {
    //CAN I GET STATE FROM SubmitProject.js FILE IN HERE? By Restructuring my App Somehow.
    render() {
        return(
            <React.Fragment>
                <h1>Portfolio Page</h1>
                <h2>List of projects</h2>     
            </React.Fragment>
        )
    }
}

export default Portfolio;

更新代码 我现在收到一条错误消息,上面写着FooContext is not defined

components/App.js

import React from 'react';
import SubmitProject from './SubmitProject';
import {BrowserRouter, Route, Switch} from 'react-router-dom';

const FooContext = React.createContext();

class App extends React.Component {
  state = {
    sections:{}
  };
  addSection = section =>{
      const sections = {...this.state.sections};
      sections[`section${Date.now()}`] = section;
      this.setState({
          sections: sections
      });
  }

  render() {
      return (
        <FooContext.Provider value={this.state.sections}>
          <Router/>;
        </FooContext.Provider>
      )
  }

}

class Router extends React.PureComponent {
  render() {
    return 
    <BrowserRouter>
        <Switch>
            <Route exact path="/" component={Root} />      
        </Switch>
    </BrowserRouter>
  }
}

const Root = props => <FooContext.Consumer>{sections => <SubmitProject/> }</FooContext.Consumer>;

export default App;

更新的代码 V#2 App.js

import React from 'react';
import SubmitProject from './SubmitProject';
import Home from './Home';
import {BrowserRouter, Route, Switch} from 'react-router-dom';

const FooContext = React.createContext();

class App extends React.Component {
  state = {
    sections:{}
  };
  addSection = section =>{
      const sections = {...this.state.sections};
      sections[`section${Date.now()}`] = section;
      this.setState({
          sections: sections
      });
  }

  render() {
      return (
        <FooContext.Provider value={this.state.sections}>
          <Router/>;
        </FooContext.Provider>
      )
  }

}

class Router extends React.PureComponent {
  render() {
    return (
      <BrowserRouter>
        <Switch>
            <Route exact path="/" component={Home} /> 
            <Route exact path="/portfolio" component={Portfolio} />      
        </Switch>
      </BrowserRouter> 
    )

  }
}

const Portfolio = props => <FooContext.Consumer>{foo => <SubmitProject/>}</FooContext.Consumer>;

export default App;

SubmitProject.js

import React from 'react';
import PortfolioForm from './PortfolioForm';
import Section from './Section';

class SubmitProject extends React.Component {

    render() {
        return(
                <React.Fragment>
                    <h1>Submit Project</h1>
                    <h2>Enter Project Data</h2>
                    <ul className="section">
                        {Object.keys(this.state.sections).map(key => <Section key={key} details={this.state.sections[key]}/>)}
                    </ul>
                    <PortfolioForm addSection={this.addSection} />
                </React.Fragment>   

        )
    }
}

export default SubmitProject;

【问题讨论】:

  • 所以基本上你没有根组件?整个应用程序的每一次路由更改都会重新渲染。
  • 好吧,我应该把我的路由器组件放在我的应用程序中吧?然后我在哪里声明我的状态?以便它可以传递给我的所有其他组件。
  • 如果你有一个根组件持续存在并且不会在每次路由更改时重新挂载(例如App),那么你可以在那里管理一个本地状态并将其作为道具传递给它的孩子。记住,&lt;Route /&gt; 只是一个组件,你可以在任何地方渲染它,你可能需要使用render 属性而不是component 属性
  • 你能用代码告诉我你的意思吗?谢谢!

标签: javascript reactjs


【解决方案1】:

建议使用上下文 api 之类的状态管理器或其他东西,但我想知道是否有办法重组我的应用程序并能够从每个组件和路由的顶级组件传递状态分享。

这种方法存在问题。

考虑到App 维护应用程序状态,有必要将其作为prop 传递给&lt;Router&gt;,然后路由依赖它的组件:

class App extends React.Component {
  state = { foo: true };

  render() {
      return <Router foo={this.state.foo}/>
  }
}

const Router = props => (
    const RootWithFoo = props => <Root foo={props.foo}/>;

    return <BrowserRouter>
        <Switch>
            <Route exact path="/" component={RootWithFoo} />
            ...
        </Switch>
    </BrowserRouter>
);

这对组件结构进行了限制;为了避免深度传递props,Router组件应该被移除,Route应该直接在App中渲染:

class App extends React.Component {
  state = { foo: true };

  render() {
    const RootWithFoo = props => <Root foo={this.state.foo}/>;

    return <BrowserRouter>
        <Switch>
            <Route exact path="/" component={RootWithFoo} />
            ...
        </Switch>
    </BrowserRouter>
  }
}

这是上下文 API 和状态管理库(例如 Redux)解决的问题。它们允许为使用它的嵌套组件提供全局状态。

另一个问题是性能。整个路由器将在每次状态更新时重新渲染。同样,上下文 API 和状态管理库解决了这个问题。正如context API manual 所说:

只要 Provider 的 value prop 发生变化,所有作为 Provider 后代的 Consumer 都会重新渲染。从 Provider 到其后代 Consumer 的传播不受 shouldComponentUpdate 方法的约束,因此即使祖先组件退出更新,Consumer 也会更新。

因此,如果上下文提供程序值更新,则无需重新渲染整个树。无论如何,上下文使用者都将被重新渲染。由于默认情况下会重新渲染整个树,Providerchild(ren) 应该是一个纯组件以避免不必要的重新渲染。这就是为什么分开 AppRouter 组件可能更可取的原因:

class App extends React.Component {
  state = { foo: true };

  render() {
    return <FooContext.Provider value={this.state.foo}>
      <Router/>;
    </FooContext.Provider>
  }
}

class Router extends React.PureComponent {
  render() {
    return <BrowserRouter>
        <Switch>
            <Route exact path="/" component={Root} />
            ...
        </Switch>
    </BrowserRouter>
  }
}

const Root = props => <FooContext.Consumer>{foo => ...}</FooContext.Consumer>;

当全局状态更新时,只有App 和依赖它的路由组件(Root 等)会重新渲染,Router 不会重新渲染。

【讨论】:

  • 您是否建议我使用 contextAPI 并将我的路由移动到 App.js 中,您所说的“将其作为道具传递给 ”是什么意思,您能否举个例子说明一下看起来,这是我尝试构建的第一个非常菜鸟的反应应用程序
  • 我试图让示例更具说明性。希望这可以帮助。它与 Firebase 的集成方式取决于您,但我认为 App 应该监听 Firebase 更新并在需要时更新其状态。
  • 您好,感谢您让这个更具说明性。在最后一行我应该在foo =&gt; ... 部分中添加什么以及将状态传递给不同的路由器是什么样子的,即; SubmitProject.js & Porfolio.js
  • ... 是依赖于全局状态(foo)的路由组件的内容。其他路由组件可能类似于Root。组件内容用&lt;FooContext.Consumer&gt;{foo =&gt; ...}&lt;/FooContext.Consumer&gt; 包裹。如果它是一个类,那么 render 函数返回值会被它包裹起来。
  • 我已经根据您的回答添加了一些新代码,这给了我一个错误,提示 FooContext is not defined 您可以在上面看到更新的代码。谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-16
  • 2021-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多