跳到主要内容

实际应用

函数式编程不仅是一套理论概念,更是一种实用的编程思维方式。在现代前端开发中,函数式编程思想已经深入到 React、Redux、RxJS 等主流框架和库的设计中。本章将详细介绍函数式编程在实际项目中的应用场景。

函数式编程的核心价值

在深入具体应用之前,让我们回顾一下函数式编程在实际项目中带来的核心价值:

  1. 可预测性:纯函数的输出完全由输入决定,相同输入永远产生相同输出,这让代码行为变得可预测
  2. 可测试性:没有副作用的纯函数不需要复杂的 mock 和 setup,测试变得简单直接
  3. 可组合性:小的、独立的函数可以像积木一样组合成复杂功能,提高代码复用率
  4. 并发安全:不可变数据天然避免了竞态条件,多线程环境下的数据竞争问题不复存在
  5. 调试友好:每一步操作都是确定性的,错误追踪和调试更加直观

React 中的函数式编程

React 从设计之初就深受函数式编程思想的影响。组件可以是纯函数,状态管理强调不可变性,Hooks 让函数组件拥有了状态管理能力。

函数组件的本质

函数组件本质上就是纯函数:接收 props 作为输入,返回 JSX 作为输出。

// 纯函数组件:相同的 props 总是渲染相同的 UI
const UserCard = ({ name, email, avatar }) => (
<div className="user-card">
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
</div>
);

// 更复杂的纯函数组件
const PriceDisplay = ({ price, currency = 'CNY', locale = 'zh-CN' }) => {
// 纯计算:格式化价格
const formatter = new Intl.NumberFormat(locale, {
style: 'currency',
currency
});

return (
<span className="price">
{formatter.format(price)}
</span>
);
};

// 使用
<UserCard
name="张三"
email="[email protected]"
avatar="/avatar.png"
/>

这种设计带来一个重要好处:组件渲染结果完全由 props 决定,这使得组件行为可预测,也便于测试和调试。

使用 Hooks 进行状态管理

React Hooks 让函数组件能够管理状态,同时保持函数式编程的风格。关键原则是:状态更新必须是不可变的

import { useState, useCallback, useMemo } from 'react';

function UserList({ users }) {
// 状态管理
const [filter, setFilter] = useState('');
const [sortBy, setSortBy] = useState('name');
const [selectedIds, setSelectedIds] = useState(new Set());

// useMemo:记忆化计算结果,只有依赖变化时才重新计算
// 这是函数式编程中"惰性求值"思想的体现
const filteredUsers = useMemo(() => {
console.log('重新计算过滤结果...');

return users
.filter(user =>
user.name.toLowerCase().includes(filter.toLowerCase()) ||
user.email.toLowerCase().includes(filter.toLowerCase())
)
.sort((a, b) => {
if (sortBy === 'name') return a.name.localeCompare(b.name, 'zh-CN');
if (sortBy === 'email') return a.email.localeCompare(b.email);
return 0;
});
}, [users, filter, sortBy]);

// useCallback:记忆化回调函数,避免不必要的重渲染
// 这保持了函数的引用稳定性,符合函数式编程的不可变原则
const handleFilterChange = useCallback((e) => {
setFilter(e.target.value);
}, []);

const handleSortChange = useCallback((newSortBy) => {
setSortBy(newSortBy);
}, []);

// 不可变的状态更新
const toggleSelection = useCallback((userId) => {
setSelectedIds(prevSelected => {
// 创建新的 Set,而不是修改原来的
const newSelected = new Set(prevSelected);
if (newSelected.has(userId)) {
newSelected.delete(userId);
} else {
newSelected.add(userId);
}
return newSelected;
});
}, []);

// 批量操作
const selectAll = useCallback(() => {
setSelectedIds(new Set(filteredUsers.map(u => u.id)));
}, [filteredUsers]);

const clearSelection = useCallback(() => {
setSelectedIds(new Set());
}, []);

return (
<div className="user-list">
<div className="controls">
<input
value={filter}
onChange={handleFilterChange}
placeholder="搜索用户..."
/>
<select value={sortBy} onChange={(e) => handleSortChange(e.target.value)}>
<option value="name">按姓名排序</option>
<option value="email">按邮箱排序</option>
</select>
<button onClick={selectAll}>全选</button>
<button onClick={clearSelection}>清除选择</button>
</div>

<ul>
{filteredUsers.map(user => (
<li
key={user.id}
onClick={() => toggleSelection(user.id)}
className={selectedIds.has(user.id) ? 'selected' : ''}
>
<span>{user.name}</span>
<span>{user.email}</span>
</li>
))}
</ul>

<p>已选择 {selectedIds.size} 个用户</p>
</div>
);
}

自定义 Hooks:复用逻辑

自定义 Hooks 是函数式编程中"组合"思想的完美体现。我们可以将复杂逻辑拆分成小的、可复用的函数。

import { useState, useEffect, useCallback, useMemo } from 'react';

// 自定义 Hook:表单状态管理
function useForm(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [touched, setTouched] = useState({});
const [errors, setErrors] = useState({});

// 计算验证结果
const validationResult = useMemo(() => {
if (typeof validate !== 'function') return { isValid: true, errors: {} };
return validate(values);
}, [values, validate]);

useEffect(() => {
setErrors(validationResult.errors);
}, [validationResult]);

// 不可变的状态更新
const handleChange = useCallback((name) => (e) => {
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
setValues(prev => ({
...prev,
[name]: value
}));
}, []);

const handleBlur = useCallback((name) => () => {
setTouched(prev => ({
...prev,
[name]: true
}));
}, []);

const setFieldValue = useCallback((name, value) => {
setValues(prev => ({
...prev,
[name]: value
}));
}, []);

const reset = useCallback(() => {
setValues(initialValues);
setTouched({});
}, [initialValues]);

return {
values,
errors,
touched,
isValid: validationResult.isValid,
handleChange,
handleBlur,
setFieldValue,
reset
};
}

// 自定义 Hook:异步数据获取
function useAsync(asyncFunction, immediate = true) {
const [state, setState] = useState({
loading: immediate,
data: null,
error: null
});

const execute = useCallback(async (...params) => {
setState(prev => ({ ...prev, loading: true, error: null }));

try {
const data = await asyncFunction(...params);
setState({ loading: false, data, error: null });
return data;
} catch (error) {
setState({ loading: false, data: null, error });
throw error;
}
}, [asyncFunction]);

useEffect(() => {
if (immediate) {
execute();
}
}, [immediate, execute]);

return { ...state, execute };
}

// 使用示例
function UserForm({ onSubmit, initialValues }) {
const validate = (values) => {
const errors = {};

if (!values.name || values.name.trim() === '') {
errors.name = '姓名不能为空';
}

if (!values.email) {
errors.email = '邮箱不能为空';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email)) {
errors.email = '邮箱格式不正确';
}

return {
isValid: Object.keys(errors).length === 0,
errors
};
};

const { values, errors, touched, isValid, handleChange, handleBlur, reset } =
useForm(initialValues || { name: '', email: '' }, validate);

const handleSubmit = (e) => {
e.preventDefault();
if (isValid) {
onSubmit(values);
reset();
}
};

return (
<form onSubmit={handleSubmit}>
<div>
<input
name="name"
value={values.name}
onChange={handleChange('name')}
onBlur={handleBlur('name')}
placeholder="姓名"
/>
{touched.name && errors.name && <span className="error">{errors.name}</span>}
</div>

<div>
<input
name="email"
value={values.email}
onChange={handleChange('email')}
onBlur={handleBlur('email')}
placeholder="邮箱"
/>
{touched.email && errors.email && <span className="error">{errors.email}</span>}
</div>

<button type="submit" disabled={!isValid}>提交</button>
<button type="button" onClick={reset}>重置</button>
</form>
);
}

Redux 中的函数式编程

Redux 是函数式编程思想在前端状态管理中的典型应用。它的三大原则——单一数据源、状态只读、纯函数修改——都是函数式编程核心理念的体现。

Reducer:纯函数状态转换

Reducer 是最核心的概念,它是一个纯函数,接收旧状态和 action,返回新状态。

// Action Types
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';
const REMOVE_TODO = 'REMOVE_TODO';
const SET_FILTER = 'SET_FILTER';

// Action Creators:返回 action 对象的纯函数
const addTodo = (text) => ({
type: ADD_TODO,
payload: {
id: Date.now(),
text,
completed: false,
createdAt: new Date().toISOString()
}
});

const toggleTodo = (id) => ({
type: TOGGLE_TODO,
payload: { id }
});

const removeTodo = (id) => ({
type: REMOVE_TODO,
payload: { id }
});

const setFilter = (filter) => ({
type: SET_FILTER,
payload: { filter }
});

// Reducer:纯函数,不修改原状态,返回新状态
const todosReducer = (state = [], action) => {
switch (action.type) {
case ADD_TODO:
// 使用展开运算符创建新数组
return [...state, action.payload];

case TOGGLE_TODO:
// 使用 map 创建新数组,不修改原数组
return state.map(todo =>
todo.id === action.payload.id
? { ...todo, completed: !todo.completed }
: todo
);

case REMOVE_TODO:
// 使用 filter 创建新数组
return state.filter(todo => todo.id !== action.payload.id);

default:
return state;
}
};

const filterReducer = (state = 'ALL', action) => {
switch (action.type) {
case SET_FILTER:
return action.payload.filter;
default:
return state;
}
};

// 组合 Reducer
const rootReducer = (state = {}, action) => ({
todos: todosReducer(state.todos, action),
filter: filterReducer(state.filter, action)
});

// Selectors:从状态中提取数据的纯函数
// 这是一种"查询"模式,将状态结构与组件解耦
const getTodos = (state) => state.todos;
const getFilter = (state) => state.filter;

const getVisibleTodos = (state) => {
const todos = getTodos(state);
const filter = getFilter(state);

switch (filter) {
case 'COMPLETED':
return todos.filter(todo => todo.completed);
case 'ACTIVE':
return todos.filter(todo => !todo.completed);
default:
return todos;
}
};

const getTodoStats = (state) => {
const todos = getTodos(state);

return {
total: todos.length,
completed: todos.filter(todo => todo.completed).length,
active: todos.filter(todo => !todo.completed).length
};
};

使用 Immer 简化 Reducer

Immer 让我们可以用可变的写法来编写不可变的更新,大大简化了复杂状态更新的代码。

import { produce } from 'immer';

// 使用 Immer 的 reducer
const todosReducerImmer = (state = [], action) =>
produce(state, draft => {
switch (action.type) {
case ADD_TODO:
// 直接 push,Immer 会自动处理不可变性
draft.push(action.payload);
break;

case TOGGLE_TODO:
const todo = draft.find(t => t.id === action.payload.id);
if (todo) {
todo.completed = !todo.completed;
}
break;

case REMOVE_TODO:
const index = draft.findIndex(t => t.id === action.payload.id);
if (index !== -1) {
draft.splice(index, 1);
}
break;

case UPDATE_TODO_TEXT:
const todoToUpdate = draft.find(t => t.id === action.payload.id);
if (todoToUpdate) {
todoToUpdate.text = action.payload.text;
todoToUpdate.updatedAt = new Date().toISOString();
}
break;
}
});

// 嵌套状态的更新
const nestedReducer = produce((draft, action) => {
switch (action.type) {
case 'UPDATE_USER_PROFILE':
// 直接修改嵌套属性
draft.user.profile.name = action.payload.name;
draft.user.profile.updatedAt = new Date().toISOString();
break;

case 'ADD_ITEM_TO_CATEGORY':
const category = draft.categories.find(c => c.id === action.payload.categoryId);
if (category) {
category.items.push(action.payload.item);
}
break;
}
}, {});

Redux Toolkit:现代化的 Redux

Redux Toolkit 内置了 Immer,让状态管理更加简洁。

import { createSlice, createAsyncThunk, createSelector } from '@reduxjs/toolkit';

// 异步 Thunk
export const fetchTodos = createAsyncThunk(
'todos/fetchTodos',
async (userId, { rejectWithValue }) => {
try {
const response = await fetch(`/api/users/${userId}/todos`);
if (!response.ok) throw new Error('Failed to fetch');
return await response.json();
} catch (error) {
return rejectWithValue(error.message);
}
}
);

const todosSlice = createSlice({
name: 'todos',
initialState: {
items: [],
filter: 'ALL',
loading: false,
error: null
},
reducers: {
addTodo: (state, action) => {
// 可以直接"修改"state,因为 Immer 会处理不可变性
state.items.push({
id: Date.now(),
text: action.payload,
completed: false,
createdAt: new Date().toISOString()
});
},
toggleTodo: (state, action) => {
const todo = state.items.find(t => t.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
removeTodo: (state, action) => {
const index = state.items.findIndex(t => t.id === action.payload);
if (index !== -1) {
state.items.splice(index, 1);
}
},
setFilter: (state, action) => {
state.filter = action.payload;
},
clearCompleted: (state) => {
state.items = state.items.filter(todo => !todo.completed);
}
},
extraReducers: (builder) => {
builder
.addCase(fetchTodos.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchTodos.fulfilled, (state, action) => {
state.loading = false;
state.items = action.payload;
})
.addCase(fetchTodos.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
});
}
});

// Selectors
const selectTodos = (state) => state.todos.items;
const selectFilter = (state) => state.todos.filter;
const selectLoading = (state) => state.todos.loading;

// 使用 createSelector 创建记忆化 selector
// 只有当输入 selector 的结果变化时才重新计算
export const selectVisibleTodos = createSelector(
[selectTodos, selectFilter],
(todos, filter) => {
switch (filter) {
case 'COMPLETED':
return todos.filter(todo => todo.completed);
case 'ACTIVE':
return todos.filter(todo => !todo.completed);
default:
return todos;
}
}
);

export const selectTodoStats = createSelector(
[selectTodos],
(todos) => ({
total: todos.length,
completed: todos.filter(todo => todo.completed).length,
active: todos.filter(todo => !todo.completed).length
})
);

export const { addTodo, toggleTodo, removeTodo, setFilter, clearCompleted } = todosSlice.actions;
export default todosSlice.reducer;

数据处理管道

函数式编程非常适合数据处理场景。通过组合小的、纯函数式的转换函数,可以构建清晰、可维护的数据处理管道。

API 数据转换

// 定义可复用的转换函数
const extractData = (response) => response.data;
const filterValid = (items) => items.filter(item => item.id && item.name);
const normalizeItem = (item) => ({
id: Number(item.id),
name: item.name.trim(),
email: item.email?.toLowerCase() || '',
active: Boolean(item.active)
});
const sortByField = (field) => (items) =>
[...items].sort((a, b) => String(a[field]).localeCompare(String(b[field])));

// 组合管道
const processApiResponse = (response) => {
return [
extractData,
filterValid,
(items) => items.map(normalizeItem),
sortByField('name')
].reduce((data, fn) => fn(data), response);
};

// 使用
const apiResponse = {
data: [
{ id: '1', name: ' 张三 ', email: '[email protected]', active: 'true' },
{ id: '2', name: '李四', email: '[email protected]', active: 'false' },
{ id: null, name: '无效数据', active: 'true' },
{ id: '3', name: '王五', email: '[email protected]', active: 'true' }
]
};

const processedUsers = processApiResponse(apiResponse);
// [
// { id: 2, name: '李四', email: '[email protected]', active: false },
// { id: 3, name: '王五', email: '[email protected]', active: true },
// { id: 1, name: '张三', email: '[email protected]', active: true }
// ]

使用 Ramda 构建数据管道

Ramda 提供了丰富的函数式工具,让数据处理更加声明式。

import * as R from 'ramda';

// API 响应处理管道
const processUsers = R.pipe(
// 安全获取嵌套数据
R.pathOr([], ['data', 'items']),

// 过滤无效数据
R.filter(R.where({
id: R.isNotNil,
name: R.complement(R.isEmpty)
})),

// 规范化每条数据
R.map(R.evolve({
id: Number,
name: R.trim,
email: R.pipe(R.defaultTo(''), R.toLower),
active: Boolean
})),

// 按名称排序
R.sortBy(R.prop('name'))
);

// 复杂的数据聚合
const analyzeSalesData = R.pipe(
// 按产品分组
R.groupBy(R.prop('productId')),

// 计算每个产品的统计信息
R.map(R.pipe(
R.project(['quantity', 'price', 'date']),
(items) => ({
totalQuantity: R.sum(R.pluck('quantity')(items)),
totalRevenue: R.sum(
R.map((item) => item.quantity * item.price)(items)
),
averagePrice: R.mean(R.pluck('price')(items)),
transactionCount: items.length
})
)),

// 转换为数组并排序
R.toPairs,
R.map(([productId, stats]) => ({ productId, ...stats })),
R.sortBy(R.prop('totalRevenue')),
R.reverse
);

// 使用
const salesData = [
{ productId: 'A', quantity: 10, price: 100, date: '2024-01-01' },
{ productId: 'B', quantity: 5, price: 200, date: '2024-01-02' },
{ productId: 'A', quantity: 8, price: 110, date: '2024-01-03' },
{ productId: 'B', quantity: 3, price: 190, date: '2024-01-04' }
];

const analysis = analyzeSalesData(salesData);
// [
// { productId: 'A', totalQuantity: 18, totalRevenue: 1880, averagePrice: 105, transactionCount: 2 },
// { productId: 'B', totalQuantity: 8, totalRevenue: 1570, averagePrice: 195, transactionCount: 2 }
// ]

表单验证

函数式编程非常适合表单验证场景:验证规则是纯函数,可以自由组合和复用。

组合式验证器

// 基础验证器工厂函数
const required = (message = '此字段为必填项') => (value) => {
if (value === null || value === undefined || value === '') {
return message;
}
return null;
};

const minLength = (min, message) => (value) => {
if (value && value.length < min) {
return message || `长度至少需要 ${min} 个字符`;
}
return null;
};

const maxLength = (max, message) => (value) => {
if (value && value.length > max) {
return message || `长度不能超过 ${max} 个字符`;
}
return null;
};

const pattern = (regex, message) => (value) => {
if (value && !regex.test(value)) {
return message || '格式不正确';
}
return null;
};

const email = (message = '请输入有效的邮箱地址') =>
pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, message);

const range = (min, max, message) => (value) => {
const num = Number(value);
if (isNaN(num) || num < min || num > max) {
return message || `值必须在 ${min}${max} 之间`;
}
return null;
};

// 组合验证器
const composeValidators = (...validators) => (value) => {
for (const validate of validators) {
const error = validate(value);
if (error) return error;
}
return null;
};

// 异步验证器
const asyncValidator = (checkFn, message) => async (value) => {
if (!value) return null;

try {
const isValid = await checkFn(value);
return isValid ? null : message;
} catch {
return '验证失败,请稍后重试';
}
};

// 使用示例
const validateUsername = composeValidators(
required('请输入用户名'),
minLength(3, '用户名至少 3 个字符'),
maxLength(20, '用户名不能超过 20 个字符'),
pattern(/^[a-zA-Z0-9_]+$/, '用户名只能包含字母、数字和下划线')
);

const validatePassword = composeValidators(
required('请输入密码'),
minLength(8, '密码至少 8 个字符'),
pattern(/[A-Z]/, '密码必须包含至少一个大写字母'),
pattern(/[a-z]/, '密码必须包含至少一个小写字母'),
pattern(/[0-9]/, '密码必须包含至少一个数字')
);

const validateAge = composeValidators(
required('请输入年龄'),
range(1, 150, '请输入有效的年龄')
);

// 表单级验证
const validateForm = (values) => {
const errors = {};

errors.username = validateUsername(values.username);
errors.email = composeValidators(required(), email())(values.email);
errors.password = validatePassword(values.password);
errors.age = validateAge(values.age);

// 自定义跨字段验证
if (values.password !== values.confirmPassword) {
errors.confirmPassword = '两次输入的密码不一致';
}

// 返回验证结果
const isValid = Object.values(errors).every(error => error === null);

return { isValid, errors };
};

// 使用
const formValues = {
username: 'ab',
email: 'invalid-email',
password: 'weak',
age: 200,
confirmPassword: 'different'
};

const { isValid, errors } = validateForm(formValues);
console.log(errors);
// {
// username: '用户名至少 3 个字符',
// email: '请输入有效的邮箱地址',
// password: '密码至少 8 个字符',
// age: '请输入有效的年龄',
// confirmPassword: '两次输入的密码不一致'
// }

异步流程控制

函数式编程可以优雅地处理异步操作,通过 Promise 和 async/await 的组合,实现清晰的异步流程控制。

Promise 链式调用

// 纯函数包装异步操作
const fetchJSON = (url, options = {}) =>
fetch(url, options).then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
});

// 组合多个异步操作
const fetchUser = (id) => fetchJSON(`/api/users/${id}`);
const fetchUserPosts = (userId) => fetchJSON(`/api/posts?userId=${userId}`);
const fetchPostComments = (postId) => fetchJSON(`/api/comments?postId=${postId}`);

// 链式调用
const getUserWithPostsAndComments = (userId) =>
fetchUser(userId)
.then(user =>
fetchUserPosts(user.id)
.then(posts => ({
...user,
posts: posts.map(post => ({ ...post, comments: [] }))
}))
);

// 使用 async/await 更清晰
const getUserWithDetails = async (userId) => {
const user = await fetchUser(userId);
const posts = await fetchUserPosts(user.id);

// 并行获取所有文章的评论
const postsWithComments = await Promise.all(
posts.map(async (post) => {
const comments = await fetchPostComments(post.id);
return { ...post, comments };
})
);

return {
...user,
posts: postsWithComments
};
};

// 重试逻辑
const withRetry = (fn, maxRetries = 3, delay = 1000) => {
return async (...args) => {
let lastError;

for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn(...args);
} catch (error) {
lastError = error;

if (attempt < maxRetries) {
console.log(`${attempt} 次尝试失败,${delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2; // 指数退避
}
}
}

throw lastError;
};
};

// 超时控制
const withTimeout = (fn, timeoutMs) => {
return async (...args) => {
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), timeoutMs)
);

return Promise.race([
fn(...args),
timeoutPromise
]);
};
};

// 使用
const fetchWithRetry = withRetry(fetchJSON, 3, 1000);
const fetchWithTimeout = withTimeout(fetchJSON, 5000);

使用 RxJS 处理复杂异步流

对于更复杂的异步场景,RxJS 提供了强大的响应式编程能力。

import { fromEvent, from, of } from 'rxjs';
import {
debounceTime,
distinctUntilChanged,
switchMap,
catchError,
map,
filter
} from 'rxjs/operators';

// 搜索输入流
const searchInput = document.getElementById('search');

const search$ = fromEvent(searchInput, 'input').pipe(
// 获取输入值
map(event => event.target.value),

// 过滤空值
filter(query => query.trim().length >= 2),

// 防抖:300ms 内无新输入才触发
debounceTime(300),

// 去重:相同查询不重复请求
distinctUntilChanged(),

// 切换到最新请求,自动取消之前的请求
switchMap(query =>
from(fetchJSON(`/api/search?q=${encodeURIComponent(query)}`)).pipe(
// 错误处理:返回空结果而不是中断流
catchError(error => {
console.error('搜索失败:', error);
return of({ results: [] });
})
)
)
);

// 订阅结果
search$.subscribe({
next: (response) => {
renderSearchResults(response.results);
},
error: (error) => {
console.error('流错误:', error);
}
});

错误处理

函数式编程提供了一种优雅的错误处理方式,通过 Maybe 和 Either 等类型来避免异常抛出,让错误处理成为程序逻辑的一部分。

函数式错误处理

// Result 类型:成功或失败
const Result = {
ok: (value) => ({ ok: true, value }),
err: (error) => ({ ok: false, error }),

// 从可能抛出异常的函数创建
fromTry: (fn) => {
try {
return Result.ok(fn());
} catch (error) {
return Result.err(error);
}
},

// 从 Promise 创建
fromPromise: async (promise) => {
try {
const value = await promise;
return Result.ok(value);
} catch (error) {
return Result.err(error);
}
},

// 链式操作
map: (result, fn) =>
result.ok ? Result.fromTry(() => fn(result.value)) : result,

// 链式操作(flatMap)
andThen: (result, fn) =>
result.ok ? fn(result.value) : result,

// 获取值或默认值
unwrapOr: (result, defaultValue) =>
result.ok ? result.value : defaultValue,

// 模式匹配
match: (result, { ok: onOk, err: onErr }) =>
result.ok ? onOk(result.value) : onErr(result.error)
};

// 使用示例
const parseJSON = (str) => Result.fromTry(() => JSON.parse(str));
const safeDivide = (a, b) =>
b === 0 ? Result.err(new Error('除数不能为零')) : Result.ok(a / b);

// 链式操作
const calculate = (jsonStr) =>
parseJSON(jsonStr)
.andThen(data =>
data.x && data.y
? safeDivide(data.x, data.y)
: Result.err(new Error('缺少必要字段'))
);

// 处理结果
const result = calculate('{"x": 10, "y": 2}');
Result.match(result, {
ok: (value) => console.log(`结果: ${value}`), // 结果: 5
err: (error) => console.error(`错误: ${error.message}`)
});

实际应用:API 请求封装

// API 请求封装
const api = {
get: (endpoint) =>
Result.fromPromise(
fetch(`/api${endpoint}`).then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
})
),

post: (endpoint, data) =>
Result.fromPromise(
fetch(`/api${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
})
)
};

// 使用
const fetchUser = async (id) => {
const result = await api.get(`/users/${id}`);

return Result.match(result, {
ok: (user) => ({ found: true, user }),
err: (error) => ({ found: false, error: error.message })
});
};

测试

纯函数天生易于测试。没有外部依赖,没有副作用,测试只需要关注输入和输出。

纯函数测试

// 纯函数测试示例
describe('calculateTotal', () => {
it('should calculate total with tax', () => {
expect(calculateTotal(100, 0.1)).toBe(110);
expect(calculateTotal(50, 0.2)).toBe(60);
expect(calculateTotal(0, 0.1)).toBe(0);
});

it('should handle edge cases', () => {
expect(calculateTotal(-100, 0.1)).toBe(-110);
expect(calculateTotal(100, 0)).toBe(100);
expect(calculateTotal(100, 1)).toBe(200);
});
});

describe('formatUserName', () => {
it('should format user name correctly', () => {
expect(formatUserName({ firstName: '张', lastName: '三' })).toBe('张三');
expect(formatUserName({ firstName: 'John', lastName: 'Doe' })).toBe('John Doe');
});
});

describe('validateEmail', () => {
it('should validate email format', () => {
expect(validateEmail('[email protected]')).toBeNull();
expect(validateEmail('invalid')).toBe('请输入有效的邮箱地址');
expect(validateEmail('')).toBe('此字段为必填项');
});
});

测试高阶函数

describe('memoize', () => {
it('should cache results', () => {
let callCount = 0;
const expensiveFn = (x) => {
callCount++;
return x * 2;
};

const memoized = memoize(expensiveFn);

expect(memoized(5)).toBe(10);
expect(callCount).toBe(1);

expect(memoized(5)).toBe(10);
expect(callCount).toBe(1); // 未增加,使用了缓存

expect(memoized(10)).toBe(20);
expect(callCount).toBe(2); // 新参数,重新计算
});
});

describe('curry', () => {
it('should curry functions', () => {
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);

expect(curriedAdd(1)(2)(3)).toBe(6);
expect(curriedAdd(1, 2)(3)).toBe(6);
expect(curriedAdd(1)(2, 3)).toBe(6);
});
});

最佳实践总结

1. 分离纯函数和不纯函数

将核心业务逻辑放在纯函数中,将副作用(I/O、网络请求、DOM 操作)隔离在边界处。

// 核心逻辑:纯函数
const processData = (data) => data
.filter(item => item.active)
.map(transformItem)
.reduce(aggregate, {});

// 边界:副作用
const main = async () => {
const data = await fetchData(); // 副作用:网络请求
const result = processData(data); // 纯函数处理
renderUI(result); // 副作用:DOM 操作
};

2. 使用不可变数据

永远不要直接修改传入的参数,而是返回新的数据结构。

// 不好:直接修改参数
const addItem = (arr, item) => {
arr.push(item);
return arr;
};

// 好:返回新数组
const addItem = (arr, item) => [...arr, item];

3. 组合而非继承

通过函数组合构建复杂功能,而不是通过类继承。

// 小函数
const filterActive = (users) => users.filter(u => u.active);
const sortByAge = (users) => [...users].sort((a, b) => a.age - b.age);
const getNames = (users) => users.map(u => u.name);

// 组合
const getActiveUserNamesByAge = (users) =>
getNames(sortByAge(filterActive(users)));

4. 合理使用柯里化和偏应用

创建可复用的特定函数,提高代码复用率。

// 通用函数
const filterBy = (key, value, arr) => arr.filter(item => item[key] === value);

// 柯里化后创建特定函数
const getActiveUsers = filterBy('active', true);
const getAdminUsers = filterBy('role', 'admin');

5. 利用类型系统

使用 TypeScript 添加类型约束,提高代码可靠性。

// 类型定义
type User = {
id: number;
name: string;
email: string;
active: boolean;
};

type ValidationResult = {
isValid: boolean;
errors: Record<string, string>;
};

// 类型安全的函数
const validateUser = (user: Partial<User>): ValidationResult => {
const errors: Record<string, string> = {};

if (!user.name) errors.name = '姓名不能为空';
if (!user.email) errors.email = '邮箱不能为空';

return {
isValid: Object.keys(errors).length === 0,
errors
};
};

函数式编程不是一种银弹,而是一种思维方式和编程风格。在实际项目中,我们应该根据场景灵活运用:核心业务逻辑优先使用纯函数和不可变数据,在边界处理副作用,通过组合构建复杂功能。这样既能享受函数式编程带来的可靠性和可维护性,又能保持代码的实用性。

参考资源