Parsers

自定义解析器

为自定义数据类型和美观 URL 创建自己的解析器

您可能希望自定义数据类型的渲染查询字符串。 为此,nuqs 提供了 createParser 函数来创建自己的解析器。

您需要传递两个函数:

  1. parse:一个函数,它接受一个字符串并返回解析后的值,如果无效则返回 null
  2. serialize:一个函数,它接受解析后的值并返回一个字符串。
import { createParser } from 'nuqs'

const parseAsStarRating = createParser({
  parse(queryValue) {
    const inBetween = queryValue.split('★')
    const isValid = inBetween.length > 1 && inBetween.every(s => s === '')
    if (!isValid) return null
    const numStars = inBetween.length - 1
    return Math.min(5, numStars)
  },
  serialize(value) {
    return Array.from({length: value}, () => '★').join('')
  }
})

等式函数

对于无法通过 === 操作符比较的状态类型,您还需要提供一个 eq 函数:


// Eg: TanStack Table sorting state
// /?sort=foo:asc → { id: 'foo', desc: false }
const parseAsSort = createParser({
  parse(query) {
    const [key = '', direction = ''] = query.split(':')
    const desc = parseAsStringLiteral(['asc', 'desc']).parse(direction) ?? 'asc'
    return {
      id: key,
      desc: desc === 'desc'
    }
  },
  serialize(value) {
    return `${value.id}:${value.desc ? 'desc' : 'asc'}`
  },
  eq(a, b) {
    return a.id === b.id && a.desc === b.desc
  }
})

此函数用于 clearOnDefault 选项, 以检查当前值是否等于默认值。

自定义多值解析器

我们之前看到的解析器是 SingleParsers:它们操作 URL 中键的第一个出现位置,并且在可用时为您提供一个字符串值来解析。

MultiParsers 的工作方式类似于 SingleParsers,但它们操作数组,以支持键重复

/?tag=type-safe&tag=url-state&tag=react

这意味着:

  1. parse 接受一个 Array<string>。它接收操作键的所有匹配值,并返回解析后的值,如果无效则返回 null
  2. serialize 接受解析后的值并返回一个 Array<string>,其中每个项目将单独添加到 URL 中。

然后,您可以将此数组组合并归约以形成复杂数据类型

/**
 * 100~200 <=> { gte: 100, lte: 200 }
 * 150     <=> { eq: 150 }
 */
const parseAsFromTo = createParser({
  parse: value => {
    const [min = null, max = null] = value.split('~').map(parseAsInteger.parse)
    if (min === null) return null
    if (max === null) return { eq: min }
    return { gte: min, lte: max }
  },
  serialize: value => {
    return value.eq !== undefined ? String(value.eq) : `${value.gte}~${value.lte}`
  }
})

/**
 * foo:bar <=> { key: 'foo', value: 'bar' }
 */
const parseAsKeyValue = createParser({
  parse: value => {
    const [key, val] = value.split(':')
    if (!key || !val) return null
    return { key, value: val }
  },
  serialize: value => {
    return `${value.key}:${value.value}`
  }
})

const parseAsFilters = <TItem extends {}>(itemParser: SingleParser<TItem>) => {
  return createMultiParser({
    parse: values => {
      const keyValue = values.map(parseAsKeyValue.parse).filter(v => v !== null)

      const result = Object.fromEntries(
        keyValue.flatMap(({ key, value }) => {
          const parsedValue: TItem | null = itemParser.parse(value)
          return parsedValue === null ? [] : [[key, parsedValue]]
        })
      )

      return Object.keys(result).length === 0 ? null : result
    },
    serialize: values => {
      return Object.entries(values).map(([key, value]) => {
        if (!itemParser.serialize) return null
        return parseAsKeyValue.serialize({ key, value: itemParser.serialize(value) })
      }).filter(v => v !== null)
    }
  })
}

const [filters, setFilters] = useQueryState(
  'filters',
  parseAsFilters(parseAsFromTo).withDefault({})
)

注意事项:有损序列化器

如果您的序列化器丢失精度或无法准确表示底层状态值,那么在重新加载页面或从 URL 恢复状态(例如:在导航时)时,您将丢失此精度。

示例:

const geoCoordParser = {
  parse: parseFloat,
  serialize: v => v.toFixed(4) // Loses precision
}

const [lat, setLat] = useQueryState('lat', geoCoordParser)

在这里,将纬度设置为 1.23456789 将渲染 URL 查询字符串 为 lat=1.2345,而内部 lat 状态将正确设置为 1.23456789。

重新加载页面后,状态将错误地设置为 1.2345。