面包屑导航:网站中用于展示页面层级关系的导航栏,同时也可以清楚的告知用户它当前身处于哪个位置(层级),以确定网站的结构和增强用户浏览体验。
简单示例:
这里以php手册为例,展示一个简单的面包屑导航。
接下来就手动实现一下吧
测试数据
const arr = [
['id' => 1, 'pid' => 0, 'city' => '江西'],
['id' => 2, 'pid' => 0, 'city' => '广东'],
['id' => 3, 'pid' => 1, 'city' => '宜春'],
['id' => 4, 'pid' => 3, 'city' => '丰城'],
['id' => 5, 'pid' => 4, 'city' => '尚庄'],
['id' => 6, 'pid' => 2, 'city' => '深圳'],
['id' => 7, 'pid' => 6, 'city' => '翻身'],
];
需求效果:
思路分析:
结合需求效果来分析测试数据可以看到最终要求的效果是从顶级名称一直延续到它的最下级子级名称。
所以可以衍生出两种思路:
1.传入顶级id,得到它本身与它的n多子级,组成一个新的数组,循环拼接html展示。
顶级名称是江西,江西的id是1,所以根据 子级的pid = 父级的id ,找到它的子级;
遍历整个数组,pid是1的只有宜春,可以看到宜春的id是3,那么又得根据3来再次遍历整个数组,找到宜春的子级,也就是丰城;
再找到丰城的id,id为4,遍历整个数组pid为4的只有尚庄;
尚庄的id为5,再拿5去数组中寻找有没有pid为5的数据,此时可以看到没有了,那么整个流程结束。
接下来只需要将每次找到的子级数组追加到一个新数组中即可组成想要的数据。
整体思路下可以得知:拿到每次循环后找到的子级id当成它的子级的pid来传入,以此实现不断循环,也就是递归,不断调用自身,以达到最终效果。
2.传入最子级id,得到它本身与它的n多父级,组成一个新的数组,循环拼接html展示。
与思路一相反,思路一是根据父亲找与儿子的关系图,而思路二是根据儿子找与父亲的关系图。
最子级名称是尚庄,尚庄的pid是4,所以根据 父级的id = 子级的pid,找到它的父级;
遍历整个数组,id是4的只有丰城,可以看到丰城的pid是3,那么又得根据pid为3来再次遍历整个数组,找到丰城的父级,也就是宜春;
再找到宜春的pid,pid为1,遍历整个数组pid为1的只有江西;
江西的pid为0,再拿0去数组中寻找有没有id为0的数据,此时可以看到没有了,那么整个流程结束。
接下来只需要将每次找到的子级数组追加到一个新数组中即可组成想要的数据。
整体思路下可以得知:拿到每次循环后找到的父级id当成它的子级的pid来传入,以此实现不断循环,也就是递归,不断调用自身,以达到最终效果。
代码实践:
无论是思路一还是思路二,要实现递归,并把所有数据汇总到一个数组中,那么就得使用static或&array或global来存储每次遍历的值(不然这次拿到了想要的结果,下次往新数组里追加数据的时候,上次追加的数据就丢掉了),这里就以static(静态数组)和&array(传值引用)为例来实现吧。
思路一:
静态数组法(代码思路参照思路1的分析):
/**
* 无限分类之得到导航完整层级数组(传入顶级)
* @param array $data
* @param int $id 顶级id
* @return array
*/
public function getFatherToLink(array $data, int $id): array
{
static $arr = []; //声明静态空数组,存储每次遍历后得到的数据
foreach ($data as $value) {
//由于下列递归只会找子级,不会传入本身,所以当新数组为空且id为传入的id时就说明数据是其本身,需要将它追加到新数组中
if (count($arr) == 0 && $value['id'] == $id) {
$arr[] = $value;
}
//这里递归只会找子级
if ($value['pid'] == $id) {
$arr[] = $value; //将子级追加到新数组中
$this->getFatherToLink($data, $value['id']);
}
}
return $arr;
}
传值引用法(代码思路参照思路1的分析):
/**
* 无限分类之得到导航完整层级数组(传入顶级)
* @param array $data
* @param int $id 顶级id
* @param array $arr
* @return array
*/
public function getFatherToLinkAAA(array $data, int $id, &$arr = []): array
{
foreach ($data as $value) {
//由于下列递归只会找子级,不会传入本身,所以当新数组为空且id为传入的id时就说明数据是其本身,需要将它追加到新数组中
if (count($arr) == 0 && $value['id'] == $id) {
$arr[] = $value;
}
//这里递归只会找子级
if ($value['pid'] == $id) {
$arr[] = $value;
$this->getFatherToLinkAAA($data, $value['id'], $arr); //由于形参已经加了&,所以每次$arr的值将会保留继续使用(而不是清除)
}
}
return $arr;
}
思路二:
这里讲一下,为什么要反转数组。
根据代码思路来看,传入子级id为5,因为$arr[]=$value在递归触发之前调用,那么就会逐级向上寻找父类依次得到:
尚庄 -> 丰城 --> 宜春 -> 江西
但我们实际要的效果是:
江西 -> 宜春 --> 丰城 -> 尚庄
所以需要将最终的$arr数组,反转一下来得到正确的效果。
静态数组法(代码思路参照思路2的分析):
/**
* 无限分类之得到导航完整层级数组(传入子级)
* @param array $data
* @param int $id 子级id
* @param array $arr
* @return array
*/
public function getSonToLinkAAA(array $data, int $id): array
{
static $arr = []; //声明静态空数组
foreach ($data as $value) {
//这里只会找父级
if ($value['id'] == $id) {
$arr[] = $value;
$this->getSonToLinkAAA($data, $value['pid']);
}
}
return array_reverse($arr);//array_reverse直接反转数组
}
传值引用法(代码思路参照思路2的分析):
/**
* 无限分类之得到导航完整层级数组(传入子级)
* @param array $data
* @param int $id 子级id
* @param array $arr
* @return array
*/
public function getSonToLink(array $data, int $id, array &$arr = []): array
{
foreach ($data as $value) {
if ($value['id'] == $id) {
$arr[] = $value;
$this->getSonToLink($data, $value['pid'], $arr);
}
}
krsort($arr); //不通过array_reverse也可以使用krsort根据键名排序 来反转数组
return $arr;
}
试验论证:
到了现在,我们知道可以有两种思路来实现效果,效果也是实现了的;但是这里的测试数据只有一个同级元素呀,没有干扰性,不足以论证思路正确性。那么现在往原始测试数据中再多追加几个同级元素来增强干扰性,再来测试这两种思路是否可以禁得住考验吧!
//增强干扰性的测试数据
const arr = [
['id' => 1, 'pid' => 0, 'city' => '江西'],
['id' => 2, 'pid' => 0, 'city' => '广东'],
['id' => 3, 'pid' => 1, 'city' => '宜春'],
['id' => 4, 'pid' => 3, 'city' => '丰城'],
['id' => 5, 'pid' => 4, 'city' => '尚庄'],
['id' => 6, 'pid' => 2, 'city' => '深圳'],
['id' => 7, 'pid' => 6, 'city' => '翻身'],
#以下是追加的同级元素数据,用来试验论证,也是增强干扰性
['id' => 8, 'pid' => 1, 'city' => '南昌'],
['id' => 9, 'pid' => 8, 'city' => '高新区'],
];
思路一测试后的数据如下:
思路二测试后的数据如下:
好了,结果已经明了,显然思路一这种通过传入顶级id得到本身和子类的方式,
在数据具备同级元素的时候是不适用的,只适用于数据不具备同级元素的时候;
这局限性太大了,后台无限分类后的数据怎么,可能没有同级元素数据呢?
所以,思路1pass! 我们只能选取思路2来实现面包屑导航!
进阶讨论:
思路1被干掉了,合适的只有思路2。
仔细看思路2中,我们是借助array_reverse或krsort反转了数组再返回最终值的。
实际上是可以直接通过遍历得到最终值,而不需要借助php函数再反转的,上述例子只是为了方便理解才这么写。
那么不借助php函数改怎么写呢?
我想你应该猜到了,那就是把
$arr[] = $value
写在递归之后调用,这样就会从最顶级开始自上而下往$arr里追加数据,而不是从传入的子级本身开始自下而上往$arr里追加数据。为了方便,传值引用和静态数组进阶版就直接放一起展示了。
/**
* 无限分类之得到导航完整层级数组(传入子级)
* @param array $data
* @param int $id 子级id
* @param array $arr
* @return array
*/
public function getSonToLinkAAABBB(array $data, int $id, array &$arr = []): array
{
foreach ($data as $value) {
//这里只会找父级
if ($value['id'] == $id) {
//这里会一直找到最顶级
$this->getSonToLinkAAABBB($data, $value['pid'], $arr);
$arr[] = $value; //从最顶级开始插入数据,再跳出当前级别递归,往外级递归继续插入。
}
}
return $arr;
}
/**
* 无限分类之得到导航完整层级数组(传入子级)
* @param array $data
* @param int $id 子级id
* @param array $arr
* @return array
*/
public function getSonToLinkAAACCC(array $data, int $id): array
{
static $arr = []; //声明静态空数组
foreach ($data as $value) {
//这里只会找父级
if ($value['id'] == $id) {
//这里会一直找到最顶级
$this->getSonToLinkAAACCC($data, $value['pid']);
$arr[] = $value; //从最顶级开始插入数据,再跳出当前级别递归,往外级递归继续插入。
}
}
return $arr;
}
最终效果:
<?php
/**
* Created by phpStrom
* User: Anbin
* Date: 2022/8/26
* Time: 16:37
*/
/**
* 无限分类之面包屑导航案例
* Class Link
*/
class Link
{
//测试数据
const arr = [
['id' => 1, 'pid' => 0, 'city' => '江西'],
['id' => 2, 'pid' => 0, 'city' => '广东'],
['id' => 3, 'pid' => 1, 'city' => '宜春'],
['id' => 4, 'pid' => 3, 'city' => '丰城'],
['id' => 5, 'pid' => 4, 'city' => '尚庄'],
['id' => 6, 'pid' => 2, 'city' => '深圳'],
['id' => 7, 'pid' => 6, 'city' => '翻身'],
#以下是追加的同级元素数据,用来试验论证,也是增强干扰性
['id' => 8, 'pid' => 1, 'city' => '南昌'],
['id' => 9, 'pid' => 8, 'city' => '高新区'],
];
//思路一
/**
* 无限分类之得到导航完整层级数组(传入父级)
* @param array $data
* @param int $id 父级id
* @return array
*/
public function getFatherToLink(array $data, int $id): array
{
static $arr = [];
foreach ($data as $value) {
if (count($arr) == 0 && $value['id'] == $id) {
$arr[] = $value;
}
if ($value['pid'] == $id) {
$arr[] = $value;
$this->getFatherToLink($data, $value['id']);
}
}
return $arr;
}
/**
* 无限分类之得到导航完整层级数组(传入顶级)
* @param array $data
* @param int $id 顶级id
* @param array $arr
* @return array
*/
public function getFatherToLinkAAA(array $data, int $id, &$arr = []): array
{
foreach ($data as $value) {
//由于下列递归只会找子级,不会传入本身,所以当新数组为空且id为传入的id时就说明数据是其本身,需要将它追加到新数组中
if (count($arr) == 0 && $value['id'] == $id) {
$arr[] = $value;
}
//这里递归只会找子级
if ($value['pid'] == $id) {
$arr[] = $value;
$this->getFatherToLinkAAA($data, $value['id'], $arr); //由于形参已经加了&,所以每次$arr的值将会保留继续使用(而不是清除)
}
}
return $arr;
}
#################################################################################################################################
//这里是分割线---这里是分割线---这里是分割线---这里是分割线---这里是分割线---这里是分割线---这里是分割线---这里是分割线---这里是分割线---这里是分割线---
#################################################################################################################################
//思路二
/**
* 无限分类之得到导航完整层级数组(传入子级)
* @param array $data
* @param int $id 子级id
* @param array $arr
* @return array
*/
public function getSonToLink(array $data, int $id, array &$arr = []): array
{
foreach ($data as $value) {
if ($value['id'] == $id) {
$arr[] = $value;
$this->getSonToLink($data, $value['pid'], $arr);
}
}
krsort($arr); //不通过array_reverse也可以使用krsort根据键名排序 来反转数组
return $arr;
}
/**
* 无限分类之得到导航完整层级数组(传入子级)
* @param array $data
* @param int $id 子级id
* @param array $arr
* @return array
*/
public function getSonToLinkAAA(array $data, int $id): array
{
static $arr = []; //声明静态空数组
foreach ($data as $value) {
//这里只会找父级
if ($value['id'] == $id) {
$arr[] = $value;
$this->getSonToLinkAAA($data, $value['pid']);
}
}
return array_reverse($arr);//array_reverse直接反转数组
}
/**
* 无限分类之得到导航完整层级数组(传入子级)
* @param array $data
* @param int $id 子级id
* @param array $arr
* @return array
*/
public function getSonToLinkAAABBB(array $data, int $id, array &$arr = []): array
{
foreach ($data as $value) {
//这里只会找父级
if ($value['id'] == $id) {
//这里会一直找到最顶级
$this->getSonToLinkAAABBB($data, $value['pid'], $arr);
$arr[] = $value; //从最顶级开始插入数据,再跳出当前级别递归,往外级递归继续插入。
}
}
return $arr;
}
/**
* 无限分类之得到导航完整层级数组(传入子级)
* @param array $data
* @param int $id 子级id
* @param array $arr
* @return array
*/
public function getSonToLinkAAACCC(array $data, int $id): array
{
static $arr = []; //声明静态空数组
foreach ($data as $value) {
//这里只会找父级
if ($value['id'] == $id) {
//这里会一直找到最顶级
$this->getSonToLinkAAACCC($data, $value['pid']);
$arr[] = $value; //从最顶级开始插入数据,再跳出当前级别递归,往外级递归继续插入。
}
}
return $arr;
}
#################################################################################################################################
//这里是分割线---这里是分割线---这里是分割线---这里是分割线---这里是分割线---这里是分割线---这里是分割线---这里是分割线---这里是分割线---这里是分割线---
#################################################################################################################################
/**
* 将无限分类后的数组转换成导航html
* @param array $data
* @return string
*/
public function arrayToLink(array $data): string
{
$str = '';
foreach ($data as $item) {
//这里做个判断,当是最后一位则不需要拼接->
$endString = $item == end($data) ? '' : '-> ';
$str .= "<a href='' >" . $item['city'] . "</a> " . $endString;
}
return $str;
}
//真正调用查看效果方法
public function link()
{
//思路一
//$data = $this->getFatherToLink(self::arr, 1);
//$data = $this->getFatherToLinkAAA(self::arr, 1);
//思路二(反转数组版)
//$data = $this->getSonToLink(self::arr, 5);
//$data = $this->getSonToLinkAAA(self::arr, 5);
//思路二(不需反转数组版)
$data = $this->getSonToLinkAAABBB(self::arr, 5);
$data = $this->getSonToLinkAAACCC(self::arr, 5);
//返回最终效果html
return $this->arrayToLink($data);
}
}