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>
}