在Next.js组件中运行异步代码,不使用UseEffect。

huangapple go评论79阅读模式
英文:

Run async code in nextjs component without using UseEffect

问题

在我的Next.js应用中,我有一个StrapiImage组件,它接受来自我的Strapi后端API的图像对象作为属性。它分配了宽度、高度和URL,以及任何其他附加属性。(实际上,它只是一个常规next/image的快捷方式)

import Image from "next/image";
import { getStrapiMedia } from "../../lib/media";

export default function StrapiImage({ image, ...props }) {
    return <Image src={getStrapiMedia(image)} // getStrapiMedia函数基本上只返回图像的URL。
                  width={image.attributes.width}
                  height={image.attributes.height}
                  alt=""
                  {...props} />
}

现在,我想要为这个图像添加一个模糊占位符,使用placeholder="blur",但由于这是外部图像,我必须在blurDataUrl属性中提供一个base64图像。

我想要使用plaiceholder库生成一个类似这个sollution的图像。

然而,有一个问题,为了生成图像,我需要使用await语句。在getStaticProps中,这不是问题,因为我可以将函数设置为async,但我正在组件内部执行这个操作,而组件必须是普通的非异步函数。

"显而易见"的解决方案是使用useEffect钩子,如下所示:

import Image from "next/image";
import { getStrapiMedia } from "../../lib/media";
import { useEffect, useState } from "react";
import { getPlaiceholder } from "plaiceholder";

export default function StrapiImage({ image, ...props }) {
    const [blur64, setBlur64] = useState("");
    useEffect(() => {
        async function generatePlaceholder() {
            // 在这里生成base64图像
        }
        generatePlaceholder();
    }, []);
    return <Image src={getStrapiMedia(image)}
                  width={image.attributes.width}
                  height={image.attributes.height}
                  alt=""
                  placeholder="blur"
                  blurDataURL={blur64}
                  {...props} />
}

这在技术上可以工作,但它只会在客户端运行,最终破坏了SSR和整个图像优化的目的。(特别是考虑到plaiceholder的巨大JS捆绑包)

有没有其他方法可以在不使用useEffect的情况下仍在组件文件中完成这个操作?理想情况下,我希望它只在服务器端运行。

我正在使用nextjs@13

英文:

In my nextjs app, I have a StrapiImage component that accepts an image object from my strapi backend api as a prop. It assigns the width, height and url + any aditional props. (It's basically a shortcut for a regular next/image)

import Image from &quot;next/image&quot;;
import { getStrapiMedia } from &quot;../../lib/media&quot;;

export default function StrapiImage({ image, ...props })
    return &lt;Image src={getStrapiMedia(image)} // the getStrapiMedia function basically just returns the url to the image.
                  width={image.attributes.width}
                  height={image.attributes.height}
                  alt=&quot;&quot;
                  {...props} /&gt;
}

Now, I wanted to add a blur placeholder for this image using placeholder=&quot;blur&quot; but since this is an external image, I have to provided a base64 image in the blurDataUrl prop.

I wanted to generate an image like this with the plaiceholder library similarly to this sollution I found.

There's a problem with this though, to generate the image, I need to use an await statement. In getStaticProps, this wouldn't be a problem as I could just make the function async, but I'm doing this inside a component and components must regular non-async functions.

The 'obvious' sollution would be to use the useEffect hook like so:

import Image from &quot;next/image&quot;;
import { getStrapiMedia } from &quot;../../lib/media&quot;;
import { useEffect, useState } from &quot;react&quot;;
import { getPlaiceholder } from &quot;plaiceholder&quot;;

export default function StrapiImage({ image, ...props })
    const [blur64, setBlur64]
    useEffect(() =&gt; {
        async function generatePlaceholder() {
            // generate base64 image here
        }
        generatePlaceholder();
    }, [])
    return &lt;Image src={getStrapiMedia(image)}
                  width={image.attributes.width}
                  height={image.attributes.height}
                  alt=&quot;&quot;
                  placeholder=&quot;blur&quot;
                  blurDataURL={blur64}
                  {...props} /&gt;
}

This would technically work, but it would only run only on the client-side, ultimately defeating the purpose of ssr and the whole image optimisation. (Especially considering plaiceholder's huge js bundle)

Is there any other way that I could do this without useEffect but still in the component file? Ideally, I'd like it to run only on the server-side.

I'm using nextjs@13

答案1

得分: 0

我通过使用 strapi 插件1来在后端生成占位图,而不是在前端生成,解决了这个问题。

标题中的问题仍然没有解决。在组件级别运行可预渲染的异步代码似乎不是 nextjs 官方支持的方式(特别是在页面目录中)。

以下是我找到的一些“解决方案”(主要是变通方法):

  • 在页面级别生成占位图。对我来说,这意味着循环遍历我的 API 数据中的所有元素,查找图像并为它们生成占位图。这可能相当不方便,但确实有效。
  • 切换到应用程序目录(如果您使用的是 next 13)。它使用了 React 服务器组件,所以在那里可以轻松实现所有这些。
  • 尝试使用 useSSE 进行工作。我还没有深入研究这个,但看起来您可以使用 useSSE 库在组件级别运行服务器端代码。这里有一篇文章介绍了这个方法。
英文:

I fixed the issue by using a strapi plugin to generate the placeholder image on the backend rather than on the frontend.

The issue in the title still remains unsolved though. Running pre-renderable async code at component-level doesn't seem to be officialy supported by nextjs. (with the pages directory)

Here's some "sollutions" I've found: (mostly workarounds)

  • Generate the placeholders on page-level. For me, that would mean looping through all elements in my api data looking for images and generating placeholders for them. It's pretty inconvenient but it does work.

  • Switch to the app directory. (provided you're using next 13) It uses react server components so all of this is possible and simple to do there.

  • Try to work with useSSE. I haven't looked into this too much but it looks like you can use the useSSE library to run server-side at component-level. Here's an article covering this.

huangapple
  • 本文由 发表于 2023年2月19日 04:29:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/75496210.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定