You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 20:24:54