React 30 秒速学:实现可折叠、无限层级的树形组件

本文同时发布于掘金,地址:https://juejin.im/post/6844903848335638535

本文译自:30-seconds-of-react。React 30 秒速学:全篇中文翻译、学习,地址:30-seconds-of-react-zh_CN-umi,所有案例进行分析、注释、上线。

树形组件

实现一个可折叠、无限层级、支持数组和对象的树组件。

  • 使用对象解构来设置某些传入属性的默认值。
  • 使用传入的 toggled 属性来确定内容的初始状态(折叠/展开)。
  • 使用React.setState() hook 来创建isToggled状态变量,并在最初为它赋予传入的 toggled的值。
  • 返回一个<div>来包装组件的内容和用于改变组件的isToggled状态的<span>元素。
  • 根据data上的isParentToggledisTogglednameArray.isArray()确定组件的外观。
  • 对于data中的每个子节点,确定它是对象还是数组,并递归渲染子树。
  • 否则,使用适当的样式渲染一个<p>元素。

样式:

/* 树节点的基本样式 */
.tree-element {
  margin: 0;
  position: relative;
}
div.tree-element:before {
  content: '';
  position: absolute;
  top: 24px;
  left: 1px;
  height: calc(100% - 48px);
  border-left: 1px solid gray;
}

/* 切换显示、隐藏的按钮元素 */
.toggler {
  position: absolute;
  top: 10px;
  left: 0px;
  width: 0;
  height: 0;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  border-left: 5px solid gray;
  cursor: pointer;
}
.toggler.closed {
  transform: rotate(90deg);
}

/* 隐藏节点内容 */
.collapsed {
  display: none;
}

树组件,组件内容较多,在学习过程中我也对其中的内容,分别进行了注释:

import styles from "./TreeView.css";

function TreeView({
  // 使用对象解构来设置某些传入属性的默认值
  data,
  toggled = true, // 折叠按钮,是否处于折叠状态
  name = null, // 当前属性名,如果子元素是对象显示
  isLast = true, // 是否最后一个
  isChildElement = false, // 是否子元素
  isParentToggled = true // 是否被父节点折叠
}) {
  const [isToggled, setIsToggled] = React.useState(toggled);

  return (
    <div
      style={{ marginLeft: isChildElement ? 16 : 4 + "px" }}
      // 如果父折叠就隐藏
      className={isParentToggled ? styles["tree-element"] : styles.collapsed}
    >
      {/* 折叠按钮,点击设置反状态 */}
      <span
        className={
          isToggled ? styles.toggler : `${styles.toggler} ${styles.closed}`
        }
        onClick={() => setIsToggled(!isToggled)}
      />
      {name ? <strong>&nbsp;&nbsp;{name}: </strong> : <span>&nbsp;&nbsp;</span>}
      {/* 开始符 */}
      {Array.isArray(data) ? "[" : "{"}
      {/* 子元素被折叠 */}
      {!isToggled && "..."}
      {/* 渲染对象的子元素 */}
      {Object.keys(data).map((v, i, a) =>
        // 是对象,递归调用自身
        typeof data[v] == "object" ? (
          <TreeView
            data={data[v]}
            key={i}
            isLast={i === a.length - 1}
            // 子元素的属性名,对象需要显示属性名,数组不显示
            name={Array.isArray(data) ? null : v}
            isChildElement
            isParentToggled={isParentToggled && isToggled}
          />
        ) : ( // 不是对象,显示内容即可
          <p
            key={i}
            style={{ marginLeft: 16 + "px" }}
            className={isToggled ? styles["tree-element"] : styles.collapsed}
          >
            {Array.isArray(data) ? "" : <strong>{v}: </strong>}
            {data[v]}
            {i === a.length - 1 ? "" : ","}
          </p>
        )
      )}
      {/* 结束符 */}
      {Array.isArray(data) ? "]" : "}"}
      {/* 不是最后元素,加个逗号 */}
      {!isLast ? "," : ""}
    </div>
  );
}

例子

export default function() {
  let data = {
    lorem: {
      ipsum: "dolor sit",
      amet: {
        consectetur: "adipiscing",
        elit: [
          "duis",
          "vitae",
          {
            semper: "orci"
          },
          {
            est: "sed ornare"
          },
          "etiam",
          ["laoreet", "tincidunt"],
          ["vestibulum", "ante"]
        ]
      },
      ipsum: "primis"
    }
  };
  return <TreeView data={data} name="data" />;
}

ps:

本文收录于专栏
译自:30-seconds-of-react 项目,全篇中文翻译、学习,所有案例进行分析、注释、上线。