Using @vue/reactivity in React: Custom Hooks and HOC for Reactive State
This article demonstrates how to leverage the @vue/reactivity package inside React by creating a custom useReactive hook for function components and a reactiveHoc higher‑order component for class components, explaining the underlying concepts, implementation steps, and practical usage with full code examples.
Preface
Since Vue 3.0 was released, its core reactivity system has been extracted into the @vue/reactivity package, allowing developers to use Vue's reactivity outside the Vue framework. The author explores using @vue/reactivity in React to build reactive state without repeatedly calling this.setState or useState .
To make the solution reusable, a custom hook useReactive is written for function components and a higher‑order component reactiveHoc for class components.
Function Component – Custom Hook useReactive
Implementation
import { reactive, effect } from '@vue/reactivity'
import React, { useRef, useEffect, useMemo, useState } from 'react'
function useReactive(initState) {
const reactiveState = useRef(initState) // state
const [, forceUpdate] = useState(0)
const state = useMemo(() => reactive(reactiveState.current), [reactiveState.current])
useEffect(() => {
let isdep = false
effect(() => {
for (let i in state) { state[i] } // dependency collection
isdep && forceUpdate(num => num + 1) // force update
if (!isdep) isdep = true
})
}, [state])
return state
}Idea
Use useRef to store the raw object so the reference stays stable across renders.
Wrap the object with reactive inside useMemo to rebuild only when the ref changes.
Use useEffect to run effect for dependency collection, guarded by a flag to avoid an extra update on the first run.
Use a dummy useState to trigger a forced re‑render when the reactive effect fires.
Usage
function Index() {
const state = useReactive({ number: 1, name: 'alien' })
return (
你的姓名是: { state.name }
{ new Array(state.number).fill(0).map(() => '👽') }
state.number++}>👽++
state.number--}>👽--
state.name = e.target.value} />
)
}Class Component – Reverse‑Inheritance HOC reactiveHoc
For class components, manually injecting reactive and effect is cumbersome. A higher‑order component that extends the original component can automatically make its state reactive.
Implementation
import { reactive, effect } from '@vue/reactivity'
import React from 'react'
function reactiveHoc(Component) {
const originalDidMount = Component.prototype.componentDidMount
return class WrapComponent extends Component {
constructor(props) {
super(props)
this.state = reactive(this.state)
}
__isFirst = false
componentDidMount() {
effect(() => {
for (let i in this.state) { this.state[i] } // make reactive
this.__isFirst && this.forceUpdate()
!this.__isFirst && (this.__isFirst = true)
})
originalDidMount && originalDidMount.call(this)
}
}
}Idea
Reverse inheritance lets the HOC access the inner component’s state directly.
The HOC hijacks componentDidMount to set up an effect that tracks state changes and forces an update after the first render.
Usage
@reactiveHoc
class Index extends React.Component {
constructor(props) {
super(props)
this.state = { number: 0, name: 'alien' }
}
componentDidMount() {
console.log(6666)
}
render() {
const { state } = this
return (
你的姓名是: { state.name }
{ new Array(state.number).fill(0).map(() => '👽') }
state.number++}>👽++
state.number--}>👽--
state.name = e.target.value} />
)
}
}Conclusion
The purpose of this article is not to promote using @vue/reactivity in production React projects, but to illustrate how to combine Vue’s reactivity with React’s component model, and to teach the creation of custom hooks and higher‑order components for a more advanced React stack.
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.