React导航组件树需求:实现关闭已展开父节点功能
实现React导航组件的单节点展开功能
现有组件背景
我已经创建了一个用于导航的React组件树,需求是实现:当展开新的父节点时,自动关闭之前已展开的父节点。
现有配置与代码
导航配置数据
let navConfigData = { isDashboardVisible: true, tierOneLinks: [ { name: "First Page", iconName: "star", iconAbbreviation: "FP", tierTwoItems: [ { name: "Tier 2 Page", iconName: "star", iconAbbreviation: "FP", tierThreeItems: [ { name: "Tier 3 Page", iconName: "star", iconAbbreviation: "FP", path: "#" } ] }, { name: "Tier 2 sec Page", iconName: "star", iconAbbreviation: "FP", path: "#" } ] }, { name: "Second Page", iconName: "", iconAbbreviation: "SP", path: "#" }, { name: "Third Page", iconAbbreviation: "TP", path: "#", tierTwoItems: [ { name: "Tier 2 Page", iconName: "star", iconAbbreviation: "FP", tierThreeItems: [ { name: "Tier 3 Page", iconName: "star", iconAbbreviation: "FP", path: "#" } ] }, { name: "Tier 2 sec Page", iconName: "star", iconAbbreviation: "FP", path: "#" } ] }, { name: "Fourth Page", iconName: "send", iconAbbreviation: "FO", path: "#" } ], supportLinks: [ { name: "First Support Page", iconName: "settings", iconAbbreviation: "FI", path: "#" }, { name: "Second Support Page", iconName: "security", iconAbbreviation: "SE", path: "#" }, { name: "Third Support Page", iconName: "help", iconAbbreviation: "TH", path: "#" } ] };
组件代码
class NavigationDrawerTierThree extends React.Component { onClick = evt => { this.props.onSelect(this.props.tierThree.name); }; render() { const { tierThree, selectedTierThreeLink } = this.props; debugger; return ( <a className="mdc-list-item my-nav-drawer__tier-three" onClick={this.onClick} > <span className="my-nav-drawer__tier-two-label"> {tierThree.name} </span> </a> ); } } class NavigationDrawerTierTwo extends React.Component { // static propTypes = { // tierTwo: object.isRequired, // showNavIcon: bool, // onSelect: func.isRequired, // selectedTierTwoLink: string.isRequired // }; state = { tierTwoIsExpanded: false }; onClick = evt => { debugger; this.props.onSelect(this.props.tierTwo.name, "tier2"); const { tierTwoIsExpanded } = this.state; this.setState({ tierTwoIsExpanded: !tierTwoIsExpanded }); }; getArrowIcon = () => { return this.state.tierTwoIsExpanded ? ( <span class="material-icons cdk-nav-drawer__tier-two-icon">></span> ) : ( <span class="material-icons cdk-nav-drawer__tier-two-icon"> ^ </span> ); }; getAnchorClassBasedOnTier = () => { return this.props.showNavIcon ? "cdk-nav-drawer__tier-two" : "cdk-nav-drawer__tier-three"; }; getLabelClassBasedOnTier = () => { return this.props.showNavIcon ? "cdk-nav-drawer__tier-two-label" : "cdk-nav-drawer__tier-three-label"; }; render() { const { tierTwo, showNavIcon, selectedTierTwoLink } = this.props; const { tierTwoIsExpanded } = this.state; debugger; return ( <div> <a className="mdc-list-item my-nav-drawer__tier-two" onClick={this.onClick} > {showNavIcon && this.getArrowIcon()} <span className="my-nav-drawer__tier-two-label"> {tierTwo.name} </span> </a> {tierTwoIsExpanded && this.props.children} </div> ); } } class NavigationDrawerTierOneAnchor extends React.Component{ // static propTypes = { // tierOne: object.isRequired, // onSelect: func.isRequired, // selectedTierOneLink: string.isRequired // }; // state = { tierIsExpanded: false }; onClick = evt => { this.props.onSelect(this.props.tierOne.name, "tier1"); // const { tierIsExpanded } = this.state; // this.setState({ tierIsExpanded: !tierIsExpanded }); }; render(){ const { tierOne, selectedTierOneLink, expanded } = this.props; return( <div> <a className="mdc-list-item my-nav-drawer__tier-one" href={tierOne.path} onClick={this.onClick} ref={a => { this.anchor = a; }} > <span className="cdk-nav-drawer__tier-one-label"> {tierOne.name} </span> </a> {expanded && this.props.children} </div> ); } } class NavigationDrawerAppTiers extends React.Component { // static propTypes = { // tierOneArray: array.isRequired, // onToggleExpand: func.isRequired, // onSelect: func.isRequired, // selectedTierOneLink: string.isRequired, // selectedTierTwoLink: string.isRequired, // selectedTierThreeLink: string.isRequired, // expandedTierOneName: string.isRequired // }; render(){ const { tierOneArray, onSelect, selectedTierOneLink, selectedTierTwoLink, selectedTierThreeLink, expandedTierOneName } = this.props; return ( <div className="mdc-drawer__content my-nav-drawer__app-tiers"> <nav className="mdc-list"> {tierOneArray.map((tierOneLink, index) => ( <NavigationDrawerTierOneAnchor tierOne={tierOneLink} onSelect={onSelect} key={index} expanded={tierOneLink.name === this.props.expandedTierOneName} selectedTierOneLink={selectedTierOneLink} > {(tierOneLink.tierTwoItems || []).map((tierTwoItem, index) => ( <NavigationDrawerTierTwo tierTwo={tierTwoItem} onSelect={onSelect} selected
功能实现方案
核心思路是将展开状态从子组件移到父组件进行集中管理,这样父组件可以全局控制所有节点的展开状态,实现点击新节点时自动关闭旧节点的效果。具体步骤如下:
1. 在父组件中维护展开状态
找到包含NavigationDrawerAppTiers的上层组件(或者直接修改NavigationDrawerAppTiers),添加状态来记录当前展开的TierOne节点名称:
state = { expandedTierOneName: null, // 初始不展开任何节点,可设置默认值如"First Page" selectedTierOneLink: '', selectedTierTwoLink: '', selectedTierThreeLink: '' };
2. 添加状态更新方法
在父组件中添加处理TierOne节点展开/关闭的方法,逻辑是:如果点击的节点已经是展开状态,就关闭它;否则展开该节点并关闭其他节点:
handleTierOneExpand = (tierOneName) => { this.setState(prevState => ({ expandedTierOneName: prevState.expandedTierOneName === tierOneName ? null : tierOneName, // 可选:展开新TierOne时自动关闭所有TierTwo节点 expandedTierTwoName: null })); };
3. 传递状态与回调给子组件
修改NavigationDrawerAppTiers的props传递,把父组件的expandedTierOneName和handleTierOneExpand传递下去:
<NavigationDrawerAppTiers tierOneArray={navConfigData.tierOneLinks} onSelect={this.handleSelect} expandedTierOneName={this.state.expandedTierOneName} onToggleTierOneExpand={this.handleTierOneExpand} selectedTierOneLink={this.state.selectedTierOneLink} // ...其他props />
然后在NavigationDrawerAppTiers的render方法中,把展开回调传递给每个NavigationDrawerTierOneAnchor:
<NavigationDrawerTierOneAnchor tierOne={tierOneLink} onSelect={onSelect} onExpand={() => this.props.onToggleTierOneExpand(tierOneLink.name)} key={index} expanded={tierOneLink.name === this.props.expandedTierOneName} selectedTierOneLink={selectedTierOneLink} >
4. 修改TierOneAnchor的点击逻辑
更新NavigationDrawerTierOneAnchor的onClick方法,调用父组件的展开回调,并阻止默认跳转(如果需要):
onClick = evt => { evt.preventDefault(); // 阻止a标签默认跳转,优先处理展开逻辑 this.props.onSelect(this.props.tierOne.name, "tier1"); this.props.onExpand(); // 触发父组件的状态更新 };
5. 可选:TierTwo节点的单展开实现
如果需要TierTwo节点也实现点击新节点关闭旧节点的功能,重复上述逻辑:
- 在父组件添加
expandedTierTwoName状态 - 添加
handleTierTwoExpand方法 - 传递状态和回调给
NavigationDrawerTierTwo组件 - 修改
NavigationDrawerTierTwo的点击逻辑,移除自身的state,改用父组件传递的状态
总结
通过集中式状态管理,我们把原本分散在子组件的展开状态统一到父组件,这样就能轻松实现"展开新节点自动关闭旧节点"的需求,同时也让组件状态更易维护和调试。
内容的提问来源于stack exchange,提问作者user804401
相关产品推荐
相关产品推荐

