reactjs - Next.js中如何根据pages目录下的文件生成菜单
问题描述
我正在尝试创建一个菜单组件,该组件在构建时读取 pages 文件夹的内容。然而我没有任何成功。这是我尝试过的:
import path from "path";
import * as ChangeCase from "change-case";
export default class Nav extends React.Component {
render() {
return (
<nav>
{this.props.pages.map((page) => (
<a href={page.link}>{page.name}</a>
))}
</nav>
);
}
async getStaticProps() {
let files = fs.readdirSync("../pages");
files = files.filter((file) => {
if (file == "_app.js") return false;
const stat = fs.lstatSync(file);
return stat.isFile();
});
const pages = files.map((file) => {
if (file == "index.js") {
const name = "home";
const link = "/";
} else {
const link = path.parse(file).name;
const name = ChangeCase.camelCase(link);
}
console.log(link, name);
return {
name: name,
link: link,
};
});
return {
props: {
pages: pages,
},
};
}
}
这不起作用,组件没有收到 pages 道具。我尝试过切换到一个功能组件,从 中返回一个 promise getStaticProps()
,切换到getServerSideProps()
,并将目录读取代码包含到 render 方法中。
前两个不起作用,因为除非组件是页面getStaticProps()
,getServerSideProps()
否则永远不会被调用,并且在 render 方法中包含代码失败,因为 fs 未定义或可导入,因为代码可能在没有 fs 的前端运行使用权。
我还尝试将代码添加到内部的getStaticProps()
函数中_app.js
,希望通过上下文将页面推送到组件,但似乎getStaticProps()
也没有在那里调用。
我可以在包含菜单的页面的 getStaticProps 函数中运行代码,但我必须对每个页面重复该操作。即使我将逻辑提取到从 getStaticProps 调用的模块中,也像:
// ...
export async function getStaticProps() {
return {
props: {
pages: MenuMaker.getPages(),
// ...
}
}
}
然后通过 Layout 组件将页面传递给页面内的导航组件:
export default function Page(props) {
return (
<Layout pages={props.pages}></Layout>
)
}
那么这仍然是很多样板要添加到网站上的每个页面。
肯定有更好的办法……不可能是没有办法在构建时给全局状态添加静态数据吧?如何在构建时生成动态菜单?
解决方案
我设法通过从 next.config.js 导出一个函数并设置一个包含菜单结构的环境变量来实现这一点。我将菜单加载代码抽象到它自己的文件中。看到结果后,我更好地理解了为什么我找不到任何人做类似事情的例子:
菜单没有按照我想要的方式排序。我可以按字母顺序或按修改日期对其进行排序,但实际上它几乎总是需要根据页面主题手动排序。我可以使用一个整数,或者附加到文件名或文件中的某个位置(可能在注释行中)。但回想起来,我认为仅仅对组件中的链接进行硬编码可能是最好的方式,因为它提供了更多的灵活性,而且即使从长远来看,也可能不会做更多的工作。
话虽如此,我正在分享我的解决方案,因为它是一种初始化应用范围静态状态的方法。这并不理想,如果您想在这里重新计算变量,则必须重新启动开发服务器,这就是为什么我仍然对其他可能的解决方案感兴趣,但它确实有效。所以这里是:
next.config.js
const menu = require("./libraries/menu.js");
module.exports = (phase, { defaultConfig }) => {
return {
// ...
env: {
// ...
menu: menu.get('pages'),
// ...
},
// ...
};
};
库/menu.js
const fs = require("fs");
const path = require("path");
const ccase = require("change-case");
module.exports = {
get: (pagePath) => {
if (pagePath.slice(-1) != "/") pagePath += "/";
let files = fs.readdirSync(pagePath);
files = files.filter((file) => {
if (file == "_app.js") return false;
const stat = fs.lstatSync(pagePath + file);
return stat.isFile();
});
return files.map((file) => {
if (file == "index.js") {
return {
name: "Home";
link: "/";
};
} else {
link = path.parse(file).name;
return {
link: link;
name: ccase.capitalCase(link);
};
}
});
},
};
然后实际的菜单是从可以包含在布局中的组件中的环境变量生成的:
组件/导航.js
import Link from "next/link";
export default class Nav extends React.Component {
render() {
return (
<nav>
{process.env.menu.map((item) => (
<Link key={item.link} href={item.link}>
<a href={item.link}>
{item.name}
</a>
</Link>
))}
</nav>
);
}
}
推荐阅读
- azure-devops - 使用 REST API 更新 Azure DevOps 服务端点(连接)
- arduino - ATMEGA328P Arduino Uno:使用 AVR 编程调节 LED 亮度
- python - 在字符串中查找相同字母的索引
- r - 带有 Rmarkdown 的 gt 表中的数学单位
- c# - 如何允许 C# UWP 应用管理另一个 UWP 应用?
- node.js - 如何在终端中输出日语(EUC-JP)字符串?
- ruby-on-rails - 如何在 Rails 中找到对象补丁的来源?
- python - 如何从 groupby 数据框中检索每一行
- php - 如何更改存储文件的路径
- javascript - 如何在 xampp 上运行的 cakephp 项目中调试 VS Code 中的 javascript?