
关于
fp-ts 与 React 结合的实用模式——Hooks、状态管理、表单、数据获取。适用于使用函数式编程模式构建 React 应用,兼容 React 18/19 和 Next.js 14/15。
name: fp-ts-react description: "在 React 中使用 fp-ts 的实用模式——hooks、状态、表单、数据获取。构建使用函数式编程模式的 React 应用时使用。适用于 React 18/19、Next.js 14/15。" risk: safe source: "https://github.com/whatiskadudoing/fp-ts-skills" date_added: "2026-02-27"
React 中的函数式编程
React 应用的实用模式。没有术语,只有能用的代码。
何时使用此技能
- 使用 fp-ts 构建类型安全状态管理的 React 应用时
- 处理数据获取中的加载/错误/成功状态时
- 实现带错误累积的表单验证时
- 使用 React 18/19 或 Next.js 14/15 配合函数式模式时
快速参考
| 模式 | 使用场景 |
|---------|----------|
| Option | 值可能不存在(用户尚未加载) |
| Either | 操作可能失败(表单验证) |
| TaskEither | 异步操作可能失败(API 调用) |
| RemoteData | 需要显示加载/错误/成功状态 |
| pipe | 链接多个转换 |
1. 使用 Option 管理状态(可能有,可能没有)
使用 Option 代替 null | undefined 以获得更清晰的意图。
基本模式
import { useState } from 'react'
import * as O from 'fp-ts/Option'
import { pipe } from 'fp-ts/function'
interface User {
id: string
name: string
email: string
}
function UserProfile() {
const [user, setUser] = useState<O.Option<User>>(O.none)
const handleLogin = (userData: User) => {
setUser(O.some(userData))
}
const handleLogout = () => {
setUser(O.none)
}
return pipe(
user,
O.match(
() => <button onClick={() => handleLogin({ id: '1', name: 'Alice', email: 'alice@example.com' })}>
Log In
</button>,
(u) => (
<div>
<p>Welcome, {u.name}!</p>
<button onClick={handleLogout}>Log Out</button>
</div>
)
)
)
}
链接可选值
import * as O from 'fp-ts/Option'
import { pipe } from 'fp-ts/function'
interface Profile {
user: O.Option<{
name: string
settings: O.Option<{
theme: string
}>
}>
}
function getTheme(profile: Profile): string {
return pipe(
profile.user,
O.flatMap(u => u.settings),
O.map(s => s.theme),
O.getOrElse(() => 'light')
)
}
2. 使用 Either 进行表单验证
Either 非常适合验证:Left = 错误,Right = 有效数据。
简单表单验证
import * as E from 'fp-ts/Either'
import * as A from 'fp-ts/Array'
import { pipe } from 'fp-ts/function'
const validateEmail = (email: string): E.Either<string, string> =>
email.includes('@')
? E.right(email)
: E.left('Invalid email address')
const validatePassword = (password: string): E.Either<string, string> =>
password.length >= 8
? E.right(password)
: E.left('Password must be at least 8 characters')
const validateName = (name: string): E.Either<string, string> =>
name.trim().length > 0
? E.right(name.trim())
: E.left('Name is required')
收集所有错误(不只是第一个)
import * as E from 'fp-ts/Either'
import { sequenceS } from 'fp-ts/Apply'
import { getSemigroup } from 'fp-ts/NonEmptyArray'
import { pipe } from 'fp-ts/function'
const validateAll = sequenceS(E.getApplicativeValidation(getSemigroup<string>()))
interface SignupForm {
name: string
email: string
password: string
}
interface ValidatedForm {
name: string
email: string
password: string
}
function validateForm(form: SignupForm): E.Either<string[], ValidatedForm> {
return pipe(
validateAll({
name: pipe(validateName(form.name), E.mapLeft(e => [e])),
email: pipe(validateEmail(form.email), E.mapLeft(e => [e])),
password: pipe(validatePassword(form.password), E.mapLeft(e => [e])),
})
)
}
function SignupForm() {
const [form, setForm] = useState({ name: '', email: '', password: '' })
const [errors, setErrors] = useState<string[]>([])
const handleSubmit = () => {
pipe(
validateForm(form),
E.match(
(errs) => setErrors(errs),
(valid) => {
setErrors([])
submitToServer(valid)
}
)
)
}
return (
<form onSubmit={e => { e.preventDefault(); handleSubmit() }}>
<input
value={form.name}
onChange={e => setForm(f => ({ ...f, name: e.target.value }))}
placeholder="Name"
/>
<input
value={form.email}
onChange={e => setForm(f => ({ ...f, email: e.target.value }))}
placeholder="Email"
/>
<input
type="password"
value={form.password}
onChange={e => setForm(f => ({ ...f, password: e.target.value }))}
placeholder="Password"
/>
{errors.map(err => <p key={err} style={{color:'red'}}>{err}</p>)}
<button type="submit">Sign Up</button>
</form>
)
}
限制
- 仅在任务明确匹配上述描述范围时使用此技能。
- 不要将输出视为环境特定验证、测试或专家审查的替代品。
- 如果缺少所需输入、权限、安全边界或成功标准,请停下来要求澄清。
兼容工具
Claude CodeCursor
标签
前端开发