Valtio
React 有许多状态管理库,但是有好几款是出自一人之手。今天推荐这款 Valtio 便是他的其中一款作品。
为什么useState
不能满足呢?
useState
是React
内置的状态管理函数,但是很多时候并不方便,特别是遇到嵌套对象的场景。当然不排除还有一群像我一样不喜欢通过函数修改状态的人。
举个列子,我们有个组件需要修改用户的信息包括但不限于:名字、年龄、性别,地址等。
我们可以通过类似下面的组件来实现。问题是随着字段增多,光是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>
}
看起来是不是简单多了,点击按钮,在回调函数修改用户年龄,然后界面就刷新了,一切都是这么顺其自然。
封装让代码更简洁
上面的代码其实还可以更简洁,因为他用了useRef
和useSnapshot
分别生成了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
来进行状态管理。