主题
关键概念
¥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)
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) .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
属性在 ip
和 server
实例之间共享,因为我们将其定义为 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)
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)
// 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.