Skip to content

关键概念

¥Key Concept

虽然 Elysia 是一个简单的库,但它有一些关键概念需要你理解才能有效地使用它。

¥Although Elysia is a simple library, it has some key concepts that you need to understand to use it effectively.

本页涵盖了你应该了解的 Elysia 最重要的概念。

¥This page covers most important concepts of Elysia that you should know.

提示

我们强烈建议你在进一步了解 Elysia 之前阅读此页面。

¥We highly recommend you to read this page before learning more about Elysia.

一切都是组件

¥Everything is a component

每个 Elysia 实例都是一个组件。

¥Every Elysia instance is a component.

组件是可以插入其他实例的插件。

¥A component is a plugin that could plug into other instances.

它可以是路由、存储、服务或其他任何东西。

¥It could be a router, a store, a service, or anything else.

ts
import { 
Elysia
} from 'elysia'
const
store
= new
Elysia
()
.
state
({
visitor
: 0 })
const
router
= new
Elysia
()
.
use
(
store
)
.
get
('/increase', ({
store
}) =>
store
.
visitor
++)
const
app
= new
Elysia
()
.
use
(
router
)
.
get
('/', ({
store
}) =>
store
)
.
listen
(3000)

这会迫使你将应用分解成小块,以便于添加或删除功能。

¥This forces you to break down your application into small pieces, making it easy for you to add or remove features.

plugin 中了解更多信息。

¥Learn more about this in plugin.

方法链

¥Method Chaining

Elysia 代码应始终使用方法链。

¥Elysia code should always use method chaining.

由于 Elysia 类型系统复杂,Elysia 中的每个方法都会返回一个新的类型引用。

¥As Elysia type system is complex, every method in Elysia returns a new type reference.

这对于确保类型完整性和推断非常重要。

¥This is important to ensure type integrity and inference.

typescript
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
state
('build', 1)
// Store is strictly typed .
get
('/', ({
store
: {
build
} }) =>
build
)
.
listen
(3000)

在上面的代码中,state 返回了一个新的 ElysiaInstance 类型,并添加了一个类型化的 build 属性。

¥In the code above, state returns a new ElysiaInstance type, adding a typed build property.

不要在没有方法链的情况下使用 Elysia

¥Don't use Elysia without method chaining

如果没有方法链,Elysia 不会保存这些新类型,从而导致无法进行类型推断。

¥Without using method chaining, Elysia doesn't save these new types, leading to no type inference.

typescript
import { 
Elysia
} from 'elysia'
const
app
= new
Elysia
()
app
.
state
('build', 1)
app
.
get
('/', ({
store
: { build } }) =>
build
)
Property 'build' does not exist on type '{}'.
app
.
listen
(3000)

我们建议始终使用方法链来提供准确的类型推断。

¥We recommend to always use method chaining to provide an accurate type inference.

范围

¥Scope

默认情况下,每个实例中的事件/生命周期彼此隔离。

¥By default, event/life-cycle in each instance is isolated from each other.

ts
import { 
Elysia
} from 'elysia'
const
ip
= new
Elysia
()
.
derive
(({
server
,
request
}) => ({
ip
:
server
?.
requestIP
(
request
)
})) .
get
('/ip', ({
ip
}) =>
ip
)
const
server
= new
Elysia
()
.
use
(
ip
)
.
get
('/ip', ({ ip }) =>
ip
)
Property 'ip' does not exist on type '{ body: unknown; query: Record<string, string>; params: {}; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<unknown>>; server: Server | null; ... 6 more ...; status: <const Code extends number | keyof StatusMap, const T = Code extends 100 | ... 59 more ... | 511 ? { ...; }[Code] : Code>(co...'.
.
listen
(3000)

在此示例中,ip 属性仅在其自身实例中共享,而不在 server 实例中共享。

¥In this example, the ip property is only shared in its own instance but not in the server instance.

要共享生命周期(在我们的例子中是 ip 属性与 server 实例),我们需要明确说明它可以共享。

¥To share the lifecycle, in our case, an ip property with server instance, we need to explicitly say that it could be shared.

ts
import { 
Elysia
} from 'elysia'
const
ip
= new
Elysia
()
.
derive
(
{
as
: 'global' },
({
server
,
request
}) => ({
ip
:
server
?.
requestIP
(
request
)
}) ) .
get
('/ip', ({
ip
}) =>
ip
)
const
server
= new
Elysia
()
.
use
(
ip
)
.
get
('/ip', ({
ip
}) =>
ip
)
.
listen
(3000)

在此示例中,ip 属性在 ipserver 实例之间共享,因为我们将其定义为 global

¥In this example, ip property is shared between ip and server instance because we define it as global.

这会迫使你考虑每个属性的作用域,防止你在实例之间意外共享属性。

¥This forces you to think about the scope of each property, preventing you from accidentally sharing the property between instances.

scope 中了解更多信息。

¥Learn more about this in scope.

依赖

¥Dependency

默认情况下,每个实例在应用于另一个实例时都会重新执行。

¥By default, each instance will be re-executed every time it's applied to another instance.

这可能会导致同一方法被重复多次应用,而某些方法(例如生命周期或路由)应该只调用一次。

¥This can cause a duplication of the same method being applied multiple times, whereas some methods, like lifecycle or routes, should only be called once.

为了防止生命周期方法重复,我们可以为实例添加唯一标识符。

¥To prevent lifecycle methods from being duplicated, we can add a unique identifier to the instance.

ts
import { 
Elysia
} from 'elysia'
const
ip
= new
Elysia
({
name
: 'ip' })
.
derive
(
{
as
: 'global' },
({
server
,
request
}) => ({
ip
:
server
?.
requestIP
(
request
)
}) ) .
get
('/ip', ({
ip
}) =>
ip
)
const
router1
= new
Elysia
()
.
use
(
ip
)
.
get
('/ip-1', ({
ip
}) =>
ip
)
const
router2
= new
Elysia
()
.
use
(
ip
)
.
get
('/ip-2', ({
ip
}) =>
ip
)
const
server
= new
Elysia
()
.
use
(
router1
)
.
use
(
router2
)

这将通过使用唯一名称应用数据去重来防止 ip 属性被多次调用。

¥This will prevent the ip property from being called multiple times by applying deduplication using a unique name.

这使我们能够多次重用同一个实例,而不会降低性能。迫使你思考每个实例的依赖。

¥This allows us to reuse the same instance multiple times without the performance penalty. Forcing you to think about the dependencies of each instance.

插件数据去重 中了解更多信息。

¥Learn more about this in plugin deduplication.

服务定位器

¥Service Locator

当你将带有状态/装饰器的插件应用于实例时,该实例将获得类型安全。

¥When you apply a plugin with state/decorators to an instance, the instance will gain type safety.

但如果你不将插件应用到另一个实例,它将无法推断类型。

¥But if you don't apply the plugin to another instance, it will not be able to infer the type.

typescript
import { 
Elysia
} from 'elysia'
const
child
= new
Elysia
()
// ❌ 'a' is missing .
get
('/', ({ a }) =>
a
)
Property 'a' does not exist on type '{ body: unknown; query: Record<string, string>; params: {}; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<unknown>>; server: Server | null; ... 6 more ...; status: <const Code extends number | keyof StatusMap, const T = Code extends 100 | ... 59 more ... | 511 ? { ...; }[Code] : Code>(co...'.
const
main
= new
Elysia
()
.
decorate
('a', 'a')
.
use
(
child
)

Elysia 引入了服务定位器模式来解决这个问题。

¥Elysia introduces the Service Locator pattern to counteract this.

我们仅提供了插件引用,方便 Elysia 查找服务以添加类型安全性。

¥We simply provide the plugin reference for Elysia to find the service to add type safety.

typescript
import { 
Elysia
} from 'elysia'
const
setup
= new
Elysia
({
name
: 'setup' })
.
decorate
('a', 'a')
// Without 'setup', type will be missing const
error
= new
Elysia
()
.
get
('/', ({ a }) =>
a
)
Property 'a' does not exist on type '{ body: unknown; query: Record<string, string>; params: {}; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<unknown>>; server: Server | null; ... 6 more ...; status: <const Code extends number | keyof StatusMap, const T = Code extends 100 | ... 59 more ... | 511 ? { ...; }[Code] : Code>(co...'.
// With `setup`, type will be inferred const
child
= new
Elysia
()
.
use
(
setup
)
.
get
('/', ({
a
}) =>
a
)
const
main
= new
Elysia
()
.
use
(
child
)

正如 dependencies 中提到的,我们可以使用 name 属性对实例进行数据去重,这样就不会有任何性能损失或生命周期重复。

¥As mentioned in dependencies, we can use the name property to deduplicate the instance so it will not have any performance penalty or lifecycle duplication.

代码顺序

¥Order of code

Elysia 生命周期代码的顺序非常重要。

¥The order of Elysia's life-cycle code is very important.

因为事件只有在注册后才会应用于路由。

¥Because event will only apply to routes after it is registered.

如果将 onError 事件放在插件之前,插件将不会继承 onError 事件。

¥If you put the onError before plugin, plugin will not inherit the onError event.

typescript
import { Elysia } from 'elysia'

new Elysia()
 	.onBeforeHandle(() => {
        console.log('1')
    })
	.get('/', () => 'hi')
    .onBeforeHandle(() => {
        console.log('2')
    })
    .listen(3000)

控制台应记录以下内容:

¥Console should log the following:

bash
1

请注意,它不会记录 2,因为该事件是在路由之后注册的,因此不会应用于路由。

¥Notice that it doesn't log 2, because the event is registered after the route so it is not applied to the route.

代码顺序 中了解更多信息。

¥Learn more about this in order of code.

类型推断

¥Type Inference

Elysia 拥有复杂的类型系统,允许你从实例推断类型。

¥Elysia has a complex type system that allows you to infer types from the instance.

ts
import { 
Elysia
,
t
} from 'elysia'
const
app
= new
Elysia
()
.
post
('/', ({
body
}) =>
body
, {
body
:
t
.
Object
({
name
:
t
.
String
()
}) })

如果可能,请始终使用内联函数来提供准确的类型推断。

¥If possible, always use an inline function to provide an accurate type inference.

如果你需要应用单独的函数,例如 MVC 的控制器模式,建议从内联函数中解构属性,以避免不必要的类型推断。

¥If you need to apply a separate function, eg. MVC's controller pattern, it's recommended to destructure properties from inline function to prevent unnecessary type inference.

ts
import { 
Elysia
,
t
} from 'elysia'
abstract class
Controller
{
static
greet
({
name
}: {
name
: string }) {
return 'hello ' +
name
} } const
app
= new
Elysia
()
.
post
('/', ({
body
}) =>
Controller
.
greet
(
body
), {
body
:
t
.
Object
({
name
:
t
.
String
()
}) })

TypeScript

我们可以通过访问 static 属性来获取每个 Elysia/TypeBox 类型的类型定义,如下所示:

¥We can get a type definitions of every Elysia/TypeBox's type by accessing static property as follows:

ts
import { 
t
} from 'elysia'
const
MyType
=
t
.
Object
({
hello
:
t
.
Literal
('Elysia')
}) type
MyType
= typeof
MyType
.
static



这允许 Elysia 自动推断并提供类型,从而减少声明重复模式的需要。

¥This allows Elysia to infer and provide type automatically, reducing the need to declare duplicate schema

单个 Elysia/TypeBox 模式可用于:

¥A single Elysia/TypeBox schema can be used for:

  • 运行时验证

  • 数据强制转换

  • TypeScript 类型

  • OpenAPI 架构

这使我们能够将模式作为单一事实来源。

¥This allows us to make a schema as a single source of truth.

最佳实践:MVC 控制器 中了解更多信息。

¥Learn more about this in Best practice: MVC Controller.