React如何从子组件获取属性?侧边栏折叠场景实践疑问
React侧边栏折叠布局的规范解决方案
针对你需要父组件获取子组件SideNav宽度来实现折叠的需求,以下是符合React单向数据流原则的几种合理方案:
方案一:状态提升 + 子组件主动传递宽度
这是最符合React设计理念的方案,核心是让子组件在挂载/宽度变化时,通过回调将自身宽度传递给父组件,父组件统一管理折叠状态和宽度值,再将状态回传给子组件控制显示。
父组件代码
import { useState } from 'react'; import SideNav from './SideNav'; function Layout() { const [leftNavWidth, setLeftNavWidth] = useState(0); const [rightNavWidth, setRightNavWidth] = useState(0); const [isLeftNavOpen, setIsLeftNavOpen] = useState(true); const [isRightNavOpen, setIsRightNavOpen] = useState(true); return ( <div className="container" style={{ display: 'flex', position: 'relative', width: '100%', height: '100vh' }}> <SideNav name="Nirvana" side="left" isOpen={isLeftNavOpen} onSetWidth={setLeftNavWidth} /> <button onClick={() => setIsLeftNavOpen(!isLeftNavOpen)}> {isLeftNavOpen ? 'Close Left' : 'Open Left'} </button> <div className="content" style={{ flex: 1, marginLeft: isLeftNavOpen ? leftNavWidth : 0, marginRight: isRightNavOpen ? rightNavWidth : 0, padding: '1rem' }} > I'm full!!! </div> <button onClick={() => setIsRightNavOpen(!isRightNavOpen)}> {isRightNavOpen ? 'Close Right' : 'Open Right'} </button> <SideNav name="Beatles" side="right" isOpen={isRightNavOpen} onSetWidth={setRightNavWidth} /> </div> ); } export default Layout;
子组件SideNav代码
import { useEffect, useRef } from 'react'; function SideNav({ name, side, isOpen, onSetWidth }) { const navRef = useRef(null); // 挂载时传递初始宽度 useEffect(() => { if (navRef.current) { onSetWidth(navRef.current.offsetWidth); } }, [onSetWidth]); // 监听窗口变化,实时更新宽度 useEffect(() => { const handleResize = () => { if (navRef.current) { onSetWidth(navRef.current.offsetWidth); } }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, [onSetWidth]); return ( <div ref={navRef} style={{ position: 'fixed', top: 0, [side]: 0, height: '100vh', width: 'auto', backgroundColor: '#f0f0f0', padding: '1rem', transition: 'transform 0.3s ease', transform: isOpen ? 'translateX(0)' : side === 'left' ? 'translateX(-100%)' : 'translateX(100%)' }} > <h2>{name}</h2> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div> ); } export default SideNav;
方案二:CSS驱动折叠(无需父组件获取宽度)
如果不需要精确控制内容区的margin值,可以完全通过CSS实现折叠逻辑,父组件只需要控制子组件的显示状态,子组件自行处理布局变化,代码更简洁。
父组件代码
import { useState } from 'react'; import SideNav from './SideNav'; function Layout() { const [isLeftNavOpen, setIsLeftNavOpen] = useState(true); const [isRightNavOpen, setIsRightNavOpen] = useState(true); return ( <div className="container" style={{ display: 'flex', width: '100%', height: '100vh', overflow: 'hidden' }}> <SideNav name="Nirvana" side="left" isOpen={isLeftNavOpen} /> <button onClick={() => setIsLeftNavOpen(!isLeftNavOpen)}> {isLeftNavOpen ? 'Close Left' : 'Open Left'} </button> <div className="content" style={{ flex: 1, padding: '1rem', overflow: 'auto' }}> I'm full!!! </div> <button onClick={() => setIsRightNavOpen(!isRightNavOpen)}> {isRightNavOpen ? 'Close Right' : 'Open Right'} </button> <SideNav name="Beatles" side="right" isOpen={isRightNavOpen} /> </div> ); } export default Layout;
子组件SideNav代码
function SideNav({ name, side, isOpen }) { return ( <div style={{ flexShrink: 0, width: 'auto', backgroundColor: '#f0f0f0', padding: '1rem', transition: 'margin 0.3s ease', marginLeft: side === 'left' && !isOpen ? '-100%' : 0, marginRight: side === 'right' && !isOpen ? '-100%' : 0 }} > <h2>{name}</h2> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div> ); } export default SideNav;
方案三:使用forwardRef + useImperativeHandle(谨慎使用)
如果必须让父组件主动获取子组件的属性,可以通过ref让子组件暴露指定方法,但这种方式会打破单向数据流,仅在必要场景使用。
子组件SideNav代码
import { forwardRef, useRef, useImperativeHandle } from 'react'; const SideNav = forwardRef(({ name, side, isOpen }, ref) => { const navRef = useRef(null); // 暴露给父组件的宽度获取方法 useImperativeHandle(ref, () => ({ getWidth: () => navRef.current?.offsetWidth || 0 })); return ( <div ref={navRef} style={{ position: 'fixed', top: 0, [side]: 0, height: '100vh', width: 'auto', backgroundColor: '#f0f0f0', padding: '1rem', transition: 'transform 0.3s ease', transform: isOpen ? 'translateX(0)' : side === 'left' ? 'translateX(-100%)' : 'translateX(100%)' }} > <h2>{name}</h2> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div> ); }); export default SideNav;
父组件代码
import { useState, useRef, useEffect } from 'react'; import SideNav from './SideNav'; function Layout() { const leftNavRef = useRef(null); const rightNavRef = useRef(null); const [leftNavWidth, setLeftNavWidth] = useState(0); const [rightNavWidth, setRightNavWidth] = useState(0); const [isLeftNavOpen, setIsLeftNavOpen] = useState(true); const [isRightNavOpen, setIsRightNavOpen] = useState(true); // 挂载时获取初始宽度 useEffect(() => { setLeftNavWidth(leftNavRef.current?.getWidth() || 0); setRightNavWidth(rightNavRef.current?.getWidth() || 0); }, []); return ( <div className="container" style={{ display: 'flex', position: 'relative', width: '100%', height: '100vh' }}> <SideNav ref={leftNavRef} name="Nirvana" side="left" isOpen={isLeftNavOpen} /> <button onClick={() => setIsLeftNavOpen(!isLeftNavOpen)}> {isLeftNavOpen ? 'Close Left' : 'Open Left'} </button> <div className="content" style={{ flex: 1, marginLeft: isLeftNavOpen ? leftNavWidth : 0, marginRight: isRightNavOpen ? rightNavWidth : 0, padding: '1rem' }} > I'm full!!! </div> <button onClick={() => setIsRightNavOpen(!isRightNavOpen)}> {isRightNavOpen ? 'Close Right' : 'Open Right'} </button> <SideNav ref={rightNavRef} name="Beatles" side="right" isOpen={isRightNavOpen} /> </div> ); } export default Layout;
内容的提问来源于stack exchange,提问作者Roberto Giordano Barra
相关产品推荐
相关产品推荐

