[英]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>
我想提供自定义联系人列表组件的选项,例如
我想公开一些与此类似的 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可用于从父组件调用子组件中的方法。 仍然反应不建议使用它
不会对特定用例发表评论,但您可以使用forwardRef和useImperativeHandle访问子方法等。 确实有必要这样做的情况。
我在这里创建了一个工作演示: 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.