Valtio

React 有许多状态管理库,但是有好几款是出自一人之手。今天推荐这款 Valtio 便是他的其中一款作品。

为什么useState不能满足呢?

useStateReact内置的状态管理函数,但是很多时候并不方便,特别是遇到嵌套对象的场景。当然不排除还有一群像我一样不喜欢通过函数修改状态的人。

举个列子,我们有个组件需要修改用户的信息包括但不限于:名字、年龄、性别,地址等。

我们可以通过类似下面的组件来实现。问题是随着字段增多,光是useState就占据了大量代码行数。

function User() {
  const [age, setAge] = useState(0)
  const [name, setName] = useState('')
  const [gender, setGender] = useState(0)
  const [address, setAddress] = useState('')
}

然而更具有现实意义的问题是用户的数据往往是通过一个json对象来后端进行数据传递的,按照上面的方式我们需要额外的拼装。

所以我们更倾向于用下面的方式。

function User() {
  const [profile, setProfile] = useState({
    age: 0,
    name: '',
    gender: 0,
    address: ''
  })
}

此时代码看起来整洁多了,然而当我们需要更新profile时还是有很多麻烦事,即使你使用了useReducer。调用setProfile时我们每次都要传递一个新的profile,这是React的检测机制的原因,这里也不深究了,我们只想寻求解决方案——valtio

Vue一样写React

让我们用valtio来实现一下。

import {proxy, useSnapShot} from 'valtio'

function User() {
  const {current: state} = useRef(proxy({
    age: 0,
    name: '',
    gender: 0,
    address: ''
  }))

  const snap = useSnapshot(state)

  return <div>
    <p>name: {snap.name}</p>
    <p>age: {snap.age}</p>
    <p>gender: {snap.gender}</p>
    <p>address: {snap.address}</p>
    <button onClick={() => state.age++}>Click</button>
  </div>
}

看起来是不是简单多了,点击按钮,在回调函数修改用户年龄,然后界面就刷新了,一切都是这么顺其自然。

封装让代码更简洁

上面的代码其实还可以更简洁,因为他用了useRefuseSnapshot分别生成了proxy对象和缓存对象。

import {useRef} from 'react'
import {proxy, useSnapshot} from 'valtio'

function useProxy<T extends object>(data: T) {
  const {current: state} = useRef(proxy(data))
  const snap = useSnapshot(state)
  return [state, snap] as const
}

// 此时上面的代码就可以直接通过 useProxy 来实现了
function User() {
  const [state, snap] = useProxy({
    age: 0,
    name: '',
    gender: 0,
    address: ''
  })

  return <div>
    <p>name: {snap.name}</p>
    <p>age: {snap.age}</p>
    <p>gender: {snap.gender}</p>
    <p>address: {snap.address}</p>
    <button onClick={() => state.age++}>Click</button>
  </div>
}

一些问题

我们来看一个Input的例子。

function App() {
  const [state, snap] = useProxy({
    name: ''
  })

  return <div>
    <input type="text" value={snap.name}
      onChange={e => {
        state.name = e.target.value
      }}
    />
  </div>
}

可以看到当光标移动到中间输入后会自动跳到末尾。这其实是valtio的优化机制与React更新机制的冲突了,怎么修复其实也很简单。

useSnapshot 其实支持额外的参数useSnapshot(..., {sync: boolean})。如果这样我们需要将上面的useProxy也改一下。

function useProxy<T extends object>(data: T, opts?: {sync: boolean}) {
  const {current: state} = useRef(proxy(data))
  const snap = useSnapshot(state, opts)
  return [state, snap] as const
}

思考

虽然上面通过sync解决了问题,但其实把valtio的默认优化也关了。所以换个思路,我们可以自己封装一个Input组件,内部通过useState来进行状态管理。

最后更新于