React - 从 children 访问 parent 的状态,没有嵌套函数

React - Access the state of a parent from the children, without nested function

您好,
我今天第一次来找你,因为我还没有找到解决我的问题的方法。
我已经使用 React 几周了,请不要对我的代码质量太苛刻。

问题:
我希望从他们的 children.So 访问 parent 的状态 我希望能够访问 setHeight 函数和高度变量,例如从 child 组件。

请注意:
但是,为了保持一些灵活性,我不想在我们的. 我查看了 redux 是否能够做到这一点,但问题是数据是全局的,因此无法创建多个下拉列表。 (除非我没太懂,redux还是挺复杂的)

图表:
我创建了一个图表来更好地解释它。,
我希望 DropdownMenu 的 children 能够访问后者的状态,另外,不同的 Dropdowns 必须有自己独立的状态。
所以理想情况下,我希望保持与 find 相同的结构非常灵活,并且可以创建多个下拉菜单。


代码:
我分享我的四个组成部分:


export default function Navbar () {
    return (
        <nav className={styles.navbar}>
            <ul className={styles.navbarNav}>
                <NavItem icon={<NotificationsIcon />} />
                <NavItem icon={<AccessTimeFilledIcon />} />
                <NavItem icon={<FileOpenIcon />}>
                    <DropdownMenu>
                        <DropdownSubMenu menuName="Home">
                            <DropdownItem>My Profile</DropdownItem>
                            <DropdownItem leftIcon={<AccessTimeFilledIcon />} rightIcon={<ChevronRightIcon />} goToMenu="pages">Pages</DropdownItem>
                            <DropdownItem>IDK</DropdownItem>
                            <DropdownItem>Test</DropdownItem>
                        </DropdownSubMenu>
                        <DropdownSubMenu menuName="pages">
                            <DropdownItem>Pages</DropdownItem>
                            <DropdownItem leftIcon={<AccessTimeFilledIcon />} rightIcon={<ChevronRightIcon />} goToMenu="home">Home</DropdownItem>
                        </DropdownSubMenu>
                    </DropdownMenu>

                    <DropdownMenu>
                        <DropdownSubMenu menuName="config">
                            <DropdownItem>Foo</DropdownItem>
                            <DropdownItem leftIcon={<AccessTimeFilledIcon />} rightIcon={<ChevronRightIcon />} goToMenu="theme">Configuration</DropdownItem>
                            <DropdownItem>Bar</DropdownItem>
                            <DropdownItem>Baz</DropdownItem>
                        </DropdownSubMenu>
                        <DropdownSubMenu menuName="theme">
                            <DropdownItem>Hi Whosebug</DropdownItem>
                            <DropdownItem leftIcon={<AccessTimeFilledIcon />} rightIcon={<ChevronRightIcon />} goToMenu="config">Theme</DropdownItem>
                        </DropdownSubMenu>
                    </DropdownMenu>
                </NavItem>
            </ul>
        </nav>
    );
};

type Props = {
    children?: React.ReactNode | React.ReactNode[];
    leftIcon?: React.ReactNode | JSX.Element | Array<React.ReactNode | JSX.Element>;
    rightIcon?: React.ReactNode | JSX.Element | Array<React.ReactNode | JSX.Element>;
    goToMenu?: string;
    goBack?: boolean;
    OnClick?: () => void;
};

export default function DropdownItem({ children, leftIcon, rightIcon, goToMenu, goBack, OnClick }: Props) {
    const handleClick = OnClick === undefined ? () => { } : OnClick;

    return (
        <a className={styles.menuItem} onClick={() => {
            goToMenu && setActiveMenu(goToMenu);
            setDirection(goBack ? 'menu-right' : 'menu-left');
            handleClick();
        }}>
            <span className={styles.iconButton}>{leftIcon}</span>
            {children}
            <span className={styles.iconRight}>{rightIcon}</span>
        </a>
    );
}

type Props = {
    menuName: string;
    children: React.ReactNode | React.ReactNode[];
}

enum Direction {
    LEFT = 'menu-left',
    RIGHT = 'menu-right'
}

export default function DropdownSubMenu (props: Props) {
    const [direction, setDirection] = useState<Direction>(Direction.LEFT);
    
    const calcHeight = (element: HTMLElement) => {
        if (element) setMenuHeight(element.offsetHeight);
    };

    return (
        <CSSTransition in={activeMenu === props.menuName} unmountOnExit timeout={500} classNames={direction} onEnter={calcHeight}>
            <div className={styles.menu}>
                {props.children}
            </div>
        </CSSTransition>
    );
}

type Props = {
    children: React.ReactNode | React.ReactNode[];
}

export default function DropdownMenu (props: Props) {
    const [activeMenu, setActiveMenu] = useState<string>('home');
    const [menuHeight, setMenuHeight] = useState<number | null>(null);
    const dropdownRef = useRef<HTMLDivElement>(null);

    useEffect(() => {
        const child = dropdownRef.current?.firstChild as HTMLElement;
        const height = getHeight(child);

        if (height)
            setMenuHeight(height);
    }, []);

    return (
        <div className={styles.dropdown} style={{ height: `calc(${menuHeight}px + 2rem)` }} ref={dropdownRef}>
            {props.children}
        </div>
    );
}

结论:
更具体地说,我不知道该放什么:

DropdownSubMenu中设置菜单高度(setMenuHeight),并获取active菜单(activeMenu)。
DropdownItem中,设置活动菜单,(setActiveMenu ) 并设置 CSS 动画的方向 (setDirection).

来源:
我的代码改编自这些来源,但我想让这段代码更专业、更灵活和多态: https://github.com/fireship-io/229-multi-level-dropdown

我试过了:
我试着查看 Redux,但我知道它只是全局状态。 所以它不允许为每个组件定义不同的上下文。

我尝试查看 React 18,但没有成功。 我搜索了 Whosebug 帖子,我搜索了 parents.

中的状态检索

在组件内部使用组件实际上解决了问题,但我们失去了所有的灵活性。

我可以说欢迎在这一刻做出反应,我为你感到高兴

好的,我可以理解你的问题。 但是没有问题,这个错误是由于您的经验不足造成的。

据我了解,您想单击下拉菜单并将其打开。 这里我们有嵌套的下拉菜单。

我认为这是你的答案: 您应该在每个下拉菜单中声明一个状态,而不是在父级中声明状态。

有多种方法可以从 children 访问 parent 状态。

将状态作为道具传递

首选方法是将状态 and/or 的更改函数传递给 children。

示例:

const App = () => {
    const [open, setOpen] = React.useState(false);

    const handleOpen = () => setOpen(true);
    const handleClose = () => setOpen(false);

    return (
        <div>
            <button onClick={handleOpen}>Open modal</button>
            <Modal onClose={handleClose} open={open} />
        </div>
    );
};

const Modal = ({ open, onClose }) => (
    <div className={open ? "open" : "close"}>
        <h1>Modal</h1>
        <button onClick={onClose}>Close</button>
    </div>
);

ReactDOM.render(<App />, document.querySelector("#app"));

演示:https://jsfiddle.net/47s28ge5/1/

使用 React 上下文

当 children 嵌套很深并且您不想沿着组件树传递状态时,第一种方法会变得复杂。

然后您可以使用上下文在多个 children 之间共享一个状态。

const AppContext = React.createContext(undefined);

const App = () => {
    const [open, setOpen] = React.useState(false);

    const handleOpen = () => setOpen(true);
    const handleClose = () => setOpen(false);

    return (
        <AppContext.Provider value={{ open, onClose: handleClose }}>
            <div>
                <button onClick={handleOpen}>Open modal</button>
                <Modal />
            </div>
        </AppContext.Provider>
    );
};

const Modal = () => {
    const { open, onClose } = React.useContext(AppContext);

    return (
        <div className={open ? "open" : "close"}>
            <h1>Modal</h1>
            <button onClick={onClose}>Close</button>
        </div>
    );
};

ReactDOM.render(<App />, document.querySelector("#app"));

演示:https://jsfiddle.net/dho0tmc2/3/

使用减速器

如果您的代码变得更加复杂,您可以考虑使用商店在组件之间共享全局状态。

您可以查看热门选项,例如: