跳到主要内容
Deno 2.4 已发布,带来 deno bundle、字节/文本导入、OTel 稳定版等功能
了解更多
Fresh lemon

Fresh 1.3 – 简化的路由组件等

距离 Fresh 1.2 发布仅仅一个月,我们又带来了新的版本!我们计划今后每月发布一个 Fresh 的新次要版本。

这个周期包含了来自社区的许多令人惊叹的 PR,简直不可思议!文档得到了扩展和改进,许多错误得到了修复,并增加了新功能。感谢所有为本次发布做出贡献的人。闲话少说,让我们来看看 Fresh 的所有改进。

异步路由组件

我们收到了很多反馈,将数据从路由处理程序传递到页面组件需要一些繁琐的样板代码。为了确保类型安全,您总是需要为组件的 props 创建一个接口,将其作为泛型传递给 Handlers 类型,在组件定义中使用它。这相当多的步骤!

// previous Fresh versions
interface Data {
  foo: number;
}

export const handler: Handlers<Data> = {
  async GET(req, ctx) {
    const value = await loadFooValue();
    ctx.render({ foo: value });
  },
};

export default function MyPage(props: PageProps<Data>) {
  return <p>foo is: {props.data.foo}</p>;
}

由于 GET 处理程序通常与它将渲染的组件高度耦合,那么显而易见的问题是,为什么我们不直接将两者合并呢?这正是我们所做的。在 Fresh 1.3 中,我们可以显著简化之前的代码片段

export default async function MyPage(req: Request, ctx: RouteContext) {
  const value = await loadFooValue();
  return <p>foo is: {value}</p>;
}

由于处理程序和组件都在同一个函数中,因此无需声明中间接口来在两者之间传递数据;您可以直接传递数据。

但是别担心,您无需重写所有路由。事实上,我们不喜欢自己重写代码。新方式只不过是额外的选项,可以使简单路由的编写变得更容易一些。使用异步路由并不是强制性的。

当您响应其他 HTTP 动词(例如 POST)时,无论如何您可能仍然需要一个处理程序

export const handler: Handlers<{}> = {
  POST(req) {
    // ... do something here
  },
};

export default async function MyPage(req: Request, ctx: RouteContext) {
  const value = await loadFooValue();
  return <p>foo is: {value}</p>;
}

此外,通过 ctx.render() 和单独的组件函数从处理程序渲染路由的现有方式将继续有效,并使得能够独立测试渲染和数据检索机制。

从插件添加路由和/或中间件

插件在扩展 Fresh 的内置功能方面非常强大。通过 Fresh 1.3,它们可以注入虚拟路由和中间件。这对于添加开发特定路由或管理仪表板的插件特别有用。

function myPlugin() {
  return {
    name: "my-plugin",
    middlewares: [
      {
        middleware: { handler: () => new Response("Hello!") },
        path: "/hello",
      },
    ],
    routes: [
      {
        path: "/admin/hello",
        component: () => <p>Hello from /admin/hello</p>,
      },
    ],
  };
}

感谢 Reed von Redwitziccee0 的贡献。

500 错误模板回退

尽管我们程序员努力考虑每种情况,但仍然经常出现意外的错误情况。我们简化了错误处理,当路由处理程序中抛出错误时,Fresh 现在将自动渲染 _500.tsx 模板作为回退。

export const handler = (req: Request, ctx: HandlerContext): Response => {
  // Fresh will catch this errors and render the 500 error template
  throw new Error("Catch me if you can");
};

感谢 Kamil Ogórek 添加此功能。

错误边界

尽管渲染时发生的错误比处理程序中的错误少见,但渲染时也可能抛出错误。因此,我们增加了对错误边界的基本支持。当 Preact 检测到具有 componentDidCatch() 方法或静态 getDerivedStateFromError 方法的类组件时,该组件将被视为错误边界。当渲染过程中抛出错误时,这些组件可以捕获错误并渲染备用 UI

class ErrorBoundary extends Component {
  state = { error: null };

  static getDerivedStateFromError(error) {
    return { error };
  }

  render() {
    return this.state.error
      ? this.props.fallback(this.state.error)
      : this.props.children;
  }
}

// Usage:
<ErrorBoundary fallback={(error) => <p>Error happened: {error.message}</p>}>
  <SomeComponentThatThrows />
</ErrorBoundary>;

这在您处理高度动态数据并希望渲染备用 UI 而不是渲染 500 错误页面时非常有用。虽然 Fresh 1.3 中的更改为捕获渲染错误奠定了基础,但我们已经在思考如何让 API 更深入地融入 Fresh 本身。

在同一个文件中导出多个 Islands

在之前的 Fresh 版本中,每个 island 都应位于自己的文件中,并通过 default 导出。每个 island 文件都被视为自己的入口点,并作为单独的 JavaScript 文件发送到浏览器。此限制已被移除,您现在可以在单个文件中导出任意数量的 island。

// ./islands/MyIsland.tsx
// Export multiple islands in Fresh 1.3
export default function SayHello() {
  // ...island code here
}

export function OtherIsland() {
  // ...island code here
}

export function AndAnotherIsland() {
  // ...island code here
}

将 island 组合到单个文件中可以减少网站发出的请求数量,甚至可以使某些网站更快!请注意,虽然将所有 island 放在同一个文件中很有吸引力,但这种模式可能会产生相反的效果。一个好的经验法则是,将同一页面上一起使用的 island 分组,可以获得最佳性能。

感谢 Reed von Redwitz 的贡献。

Fresh Linting 规则

提供反馈的最佳方式就在编辑器中。我们开始追踪常见的意外错误,例如将处理程序导出命名为 handlers 而不是 handler,并将其集成到我们的 linter 中。每当我们检测到错误时,您就会在编辑器中看到那些波浪线。

screenshot of lint

一个常见的困惑来源是 Fresh 在配置错误或完全出错时向用户呈现的通常难以理解的错误消息。我们检查了最常见的错误,并重写了错误消息,使其更具可读性。虽然我们认为已经涵盖了很大一部分,但可能还有更多我们可以做得更好的情况。因此,如果您遇到任何让您感到困惑的错误消息,请联系我们

新的 linting 规则对于新的 Fresh 项目开箱即用。要在现有项目中使用它们,只需将其放入您的 deno.json 文件中

{
  "lint": {
    "rules": {
      "tags": ["fresh", "recommended"]
    }
  }
}

支持 Deno.serve

随着最近 Deno 1.35.0 版本的发布,Deno.serve API 被标记为稳定版。Fresh 也紧随其后,在 1.3 版本中,我们将使用可用的 Deno.serve。这个新的 API 不仅更快,而且比之前 std/http 中的 serve API 简单得多。

感谢 Lino Le Van 的贡献。

更多体验改进

我们发布了一些小的改进,例如能够从每个处理程序或中间件访问路由参数。BigInt 值现在可以作为 props 传递给 island。入门模板现在看起来好多了,还有更多改进!

Screenshot