上下文(Context)
在 Node.js 世界中,上下文(Context)允许我们在同一个请求中共享数据和状态。在 GraphQL 中,上下文允许在同一个请求的多个解析函数和中间件之间共享数据。
一个常见的用例是将当前访问者的身份信息存储在上下文中,以便在解析函数和中间件中访问。
访问上下文
在 GQLoom
中,我们通过 useContext()
函数访问上下文。
GQLoom 的 useContext
函数的设计参考了 React 的 useContext
函数。
你可以在解析器内的任何地方调用 useContext
函数以访问当前请求的上下文,而不需要显式地传递 context
函数。
在幕后,useContext
使用 Node.js 的 AsyncLocalStorage 来隐式传递上下文。
启用上下文
对于不支持 AsyncLocalStorage
的环境,如浏览器或 Cloudflare Workers,可以使用 resolverPayload 中的 context
属性。
我们通过在 weave 函数中传入 asyncContextProvider
来启用上下文。asyncContextProvider
实际上是一个全局中间件。
接下来,让我们尝试在各个地方访问上下文。 我们将使用 graphql-yoga 作为适配器。
在解析函数中访问上下文
在上面的代码中,我们使用 useContext
函数从上下文中获取当前请求的 Authorization
头部,并将其与 Hello
字符串连接起来。
useContext
函数接受一个泛型参数,该参数指定上下文的类型,在这里,我们传入了 YogaInitialContext
类型。
让我们尝试调用这个查询:
你应该会得到以下响应:
在中间件中访问上下文
在上面的代码中,我们创建了一个名为 useUser
的自定义钩子,它使用 useContext
函数从上下文中获取当前请求的 Authorization
头部。
然后,我们创建了一个名为 authGuard
的中间件,它使用 useUser
钩子来获取用户,并在用户未登录时抛出错误。
要了解更多关于中间件的信息,请参阅 中间件文档。
在验证输入时访问上下文
我们可以在 valibot
中自定义验证或转换,并在其中直接访问上下文。
在上面的代码中,我们在 v.transformAsync
中使用 useUser()
来获取上下文中的用户信息,并将其作为 name
的值返回。
记忆化
考虑我们通过以下自定义函数来访问用户:
我们可能在 useUser()
中执行一些昂贵的操作,例如从数据库中获取用户信息,并且我们还有可能在同一请求中多次调用它。
为了避免多次调用造成的额外开销,我们可以使用记忆化(Memoization)来缓存结果,并在后续调用中重用它们。
在 GQLoom 中,我们使用 createMemoization
函数来创建一个记忆化函数。
记忆化函数会在第一次被调用后,将其结果缓存在上下文中,并在后续调用中直接返回缓存的结果。
也就是说,在同一个请求中,记忆化函数只会被执行一次,无论它被调用多少次。
让我们将 useUser()
函数记忆化:
如你所见,我们只需要将函数包装在 createMemoization
函数中即可。
随后,我们可以在解析器内的任何地方调用 useUser()
,而无需担心多次调用带来的开销。
注入上下文
asyncContextProvider
还允许我们注入上下文。这通常与 执行器 一起使用。
访问解析器负载
除了 useContext
函数,GQLoom 还提供了 useResolverPayload
函数,用于访问解析器中的所有参数:
-
root: 上一个对象,对于根查询类型上的字段来说,通常不会使用;
-
args: 在 GraphQL 查询中为字段提供的参数;
-
context: 在各个解析函数和中间件中共享的上下文对象,即
useContext
的返回值; -
info: 包含有关当前解析器调用的信息,例如 GraphQL 查询的路径、字段名称等;
-
field: 当前解析器正在解析的字段定义;
直接访问解析器负载
对于不提供 AsyncLocalStorage
的环境,如浏览器或 Cloudflare Workers,我们可以直接在解析函数和中间件中访问解析器负载。
解析函数
在解析函数中,payload
总是最后一个参数。
中间件
在各个适配器中使用上下文
在 GraphQL 生态中,每个适配器都提供了不同的上下文对象,你可以在适配器章节中了解如何使用: