远离 hooks 的依赖数组,使 React 更对得起他的名字。
核心
20 行不到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { effect, stop } from '@vue/reactivity'
function untrack (context) { if (context.$$reactiveRender) { stop(context.$$reactiveRender) context.$$reactiveRender = null } }
function track (context, renderFunction) { untrack(context) context.$$reactiveRender = effect(renderFunction, { lazy: true, scheduler: () => { context.forceUpdate() } }) return context.$$reactiveRender() }
|
其中响应式组件上下文 context
长这样:
1 2 3 4 5 6 7
| import type { ReactiveEffect } from '@vue/reactivity' import type { ReactNode } from 'react'
declare interface ReactiveComponentContext { $$reactiveRender: ReactiveEffect<ReactNode> | null forceUpdate (callback?: () => void): void }
|
核心思想就是把渲染函数用 vue 的 effect
包一层,JSX 中访问到的响应式对象会被依赖收集,有变更时自动更新组件。
Hooks 写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| import * as React from 'react' import { ref, computed } from '@vue/reactivity'
const emptyDepList = []
function useForceUpdate () { const setState = React.useState(null)[1] return React.useCallback(() => { setState(Object.create(null)) }, emptyDepList) }
function useMutable (factory) { const ref = React.useRef() if (ref.current == null) { const maybeObject = factory() if ((typeof maybeObject !== 'object' || maybeObject === null) && (typeof maybeObject !== 'function')) { throw new TypeError('useMutable callback must return object') } ref.current = maybeObject } return ref.current }
function useReactiveContext () { const forceUpdate = useForceUpdate() return useMutable(() => ({ $$reactiveRender: null, forceUpdate })) }
function useRender (jsxFac) { const context = useReactiveContext()
React.useEffect(() => () => { untrack(context) }, emptyDepList)
return track(context, jsxFac) }
function Counter () { const _this = useMutable(() => { const localCount = ref(0) const localDoubleCount = computed(() => localCount.value * 2) const onClick = () => { localCount.value++ } return { localCount, localDoubleCount, onClick } })
return useRender(() => <div>{_this.localCount.value} * 2 = {_this.localDoubleCount.value} <button onClick={_this.onClick}>Local +</button></div> ) }
|
Class 写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import * as React from 'react' import { ref, computed } from '@vue/reactivity'
class Counter extends React.Component { constructor (props) { super(props)
this.$$reactiveRender = null
this.localCount = ref(0) this.localDoubleCount = computed(() => this.localCount.value * 2) this.onClick = () => { this.localCount.value++ } }
render () { return track(this, () => <div>{this.localCount.value} * 2 = {this.localDoubleCount.value} <button onClick={this.onClick}>Local +</button></div> ) }
componentWillUnmount () { untrack(this) } })
|
仓库
reactive-react