reactjs 其他组件的门户

l3zydbqr  于 2023-02-18  发布在  React
关注(0)|答案(3)|浏览(106)

我在官方网站上找到了这个example,它描述了如何使用portal。在这个例子中,正在为使用ReactDOM.createPortal()的模态创建新的**'root'**div。有没有一种方法可以将组件传送到另一个组件(不仅仅是不同的根)?

  • 使用portal可能无法完成-我只是认为它可能对此解决方案有用 *
    我正在尝试使用逻辑依赖于MyComponentA或MyComponentB的按钮“扩展”导航(不同按钮,不同组件指示的不同行为,但放置在导航的DOM中)。

这是我试图实现的目标的简化示例:

应用程序

这是我的应用程序的根组件,包括导航、MyComponentA和MyComponentB

class App extends React.Component {
   render() {
       return (
          <Navigation/>
          <MyComponentA/>
          <MyComponentB/>
       );
    }
}

导航

这是我想放置门户输出的组件。

class Navigation extends React.Component {
   render() {
      return (
         <OutputOfPortal>
            {/* I want to teleport something here from another component */}
         </OutputOfPortal>
      );
   }
}

我的组件A

这是我想放置门户源代码的组件。与MyComponentB的情况相同,但内容不同。

class MyComponentA extends React.Component {
   render() {
      return (
         {/* Another code */}
         <InputOfPortal>
            {/* I want to teleport something from here to navigation */}
            <button onClick={this.functionInThisComponent}> click me </button>
         </InputOfPortal>
         {/* Another code */}
      );
   }
}
x759pob2

x759pob21#

我已经将id navigation-controls设置为Navigation中的元素。然后我创建了另一个名为NavigationExtension的组件,它被用作Navigation的门户。此门户的每个子组件都将其功能保留在创建它的原始组件中。

    • 导航**

我刚刚创建了一个div,在我想传送的地方有id。

class Navigation extends React.Component {
   render() {
      return (
        // some code ... 
        <div id="navigation-controls">
            // some buttons ... 
        </div>
        // some code ...
      );
   }
}
    • 导航扩展**

这将把包裹在里面的任何东西传送到导航系统

class NavExtension extends React.Component
{
    el = document.createElement("div");

    componentDidMount() {
        // nav element is selected here by id
        this.targetEl = document.getElementById("navigation-controls");
        this.targetEl.appendChild(this.el);
    }

    componentWillUnmount() {
        this.targetEl.removeChild(this.el);
    }

    render() {
        return ReactDOM.createPortal(
            <React.Fragment>
                {this.props.children}
            </React.Fragment>, this.el);
    }
}
    • 我的组件A**

最后宣布我的按钮和传送到导航。

class MyComponentA extends React.Component {
   render() {
      return (
         // some code ...
         <NavigationExtension>
            <button onClick={this.functionInThisComponent}> I'm in navigation! </button>
         </NavigationExtension>
         // some code ...
      );
   }
}

我不知道我是否只是不明白其他的答案,但这正是我想要的。

efzxgjgh

efzxgjgh2#

  • 我想你忘了使用**render()**方法
  • 你必须到使用React路由器dom如果我unstand正确什么你想要https://www.npmjs.com/package/react-router-dom
  • 而且你必须使用 prop ,用react router dom发送你的 prop
  • 通常你使用一个布局页面来放置导航栏,然后在同一个页面中添加***props.childern***来呈现导航栏下的页面
import React, { Component } from "react";
import NavBar from "./NavBar"
class Layout extends Component {
constructor(props){
super(props);
}

render(){
return (
  <div>
      <NavBar/>

  <div className="container">
    {this.props.children}
  </div>

  </div>

);
}}

export default Layout;
  • 这里是reacr路由器dom的例子
import React, { Component } from 'react';
 import Layout from "./components/layout/Layout"
 import { Switch,Route } from "react-router-dom";
 import DashBoard from './components/dashboard/DashBoard'
 import ShopDetails from './components/shops/ShopDetails'
 import SignIn from './components/auth/SignIn'
 import CreateShop from './components/shops/CreateShop'
 import CreateProduct from './components/shops/CreateProduct'
 import './App.css';

 class App extends Component { 
    render(){
      const route=(
       <Switch>
          <Route exact path="/" component={DashBoard} />
          <Route path="/shop/:id" component={ShopDetails} />
          <Route  path="/signin" component={SignIn} />
          <Route  path="/createshop" component={CreateShop} />
          <Route  path="/createproduct" component={CreateProduct} />
          {/* <Route  path="/login" component={Login} />
         <Route  path="/shoppage" component={ShopPage} /> */}
      </Switch>
    )
  return (
  <Layout>
    {route}
  </Layout>);}}
export default App;

在那之后你能去你想要的路径的路径象localhost 3000/localhost 3000/signin localhost 3000/user/:id

erhoui1w

erhoui1w3#

我有一个position:sticky的顶部面板,有时候我需要在粘滞区域添加特定页面的控件。唯一的解决办法是以某种方式将这些控件“传送”到顶部。下面是我的做法:

<StickyProvider>

  <StickyRender>
    <nav>this is always rendered</nav>
    {/* <- other components will get "teleported" here... */}
  </StickyRender>
  
  <Sticky className='add-this-classname-to-top'>
    <span>this will not be render here, but as child of StickyRender</span>
  </Sticky>
 
  ...
  
  <Sticky>
    <span>this will be also added atop</span>
  </Sticky>

  <StickyRender/> {/* We can even duplicate everything here */}

</StickyProvider>
一米一分一秒
import React, { createContext, PropsWithChildren, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import { contextFallbackFunction } from 'src/utils'

/**
 * @author Qwerty <qwerty@qwerty.xyz>
 *
 * These components allow you to collect children from any part of the tree and render them elsewhere.
 *
 * - `<StickyProvider/>` - context
 * - `<Sticky/>` - wrap your components
 * - `<StickyRender/>` - your components will appear here
 *
 * *note* You can use multiple `<Sticky/>` in multiple places. You can also use multiple `<StickyRender/>` to duplicate children.
 *
 * @example
 * <StickyProvider>
 *   <StickyRender>
 *     <nav>always rendered</nav>
 *   </StickyRender>
 *
 *   <Sticky>
 *     <span>this will not be render here, but as child of StickyRender</span>
 *   </Sticky>
 * </StickyProvider>
 */

// Sticky --------------------------------------------------------------------------------------------------------------

interface StickyProps {
  /**
   * You need to provide the height of the **whole** sticky area, including `--nav-height` and margins!
   * e.g.
   * - `[--sticky-height:calc(96px+var(--nav-height)+40px)]`
   * - `md:![--sticky-height:248px] ![--sticky-height:152px]`
   *
   * *note:* It might be necessary to use `!` to override default value.
   */
  className: string
}

/**
 * This component *"consumes"* children and passes them through context to a `<RenderSticky/>` component,
 * which renders them to a different location, allowing us to glue them to a separate dom subtree - necessary for isolating scroll.
 */
export const Sticky: React.FC<PropsWithChildren<StickyProps>> = ({ children, className }) => {
  const ref = useRef<Children>(null)
  ref.current = children

  const { setRefEffect, setClassNameEffect, forceUpdate } = useSetStickyContext()

  useEffect(() => setRefEffect(ref), []) // eslint-disable-line react-hooks/exhaustive-deps
  useEffect(() => setClassNameEffect(className), [className]) // eslint-disable-line react-hooks/exhaustive-deps

  // Fallback to render children if <Sticky/> is used without its Context.
  if (setRefEffect === contextFallbackFunction) return <>{children}</>
  forceUpdate()
  return null
}

// RenderSticky --------------------------------------------------------------------------------------------------------

/**
 * This is where all Sticky children are rendered to.
 */
export const StickyRender: React.FC<PropsWithChildren<StickyProps>> = ({ children, className: className_ }) => {
  const { className, childrenRefs } = useGetStickyChildren()
  return (
    <header id='Sticky' className={`sticky top-0 z-10 ${className_} ${className}`}>
      {children}
      {childrenRefs.map((ref) => ref.current)}
    </header>
  )
}

// Context -------------------------------------------------------------------------------------------------------------

type Children = AllowArray<React.ReactNode>
type ChildrenRef = React.MutableRefObject<Children>

interface GetStickyContext {
  childrenRefs: ChildrenRef[]
  className: string
}

interface SetStickyContext {
  setRefEffect: (childrenRef: ChildrenRef) => () => void
  setClassNameEffect: (
    /**
     * e.g. `md:![--sticky-height:40px] ![--sticky-height:var(--nav-height)]`
     *
     * *note:* It might be necessary to use `!` to override style provided from <RenderSticky/>'s className.
     */
    className: string) => () => void
  forceUpdate: () => void
}

const defaultGetStickyContext: GetStickyContext = {
  childrenRefs: [],
  className: '',
}

const defaultSetStickyContext: SetStickyContext = {
  setRefEffect: contextFallbackFunction,
  setClassNameEffect: contextFallbackFunction,
  forceUpdate: contextFallbackFunction,
}

const GetStickyContext = createContext<GetStickyContext>(defaultGetStickyContext)
const useGetStickyChildren = () => useContext(GetStickyContext)

const SetStickyContext = createContext<SetStickyContext>(defaultSetStickyContext)
const useSetStickyContext = () => useContext(SetStickyContext)

// Provider ------------------------------------------------------------------------------------------------------------

export const StickyProvider: React.FC<PropsWithChildren> = ({ children }) => {
  const [childrenRefs, setChildrenRefs] = useState<ChildrenRef[]>([])
  const [className, setClassName] = useState('')

  const [shouldUpdate, forceUpdate] = React.useReducer(() => ({}), {})

  /* This callback returns a cleaning function for `useEffect`. */
  const setRefEffect: SetStickyContext['setRefEffect'] = useCallback((ref) => {
    setChildrenRefs((prev) => prev.includes(ref) ? prev : [...prev, ref])
    return () => setChildrenRefs((prev, ix = prev.indexOf(ref)) => ix >= 0 ? [...prev.slice(0, ix), ...prev.slice(ix + 1)] : prev)
  }, [])

  /* This callback returns a cleaning function for `useEffect`. */
  const setClassNameEffect: SetStickyContext['setClassNameEffect'] = useCallback((className_) => {
    setClassName(className_)
    return () => setClassName('')
  }, [])

  const valueSetSticky = useMemo(() => ({ setRefEffect, setClassNameEffect, forceUpdate }), [setRefEffect, setClassNameEffect, forceUpdate])
  const valueGetSticky = useMemo(() => ({ childrenRefs, className }), [childrenRefs, className, shouldUpdate]) // eslint-disable-line react-hooks/exhaustive-deps

  return (
    <SetStickyContext.Provider value={valueSetSticky}>
      <GetStickyContext.Provider value={valueGetSticky}>
        {children}
      </GetStickyContext.Provider>
    </SetStickyContext.Provider>
  )
}
src/utils.ts
export function contextFallbackFunction(...any: any[]): any { console.error('You cannot use this context without Provider!') }

相关问题