React - доступ к состоянию родителя из дочерних элементов без вложенной функции

Привет,
Я прихожу к вам сегодня в первый раз, потому что я не нашел решение своей проблемы.
Я использую реакцию в течение нескольких недель, не будьте слишком жестоки по поводу качества моего кода ?.

Проблема :
Я хочу получить доступ к состоянию родителя от своих детей. Поэтому я хочу иметь доступ к функции setHeight и переменной высоты, например, из дочернего компонента.

Пожалуйста, обрати внимание :
Однако, чтобы сохранить некоторую гибкость, я не хочу иметь какие-либо компоненты внутри нашего. Я посмотрел на Redux, чтобы сделать это, но проблема в том, что данные глобальны, поэтому создание нескольких раскрывающихся списков невозможно. (Если я не слишком много понял, редукция довольно сложна)

Диаграмма:
Я создал диаграмму, чтобы объяснить это немного лучше.,
Я бы хотел, чтобы дети DropdownMenu могли получить доступ к состоянию последнего. Кроме того, разные выпадающие списки должны иметь свое собственное состояние независимо друг от друга.
Поэтому в идеале я хочу сохранить ту же структуру, что и найти очень гибкой, и возможность создать несколько раскрывающихся списков.

React - доступ к состоянию родителя из дочерних элементов без вложенной функции


Код :
Я разделяю свои четыре компонента:


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 StackOverflow</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>
    );
}

Вывод :
Более конкретно, я не знаю, что поставить вместо:

В Выпадающее менюПодменю установить высоту меню (setMenuHeight) и получить активное меню (активное меню).
В Выпадающий элемент установите активное меню (setActiveMenu) и задайте направление анимации CSS (установитьнаправление).

Источник :
Мой код адаптирован из этих исходников, но я хочу сделать этот код более профессиональным, гибким и полиморфным: https://github.com/fireship-io/229-многоуровневое выпадающее меню

Меня судили:
Я попытался посмотреть на Redux, но понял, что это только state global. Таким образом, это не позволяет определить другой контекст для каждого компонента.

Я пытался посмотреть на React 18, но безуспешно. Я искал сообщения StackOverflow, я искал состояние, полученное от родителей.

Использование компонентов внутри компонента фактически решает проблему, но мы теряем всю гибкость.

Привет, Хардел, смотрели ли вы useReducer и глобальное состояние?

fuzzybear 22.03.2022 22:52

Да, конечно, я посмотрел, но если я правильно понял, globalState будет применяться ко всему приложению. Поэтому, если я создам несколько раскрывающихся списков, они будут использовать это глобальное состояние. Значит, у меня не может быть несколько раскрывающихся списков с разными значениями состояния?

Hardel 22.03.2022 22:57
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
2
37
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я могу сказать, добро пожаловать, чтобы отреагировать в этот момент, и я рад за вас

Хорошо, я мог понять, в чем твоя проблема. но это не проблема, и эта ошибка вызвана вашим низким опытом.

Насколько я понимаю, вы хотите щелкнуть раскрывающийся список и открыть его. и здесь у нас есть вложенный раскрывающийся список.

Я думаю, это ваш ответ: Вы должны объявить состояние в каждом раскрывающемся списке и не объявлять состояние в родительском.

Ответ принят как подходящий

Есть несколько способов получить доступ к родительскому состоянию из его дочерних элементов.

Передать состояние в качестве реквизита

Предпочтительным способом является передача состояния и/или функции изменения дочерним элементам.

Пример :

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/

Используйте контекст реакции

Первый способ усложняется, когда дочерние элементы глубоко вложены друг в друга и вы не хотите переносить состояние по дереву компонентов.

Затем вы можете поделиться состоянием между несколькими дочерними элементами с помощью контекста.

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/

Использование редуктора

Если ваш код становится еще более сложным, вы можете рассмотреть возможность использования хранилища для совместного использования глобального состояния между вашими компонентами.

Вы можете ознакомиться с популярными вариантами, такими как:

Мне удалось с React Context, чего я не знал. Это великолепно!

Hardel 25.03.2022 17:45

Другие вопросы по теме