7

我正在尝试创建一个菜单组件,该组件在构建时读取 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>
    )
}

那么这仍然是很多样板要添加到网站上的每个页面。

肯定有更好的办法……不可能是没有办法在构建时给全局状态添加静态数据吧?如何在构建时生成动态菜单?

4

2 回答 2

10

我设法通过从 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>
        );
    }
}
于 2020-09-02T20:47:20.910 回答
-2

你可以试试这个:

const fg = require('fast-glob');
const pages = await fg(['pages/**/*.js'], { dot: true });
于 2020-12-17T18:24:15.857 回答