如何使用 React 组件处理递归数组映射?

Lux*_*Lux 2 javascript recursion reactjs

这是index()我用来从 Markdown 文件返回标题索引的示例函数。正如您所看到的,只有 3 个级别的标题,它很快就会失控。

我尝试了各种递归想法,但最终在create-react-app.

任何人都可以就如何清理它甚至允许无限/更高级别的嵌套提供任何指导吗?

存储一个数组this.state.index,类似于下面的数组。

[
  {
    label: 'Header title',
    children: [
      {
        label: 'Sub-header title'
        children: [...]
      }
    ]
  }
]
Run Code Online (Sandbox Code Playgroud)

然后在下面的函数中使用该数据来生成ul索引。

  index() {
    if ( this.state.index === undefined || !this.state.index.length )
      return;
    return (
      <ul className="docs--index">
        {
          this.state.index.map((elm,i=0) => {
            let key = i++;
            let anchor = '#'+elm.label.split(' ').join('_') + '--' + key;
            return (
              <li key={key}>
                <label><AnchorScroll href={anchor}>{elm.label}</AnchorScroll></label>
                <ul>
                  {
                    elm.children.map((child,k=0) => {
                      let key = i+'-'+k++;
                      let anchor = '#'+child.label.split(' ').join('_') + '--' + key;
                      return (
                        <li key={key}>
                          <label><AnchorScroll href={anchor}>{child.label}</AnchorScroll></label>
                            <ul>
                              {
                                child.children.map((grandchild,j=0) => {
                                  let key = i+'-'+k+'-'+j++;
                                  let anchor = '#'+grandchild.label.split(' ').join('_') + '--' + key;
                                  return (
                                    <li key={key}>
                                      <label><AnchorScroll href={anchor}>{grandchild.label}</AnchorScroll></label>
                                    </li>
                                  );
                                })
                              }
                            </ul>
                        </li>
                      );
                    })
                  }
                </ul>
              </li>
            );
          })
        }
      </ul>
    );
  }
Run Code Online (Sandbox Code Playgroud)

就像我说的,这是一团糟!我是 React 和编码的新手,如果这是一个愚蠢的问题,我很抱歉。

Sag*_*b.g 5

好像你想要一个组件的递归调用。
例如,组件Item将根据条件(children数组的存在)自行呈现。

运行示例:

const data = [
  {
    label: 'Header title',
    children: [
      {
        label: 'Sub-header title',
        children: [
          { label: '3rd level #1' },
          {
            label: '3rd level #2',
            children: [
              { label: 'Level 4' }
            ]
          }
        ]
      }
    ]
  }
]

class Item extends React.Component {
  render() {
    const { label, children } = this.props;
    return (
      <div>
        <div>{label}</div>
        <div style={{margin: '5px 25px'}}>
          {children && children.map((item, index) => <Item key={index} {...item} />)}
        </div>
      </div>
    )
  }
}

const App = () => (
  <div>
    {data.map((item, index) => <Item key={index} {...item} />)}
  </div>
);

ReactDOM.render(<App />, document.getElementById('root'));
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Run Code Online (Sandbox Code Playgroud)

编辑

为了完整起见,我添加了另一个示例,但这次我们在更高级别处理本地组件状态isOpen之外的属性Item
这可以很容易地移动到redux减速器或任何其他状态管理库,或者只是让更高级别的组件像App在这种情况下管理更改。

因此,要处理递归组件的更改,您可能会编写一个递归处理程序:

const data = [
  {
    label: 'Header title',
    id: 1,
    children: [
      {
        label: 'Sub-header title',
        id: 2,
        children: [
          { label: '3rd level #1', id: 3 },
          {
            label: '3rd level #2',
            id: 4,
            children: [
              { label: 'Level 4', id: 5 }
            ]
          }
        ]
      },
    ]
  },
  {
    label: 'Header #2',
    id: 6,
    children: [
      { label: '2nd level #1', id: 7, },
      {
        label: '2nd level #2',
        id: 8,
        children: [
          { label: 'Level 3', id: 9 }
        ]
      }
    ]
  }
]

class Item extends React.Component {

  toggleOpen = e => {
    e.preventDefault();
    e.stopPropagation();
    const {onClick, id} = this.props;
    onClick(id);
  };

  render() {
    const { label, children, isOpen, onClick } = this.props;
    return (
      <div className="item">
        <div
          className={`${children && "clickable"}`}
          onClick={children && this.toggleOpen}
        >
          <div
            className={`
            title-icon
            ${isOpen && " open"}
            ${children && "has-children"}
            `}
          />
          <div className="title">{label}</div>
        </div>
        <div className="children">
          {children &&
            isOpen &&
            children.map((item, index) => <Item key={index} {...item} onClick={onClick} />)}
        </div>
      </div>
    );
  }
}

class App extends React.Component {
  state = {
    items: data
  };

  toggleItem = (items, id) => {
    const nextState = items.map(item => {
      if (item.id !== id) {
        if (item.children) {
          return {
            ...item,
            children: this.toggleItem(item.children, id)
          };
        }
        return item;
      }
      return {
        ...item,
        isOpen: !item.isOpen
      };
    });
    return nextState;
  };

  onItemClick = id => {
    this.setState(prev => {
      const nextState = this.toggleItem(prev.items, id);
      return {
        items: nextState
      };
    });
  };

  render() {
    const { items } = this.state;
    return (
      <div>
        {items.map((item, index) => (
          <Item key={index} {...item} onClick={this.onItemClick} />
        ))}
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)
.item {
  padding: 0 5px;
}

.title-icon {
  display: inline-block;
  margin: 0 10px;
}

.title-icon::before {
  margin: 12px 0;
  content: "\2219";
}

.title-icon.has-children::before {
  content: "\25B6";
}

.title-icon.open::before {
  content: "\25E2";
}

.title-icon:not(.has-children)::before {
  content: "\2219";
}

.title {
  display: inline-block;
  margin: 5px 0;
}

.clickable {
  cursor: pointer;
  user-select: none;
}

.open {
  color: green;
}

.children {
  margin: 0 15px;
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Run Code Online (Sandbox Code Playgroud)