Skip to content

React 场景(Solutions for React)

针对 React(Hooks)业务的复杂方案,结合 nex-lib 在函数组件中的使用。

搜索框(防抖 + 超时 + 重试)

jsx
import { useState } from 'react'
import { debounce, Http } from 'nex-lib'
export default function Search() {
  const [q, setQ] = useState('')
  const run = debounce(async (kw) => {
    const data = await Http.get('/api/search', { query: { q: kw }, timeoutMs: 3000, retry: { times: 3, delay: 200 } })
    // setList(data)
  }, 300)
  return <input value={q} onChange={e => { setQ(e.target.value); run(e.target.value) }} />
}
tsx
import { useState } from 'react'
import { debounce, Http } from 'nex-lib'
export default function Search() {
  const [q, setQ] = useState('')
  const run = debounce(async (kw: string) => {
    const data = await Http.get('/api/search', { query: { q: kw }, timeoutMs: 3000, retry: { times: 3, delay: 200 } })
    // setList(data)
  }, 300)
  return <input value={q} onChange={e => { setQ(e.target.value); run(e.target.value) }} />
}

倒计时(TTL + 校时)

jsx
import { useEffect, useState } from 'react'
import { nextAt, nowSeconds, formatDuration, Http, StorageUtils } from 'nex-lib'
export default function Countdown() {
  const [remain, setRemain] = useState('00:00:00')
  // 目标:三天后 18:00(本地时区)
  const cached = StorageUtils.getWithTTL('holiday_target')
  const computedTarget = nextAt(3, 18)
  const target = typeof cached === 'number' ? cached : computedTarget
  // 缓存 24 小时,刷新页面仍能继续倒计时
  StorageUtils.setWithTTL('holiday_target', target, 24*60*60*1000)
  useEffect(() => {
    let t
    async function tick() {
      // 校时:优先服务端时间,失败回退本地时间
      const now = await Http.get('/api/time', { timeoutMs: 3000, retry: 2 })
        .then((r) => r.now).catch(() => nowSeconds())
      // 格式化显示,避免出现负数
      setRemain(formatDuration(Math.max(0, (target - now) * 1000)))
    }
    // 每秒刷新一次
    t = setInterval(tick, 1000)
    return () => clearInterval(t)
  }, [])
  return <span>{remain}</span>
}
tsx
import { useEffect, useState } from 'react'
import { nextAt, nowSeconds, formatDuration, Http, StorageUtils } from 'nex-lib'
export default function Countdown() {
  const [remain, setRemain] = useState('00:00:00')
  // 目标:三天后 18:00(本地时区)
  const cached = StorageUtils.getWithTTL('holiday_target')
  const computedTarget = nextAt(3, 18)
  const target = typeof cached === 'number' ? cached : computedTarget
  // 缓存 24 小时,刷新页面仍能继续倒计时
  StorageUtils.setWithTTL('holiday_target', target, 24*60*60*1000)
  useEffect(() => {
    let t: any
    async function tick() {
      // 校时:优先服务端时间,失败回退本地时间
      const now = await Http.get('/api/time', { timeoutMs: 3000, retry: 2 })
        .then((r: any) => r.now).catch(() => nowSeconds())
      // 格式化显示,避免出现负数
      setRemain(formatDuration(Math.max(0, (target - now) * 1000)))
    }
    // 每秒刷新一次
    t = setInterval(tick, 1000)
    return () => clearInterval(t)
  }, [])
  return <span>{remain}</span>
}

参数策略与分享

jsx
import { createWURL, StorageUtils, browserUtils } from 'nex-lib'
export function Share({ sku }) {
  const w = createWURL(location.href)
  const merged = w.replaceParams({ ...w.parseQueryParams(), utm_source: 'web', sku })
  async function copy() {
    if (!w.isSameOrigin(merged)) throw new Error('unsafe')
    StorageUtils.setWithTTL('utm', merged, 7*24*60*60*1000)
    await browserUtils.copyToClipboard(merged)
  }
  return <button onClick={copy}>Copy</button>
}
tsx
import { createWURL, StorageUtils, browserUtils } from 'nex-lib'
export function Share({ sku }: { sku: string }) {
  const w = createWURL(location.href)
  const merged = w.replaceParams({ ...w.parseQueryParams(), utm_source: 'web', sku })
  async function copy() {
    if (!w.isSameOrigin(merged)) throw new Error('unsafe')
    StorageUtils.setWithTTL('utm', merged, 7*24*60*60*1000)
    await browserUtils.copyToClipboard(merged)
  }
  return <button onClick={copy}>Copy</button>
}

主题色(CSS 变量)

jsx
import { useEffect } from 'react'
import { ColorUtils } from 'nex-lib'
export default function Theme() {
  useEffect(() => {
    const brand = '#ffcc00'
    const btnBg = ColorUtils.darken(brand, .1)
    const blockBg = ColorUtils.lighten(brand, .15)
    const textColor = ColorUtils.contrastColor(btnBg)
    const root = document.documentElement
    root.style.setProperty('--btn-bg', btnBg)
    root.style.setProperty('--block-bg', blockBg)
    root.style.setProperty('--text', textColor)
  }, [])
  return null
}
tsx
import { useEffect } from 'react'
import { ColorUtils } from 'nex-lib'
export default function Theme() {
  useEffect(() => {
    const brand = '#ffcc00'
    const btnBg = ColorUtils.darken(brand, .1)
    const blockBg = ColorUtils.lighten(brand, .15)
    const textColor = ColorUtils.contrastColor(btnBg)
    const root = document.documentElement
    root.style.setProperty('--btn-bg', btnBg)
    root.style.setProperty('--block-bg', blockBg)
    root.style.setProperty('--text', textColor)
  }, [])
  return null
}

国际化(IP/语言/时区)

I18n Highlights

  • 自动 Locale 与语言/国家英文名
  • 本地时区与 UTC 偏移
  • 一行获取公共 IP(带超时与重试)
  • 示例展示统一 JSON 输出
jsx
import { useEffect, useState } from 'react'
import { browserUtils, TimeUtils } from 'nex-lib'
export default function I18nInfo() {
  const [info, setInfo] = useState({ ip: '-', locale: '', language: '', country: '', timezone: '', offsetMin: 0, languages: '' })
  useEffect(() => {
    (async () => {
      // 浏览器首选语言(规范化),以及语言/国家英文名
      const locale = browserUtils.getPreferredLocale()
      const li = browserUtils.getLocaleInfo(locale)
      // 本地时区与与 UTC 的分钟偏移
      const timezone = TimeUtils.getTimezone()
      const offsetMin = TimeUtils.getTimezoneOffsetMinutes()
      const ip = await browserUtils.getPublicIP()
      setInfo({ ip, locale: li.code, language: li.language.name, country: li.country.name || '', timezone, offsetMin, languages: browserUtils.getLanguage() })
    })()
  }, [])
  return <pre>{JSON.stringify(info, null, 2)}</pre>
}
tsx
import { useEffect, useState } from 'react'
import { browserUtils, TimeUtils } from 'nex-lib'
type Info = { ip: string; locale: string; language: string; country: string; timezone: string; offsetMin: number; languages: string }
export default function I18nInfo() {
  const [info, setInfo] = useState<Info>({ ip: '-', locale: '', language: '', country: '', timezone: '', offsetMin: 0, languages: '' })
  useEffect(() => {
    (async () => {
      const locale = browserUtils.getPreferredLocale()
      const li = browserUtils.getLocaleInfo(locale)
      const timezone = TimeUtils.getTimezone()
      const offsetMin = TimeUtils.getTimezoneOffsetMinutes()
      const ip = await browserUtils.getPublicIP()
      setInfo({ ip, locale: li.code, language: li.language.name, country: li.country.name || '', timezone, offsetMin, languages: browserUtils.getLanguage() })
    })()
  }, [])
  return <pre>{JSON.stringify(info, null, 2)}</pre>
}

Released under the ISC License.