BFE.devAI解答 - React 代码题
13. proxy-state valtio
这道题目要求我们实现 valtio
库中的 proxy()
和 useSnapshot()
函数,让上面的示例代码能够正常工作。
具体来说,proxy()
用来创建一个 state 对象的代理,useSnapshot()
用来在 React 组件中订阅该 state 对象的变化。
我们来按照题目要求一步步实现这两个函数。
首先是 proxy()
函数。按照 valtio
的文档,我们知道它的作用是创建一个 state 对象的代理。这个代理对象可以拦截对 state 对象的读取和修改操作,并且可以触发相应的事件通知订阅者。
我们可以通过 Proxy
对象来实现代理功能。具体来说,我们创建一个 ProxyState
类,它的构造函数接受一个 state 对象作为参数,并且将其代理化。此外,我们还需要为 ProxyState
类提供以下方法:
subscribe
:订阅 state 对象的变化。这个方法接受一个回调函数作为参数,每当 state 对象发生变化时,它就会被调用。unsubscribe
:取消订阅 state 对象的变化。这个方法接受一个回调函数作为参数,它会将这个回调函数从订阅列表中删除。getState
:获取代理后的 state 对象的当前值。setState
:设置代理后的 state 对象的新值。这个方法将新的值设置给 state 对象,然后触发订阅者的回调函数。
下面是 proxy()
函数的实现:
function proxy(state) {
const proxyState = new ProxyState(state)
// 隐藏 ProxyState 对象
return new Proxy(proxyState, {
get(target, key) {
// 如果是 getState 方法,则直接返回 ProxyState 对象
if (key === 'getState') {
return () => target
}
// 否则返回 ProxyState 对象的属性值
return target[key]
},
set(target, key, value) {
// 设置 ProxyState 对象的属性值
target[key] = value
// 触发订阅者的回调函数
proxyState.notify()
// 返回 true 表示设置成功
return true
}
})
}
class ProxyState {
constructor(state) {
this.state = state
this.subscribers = []
this.proxy = new Proxy(state, {
get(target, key) {
if (typeof target[key] === 'object' && target[key] !== null) {
return new ProxyState(target[key])
} else {
return target[key]
}
},
set(target, key, value) {
target[key] = value
// 触发代理上的 setter 方法
return true
}
})
}
subscribe(callback) {
this.subscribers.push(callback)
}
unsubscribe(callback) {
const index = this.subscribers.indexOf(callback)
if (index >= 0) {
this.subscribers.splice(index, 1)
}
}
notify() {
this.subscribers.forEach(cb => cb())
}
getState() {
return this.proxy
}
setState(newState) {
this.state = newState
this.proxy = new ProxyState(newState).proxy
this.notify()
}
}
现在我们已经有了代理对象了,下面是 useSnapshot()
函数的实现。这个函数应该是一个 React hooks,用来在组件中订阅代理对象的变化,并返回它的当前值。
useSnapshot()
函数的实现很简单,只需要利用 React 的 useState()
hooks 来存储代理对象的当前值,然后创建一个订阅者,每当代理对象发生变化时就更新这个值即可。注意到,我们需要在组件卸载的时候取消订阅,以避免内存泄漏。
下面是 useSnapshot()
函数的实现:
function useSnapshot(proxyState) {
const [state, setState] = useState(proxyState.getState())
useEffect(() => {
const subscriber = () => {
setState(proxyState.getState())
}
proxyState.subscribe(subscriber)
return () => {
proxyState.unsubscribe(subscriber)
}
}, [proxyState])
return state
}
现在,我们就完成了主要的工作了。接下来,我们可以用我们自己实现的 proxy()
和 useSnapshot()
函数来重写题目中的示例代码,检查它们能否正常工作:
function Counter() {
const state = proxy({ count: 0, text: 'hello' })
const snap = useSnapshot(state)
return (
<div>
{snap.count}
<button onClick={() => ++state.count}>
+1
</button>
</div>
)
}
setInterval(() => {
++state.count
}, 1000)
至此,我们就完成了这道关于 valtio
的代码题目。