useRefState

package version >0.2.0

shadcn any version

author: cmtlyt

update time: 2026/04/07 11:16:55

一个结合了 refstate 特性的 React Hook,提供灵活的状态管理能力。状态存储在 ref 中,避免了闭包陷阱问题,同时支持选择性地触发组件重新渲染。

特性

  • 避免闭包陷阱:状态存储在 ref 中,始终能获取最新值
  • 灵活的渲染控制:可以选择是否触发组件重新渲染
  • 完整的控制 API:提供多种状态操作方法
  • 深拷贝支持:自动进行状态深拷贝,确保数据隔离
  • 类型安全:完整的 TypeScript 类型支持

安装

npm
npm i @cmtlyt/lingshu-toolkit
shadcn
npx shadcn@latest add https://cmtlyt.github.io/lingshu-toolkit/r/reactUseRefState.json

用法

import { useRefState } from '@cmtlyt/lingshu-toolkit/react'
// or
import { useRefState } from '@cmtlyt/lingshu-toolkit/react/use-ref-state'

基础示例

简单状态管理

import { useRefState } from '@cmtlyt/lingshu-toolkit/react'

function Counter() {
  const [count, { setState, getState, forceUpdate }] = useRefState(0)

  const increment = () => {
    setState(getState() + 1)
  }

  const double = () => {
    setState(getState() * 2)
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={double}>×2</button>
    </div>
  )
}

对象状态管理

import { useRefState } from '@cmtlyt/lingshu-toolkit/react'

interface UserProfile {
  name: string
  age: number
  email: string
}

function UserProfileForm() {
  const [profile, { patchState, reset }] = useRefState<UserProfile>({
    name: '',
    age: 0,
    email: '',
  })

  const updateName = (name: string) => {
    patchState((draft) => {
      draft.name = name
    })
  }

  const handleSubmit = () => {
    console.log('Submitting:', profile)
    // 提交逻辑...
  }

  return (
    <form>
      <input
        value={profile.name}
        onChange={(e) => updateName(e.target.value)}
        placeholder="Name"
      />
      <input
        value={profile.age}
        onChange={(e) => patchState((draft) => { draft.age = Number(e.target.value) })}
        type="number"
        placeholder="Age"
      />
      <button type="button" onClick={handleSubmit}>Submit</button>
      <button type="button" onClick={() => reset()}>Reset</button>
    </form>
  )
}

高级示例

不触发渲染的状态更新

import { useRefState } from '@cmtlyt/lingshu-toolkit/react'

function DataProcessor() {
  const [data, { setState, patchState, forceUpdate }] = useRefState<number[]>([])

  // 批量添加数据,不触发渲染
  const batchAdd = (items: number[]) => {
    items.forEach((item) => {
      patchState((draft) => {
        draft.push(item)
      }, false) // 第二个参数 false 表示不触发渲染
    })
    // 所有操作完成后手动触发一次渲染
    forceUpdate()
  }

  return (
    <div>
      <p>Data count: {data.length}</p>
      <button onClick={() => batchAdd([1, 2, 3, 4, 5])}>
        Batch Add
      </button>
    </div>
  )
}

在事件处理器中使用

import { useRefState } from '@cmtlyt/lingshu-toolkit/react'

function Timer() {
  const [timer, { setState, getState }] = useRefState(0)

  const startTimer = () => {
    const interval = setInterval(() => {
      const current = getState()
      setState(current + 1)
    }, 1000)

    // 清理定时器
    return () => clearInterval(interval)
  }

  return (
    <div>
      <p>Timer: {timer}s</p>
      <button onClick={startTimer}>Start</button>
    </div>
  )
}

复杂状态管理

import { useRefState } from '@cmtlyt/lingshu-toolkit/react'

interface Todo {
  id: number
  text: string
  completed: boolean
}

function TodoList() {
  const [todos, { patchState, setState, reset }] = useRefState<Todo[]>([])

  const addTodo = (text: string) => {
    patchState((draft) => {
      draft.push({
        id: Date.now(),
        text,
        completed: false,
      })
    })
  }

  const toggleTodo = (id: number) => {
    patchState((draft) => {
      const todo = draft.find((t) => t.id === id)
      if (todo) {
        todo.completed = !todo.completed
      }
    })
  }

  const clearCompleted = () => {
    patchState((draft) => {
      const index = draft.findIndex((t) => t.completed)
      if (index !== -1) {
        draft.splice(index, 1)
      }
    })
  }

  return (
    <div>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
          </li>
        ))}
      </ul>
      <button onClick={() => addTodo('New todo')}>Add Todo</button>
      <button onClick={clearCompleted}>Clear Completed</button>
      <button onClick={() => reset()}>Reset</button>
    </div>
  )
}

API

useRefState

function useRefState<T>(initialState: T): [T, UseRefStateCtrl<T>]

参数

  • initialState: T - 初始状态值

返回值

返回一个元组:

  • [0]: T - 当前状态值
  • [1]: UseRefStateCtrl<T> - 控制对象

UseRefStateCtrl

控制对象包含以下方法:

patchState

patchState: (updater: (draft: T) => void, update?: boolean) => void

更新状态的局部字段。

参数:

  • updater: (draft: T) => void - 更新函数,直接修改 draft 对象
  • update?: boolean - 是否触发组件重新渲染,默认为 true

示例:

const [user, { patchState }] = useRefState({ name: '', age: 0 })
patchState((draft) => {
  draft.name = 'John'
  draft.age = 25
})

forceUpdate

forceUpdate: () => void

强制组件重新渲染。

示例:

const [, { forceUpdate }] = useRefState(0)
forceUpdate()

getState

getState: () => T

获取当前状态的最新值。

返回值: 当前状态值

示例:

const [, { getState }] = useRefState(0)
const currentValue = getState()

setState

setState: (state: T, update?: boolean) => void

设置新的状态值。

参数:

  • state: T - 新的状态值
  • update?: boolean - 是否触发组件重新渲染,默认为 true

示例:

const [count, { setState }] = useRefState(0)
setState(10)

reset

reset: (update?: boolean) => void

重置状态为初始值。

参数:

  • update?: boolean - 是否触发组件重新渲染,默认为 true

示例:

const [form, { reset }] = useRefState({ name: '', email: '' })
reset()

使用场景

1. 避免闭包陷阱

在事件处理器、定时器、异步操作中,useRefState 可以避免闭包问题:

function Component() {
  const [count, { setState, getState }] = useRefState(0)

  useEffect(() => {
    const interval = setInterval(() => {
      // 始终获取最新的 count 值
      setState(getState() + 1)
    }, 1000)

    return () => clearInterval(interval)
  }, [])

  return <div>{count}</div>
}

2. 批量更新优化

当需要执行多个状态更新但只想触发一次渲染时:

function Component() {
  const [items, { patchState, forceUpdate }] = useRefState<string[]>([])

  const addMultipleItems = (newItems: string[]) => {
    newItems.forEach((item) => {
      patchState((draft) => draft.push(item), false)
    })
    forceUpdate() // 只触发一次渲染
  }
}

3. 复杂对象状态管理

对于复杂的嵌套对象,patchState 提供了便捷的更新方式:

interface ComplexState {
  user: { name: string; profile: { age: number; location: string } }
  settings: { theme: string; notifications: boolean }
}

function Component() {
  const [state, { patchState }] = useRefState<ComplexState>({
    user: { name: '', profile: { age: 0, location: '' } },
    settings: { theme: 'light', notifications: true },
  })

  const updateAge = (age: number) => {
    patchState((draft) => {
      draft.user.profile.age = age
    })
  }
}

4. 表单状态管理

function FormComponent() {
  const [formData, { patchState, reset }] = useRefState({
    username: '',
    password: '',
    email: '',
  })

  const handleChange = (field: string, value: string) => {
    patchState((draft) => {
      draft[field] = value
    })
  }

  const handleSubmit = () => {
    // 提交逻辑
    console.log(formData)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={formData.username}
        onChange={(e) => handleChange('username', e.target.value)}
      />
      <button type="button" onClick={() => reset()}>
        Reset
      </button>
    </form>
  )
}

注意事项

1. 状态深拷贝

状态会自动进行深拷贝处理,这意味着:

  • 优点:确保数据隔离,避免引用问题
  • 缺点:对于大型对象可能影响性能
  • 建议:对于大型对象,考虑使用浅拷贝或不可变数据结构

2. 渲染控制

  • 默认情况下,所有状态更新都会触发组件重新渲染
  • 使用 update: false 参数可以避免触发渲染,适用于批量操作
  • 记得在批量操作完成后调用 forceUpdate() 触发渲染

3. 与 useState 的区别

特性useStateuseRefState
状态存储React 内部ref
闭包问题需要使用函数式更新无闭包问题
渲染控制每次更新都渲染可选择是否渲染
性能优化较好需要手动控制
使用场景一般状态管理复杂状态、批量更新、避免闭包

4. TypeScript 类型推断

确保为泛型参数提供正确的类型:

// ✅ 正确
const [user, { setState }] = useRefState<UserProfile>({ name: '', age: 0 })

// ✅ 类型推断
const [count, { setState }] = useRefState(0)

// ❌ 不推荐(类型可能不准确)
const [data, { setState }] = useRefState({} as any)

5. 性能考虑

  • 对于频繁更新的状态,考虑使用 update: false 批量处理
  • 对于大型对象,评估深拷贝的性能影响
  • 在需要时使用 forceUpdate() 手动控制渲染时机

常见问题

Q: 什么时候应该使用 useRefState 而不是 useState?

A: 当你需要:

  • 在闭包中访问最新状态值
  • 批量更新状态并控制渲染时机
  • 管理复杂的嵌套对象状态
  • 在事件处理器或定时器中更新状态

Q: 如何在不触发渲染的情况下更新状态?

A: 使用 patchStatesetState 的第二个参数:

patchState((draft) => { /* 更新逻辑 */ }, false)
setState(newValue, false)

Q: reset 方法会重置为什么值?

A: reset 会将状态重置为调用 useRefState 时传入的初始值。

Q: 可以在 useEffect 中使用吗?

A: 可以,而且特别适合在 useEffect 中使用,因为可以避免闭包陷阱:

useEffect(() => {
  const interval = setInterval(() => {
    setState(getState() + 1)
  }, 1000)
  return () => clearInterval(interval)
}, []) // 空依赖数组也可以正常工作