partob
將函數的部分參數轉換為對象參數,支持參數綁定和柯裡化。
基礎用法
typescript
import { partob } from 'radash'
const add = (a: number, b: number) => a + b
const addWithObj = partob(add, { a: 5 })
console.log(addWithObj({ b: 3 })) // 8
語法
typescript
function partob<T extends any[], U extends Record<string, any>, R>(
fn: (...args: T) => R,
partial: Partial<U>
): (remaining: Partial<U>) => R
參數
fn
(function): 要部分應用的函數partial
(object): 部分參數對象
返回值
返回一個新函數,接受剩余的參數對象。
示例
基本用法
typescript
import { partob } from 'radash'
const multiply = (a: number, b: number) => a * b
const multiplyBy2 = partob(multiply, { a: 2 })
console.log(multiplyBy2({ b: 5 })) // 10
console.log(multiplyBy2({ b: 10 })) // 20
處理多個參數
typescript
import { partob } from 'radash'
const createUser = (name: string, age: number, email: string, city: string) => ({
name,
age,
email,
city
})
const createUserWithDefaults = partob(createUser, {
name: 'Anonymous',
age: 25
})
const user1 = createUserWithDefaults({
email: 'user1@example.com',
city: 'Beijing'
})
console.log(user1) // { name: 'Anonymous', age: 25, email: 'user1@example.com', city: 'Beijing' }
const user2 = createUserWithDefaults({
email: 'user2@example.com',
city: 'Shanghai'
})
console.log(user2) // { name: 'Anonymous', age: 25, email: 'user2@example.com', city: 'Shanghai' }
處理異步函數
typescript
import { partob } from 'radash'
const fetchData = async (url: string, options: { method: string, headers?: Record<string, string> }) => {
const response = await fetch(url, options)
return response.json()
}
const fetchWithGet = partob(fetchData, {
options: { method: 'GET' }
})
// 使用部分應用的函數
const data = await fetchWithGet({
url: 'https://api.example.com/users'
})
處理配置對象
typescript
import { partob } from 'radash'
const sendEmail = (to: string, subject: string, body: string, config: {
from: string,
cc?: string[],
bcc?: string[],
priority?: 'low' | 'normal' | 'high'
}) => {
return `Sending email to ${to} with subject "${subject}"`
}
const sendHighPriorityEmail = partob(sendEmail, {
config: {
from: 'noreply@company.com',
priority: 'high'
}
})
const result = sendHighPriorityEmail({
to: 'user@example.com',
subject: 'Important Update',
body: 'Please check this important update.'
})
console.log(result) // "Sending email to user@example.com with subject "Important Update""
處理數據庫查詢
typescript
import { partob } from 'radash'
const queryDatabase = (table: string, conditions: Record<string, any>, options: {
limit?: number,
offset?: number,
orderBy?: string,
select?: string[]
}) => {
return `SELECT ${options.select?.join(', ') || '*'} FROM ${table} WHERE ${Object.entries(conditions).map(([k, v]) => `${k} = ${v}`).join(' AND ')}`
}
const queryUsers = partob(queryDatabase, {
table: 'users',
options: {
select: ['id', 'name', 'email'],
limit: 10
}
})
const query = queryUsers({
conditions: { status: 'active', age: 25 }
})
console.log(query) // "SELECT id, name, email FROM users WHERE status = active AND age = 25"
處理API請求
typescript
import { partob } from 'radash'
const apiRequest = (endpoint: string, data: any, config: {
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
headers?: Record<string, string>,
timeout?: number
}) => {
return `Making ${config.method} request to ${endpoint}`
}
const postRequest = partob(apiRequest, {
config: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
timeout: 5000
}
})
const result = postRequest({
endpoint: '/api/users',
data: { name: 'Alice', email: 'alice@example.com' }
})
console.log(result) // "Making POST request to /api/users"
處理表單驗證
typescript
import { partob } from 'radash'
const validateField = (value: any, rules: {
required?: boolean,
minLength?: number,
maxLength?: number,
pattern?: RegExp,
custom?: (value: any) => boolean
}) => {
const errors: string[] = []
if (rules.required && !value) {
errors.push('Field is required')
}
if (rules.minLength && value && value.length < rules.minLength) {
errors.push(`Minimum length is ${rules.minLength}`)
}
if (rules.maxLength && value && value.length > rules.maxLength) {
errors.push(`Maximum length is ${rules.maxLength}`)
}
if (rules.pattern && value && !rules.pattern.test(value)) {
errors.push('Invalid format')
}
if (rules.custom && !rules.custom(value)) {
errors.push('Custom validation failed')
}
return errors
}
const validateEmail = partob(validateField, {
rules: {
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
}
})
const validatePassword = partob(validateField, {
rules: {
required: true,
minLength: 8,
maxLength: 50
}
})
console.log(validateEmail('test@example.com')) // []
console.log(validateEmail('invalid-email')) // ['Invalid format']
console.log(validatePassword('123')) // ['Minimum length is 8']
console.log(validatePassword('securepassword123')) // []
處理日志記錄
typescript
import { partob } from 'radash'
const logMessage = (level: 'info' | 'warn' | 'error', message: string, context: {
userId?: number,
requestId?: string,
timestamp?: Date,
metadata?: Record<string, any>
}) => {
const logEntry = {
level,
message,
timestamp: context.timestamp || new Date(),
...context
}
console.log(JSON.stringify(logEntry))
return logEntry
}
const logError = partob(logMessage, {
level: 'error'
})
const logUserAction = partob(logMessage, {
level: 'info',
context: {
timestamp: new Date()
}
})
logError({
message: 'Database connection failed',
context: {
userId: 123,
requestId: 'req_456'
}
})
logUserAction({
message: 'User logged in',
context: {
userId: 123
}
})
處理事件處理
typescript
import { partob } from 'radash'
const handleEvent = (eventType: string, handler: (event: any) => void, options: {
once?: boolean,
capture?: boolean,
passive?: boolean
}) => {
return `Adding ${eventType} event listener with options: ${JSON.stringify(options)}`
}
const handleClick = partob(handleEvent, {
eventType: 'click',
options: {
capture: false,
passive: true
}
})
const handleKeydown = partob(handleEvent, {
eventType: 'keydown',
options: {
once: true
}
})
console.log(handleClick({ handler: () => console.log('clicked') }))
console.log(handleKeydown({ handler: () => console.log('key pressed') }))
處理文件操作
typescript
import { partob } from 'radash'
const readFile = (path: string, options: {
encoding?: string,
flag?: string,
signal?: AbortSignal
}) => {
return `Reading file: ${path} with encoding: ${options.encoding || 'utf8'}`
}
const readTextFile = partob(readFile, {
options: {
encoding: 'utf8',
flag: 'r'
}
})
const readBinaryFile = partob(readFile, {
options: {
encoding: 'binary',
flag: 'rb'
}
})
console.log(readTextFile({ path: '/path/to/text.txt' }))
console.log(readBinaryFile({ path: '/path/to/image.jpg' }))
處理數學計算
typescript
import { partob } from 'radash'
const calculate = (operation: 'add' | 'subtract' | 'multiply' | 'divide', a: number, b: number, options: {
precision?: number,
round?: boolean
}) => {
let result: number
switch (operation) {
case 'add':
result = a + b
break
case 'subtract':
result = a - b
break
case 'multiply':
result = a * b
break
case 'divide':
result = a / b
break
}
if (options.precision) {
result = Number(result.toFixed(options.precision))
}
if (options.round) {
result = Math.round(result)
}
return result
}
const addWithPrecision = partob(calculate, {
operation: 'add',
options: {
precision: 2
}
})
const multiplyWithRound = partob(calculate, {
operation: 'multiply',
options: {
round: true
}
})
console.log(addWithPrecision({ a: 3.14159, b: 2.71828 })) // 5.86
console.log(multiplyWithRound({ a: 3.7, b: 2.3 })) // 9
處理字符串操作
typescript
import { partob } from 'radash'
const formatString = (template: string, values: Record<string, any>, options: {
case?: 'lower' | 'upper' | 'title',
trim?: boolean,
escape?: boolean
}) => {
let result = template
// 替換模板變量
Object.entries(values).forEach(([key, value]) => {
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), String(value))
})
if (options.case === 'lower') {
result = result.toLowerCase()
} else if (options.case === 'upper') {
result = result.toUpperCase()
} else if (options.case === 'title') {
result = result.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase())
}
if (options.trim) {
result = result.trim()
}
if (options.escape) {
result = result.replace(/[&<>"']/g, (char) => {
const escapeMap: Record<string, string> = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}
return escapeMap[char] || char
})
}
return result
}
const formatTitle = partob(formatString, {
options: {
case: 'title',
trim: true
}
})
const formatHtml = partob(formatString, {
options: {
escape: true
}
})
console.log(formatTitle({
template: 'Hello {{name}}, welcome to {{company}}!',
values: { name: 'alice', company: 'tech corp' }
})) // "Hello Alice, Welcome To Tech Corp!"
console.log(formatHtml({
template: '<h1>{{title}}</h1><p>{{content}}</p>',
values: { title: 'My Title', content: '<script>alert("xss")</script>' }
})) // "<h1>My Title</h1><p><script>alert("xss")</script></p>"
處理數組操作
typescript
import { partob } from 'radash'
const processArray = (array: any[], operation: 'filter' | 'map' | 'reduce', options: {
predicate?: (item: any) => boolean,
transform?: (item: any) => any,
initialValue?: any,
limit?: number
}) => {
let result: any[] = array
if (operation === 'filter' && options.predicate) {
result = result.filter(options.predicate)
} else if (operation === 'map' && options.transform) {
result = result.map(options.transform)
} else if (operation === 'reduce') {
result = [result.reduce((acc, item) => {
if (options.transform) {
return options.transform(acc, item)
}
return acc + item
}, options.initialValue || 0)]
}
if (options.limit) {
result = result.slice(0, options.limit)
}
return result
}
const filterNumbers = partob(processArray, {
operation: 'filter',
options: {
predicate: (item: any) => typeof item === 'number' && item > 0
}
})
const mapToStrings = partob(processArray, {
operation: 'map',
options: {
transform: (item: any) => String(item)
}
})
console.log(filterNumbers({ array: [1, -2, 3, 0, 5, -1] })) // [1, 3, 5]
console.log(mapToStrings({ array: [1, 2, 3, 4, 5] })) // ['1', '2', '3', '4', '5']
處理對象操作
typescript
import { partob } from 'radash'
const transformObject = (obj: Record<string, any>, operation: 'pick' | 'omit' | 'map', options: {
keys?: string[],
transform?: (key: string, value: any) => [string, any]
}) => {
if (operation === 'pick' && options.keys) {
const result: Record<string, any> = {}
options.keys.forEach(key => {
if (key in obj) {
result[key] = obj[key]
}
})
return result
} else if (operation === 'omit' && options.keys) {
const result: Record<string, any> = {}
Object.keys(obj).forEach(key => {
if (!options.keys!.includes(key)) {
result[key] = obj[key]
}
})
return result
} else if (operation === 'map' && options.transform) {
const result: Record<string, any> = {}
Object.entries(obj).forEach(([key, value]) => {
const [newKey, newValue] = options.transform!(key, value)
result[newKey] = newValue
})
return result
}
return obj
}
const pickNameAndAge = partob(transformObject, {
operation: 'pick',
options: {
keys: ['name', 'age']
}
})
const omitId = partob(transformObject, {
operation: 'omit',
options: {
keys: ['id']
}
})
const toUpperCase = partob(transformObject, {
operation: 'map',
options: {
transform: (key: string, value: any) => [key.toUpperCase(), value]
}
})
const user = { id: 1, name: 'Alice', age: 25, email: 'alice@example.com' }
console.log(pickNameAndAge({ obj: user })) // { name: 'Alice', age: 25 }
console.log(omitId({ obj: user })) // { name: 'Alice', age: 25, email: 'alice@example.com' }
console.log(toUpperCase({ obj: user })) // { ID: 1, NAME: 'Alice', AGE: 25, EMAIL: 'alice@example.com' }
注意事項
- 參數綁定: 部分參數會覆蓋後續傳入的同名參數
- 類型安全: 支持TypeScript類型推斷
- 函數組合: 可以與柯裡化函數組合使用
- 不可變性: 原函數不會被修改
- 性能: 適合需要重復使用相同參數的場景
與其他方法的區別
curry()
: 柯裡化函數,按順序綁定參數partob()
: 按對象屬性名綁定參數partial()
: 按位置綁定參數bind()
: 原生JavaScript方法,按位置綁定參數
實際應用場景
- 配置管理: 預設常用配置參數
- API封裝: 預設請求頭和認證信息
- 數據轉換: 預設轉換規則
- 事件處理: 預設事件處理選項
- 工具函數: 創建特定用途的函數變體