首页 > 解决方案 > React: Change component's children from the outside

问题描述

In a React project, I have a component Header.js, which is part of the global layout and is used on every page. Routing is done with React Router v4.

The Header.js component should have different children (like breadcrumbs or titles), depending on the page I'm currently on.

Example: on the HomePage.js, I want to display breadcrumbs. But on the AboutPage.js, I want to have just a title.

<App>
  <Header />

  <AnotherComponent />
  <MoreComponents />
  <BrowserRouter>
  ...
  </BrowserRouter>
</App>

I thought about making Header.js a HOC, but I can't embed it onto every page because of layout and styling reasons. It needs to be in App.js in the exact same position.

Any ideas / techniques?

EDIT: Ideally, I want to have the content of Header.js in every page, like the Helmet package does it:

<>
  {/* HomePage.js */}
  <HeaderPortal>
    <p>Content for the header</p>
  </HeaderPortal>

  <PageContent />
</>

标签: javascriptreactjs

解决方案


一种方法是使用 React 的上下文 API,但您必须在 DOM 中进行相当高的包装,以便能够从任何地方提供标头内容的 setter 和 getter。

Header 组件如下所示:

const HeaderContext = React.createContext();

// Provides the context and state - wrap app
export class HeaderProvider extends React.Component {
  state = {};
  render() {
    const { children } = this.props;
    return (
      <HeaderContext.Provider
        value={{
          header: this.state.header,
          setHeader: ({ header }) => this.setState({ header })
        }}
      >
        {children}
      </HeaderContext.Provider>
    );
  }
}
// Wrapper around WHERE the header will be rendered
export function HeaderPlacement() {
  return (
    <HeaderContext.Consumer>{({ header }) => header}</HeaderContext.Consumer>
  );
}
// Wrapper for setting the header CONTENT
export function HeaderPortal({ children }) {
  return (
    <HeaderContext.Consumer>
      {({ header, setHeader }) => 
        !header // Guard against setting the header in an endless loop
        && setHeader({ header: children })}
    </HeaderContext.Consumer>
  );
}

然后用法如下所示:

function App() {
  return (
    <HeaderProvider>
      <div className="App">
        <HeaderPlacement />
        <Page />
      </div>
    </HeaderProvider>
  );
}

function Page() {
  return (
    <React.Fragment>
      <HeaderPortal><h1>Page A Test</h1></HeaderPortal>
      <div>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ex odio,
        eleifend in consequat nec, maximus lobortis ligula. Duis elementum, diam
        et imperdiet pulvinar, nulla ligula commodo elit, ac ullamcorper justo
        mauris quis velit. 
      </div>
    </React.Fragment>
  );
}

如果多个后代尝试设置标题内容,则必须小心,因为您最终会遇到循环setState问题,或者一个会覆盖另一个。

这绝对不是唯一的方法,我避免使用钩子,但这肯定React-y 的方法。

在此处查看代码框: https ://codesandbox.io/s/pj2q10700j


推荐阅读