Skip to content

Instantly share code, notes, and snippets.

@Lxxyx
Last active January 17, 2022 03:29
Show Gist options
  • Save Lxxyx/bfb354781e2e491bc65cab795c351519 to your computer and use it in GitHub Desktop.
Save Lxxyx/bfb354781e2e491bc65cab795c351519 to your computer and use it in GitHub Desktop.
Hooks 3.0 RFC
/**
* [RFC] Hooks Http 客户端
*
* @author Lxxyx
* @version 1.0.0
*
* 特性说明:
* 1. 支持 `Decorate` 语法,自动生成 Query/Params/Headers 参数
* 2. 新增请求中间件
* 3. 新增 `setupHttpClient` 方法,用于自定义客户端
* 4. 支持 `withCredentials`
* 5. 【TBD】支持文件上传
*/
/**
* ICE 自定义请求客户端
*/
// app.ts
import { setupHttpClient } from '@midwayjs/rpc'
import { request } from 'ice'
setupHttpClient({
fetcher: request,
baseUrl: '<API_URL>',
withCredentials: true,
})
/**
* 请求中间件
*/
// 错误处理
import { setupHttpClient } from '@midwayjs/rpc'
import { request } from 'ice'
const onError = async (ctx, next) => {
try {
await next()
} catch (error) {
if (error.code === '401') {
location.href = '/login'
} else {
alert(error.message)
}
}
}
setupHttpClient({
fetcher: request,
middlewares: [onError]
})
/**
* 文件上传
*/
// Server
export default Decorate(
Post(),
File('files'),
async () => {
const ctx = useContext()
console.log(ctx.files)
}
)
// Client
import upload from './api/upload'
const form = new FormData()
form.append('files', file)
await upload({ formData: form })
import { Decorate, Post, Validate } from '@midwayjs/hooks-core'
import { z } from 'zod'
/**
* Server
*/
// src/api/index.ts
const query = z.object({
pageSize: z.string(),
pageNumber: z.string(),
})
const params = z.object({ id: z.string() })
const headers = z.object({ token: z.string() })
const body = z.array(z.number())
export const getMembers = Decorate(
Post<{
query: z.infer<typeof query>
params: z.infer<typeof params>
headers: z.infer<typeof headers>
}>('/:id/members'),
Validate({ query, params, headers, body }),
async (type: number) => {
const { query, params, headers } = useContext()
const prisma = usePrisma()
const members = await prisma.members.findMany({
data: {
id: params.id,
pageSize: query.pageSize,
pageNumber: query.pageNumber,
token: headers.token,
type,
},
})
return { code: 200, data: members }
}
)
export const getMembersV2 = Decorate(
Post(),
Params(z.object({ id: z.string() })),
async () => {}
)
/**
* Client
*/
// pages/dashboard.tsx
import { Suspense } from 'React'
import Loading from './Loading'
export default function App() {
return (
<Dashboard>
<Suspense fallback={<Loading />}>
<Members />
</Suspense>
</Dashboard>
)
}
// components/members.tsx
import { getMembers } from './api'
import { useRPC } from '@midwayjs/rpc'
export default function Members() {
const members = useRPC(() =>
getMembers(ADMIN, {
query: {
pageSize: '10',
pageNumber: '1',
},
params: { id: '1' },
headers: { token: '123' },
})
)
return (
<div>
{members.map((member) => (
<div key={member.id}>{member.name}</div>
))}
</div>
)
}
type GlobExpression = string
type IgnoreHandler = (req: { url: string; [key: string]: any }) => boolean
type MidwayBundlerOptions = {
plugins?: any[]
devServer: {
// 命中的请求将由后端服务器处理,filter 存在时 include 选项将被忽略
include?: GlobExpression[]
// 命中的请求将由前端服务器处理,filter 存在时 exclude 选项将被忽略
exclude?: GlobExpression[]
// 返回 True 则交由后端处理,返回 false 则前端 devServer 处理
filter?: IgnoreHandler
}
}
import { plugin } from '@midwayjs/hooks-bundler'
// use include & exclude
plugin.webpack({
devServer: {
// 后端 Server 处理
include: ['**/*.js'],
// 前端 Server 处理
exclude: ['_vite/*']
}
})
// use filter
plugin.webpack({
devServer: {
filter: (req) => {
return req.includes('/api') === 0
},
// filter 存在时,include 会被忽略
include: ['**/*.js'],
// filter 存在时,exclude 会被忽略
exclude: ['_vite/*']
}
})
/**
* [RFC] 一体化新路由机制
*
* @author Lxxyx
* @version 1.0.0
*
* 特性说明:
* 1. 缩短路由长度,由基于文件系统路径生成路由变为基于函数名生成路由,公共前缀 /api
* 2. 支持指定路径
* 3. 只支持 Decorate 语法
*
* 优势:
* 1. 避免文件系统路径长度过长
* 2. 可以支持 Params 传参,自定义路径等特殊需求
* 3. 支持在单文件中存在多种触发器的情况
* 4. 更简洁,最简单情况下,整个后端目录只需要 1 个文件就可以写 API,方便内嵌到前端框架中
*
* 注:
* 1. 原有文件路由模式保留
*/
/**
* 路径生成机制,基于函数名生成路由
*/
// URL: /api/getArticle
export const getArticle = Decorate(
Get(),
async () => {}
)
// URL: /api/createArticle
export const createArticle = Decorate(
Post(),
async () => {}
)
/**
* export default 情况下,取文件名作为函数名生成路径
*/
// FILE: /api/login.ts
// URL: /api/login
export default Decorate(
All(),
async () => {}
)
/**
* 2. 支持指定路径
*/
// URL: /*
export const render = Decorate(
Get('/*'),
async () => {}
)
// URL: /updateProfile/:id
export const updateProfile = Decorate(
Put('/updateProfile/:id'),
Param<{ id: string }>(),
async () => {}
)
/**
* 单文件 + 多触发器
*/
export const render = Decorate(
Get('/*'),
async () => {}
)
export const timer = Decorate(
Timer({
type: 'cron',
expression: '0 0 0 * * *',
}),
async () => {}
)
export const like = Decorate(
Mtop(),
async (id: number) => {}
)
/**
* [RFC] 一体化单测机制
*
* @author Lxxyx
* @version 1.0.0
*
* ✨ 特性:
* 1. 支持 Hooks 单元测试
*
* 💥 Breaking Changes:
* 1. 移除 @midwayjs/hooks-testing-library
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment