如何在后代组件向祖先组件通知事件而无需使用 props。

huangapple go评论59阅读模式
英文:

How to notify an event without props from descendant to ancestor component

问题

我有一个组件(<Grid>),其中包含n个嵌套组件

<Grid>
    <Header>
        <Toolbar>
           ...
        </Toolbar>
        <Filters>
            <Form>
               ...
            </Form>
        </Filters>
    </Header>
    <Body>
        <Datazone>
            ...
        </Datazone>
        <Summary>
            ...
        </Summary>
    </Body>
    <Footer>
        ...
    </Footer>
</Grid>

我需要创建一个组件(<MagicButton>),可以将其放置在<Grid>树的任何位置。无论放置在树的哪个位置,按下它后应该执行某些操作,然后向<Grid>组件“通知”事件。
先决条件:我不想完全使用属性传递,所以我考虑了以下解决方案:
在上下文中包装<Grid>,并传递一个函数,该函数可以通过上下文值通知/更新<Grid>。这个函数是由使用上下文useContext<MagicButton>组件“触发”的。

这个方法是否正确?
如果是的话,是否可能通过上下文提供<MagicButton>组件,而不是“导入”,然后将<MagicButton>组件添加到网格的子组件<Toolbar>中,例如?
如果不是,是否有其他通知祖先组件的方法(显然不使用属性传递)?

<GridPersistenceStateContext.Provider value={NotificationFunction}>
    <Grid>
        <Header>
            <Toolbar>
                <MagicButton />
            </Toolbar>
            <Filters>
                <Form>
                ...
                </Form>
            </Filters>
        </Header>
        <Body>
            <Datazone>
                ...
            </Datazone>
            <Summary>
                <MagicButton />
            </Summary>
        </Body>
        <Footer>
            <MagicButton />
        </Footer>
    </Grid>
</GridPersistenceStateContext.Provider>

“自定义事件”方法是否代表可行的替代方法?
“自定义事件”是否会对正常的React生命周期造成问题?

英文:

I've a component (&lt;Grid&gt;) with n nested components

&lt;Grid&gt;
	&lt;Header&gt;
        &lt;Toolbar&gt;
           ...
        &lt;/Toolbar&gt;
        &lt;Filters&gt;
            &lt;Form&gt;
               ...
            &lt;/Form&gt;
        &lt;/Filters&gt;
	&lt;/Header&gt;
    &lt;Body&gt;
        &lt;Datazone&gt;
            ...
        &lt;/Datazone&gt;
        &lt;Summary&gt;
            ...
        &lt;/Summary&gt;
    &lt;/Body&gt;
    &lt;Footer&gt;
        ...
    &lt;/Footer&gt;
&lt;/Grid&gt;

I need to create a component (&lt;MagicButton&gt;) that can be placed everywhere into the &lt;Grid&gt; tree, wherever it is placed along the tree, when it's pressed it should do something and then "notify" an event to the &lt;Grid&gt; component.
prerequisite: I don't want to use props drilling at all, so I've thought of this solution:
To wrap the &lt;Grid&gt; in a context and pass a function that can notify/update the &lt;Grid&gt; in the context value. This function is "tirggered" from a &lt;MagicButton&gt; component that use the context useContext

is this approch correct?
If yes, is it possible to provide the &lt;MagicButton&gt; component through/by the context instead of "import" then &lt;MagicButton&gt; component into a grid's child component &lt;Toolbar&gt; e.g.
If not, is there another approch to notify to an anchestor component (without no props drilling obviously)

&lt;GridPersistenceStateContext.Provider value={NotificationFunction}&gt;
    &lt;Grid&gt;
        &lt;Header&gt;
            &lt;Toolbar&gt;
            &lt;MagicButton /&gt;
            &lt;/Toolbar&gt;
            &lt;Filters&gt;
                &lt;Form&gt;
                ...
                &lt;/Form&gt;
            &lt;/Filters&gt;
        &lt;/Header&gt;
        &lt;Body&gt;
            &lt;Datazone&gt;
                ...
            &lt;/Datazone&gt;
            &lt;Summary&gt;
                &lt;MagicButton /&gt;
            &lt;/Summary&gt;
        &lt;/Body&gt;
        &lt;Footer&gt;
            &lt;MagicButton /&gt;
        &lt;/Footer&gt;
    &lt;/Grid&gt;
&lt;GridPersistenceStateContext.Provider /&gt;

Does the "custom event" approch reppresent a viable alternative?
can "custom events" cause problems to the normal react lifecicle?

答案1

得分: 1

  1. 上下文提供了一种在组件之间共享这些值的方式,而不必显式地通过树的每一级传递prop。这是正确的方法。

  2. 可以通过上下文提供<MagicButton>组件,而不是在每个地方都导入<MagicButton>组件。我们可以使用SomeContext.Consumer,这是一种较旧的读取上下文的方式。这样我们就可以获取MagicButton组件并渲染它。

例如:

MagicButton.tsx

import React from 'react';

export const MagicButton = (props: any) => <button {...props}>magic button</button>;

context.ts

import React from 'react';

export const GridPersistenceStateContext = React.createContext<{
	notify: (from: string) => void;
	MagicButton: (props: any) => JSX.Element;
}>(null!);
import React from 'react';
import { GridPersistenceStateContext } from './context';
import { MagicButton } from './MagicButton';

const Toolbar = ({ children }: React.PropsWithChildren) => <div>{children}</div>;

export default function App() {
	const notificationFunction = (from: string) => {
		console.log('notified from: ', from);
	};

	return (
		<div className="App">
			<GridPersistenceStateContext.Provider value={{ notify: notificationFunction, MagicButton }}>
				<div className="Header">
					<Toolbar>
						Toolbar:
						<GridPersistenceStateContext.Consumer>
							{({ MagicButton, notify }) => (
								<MagicButton
									onClick={() => {
										notify('toolbar');
									}}
								/>
							)}
						</GridPersistenceStateContext.Consumer>
					</Toolbar>
				</div>

				<div className="Body">
					<div className="Summary">
						Summary:
						<GridPersistenceStateContext.Consumer>
							{({ notify, MagicButton }) => (
								<MagicButton
									onClick={() => {
										notify('summary');
									}}
								/>
							)}
						</GridPersistenceStateContext.Consumer>
					</div>
				</div>
			</GridPersistenceStateContext.Provider>
		</div>
	);
}

codesandbox

英文:
  1. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree. It's the correct approach.

  2. It's possible to provide the &lt;MagicButton&gt; component through the context instead of import the &lt;MagicButton&gt; component everywhere. we can use SomeContext.Consumer, an older way to read context. So that we can get the MagicButton component and render it.

E.g.

MagicButton.tsx:

import React from &#39;react&#39;;

export const MagicButton = (props: any) =&gt; &lt;button {...props}&gt;magic button&lt;/button&gt;;

context.ts:

import React from &#39;react&#39;;

export const GridPersistenceStateContext = React.createContext&lt;{
	notify: (from: string) =&gt; void;
	MagicButton: (props: any) =&gt; JSX.Element;
}&gt;(null!);
import React from &#39;react&#39;;
import { GridPersistenceStateContext } from &#39;./context&#39;;
import { MagicButton } from &#39;./MagicButton&#39;;

const Toolbar = ({ children }: React.PropsWithChildren) =&gt; &lt;div&gt;{children}&lt;/div&gt;;

export default function App() {
	const notificationFunction = (from: string) =&gt; {
		console.log(&#39;notified from: &#39;, from);
	};

	return (
		&lt;div className=&quot;App&quot;&gt;
			&lt;GridPersistenceStateContext.Provider value={{ notify: notificationFunction, MagicButton }}&gt;
				&lt;div className=&quot;Header&quot;&gt;
					&lt;Toolbar&gt;
						Toolbar:
						&lt;GridPersistenceStateContext.Consumer&gt;
							{({ MagicButton, notify }) =&gt; (
								&lt;MagicButton
									onClick={() =&gt; {
										notify(&#39;toolbar&#39;);
									}}
								/&gt;
							)}
						&lt;/GridPersistenceStateContext.Consumer&gt;
					&lt;/Toolbar&gt;
				&lt;/div&gt;

				&lt;div className=&quot;Body&quot;&gt;
					&lt;div className=&quot;Summary&quot;&gt;
						Summary:
						&lt;GridPersistenceStateContext.Consumer&gt;
							{({ notify, MagicButton }) =&gt; (
								&lt;MagicButton
									onClick={() =&gt; {
										notify(&#39;summary&#39;);
									}}
								/&gt;
							)}
						&lt;/GridPersistenceStateContext.Consumer&gt;
					&lt;/div&gt;
				&lt;/div&gt;
			&lt;/GridPersistenceStateContext.Provider&gt;
		&lt;/div&gt;
	);
}

codesandbox

huangapple
  • 本文由 发表于 2023年7月20日 14:01:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76727065.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定