ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Midway Hooks 一体化开发指南:用 React Hooks 语法编写全栈应用

Midway Hooks 一体化开发指南:用 React Hooks 语法编写全栈应用 后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载本文是 MidwayGitHub 加速计划 / mi / midway官方文档中 Midway Hooks 系列的中文深度解读。Midway Hooks 是 Midway 2.0 时代的一体化BFF/Full-stack开发范式在src一个目录中同时编写前端与后端代码后端以函数形式导出、前端直接import调用调用时自动转换为 HTTP 请求零 API。读完本文你将掌握Hooks 式函数接口的写法与路由规则、内置 HooksuseContext / useInject / useConfig / useLogger的用法、项目初始化与部署流程以及中间件、自定义前端 SDK 等进阶技巧。✨ 核心特性Midway Hooks 是基于 Midway 的一体化开发方案其核心设计目标是让前后端开发像写一个 React 组件一样简单。官方文档site/versioned_docs/version-2.0.0/hooks/intro.md列出的特性包括☁️全栈在src一个目录中开发前后端代码同一仓库、无缝融合零 API从后端import函数调用时自动转换为 API 请求不再手动拼接 URL熟悉的语法使用 React Hooks | Vue Composition API 的思维方式开发后端⚡️极快的启动速度小于 3 秒⚙️基于 Vite支持 React / Vue 等前端框架✈️可部署至 Server 或 Serverless完善的 TypeScript 支持。应用结构优势基于 Midway Hooks 开发应用官方文档总结了三点收益易于开发前后端同仓库一体化开发不再需要跨仓库联调易于部署前后端一同发布与部署一个构建产物包含完整应用易于维护在同一仓库中排查问题后端支持使用 Serverless 部署降低运维难度。当前仓库中的 samples/react-functional-api 与 samples/vue-functional-api 即是一体化应用的落地示例其典型目录结构为src/server后端函数src/web前端页面并存于同一个src之下。 第一个示例函数接口与前端调用Midway Hooks 的核心体验可以用下面这段官方示例完整概括——后端是普通的导出函数前端是直接的函数调用。后端接口apis/lambda.tsimport { useContext } from midwayjs/hooks; export async function getPath() { // Get HTTP request context by Hooks const ctx useContext(); return ctx.path; } export async function post(name: string) { const ctx useContext(); return { message: Hello ${name}!, method: ctx.method, }; }前端调用import { getPath, post } from ./apis/lambda; // send GET request to /api/getPath const path await getPath(); console.assert(path /api/getPath); const { message, method } await post(Jake); console.assert(message Hello Jake!); console.assert(method POST);这段示例揭示了两个关键机制后端即函数getPath与post只是普通的异步函数但通过useContext()可以拿到当前 HTTP 请求上下文ctx.path、ctx.method从而让纯函数具备了 Web 服务能力前端零 API前端直接import后端导出的函数并await调用框架自动把它转换为对应的 HTTP 请求——getPath()发出 GET 到/api/getPathpost(Jake)发出 POST 到对应路径返回值被自动解析回函数返回值。 接口开发规则Get / Post / Put / Delete在 Midway Hooks 中接口默认配置在lambda文件夹下任意.ts文件中导出的异步函数详见 接口开发文档。HTTP 方法由函数是否携带参数自动推导Get无参函数导出的函数中不带参数即为Get接口。/apis/lambda/index.tsexport async function foo() { return foo; }前端调用示例src/app.tsximport { foo } from ./apis/lambda; const response await foo(); console.log(response); // fooPost有参函数函数有参数则为Post接口参数会作为请求体body提交。/apis/lambda/index.tsexport async function bar(name: string) { return hello ${name}; }前端调用示例src/app.tsximport { bar } from ./apis/lambda; const response await bar(张三); console.log(response); // hello 张三Put / Delete不支持由于 Midway Hooks 的接口信息与前端调用 SDK 均为自动生成因此不支持Put/Delete调用如有需要可参考本文后续的defineApi显式路由方案或使用中间件能力扩展。导出规则Midway Hooks 的接口必须是在模块顶层直接导出的函数以下三种写法均合法export async function demo() {} export const demo async function () {}; export default async function demo() {}注意从源码结构看文件路由解析依赖顶层导出这一静态可分析特征因此不要将接口函数嵌套在对象或闭包内部导出。 Hooks 语法函数 请求上下文Midway Hooks 采用了类似 React Hooks 的语法通过Function Hooks的方式在函数中获取当前请求数据并提供 Web 服务详见 Hooks 语法文档。import { useContext } from midwayjs/hooks; export function getHttpMethod() { const { request } useContext(); return request.method; }什么是 HooksHooks 是一些可以让你在函数里访问当前请求上下文信息的函数。Midway 提供了 5 个内置 HooksHooks作用useContext获取请求上下文useLogger获取 LoggeruseInject获取 IoC 注入的实例useConfig获取用户配置usePlugin获取 Egg 插件你也可以创建自己的 Hooks 来复用代码逻辑。与 React Hooks 的区别需要特别说明的是Midway Hooks 中的 Hooks仅用于访问信息不能保存状态或执行副作用。它不参与组件的状态生命周期管理这一点与 React Hooks 有本质区别。自定义 Hooks通过自定义 Hooks 可以封装与复用复杂逻辑。官方要求自定义 Hooks 遵守以use作为命名前缀的规范如useHeader、usePath等。定义 Hooksapis/hooks/request.tsimport { useContext } from midwayjs/hooks; export function useHeader() { const { request } useContext(); return request.headers; } export function usePath() { const { request } useContext(); return request.path; }调用自定义 Hooksapis/function/index.tsimport { useHeader, usePath } from ../hooks/request; export function get() { return { header: useHeader(), path: usePath(), }; } 路由机制文件路由与自定义配置Midway Hooks 的路由机制是文件路由根据 目录 / 文件 / 导出的方法 自动分析生成路由配置详见 路由文档。默认配置2.0 版本推荐在 2.0 中文件路由配置统一在midway.config.ts中import { defineConfig } from midwayjs/hooks; export default defineConfig({ source: ./src/apis, routes: [ { baseDir: lambda, basePath: /api, }, ], });字段说明source后端目录默认为./src/apis也可指定为./src/functions等自定义目录routes路由配置默认为数组baseDir函数文件夹该文件夹下任意.ts文件导出的异步函数都会生成为 Api 接口basePath生成的 API 地址前缀。1.0 版本1.0 一体化项目默认使用f.yml配置functionsRule: rules: - baseDir: lambda events: http: basePath: /api字段说明rules具体函数路由配置规则baseDir函数文件夹events函数触发器配置httpbasePath生成的 API 地址前缀underscore0.x 兼容逻辑默认为false添加后将在具名路由生成时在方法名前添加下划线。Index 路由目录下的index.ts文件会被作为根路由/lambda/index.ts→//lambda/about/index.ts→/about嵌套路由嵌套的文件会生成嵌套路由/lambda/about.ts→/about/lambda/blog/index.ts→/blog/lambda/about/contact.ts→/about/contact导出方法与对应路由默认导出的方法生成为根路径具名方法则在路径上拼接函数名。以/lambda/about.ts为例export default () {}→/aboutexport function contact()→/about/contact通配路由Midway Hooks 支持通过文件名生成通配符路由只需在文件名中加入...即可。例如/api/*可以匹配/api、/api/about、/api/about/a/b/c等/lambda/[...index].ts→/api/*/lambda/[...user].ts→/api/user/*/lambda/about/[...contact].ts→/api/about/contact/* 官方建议在通配路由中只保留export default方法从而避免不必要的路由冲突。 项目初始化、运行与部署创建项目使用npm init midway脚手架即可创建 Midway Hooks 一体化项目详见 项目初始化文档。Serverless 应用React 模板# npm v6 $ npm init midway --templatemidwayjs-examples/serverless-boilerplate-hooks-react # npm v7 $ npm init midway -- --templatemidwayjs-examples/serverless-boilerplate-hooks-reactWeb 应用Koa React 模板# npm v6 $ npm init midway --templatemidwayjs-examples/applicaiton-hooks-koa-react # npm v7 $ npm init midway -- --templatemidwayjs-examples/applicaiton-hooks-koa-react运行$ npm run dev启动后访问命令行提示的端口即可看到页面点击页面上的Send message to backend即可体验函数式开发的接口调用。部署部署前请先执行构建npm run build部署 Web 应用$ node bootstrap.js部署 Serverless$ npm run deploy 内置 Hooks 详解useContext获取请求上下文const ctx useContext();useContext用于获取当前请求的上下文Context 类型可以通过泛型注入// Koa import { Context } from midwayjs/koa; const ctx useContextContext(); // FaaS import { Context } from midwayjs/faas; const ctx useContextContext();上下文对象上还有两个常用属性详见 内置 Hooks 文档ctx.requestContextMidway 的 IoC 请求上下文对象是依赖注入体系的运行载体ctx.loggerMidway 自带的 Logger 对象。useInject获取 IoC 注入实例function useInjectT any(identifier: any): PromiseT;useInject是对 Midway IoC 依赖注入功能的封装用于获取注入的实例等同于 Class 中使用的Inject装饰器以及getAsync方法import { useInject } from midwayjs/hooks; import { Provide } from midwayjs/decorator; export async function getModel() { const model await useInject(Model); return model.name; } Provide() export class Model { name model; }useConfig获取业务配置const config useConfig(key);useConfig用于获取业务配置。需要注意Midway Hooks 的函数文件默认存放于./src/apis/目录下因此config 与 configuration 所在目录需为./src/apis/若修改了midway.config.ts的source字段则使用修改后的目录。useLogger输出日志const logger useLogger();支持的日志级别logger.debug(); logger.info(); logger.warn(); logger.error();usePlugin获取 Egg 插件const plugin usePlugin(pluginName);典型应用场景是通过egg-sequelize等插件查询数据库import { usePlugin } from midwayjs/hooks; export default async function getUserById(empId: number) { const sequelize usePlugin(sequelize); console.log(sequelize); }⚙️ 运行时配置与 Hooks 组件在 2.0 中Midway Hooks 被重构为 Midway 的Component同时支持createConfiguration以函数方式创建 Midway Configuration避免 Class 与函数式混用带来的迷惑感详见 运行时配置文档。./src/apis/configuration.tsimport { hooks, createConfiguration } from midwayjs/hooks; export default createConfiguration({ imports: [hooks()], });通过上述方式即可启用 Hooks 功能。若你已有 Midway Web 或 Midway FaaS 纯接口项目想迁移到 Hooks 开发方式可参考 纯接口项目增加 Hooks 支持$ npm install midwayjs/hooks -S然后在根目录新增midway.config.tsimport { defineConfig } from midwayjs/hooks; export default defineConfig({ source: ./src, routes: [ { baseDir: lambda, basePath: /api, }, ], });再修改或新建configuration.ts注册 Hooks 组件import { Configuration } from midwayjs/decorator; import { ILifeCycle } from midwayjs/core; import { join } from path; import { hooks } from midwayjs/hooks; Configuration({ importConfigs: [join(__dirname, ./config/)], conflictCheck: true, imports: [ hooks() ] }) export class ContainerLifeCycle implements ILifeCycle {最后新增接口文件src/lambda/index.tsexport default () { return Hello Midway Hooks!; };启动工程后访问/api即可验证接口是否正确返回Hello Midway Hooks!。 Web 中间件2.0 版本中Midway Hooks 支持三种粒度的中间件详见 中间件文档2.0 全局对所有 Api 调用生效2.0 文件对文件内部所有 Api 生效1.0 函数仅对该 Api 函数生效。语法中间件仅有next一个参数ctx需要通过useContext获得你也可以在中间件中使用任意 Hooksimport { Context } from midwayjs/faas; import { useContext } from midwayjs/hooks; const logger async (next: any) { const ctx useContextContext(); console.log(-- [${ctx.method}] ${ctx.url}); const start Date.now(); await next(); const cost Date.now() - start; console.log([${ctx.method}] ${ctx.url} ${cost}ms); };全局中间件在configuration.ts中定义可以传入任何框架支持的中间件import { hooks, createConfiguration } from midwayjs/hooks; import logger from ./logger; // Global Middleware export default createConfiguration({ imports: [ hooks({ middleware: [logger], }), ], });文件级中间件在 Api 文件中通过导出config.middleware定义对该文件内所有 Api 函数生效import { ApiConfig } from midwayjs/hooks; import logger from ./logger; // File Level Middleware export const config: ApiConfig { middleware: [logger], }; export default async (message: string) { return { type: POST, message }; };单函数中间件withController通过withController可以支持单函数级别的中间件。withController的第一个参数是 controller 配置middleware?: any[]可选可同时传入多个中间件支持函数定义的中间件及 IoC 定义的中间件第二个参数是要执行的 FaaS 函数type Controller { middleware?: any[]; }; function withController(controller: Controller, func);使用函数中间件import { withController } from midwayjs/hooks; import { Context } from midwayjs/faas; const logger async (next) { const ctx: Context useContext(); const start Date.now(); await next(); const cost Date.now() - start; console.log(request ${ctx.url} cost ${cost}ms); }; export default withController( { middleware: [logger], }, () { return Hello Controller; } );使用 IoC 中间件import { withController } from midwayjs/hooks; import { Context } from midwayjs/faas; import { Provide, ScopeEnum, Scope } from midwayjs/decorator; Provide(classMiddleware) Scope(ScopeEnum.Singleton) export class ClassMiddleware { resolve() { return async (ctx: Context, next) { ctx.query.from classMiddleware; await next(); }; } } export default withController( { middleware: [classMiddleware], }, () { return Hello Controller ctx.query.from; } ); 自定义前端 SDK请求函数Midway Hooks 默认使用浏览器指定的fetch发送请求。为了支持不同场景开发者可以自定义请求函数详见 自定义前端 SDK 文档。例如希望使用 Axios 作为 HTTP 客户端只需在前端页面入口处添加如下代码import { defaults, ApiParam } from midwayjs/hooks/request; import axios from axios; defaults.request async (param: ApiParam) { const resp await axios(params); return resp.data; };注意事项自定义函数对所有请求生效重复设置defaults.request会覆盖之前的设置自定义函数的返回值应该是调用的接口返回值。像axios这种对响应自行包装的客户端需要手动解析出接口的返回值即resp.data。 现代演进defineApi 显式路由与前端工程化当前仓库在 Hooks 一体化思想之上进一步演进出了midwayjs/core/functional的显式路由 APIdefineApi/defineConfiguration底层实现位于 packages/core/src/functional/api.ts 与 packages/core/src/functional/configuration.ts。这种方式在保留前端直接调用后端函数体验的同时显式声明 HTTP 方法与路径弥补了文件路由对 Put/Delete 等方法的限制。以仓库示例 samples/react-functional-api/src/server/api/user.api.ts 为例import { defineApi } from midwayjs/core/functional; export const userApi defineApi(/users, api ({ getUser: api .get(/:id) .meta({ routerName: getUser }) .handle(async ({ input }) { return { id: input.params?.[id], name: harry, }; }), createUser: api .post(/) .meta({ routerName: createUser }) .handle(async ({ input }) { return { id: u-created, name: input.body?.[name], }; }), }));从 源码结构 可以看到defineApi的第一个参数是路由前缀prefix第二个参数是路由工厂工厂内通过api.get / api.post / api.put / api.delete / api.patch / api.options / api.head / api.all显式声明 HTTP 方法与路径并调用.handle(fn)绑定处理函数返回的NormalizeDefinedRoutes类型会同步生成前端 SDK 的类型提示。前端侧通过midwayjs/web-bridge的createClient建立类型安全的调用桥接见 samples/react-functional-api/src/web/api/client.tsimport { createClient } from midwayjs/web-bridge; import { userApi } from ../../server/api/user.api.js; export const apiBridgeConfig { browserBasePath: /api, serverBasePath: http://127.0.0.1:7001/api, apiDir: src/server/api, } as const; export const api createClient( { user: userApi, }, { basePath: { browser: apiBridgeConfig.browserBasePath, server: apiBridgeConfig.serverBasePath, }, } );配套的 Vite 工程化配置见 samples/react-functional-api/vite.config.ts通过midwayjs/web-bridge/vite的apiPlugin声明apiDir与target: both由midwayjs/mock/vite的devPlugin提供本地联调服务器实现开发期前端页面 后端函数一键启动。更简单的函数式服务示例可参考 samples/functional-api-service/src/api/health.api.ts。 本地调试Midway Hooks 项目基于 Vite 与标准 npm scripts 运行因此调试方式与常规 Node 工程一致详见 本地调试文档VSCode创建JavaScript Debug Terminal在其中运行npm start等命令即可自动启用调试模式也可以打开package.json点击scripts上方的 debug 按钮选择start命令启动调试JetBrainsWebStorm/IDEA 等打开package.json选择要执行的scripts并点击 debug 按钮即可启动本地调试。 扩展小程序一体化Midway Hooks 还支撑了与 Rax 团队合作的小程序一体化方案详见 小程序一体化文档核心流程为npm init rax创建项目选择小程序云开发一体化应用在根目录build.json的wechat-miniprogram.nativeConfig.appid中配置小程序 AppIDnpm start启动本地服务云函数位于src/cloud/functions目录使用 Midway Hooks 语法开发例如通过cloud.getWXContext()获取微信上下文 OpenId通过微信小程序开发者工具导入项目根目录/build/wechat-miniprogram预览、调试并上传部署云函数。总结Midway Hooks 的核心价值在于将 Web 后端函数化 Hooks 化函数即接口、useContext等内置 Hooks 提供请求上下文访问能力、文件路由自动生成 API 路径而前端通过import后端函数实现零 API 调用。配合 Vite 与 Serverless 部署能力它让前后端同仓库的一体化开发从目录组织到运行部署形成了完整闭环。如需深入了解可继续阅读仓库内同目录下的 hooks_api.md、hooks_route.md、hooks_middleware.md 等文档或直接运行仓库中的 samples 示例体验一体化开发流程。赞分享后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载相关推荐Midway Hooks 全栈套件实战用 midwayjs/hooks-kit 构建前后端一体化应用Midway Hooks 全栈套件实战用 midwayjs/hooks kit 构建前后端一体化应用 在 Midway Hooks 体系中全栈套件Ful后端微服务云原生Midway Hooks 全栈套件实战指南使用 midwayjs/hooks-kit 快速构建前后端一体化应用Midway Hooks 全栈套件实战指南使用 midwayjs/hooks kit 快速构建前后端一体化应用 本文以 Midway 官方文档《Full s后端微服务云原生Midway Hooks 一体化全栈应用部署指南三种部署模式与配置实践Midway Hooks 一体化全栈应用部署指南三种部署模式与配置实践 导读 Midway Hooks 是 Midway 一体化Full Stack方案的后端微服务云原生上一篇零基础18课速通AI智能体开发从第一个Agent到企业级上线的完整路线下一篇Microsoft Activation Scripts免费激活指南零基础3分钟搞定Windows与Office授权创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进