问题描述
我试图创建一个菜单组件,该组件在构建时读取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,},};
}
}
这不起作用,该组件未收到props页面。我尝试过切换到功能组件,从getStaticProps()
返回承诺,切换到getServerSideProps()
,并将目录读取代码包含到render方法中。
前两个不起作用,因为除非组件是页面,否则getStaticProps()
和getServerSideProps()
永远不会被调用,并且由于自定义fs以来未定义或导入fs,因此在render方法中包含代码失败代码可能在没有fs访问权限的前端运行。
我还尝试将代码添加到getStaticProps()
内的_app.js
函数中,希望通过上下文将页面推送到组件,但似乎getStaticProps()
并没有也可以在那里打电话。
我可以在包含菜单的页面的getStaticProps函数中运行代码,但是我必须对每个页面重复此操作。即使我将逻辑提取到从getStaticProps调用的模块中,也是如此:
// ...
export async function getStaticProps() {
return {
props: {
pages: MenuMaker.getPages(),// ...
}
}
}
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'),// ...
},// ...
};
};
libraries / 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);
};
}
});
},};
然后从可包含在布局中的组件中的环境变量生成实际菜单:
components / nav.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>
);
}
}
,
您可以尝试以下方法:
const fg = require('fast-glob');
const pages = await fg(['pages/**/*.js'],{ dot: true });