【问题标题】:Alternately style items in deeply nested lists with React使用 React 交替样式化深度嵌套列表中的项目
【发布时间】:2021-03-21 09:59:16
【问题描述】:

所以我使用 react 从 json 文件中呈现 NESTED 无序列表。为了便于阅读,我希望以每行交替的背景颜色显示它们。 所以是这样的:

1 (white)
2 (gray)
    3 (white)
        4 (gray)
        5 (white)
    6 (gray)
7 (white)

我尝试使用纯 CSS,但它似乎不起作用,因为 nth-of-type() 只检查相对索引。仅供参考,它看起来像这样:

1 (white)
2 (gray)
    1 (white)
        1 (white)
        2 (gray)
    2 (gray)
3 (white)

然后我尝试在递归渲染函数中使用计数器来确定当前行是偶数还是奇数:

class Tree extends React.Component{
    renderChild = (child, count=0, key=0) => {
        //if 
        if (child.children) {
            return (
                //I need some state management so I need another component. Kind of like double recursion.
                <Subtree renderChild={this.renderChild} child={child} count={count} key={key}/>
            ); 
        }
        else if (child.name) {
            return <Node child={child} count={count} key={key}/>; 
        }
        count += 1
        return null;
    }
    render(){
        return (
            <div className="tree">
                {this.renderChild(this.props.data)}
            </div>
        )
    }
}

function Subtree (props){
    // just an example of many states it's supposed to have so I have to put it in a separate component
    // collapse will trigger recount since it will change the even/odd of subsequent nodes
    const [ collapsed, setCollapsed ] = useState(false)
    return (
        <figure>
            //title for the ul. needs to be counted in the sequence as well.
            <figcaption onClick={()=>setCollapsed(!collapsed)} className={`${(props.count % 2) === 1 ? "gray field" : "field"}`}>
                {props.child.name}
            </figcaption>
            {collapsed === false
                ?
                <ul> 
                    //needs to be async here
                    {props.child.children.map((item, index) => {
                        //calls to parent to recursively render all the list
                        //add 1 counting for the title
                        //need to wait for this to finish before moving to the next item.
                        return props.renderChild(item, props.count + index + 1, uuid());
                    })}
                </ul>
                :
                null
            }
        </figure>
    ); 
}

function Node (props){
    return(
        //check if count is even or odd
        <li className={`${props.count % 2===1 ? "gray field" : "field"}`}>
            {props.child.name}
        </li>
    )
}

但是,正如您已经知道的那样,序列更加混乱,因为序列中相同级别的流程节点的递归类型。所以我所拥有的是这样的:

1 (white)
2 (gray)
    4 (gray)
        6 (gray)
        7 (white)
    5 (white)
3 (white)

我知道我可以展平树,然后使用匹配字段来确定偶数/奇数。但它在时间和空间方面似乎都非常低效。

【问题讨论】:

    标签: javascript css reactjs


    【解决方案1】:

    在渲染之后通过useEffect(或useLayoutEffect,如果您需要同步调用)中的直接DOM 操作似乎最直接。

    这是一个 sn-p,它将 ref 应用于列表容器并实现 useEffect 具有状态依赖项,该状态依赖项查询和迭代所有子 li 元素,并清除返回回调中传递的操作.

     const ulContainerRef = React.useRef(null);
    
      React.useEffect(() => {
        const listItems = ulContainerRef.current.querySelectorAll('li');
        listItems.forEach((item, i) => {
          item.classList.add(i % 2 ? 'even' : 'odd');
        });
    
        return () => {
          listItems.forEach((item, i) => {
            item.classList.remove('even', 'odd');
          });
        }
      }, [data]);
    

    var initData = [{ id: 1, title: 'Title 1', children: [{ id: 1.1, title: 'Title  1.1' }, { id: 1.2, title: 'Title 1.2' }] }, { id: 2, title: 'Title 2', children: [{ id: 2.1, title: 'Title 2.1' }] }, { id: 3, title: 'Title 3', children: [{ id: 3.1, title: 'Title 3.1' }, { id: 2.2, title: 'Title 3.2' }] }]
    
    const App = () => {
      const [data, setData] = React.useState(initData);
      const ulContainerRef = React.useRef(null);
    
      React.useEffect(() => {
        const listItems = ulContainerRef.current.querySelectorAll('li');
        listItems.forEach((item, i) => {
          item.setAttribute('data-row', i);
          item.classList.add(i % 2 ? 'odd' : 'even');
        });
    
        return () => {
          listItems.forEach((item, i) => {
            item.classList.remove('odd', 'even');
          });
        }
      }, [data]);
    
      const alterData = () => {
        let i = 1;
        setData(prevData => (
          [...prevData.slice(0, i),
          {
            ...prevData[i],
            children: [...prevData[i].children,
            {
              id: +'2.' + (prevData[i].children.length + 1),
              title: 'Title 2.' + (prevData[i].children.length + 1)
            }
            ]
          },
          ...prevData.slice(i + 1)]
        ));
      }
    
      return (
        <div ref={ulContainerRef}>
          <Ul list={data} />
          <button type='button' onClick={alterData}>Alter Data</button>
        </div>
      )
    }
    
    const Ul = ({ list }) => {
    
      return (
        <ul>
          {list.length > 0 && list.map((item, i) => (
            <li key={item.id}>
              {item.title}
              {(item.hasOwnProperty('children') && item.children.length > 0) &&
                <Ul key={item.id + '_c'} list={item.children} />
              }
            </li>
          )
          )}
        </ul>
      )
    }
    
    ReactDOM.render(
      <App />,
      document.getElementById("react")
    );
    body {
      font-family: monospace;
    }
    
    ul {
      list-style-type: none;
      width: 160px;
    }
    
    .odd::before {
      content: "(odd: " attr(data-row) ") ";
      background-color: gray;
    }
    
    .even::before {
      content: " (even: " attr(data-row) ") ";
      background-color: aquamarine;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    <div id="react"></div>

    【讨论】:

      【解决方案2】:

      此方法通过计算树呈现的每个项目的所有先前兄弟分支(及其嵌套子级)来工作。虽然它允许您直接内联映射您的数据结构(没有展平),但如果树足够大且足够深,这样做的性能成本可能使其在现实生活用例中不切实际。

      我认为这真的取决于您的data 多久刷新一次。如果这只是一个导航菜单,其中内容在初始加载后不会改变,那么将下面的内容包装在一个 memoised 组件中就可以了。但是,如果您希望数据经常更改,那么我真的建议展平数据结构并记住计算,这样可以更有效地编写,而不是像这种方法那样尝试在每次渲染时动态计算它.

      附注:这个问题确实与异步编程无关。不能在 map 调用中间使用 await - 数据要么存在于映射数组中,要么不存在。

      const data = [
        {
          name: "Level 1-1"
        },
        {
          name: "Level 1-2",
          children: [
            {
              name: "Level 2-1",
              children: [
                {
                  name: "Level 3-1"
                },
                {
                  name: "Level 3-2"
                }
              ]
            },
            {
              name: "Level 2-2"
            }
          ]
        },
        {
          name: "Level 1-3"
        }
      ];
      
      const getSibCount = (itemArr, count = 0) => {
        itemArr.forEach((item) => {
          count += 1;
          item.children && (count += getSibCount(item.children));
        });
        return count;
      };
      
      const getClass = (count) => (count % 2 === 1 ? "grey" : "white");
      
      function Tree({ data, count, depth }) {
        return (
          <ul>
            {data.map((item, i, arr) => {
              const newCount = count + getSibCount(arr.slice(0, i));
      
              return item.children ? (
                <li key={item.name}>
                  <p style={{paddingLeft: `${depth*15}px`}} className={getClass(newCount)}>
                    {newCount} {item.name}
                  </p>
                  <Tree data={item.children} count={newCount+1} depth={depth+1} />
                </li>
              ) : (
                <li style={{paddingLeft: `${depth*15}px`}} className={getClass(newCount)} key={item.name}>
                  {newCount} {item.name}
                </li>
              );
            })}
          </ul>
        );
      }
      
      ReactDOM.render(
        <Tree data={data} count={0} depth={0} />,
        document.getElementById('root')
      );
      * {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
        list-style: none;
      }
      
      .grey {
        background: grey;
      }
      
      .white {
        background: cornsilk;
      }
      <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
      <div id="root"></div>

      【讨论】:

        猜你喜欢
        • 2010-10-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-06
        • 2021-07-24
        • 2012-09-20
        • 2020-03-21
        • 2017-08-27
        相关资源
        最近更新 更多