首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >响应面包屑组件实现

响应面包屑组件实现
EN

Code Review用户
提问于 2020-12-14 04:09:11
回答 1查看 366关注 0票数 3

我编写了一个面包屑组件(RouteBreadcrumbs),它具有以下特性/行为:

  • 接收每个面包屑的标签数组(例如:['Settings', 'System settings', 'Display'] )。该组件按照放置在数组中的顺序显示这些标签。
  • 假设数组中的最后一个标签表示当前查看的页。对于其前面的其他标签,用户可以单击其中一个以导航到该页面。
  • 获取呈现的当前路径,并对其进行解析,以便为每个面包屑分配正确的路径(最后一个除外)。
  • 如果当前路径没有包含足够的“子路径”以满足传入的标签数量,我选择不呈现组件。例如,当当前路径包含三个项时,如果当前路径仅为/system-settings/display,则组件不会呈现。

最终,组件呈现时会像这样(我还没有实现每个面包屑之间的箭头):

代码语言:javascript
复制
Settings > System settings > Display

在这种情况下,"Display“将是当前查看的页面。用户可以单击“设置”或“系统设置”,以跳转到相应的页面。

我很想得到关于我的执行情况的反馈。我还想了解更多关于最佳编码实践的知识,以及我的组件中是否有任何强大的代码气味(如果有,为什么会认为它是一种代码气味)。

我认为有些领域可能是代码的气味,但不太确定它们是否是或为什么是这样的:

  • pathNameIdx--循环中调用while,并在循环退出后再次调用
  • 在组件中的多个位置有一个return语句(在我的例子中,只有两个地方)
  • 使用常规的for循环,而不是其他形式,如for..offor..in.forEach
代码语言:javascript
复制
import React from "react";
import { useHistory, useLocation } from "react-router-dom";

const RouteBreadcrumbs = ({labels = []}) => {
    const location = useLocation();
    const history = useHistory();
    const pathName = location.pathname;

    const numBackslash = (pathName.match(/\//g) || []).length;
    if (numBackslash < labels.length) {
        return null;
    }

    let breadcrumbPaths = [];
    let pathNameIdx = pathName.length - 1;
    for (let i = labels.length - 1; i > 0 ; i--) {
        while ((pathName[pathNameIdx] !== "/")) {
            console.assert(pathNameIdx >= 0, "pathNameIdx is no longer a valid index");
            pathNameIdx--;
        }

        const newPath = pathName.substring(0, pathNameIdx);
        breadcrumbPaths = [newPath, ...breadcrumbPaths];
        pathNameIdx--;
    }
    
    return (
        
            {labels.map((label, idx) =>  history.push(breadcrumbPaths[idx]) : null} className={"label2"}>{label})}
        
    )
};

```#qcStackCode#
代码语言:javascript
复制
EN

回答 1

Code Review用户

回答已采纳

发布于 2020-12-19 05:12:21

你的代码看起来不错。以下是一些你可以尝试的建议:

本部分

代码语言:javascript
复制
{labels.map((label, idx) =>  history.push(breadcrumbPaths[idx]) : null} className={"label2"}>{label})}

是很长的代码行。试着打破它,它使它更容易阅读。

代码语言:javascript
复制
{labels.map((label, idx) => (
   history.push(breadcrumbPaths[idx])
        : null
    }
    className={"label2"}
  >
    {label}
  
))}

(我倾向于使用带有javascript和HTML的2空格缩进,因为它们都倾向于缩进很重,而且在javascript社区中似乎更常见--因此我将在下面的示例中使用这一点)

此外,如果可能的话,不要为元素键使用索引(医生的反应讨论了为什么)。您可以使用breadcrumbPath,因为这对每个元素都是唯一的,并且当路径发生变化时,键将正确地更改。

代码语言:javascript
复制
{labels.map((label, idx) => (
  
    {label}
  
))}

您有一些逻辑来计算URL包含的反斜杠的数量,但是如果您在早期返回时稍等一会儿,您就可以轻松地访问该数字,因为它与breadcrumbsPath.length + 1一样。

代码语言:javascript
复制
if (breadcrumbPaths.length + 1 < labels.length) {
  return null;
}

在这一点上,当URL上有一个前导斜杠(即'example.com/a/b/')时,这个函数将不能正确地处理这个情况--这需要处理。如果您使用上面的代码片段,那么您只需要在一个地方而不是两个地方修复这个问题--在这里创建breadcrumbPaths数组。

要解决有多个返回的问题:在一个函数中有多个返回没有什么错--实际上,它通常会导致更干净的代码。通常,替代方法意味着较大的if-thens或标志变量,这两个变量都很难理解。我听说,从前,有一个只有一个出口的函数是被鼓励的,但现在已经不是这样了。

现在进入您可能最关心的部分--如何使用c样式的for循环和多个pathNameIdx--s来处理这部分逻辑。

首先,我可能会将逻辑的核心部分分解成一个助手函数。有许多低层次的工作正在进行,阻碍了阅读和理解组件。

下面是您的组件在执行此操作后的处理方法

代码语言:javascript
复制
import React from "react";
import { useHistory, useLocation } from "react-router-dom";

const RouteBreadcrumbs = ({labels = []}) => {
  const location = useLocation();
  const history = useHistory();
  const pathName = location.pathname;

  const breadcrumbPaths = ancestorPaths(pathName);
  if (breadcrumbPaths.length + 1 < labels.length) {
    return null;
  }

  return (
    
      {labels.map((label, idx) => (
         history.push(breadcrumbPaths[idx])
              : null
          }
          className={"label2"}
        >
          {label}
        
      ))}
    
  )
};

function ancestorPaths(pathName) {
  let breadcrumbPaths = [];
  let pathNameIdx = pathName.length - 1;
  while (true) {
    while (pathName[pathNameIdx] !== "/") {
      if (pathNameIdx === 1) {
        return breadcrumbPaths;
      }
      pathNameIdx--;
    }

    const newPath = pathName.slice(0, pathNameIdx);
    breadcrumbPaths = [newPath, ...breadcrumbPaths];
    pathNameIdx--;
  }
}

注意,在实际的RouteBreadcrumbs组件中,低层次的逻辑是如何发生的?阅读起来要容易得多,并且可以很好地了解正在发生的事情--除了实际的jsx被返回之外--这对我来说有点太嵌套了,但很快就会简化了。

我将在您的原始代码中注意到其他几点:

  • 更喜欢string.slice()而不是string.substring() -它有更少的夸克
  • 很好地利用console.assert()。关于console.assert(),我不喜欢的一点是它实际上不会抛出一个错误,它只是记录一个错误。有时候这没什么大不了的,就在这里。如果这个断言碰巧实现了,它将被困在一个无限循环中,永远日志记录。您可以直接执行if (condition) throw new Error(...),或者创建您自己的断言函数,该函数实际上会引发错误。

现在让我们看看如何简化我们的新的ancestorPaths()助手函数。可以使用regex收集所有斜杠的指示符,这将与您的工作方式类似,但大大简化了逻辑(请参阅这里)。相反,我只需要拆分'/',构建一个列表,并使用生成器函数,如下所示:

代码语言:javascript
复制
function* iterAncestorPaths(pathName) {
  const parts = pathName.split("/").filter(x => x !== "");
  for (let i = 1; i < parts.length; ++i) {
    yield "/" + parts.slice(0, i).join("/");
  }
}

请注意,开始时的.filter()将同时处理领先的/和潜在的尾随斜杠。

最后一件事:如果你的面包屑段是链接,为什么不让它们成为实际链接呢?它会简化一些事情。

这是最后的解决方案,还有一些其他的错误调整。

代码语言:javascript
复制
import React from "react";
import { Link, useLocation } from "react-router-dom";

const RouteBreadcrumbs = ({labels = []}) => {
  const pathName = useLocation().pathname;

  const ancestorPaths = [...iterAncestorPaths(pathName)];
  if (ancestorPaths.length + 1 < labels.length) {
    return null;
  }

  return (
    
      {ancestorPaths.map((path, idx) => (
        
          {labels[idx]}
        
      ))}
      {labels[labels.length - 1]}
    
  );
};

function* iterAncestorPaths(pathName) {
  const parts = pathName.split("/").filter(x => x !== "");
  for (let i = 1; i < parts.length; ++i) {
    yield "/" + parts.slice(0, i).join("/");
  }
}
票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/253443

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档