Skip to content

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> = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#39;'
      }
      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>&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</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' }

注意事项

  1. 参数绑定: 部分参数会覆盖后续传入的同名参数
  2. 类型安全: 支持TypeScript类型推断
  3. 函数组合: 可以与柯里化函数组合使用
  4. 不可变性: 原函数不会被修改
  5. 性能: 适合需要重复使用相同参数的场景

与其他方法的区别

  • curry(): 柯里化函数,按顺序绑定参数
  • partob(): 按对象属性名绑定参数
  • partial(): 按位置绑定参数
  • bind(): 原生JavaScript方法,按位置绑定参数

实际应用场景

  1. 配置管理: 预设常用配置参数
  2. API封装: 预设请求头和认证信息
  3. 数据转换: 预设转换规则
  4. 事件处理: 预设事件处理选项
  5. 工具函数: 创建特定用途的函数变体

Released under the MIT License.