繁体   English   中英

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

[英]In Reactjs, how do you manipulate the children of a child component from a parent component?

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

<Component>
    <Child1 key="child1">
    <Child2 key="child2">
    <Child3 key="child3">
</Component>

示例联系人列表组件:

<ContactList key="contact-list">
    <ContactList.Header key="contactlist-header">
    <ContactList.Body key="contactlist-body">
    <ContactList.Footer key="contactlist-footer">
</ContactList>

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

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

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

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

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

UI 是一些命名空间 / Class

所以我需要一个包装器组件,它允许我根据上面的 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>
}

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

<Parent>
    <ContactList/>
</Parent>

 function App() { return ( <div className="App"> <Parent> <ContactList /> </Parent> </div> ); } ReactDOM.render( <App />, document.getElementById('root') ); function Parent({ children }) { console.log(children); //????? how do we access ContactList's children to alter return children; } 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>; }
 <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>

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

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

您提议的 UI class 或命名空间还将在 React 之外存储您的应用程序的一些 state,其中一些常用的库,如 redux,也可以避免,但也很容易出错。做出反应。

不过,这是您想要的功能的工作演示(通过对Parent组件的道具处理,而不是外部类)。 如您所见,我并没有像 React 那样渲染组件,而是直接调用 function。

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

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

这可能不是问题的答案,但我想在这里写下这篇文章,希望对某人有所帮助。

正确设计的组件应该是可重用的并公开 API 以根据客户项目的需要自定义组件。 如果它不可重用,那是因为,它并不意味着高度重用或组件设计不好。 所以最好的办法是正确设计这些组件。 在这里,我将为应该可重用的<ContactList>组件解释一种可能的正确设计模式。


在 React 中,我们只描述 UI。 我们基于 state 和道具(由于条件,state 值等)对组件更改的 UI 描述。 我们甚至不会在 JSX 树中调用我们的自定义组件,React 是在树中调用我们的自定义组件并决定如何处理返回的 React 元素结构的人。 我们不应该搞砸这个!

在设计可重用的 UI 组件(UI 库等)时,我们可以使用一些模式。 让我们考虑一下您的组件,

function ContactList() {
  return (
    <React.Fragment>
      <ContactListHeader />
      <ContactListBody />
      <ContactListFooter />
    </React.Fragment>
  );
}

当我们检查这个组件时,我们可以看到ContactList除了将所有子组件组合在一起之外没有做任何其他事情。 从技术上讲,这应该是客户端代码的责任。 一种可能的设计模式是复合组件模式

可重用组件库公开,所有,

const ContactListHeader = () => {}
const ContactListBody = () => {}
const ContactListFooter = () => {}

const ListContext = createContext({});
export const ContactList = ({ children }) => {
  return <ListContext.Provider>{ children }</ListContext.Provider>
}
// Not required but doing this make the client code easy to understand
ContactList.ContactListHeader = ContactListHeader;
ContactList.ContactListBody = ContactListBody;
ContactList.ContactListFooter = ContactListFooter;

然后在客户端,

function MyApp() {
  const [somedata, setSomeData] = useState(DATA);
  
  return (
    <ContactList values={somedata} onToggle={someMethod}>
      <ContactList.ContactListHeader />
      <ContactList.ContactListBody />
      <ContactList.ContactListFooter />
    </ContactList>
  )
}

该库使用可以在组件库中访问的上下文 (ListContext)。 所以现在所有 3 个子组件都可以从上下文中访问值并做任何事情。


一个完美的例子是组件的activeTab={2} 现在子组件可以通过上下文访问活动选项卡并执行任何操作

return (
  <TabContainer activeTab={2}>
    <Tab index={1} />
    <Tab index={2} />
  </TabContainer>
)

回到例子,

由于MyApp是我们的组件,我们现在可以显示、隐藏ContactList组件的各个部分,还可以操作组件的 state。

这是创建高度可重用组件时可以使用的一种模式。 您可以在 MUI、Formik 和其他 UI 库等 3rd 方库中看到这些模式。 这些库被数以百万计的开发人员使用,并且没有这样的可重用性问题。 创建这些库的开发人员公开了必要的 API,以使它们具有高度可重用性。

注意:您并不总是需要使用这些高级模式。 例如,这个组件是一个高度可重用的组件,但它没有使用任何特殊的模式。 该组件所做的只是接受几十个 props 并使用这些 props 来根据需要自定义组件。

最后,我要说的是,在考虑从客户端应用程序强制操作它们之前,最好先正确设计可重用组件。 使用道具 state 来操作子组件。 此外,React 组件不应该关心它的父或子。

有一个API可用于从父组件调用子组件中的方法。 仍然反应不建议使用它

不会对特定用例发表评论,但您可以使用forwardRefuseImperativeHandle访问子方法等。 确实有必要这样做的情况。

我在这里创建了一个工作演示: https://codesandbox.io/s/mui5-react-final-form-datepicker-forked-z5rp2m?file=/src/Demo.tsx

import React from "react";
import { Typography, Button } from "@mui/material";

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

const ContactListBody = React.forwardRef((props: any, ref: any) => {
  const [count, setCount] = React.useState(0);

  React.useImperativeHandle(ref, () => ({
    increaseCount() {
      return setCount(count + 1);
    }
  }));

  return (
    <>
      <Typography>Body Content</Typography>
      <Typography>Current count: {count}</Typography>
    </>
  );
});

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

const ContactList = React.forwardRef((props: any, ref: any) => {
  return (
    <>
      <ContactListHeader key="contactlist-header" />
      <ContactListBody key="contactlist-body" ref={ref} />
      <ContactListFooter key="contactlist-footer" />
    </>
  );
});

export default function Parent() {
  const contactListRef = React.useRef<any>();
  const onButtonClick = () => {
    contactListRef.current?.increaseCount();
  };

  return (
    <div className="App">
      <ContactList ref={contactListRef} />
      <Button onClick={onButtonClick} variant="contained" color="primary">
        Increase Count
      </Button>
    </div>
  );
}

如您所见,我在 ContactListBody 中放置了一个 state 和方法,并通过onButtonClick<Parent>对其进行操作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM