MST

星途 面试题库

面试题:Next.js动态路由布局中的数据获取与性能优化

在Next.js应用中,动态路由页面与布局结合时,涉及到从后端获取数据填充页面内容。比如一个电商商品详情页,每个商品详情页面有独特的布局样式且需要从API获取商品详细信息。请阐述如何在动态路由页面组件和布局组件中高效获取数据,以避免性能问题,同时说明怎样处理数据获取过程中的加载状态与错误处理。
43.0万 热度难度
前端开发Next.js

知识考点

AI 面试

面试题答案

一键面试

1. 在动态路由页面组件中获取数据

  • 使用 getStaticProps(适用于数据不经常变化的情况)
    • 在动态路由页面组件(例如 pages/products/[productId].js)中定义 getStaticProps 函数。此函数在构建时运行,它可以通过 context.params.productId 获取动态路由参数,然后利用该参数调用 API 获取商品详细信息。
    • 示例代码:
export async function getStaticProps(context) {
    const productId = context.params.productId;
    const res = await fetch(`https://api.example.com/products/${productId}`);
    const product = await res.json();
    return {
        props: {
            product
        },
        revalidate: 60 * 60 * 24 // 一天后重新验证数据(可选,用于增量静态再生)
    };
}

const ProductPage = ({ product }) => {
    return (
        <div>
            <h1>{product.title}</h1>
            <p>{product.description}</p>
        </div>
    );
};

export default ProductPage;
  • 使用 getServerSideProps(适用于数据实时性要求高的情况)
    • 同样在动态路由页面组件中定义 getServerSideProps 函数。该函数在每次请求页面时运行,通过 context.params.productId 获取参数并调用 API。
    • 示例代码:
export async function getServerSideProps(context) {
    const productId = context.params.productId;
    const res = await fetch(`https://api.example.com/products/${productId}`);
    const product = await res.json();
    return {
        props: {
            product
        }
    };
}

const ProductPage = ({ product }) => {
    return (
        <div>
            <h1>{product.title}</h1>
            <p>{product.description}</p>
        </div>
    );
};

export default ProductPage;

2. 在布局组件中获取数据

  • 如果布局组件的数据相对固定
    • 可以在布局组件所在文件(例如 components/Layout.js)的顶层使用 getStaticProps,在构建时获取数据并传递给布局组件。这种数据通常是网站的通用信息,如网站标题、导航栏菜单等。
    • 示例代码:
export async function getStaticProps() {
    const res = await fetch('https://api.example.com/siteInfo');
    const siteInfo = await res.json();
    return {
        props: {
            siteInfo
        }
    };
}

const Layout = ({ children, siteInfo }) => {
    return (
        <div>
            <header>
                <h1>{siteInfo.siteTitle}</h1>
            </header>
            {children}
            <footer>
                <p>{siteInfo.copyright}</p>
            </footer>
        </div>
    );
};

export default Layout;
  • 如果布局组件的数据与动态路由相关
    • 可以将从动态路由页面组件获取的数据通过 props 层层传递到布局组件。例如,商品详情页布局可能需要商品的分类信息来设置特定样式,那么在动态路由页面组件获取商品详细信息后,将分类信息传递给布局组件。

3. 处理加载状态

  • 在动态路由页面组件中
    • 如果使用 getStaticPropsgetServerSideProps,在组件渲染时数据已经获取,无需额外处理加载状态。但如果使用客户端数据获取(例如在 useEffect 中调用 API),可以使用一个状态变量来表示加载状态。
    • 示例代码:
import { useState, useEffect } from'react';

const ProductPage = () => {
    const [product, setProduct] = useState(null);
    const [loading, setLoading] = useState(true);
    const productId = '123'; // 假设的商品 ID

    useEffect(() => {
        const fetchProduct = async () => {
            const res = await fetch(`https://api.example.com/products/${productId}`);
            const data = await res.json();
            setProduct(data);
            setLoading(false);
        };
        fetchProduct();
    }, []);

    if (loading) {
        return <div>Loading...</div>;
    }

    return (
        <div>
            <h1>{product.title}</h1>
            <p>{product.description}</p>
        </div>
    );
};

export default ProductPage;
  • 在布局组件中
    • 类似地,如果布局组件的数据在客户端获取,也可以使用状态变量处理加载状态。例如,获取网站导航栏菜单数据时:
import { useState, useEffect } from'react';

const Layout = ({ children }) => {
    const [menu, setMenu] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        const fetchMenu = async () => {
            const res = await fetch('https://api.example.com/menu');
            const data = await res.json();
            setMenu(data);
            setLoading(false);
        };
        fetchMenu();
    }, []);

    if (loading) {
        return <div>Loading...</div>;
    }

    return (
        <div>
            <header>
                {menu.map(item => (
                    <a href={item.link} key={item.id}>{item.name}</a>
                ))}
            </header>
            {children}
            <footer>
                <p>Copyright</p>
            </footer>
        </div>
    );
};

export default Layout;

4. 错误处理

  • 在数据获取函数中
    • 对于 fetch 操作,可以使用 try...catch 块捕获错误。在 getStaticPropsgetServerSideProps 中:
export async function getStaticProps(context) {
    try {
        const productId = context.params.productId;
        const res = await fetch(`https://api.example.com/products/${productId}`);
        if (!res.ok) {
            throw new Error('Network response was not ok');
        }
        const product = await res.json();
        return {
            props: {
                product
            },
            revalidate: 60 * 60 * 24
        };
    } catch (error) {
        return {
            notFound: true // 返回 404 页面
        };
    }
}
  • 在组件渲染中
    • 如果在组件内部(如 useEffect 中)获取数据,可以在捕获错误后设置一个错误状态,并在组件中显示相应的错误信息。
import { useState, useEffect } from'react';

const ProductPage = () => {
    const [product, setProduct] = useState(null);
    const [error, setError] = useState(null);
    const productId = '123';

    useEffect(() => {
        const fetchProduct = async () => {
            try {
                const res = await fetch(`https://api.example.com/products/${productId}`);
                if (!res.ok) {
                    throw new Error('Network response was not ok');
                }
                const data = await res.json();
                setProduct(data);
            } catch (error) {
                setError(error.message);
            }
        };
        fetchProduct();
    }, []);

    if (error) {
        return <div>{error}</div>;
    }

    if (!product) {
        return <div>Loading...</div>;
    }

    return (
        <div>
            <h1>{product.title}</h1>
            <p>{product.description}</p>
        </div>
    );
};

export default ProductPage;