Привет,
Я прихожу к вам сегодня в первый раз, потому что я не нашел решение своей проблемы.
Я использую реакцию в течение нескольких недель, не будьте слишком жестоки по поводу качества моего кода ?.
Проблема :
Я хочу получить доступ к состоянию родителя от своих детей. Поэтому я хочу иметь доступ к функции setHeight и переменной высоты, например, из дочернего компонента.
Пожалуйста, обрати внимание :
Однако, чтобы сохранить некоторую гибкость, я не хочу иметь какие-либо компоненты внутри нашего.
Я посмотрел на Redux, чтобы сделать это, но проблема в том, что данные глобальны, поэтому создание нескольких раскрывающихся списков невозможно.
(Если я не слишком много понял, редукция довольно сложна)
Диаграмма:
Я создал диаграмму, чтобы объяснить это немного лучше.,
Я бы хотел, чтобы дети DropdownMenu могли получить доступ к состоянию последнего. Кроме того, разные выпадающие списки должны иметь свое собственное состояние независимо друг от друга.
Поэтому в идеале я хочу сохранить ту же структуру, что и найти очень гибкой, и возможность создать несколько раскрывающихся списков.
Код :
Я разделяю свои четыре компонента:
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, я искал состояние, полученное от родителей.
Использование компонентов внутри компонента фактически решает проблему, но мы теряем всю гибкость.
Да, конечно, я посмотрел, но если я правильно понял, globalState будет применяться ко всему приложению. Поэтому, если я создам несколько раскрывающихся списков, они будут использовать это глобальное состояние. Значит, у меня не может быть несколько раскрывающихся списков с разными значениями состояния?
Я могу сказать, добро пожаловать, чтобы отреагировать в этот момент, и я рад за вас
Хорошо, я мог понять, в чем твоя проблема. но это не проблема, и эта ошибка вызвана вашим низким опытом.
Насколько я понимаю, вы хотите щелкнуть раскрывающийся список и открыть его. и здесь у нас есть вложенный раскрывающийся список.
Я думаю, это ваш ответ: Вы должны объявить состояние в каждом раскрывающемся списке и не объявлять состояние в родительском.
Есть несколько способов получить доступ к родительскому состоянию из его дочерних элементов.
Предпочтительным способом является передача состояния и/или функции изменения дочерним элементам.
Пример :
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, чего я не знал. Это великолепно!
Привет, Хардел, смотрели ли вы useReducer и глобальное состояние?