在 Reactjs 中,如何从父组件操作子组件的子组件?

Raj*_*han 5 javascript typescript reactjs react-children

我想制作一个具有独特模式的高度可重用的反应组件。假设这个联系人列表是由另一个团队制作的;我们无法更改组件,它遵循如下所示的结构。

<Component>
    <Child1 key="child1" />
    <Child2 key="child2" />
    <Child3 key="child3" />
</Component>
Run Code Online (Sandbox Code Playgroud)

示例联系人列表组件:

<ContactList key="contact-list">
    <ContactList.Header key="contactlist-header" />
    <ContactList.Body key="contactlist-body" />
    <ContactList.Footer key="contactlist-footer" />
</ContactList>
Run Code Online (Sandbox Code Playgroud)

我想提供自定义联系人列表组件的选项,例如

  • 在联系人列表中的任意位置添加任何组件
  • 根据“key”值删除组件
  • 更换整个组件

我想公开一些与此类似的 API。

UI.ContactList.remove("contactlist-footer") // 从 ContactList 中删除并存储在变量中以供以后使用

UI.ContactList.add(<CustomContactListFooter/>)// 将 Component 添加到 ContactList 并存储在变量中以供以后使用

其中 UI 是某个命名空间/类

所以我需要一个包装组件,它允许我根据上面的 api 操作 ContactList 的子组件,假设UI.ContactList.remove("contactlist-footer")删除 API 将数据存储在这个变量中_removeRequest = ['contactlist-footer']

在渲染组件时,我不想显示此组件 <ContactList.Footer key="contactlist-footer">,我可以通过像这样的操作在 ContactList 组件中进行操作

高层次的想法:

function ContactList({children}){
    const removeKey =  UI.ContactList._removeRequest[0]
    const newChildren = React.Children.toArray(children).filter(child => child.key !== removeKey)
    return <React.Fragement>{newChildren}</React.Fragement>
}
Run Code Online (Sandbox Code Playgroud)

这是不可能的,因为我们不允许修改 ContactList 组件。

<Parent>
    <ContactList/>
</Parent>
Run Code Online (Sandbox Code Playgroud)

<Component>
    <Child1 key="child1" />
    <Child2 key="child2" />
    <Child3 key="child3" />
</Component>
Run Code Online (Sandbox Code Playgroud)
<ContactList key="contact-list">
    <ContactList.Header key="contactlist-header" />
    <ContactList.Body key="contactlist-body" />
    <ContactList.Footer key="contactlist-footer" />
</ContactList>
Run Code Online (Sandbox Code Playgroud)

如何从父组件操作 ContactList 的子组件?任何想法都会有所帮助

Tax*_*xel 8

好吧,我想从不要这样做开始!- 你想要的不是 React 应用程序或组件应该如何工作。你应该只通过上面的 props 和 Context 来控制你的组件。这就是 React 应该如何工作的。

你建议的 UI 类或命名空间还会在 React 之外存储应用程序的一些状态,一些常用的库(如 redux、zustand 等)也会这样做,但这很容易出错,恕我直言,这是需要避免的反应。

尽管如此,这里还是您想要的功能的工作演示(通过组件的 props 处理Parent,而不是外部类)。正如您所看到的,我没有像 React 那样渲染组件,而是直接调用函数。

我很确定这对于维护和破坏很多东西来说是很糟糕的(只要事情不像这里那么微不足道),但对于这个简短的演示来说它是有效的。

function App() {
  return (
    <div className="App">
      {/* remove body and header */}
      <Parent removeKeys={["contactlist-body", "contactlist-header"]}>
        <ContactList />
      </Parent>
      <hr/>
      {/*add a second footer at array index 3 */}
      <Parent insertChildren={{3: <ContactListFooter2 />}}>
        <ContactList />
      </Parent>
      <hr />
      {/*replace the footer with a custom one */}
      <Parent removeKeys={["contactlist-footer"]} insertChildren={{2: <ContactListFooter2 />}}>
        <ContactList />
      </Parent>
      <hr/>
      {/*replace the entire component*/}
      <Parent replaceComponent={<ContactListFooter2 />}>
        <ContactList />
      </Parent>
    </div>
  );
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
);


function Parent({ children, removeKeys=[], insertChildren={}, replaceComponent=undefined }) {
  if(replaceComponent){
    return replaceComponent;
  }
  // this is really hacky - don't do it
  const renderedChildren = children["type"]();
  renderedChildren.props.children = renderedChildren.props.children.filter(child=>!removeKeys.includes(child.key));
  for(let [index, component] of Object.entries(insertChildren)){
      renderedChildren.props.children.splice(index, 0, component["type"]())
  }
  
  return renderedChildren;
}

function ContactList() {
  return (
    <React.Fragment>
      <ContactListHeader key="contactlist-header" />
      <ContactListBody key="contactlist-body" />
      <ContactListFooter key="contactlist-footer" />
    </React.Fragment>
  );
}

function ContactListHeader() {
  return <h2>Header</h2>;
}

function ContactListBody() {
  return <section>Body Content</section>;
}

function ContactListFooter() {
  return <footer>Contact List Footer</footer>;
}

function ContactListFooter2() {
  return <footer>Contact List Footer2</footer>;
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<body>
  <div id="root"></div>
</body>
Run Code Online (Sandbox Code Playgroud)