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/
使用减速器
如果您的代码变得更加复杂,您可以考虑使用商店在组件之间共享全局状态。
您可以查看热门选项,例如:
- Mobx:https://mobx.js.org/(我个人最喜欢的)
- 终极版:https://redux.js.org/
- RxJS:https://rxjs.dev/
您好,
我今天第一次来找你,因为我还没有找到解决我的问题的方法。
我已经使用 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/
使用减速器
如果您的代码变得更加复杂,您可以考虑使用商店在组件之间共享全局状态。
您可以查看热门选项,例如:
- Mobx:https://mobx.js.org/(我个人最喜欢的)
- 终极版:https://redux.js.org/
- RxJS:https://rxjs.dev/