React 快速迁移 Vue3 模块

众所周知,在开发中效率是很重要的能力体现,很多老板都是只看结果不问过程的,作为一个出色的搬砖工人,我们也常常秉持能复制就粘贴,能借鉴就不创作的开发理念。今天,让我们来看下如何在 react 项目中快速迁移 vue3 模块。

react 和 vue3 本身就有很多相似的地方,总体上他们的逻辑的差不多的,无非是几个 api 的切换。

useEffect & watch & watchEffect

这个应该就不用多说了,直接一键替换。需要注意下 watchEffect 需要自己拾取出需要监听的依赖项

注:下面的 useUpdate 和 useCreation 都是 aHook 的钩子。可以移步这里了解其用法

ahooks - React Hooks Library - ahooks 3.0

ref

vue3 的 ref 用来构造基本类型的响应式更新。react 中没有现成的 api,但我们可以借助 Proxy 实现。

ts 复制代码
/** 模拟 Vue3 ref api,全局不变值,也能响应式更新基础类型 */
type Ref<T> = {
  value: T
}

export function useRefState<T>(initial: T): Ref<T> {
  const update = useUpdate()

  const Obj = useRef(new Proxy({
    value: initial,
  }, {
    get(target, key) {
      if (key === 'value') {
        return Reflect.get(target, key)
      }
      throw new Error('请使用 value 访问值')
    },
    set(target, key, value) {
      if (key === 'value') {
        Reflect.set(target, key, value)
        update()
        return true
      } else {
        return false
      }
    },
    deleteProperty() {
      throw new Error('不允许删除操作')
    },
  })).current;

  return Obj;
}

reactive

vue3 的 reactive 用来构造引用类型的响应式更新。react 中也没有现成的 api,我们照样可以借助 Proxy 实现。

ts 复制代码
function observer<T extends Record<string, any>>(initialVal: T, cb: VoidFunction): T {

  const proxy = new Proxy<T>(initialVal, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver);

      const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
      if (!descriptor?.configurable && !descriptor?.writable) {
        return res;
      }

      return isPlainObject(res) || Array.isArray(res) ? observer(res, cb) : res;
    },
    set(target, key, val) {
      const ret = Reflect.set(target, key, val);
      cb();
      return ret;
    },
    deleteProperty(target, key) {
      const ret = Reflect.deleteProperty(target, key);
      cb();
      return ret;
    },
  });

  return proxy;
}

type RefState<T> = {
  value: T,
  /** 监察深层变动 */
  watcher: unknown,
  /** 受控依赖刷新 */
  refresh: unknown
}

/** 模拟 Vue3 reactive api,能深层响应式更新对象,通过 watcher 对比 */
export function useReactive<S extends Record<string, any>>(initialState: S): RefState<S> {
  const watcher = useRef<unknown>(Object.create(null))
  const refresh = useRef<unknown>(Object.create(null))
  const update = useUpdate()

  const state = useCreation(() => {
    return new Proxy(
      {
        value: observer(initialState, () => {
          watcher.current = Object.create(null)
          update()
        })
      },
      {
        get(target, key) {
          switch(key) {
            case 'value': {
              return Reflect.get(target, key)
            }
            case 'watcher': {
              return watcher.current
            }
            case 'refresh': {
              return refresh.current
            }
            default: {
              throw new Error('通过 value 获取数据,通过 watcher 监察变动,通过 refresh 受控依赖刷新,其他属性值无效')
            }
          }
        },
        set(target, key, value) {
          if (key === 'refresh') {
            refresh.current = Object.create(null)
            update()
          } else {
            target[key] = value
          }
          return true
        }
      }
    )

  }, []);

  return state as RefState<S>;
}

注:这里考虑到引用类型可能是深层嵌套,所以我们通过维护一个 watcher 属性进行依赖监控。有时候我们不希望每次变动都触发依赖刷新,所以通过 refresh 属性进行手动刷新

实践

vue 复制代码
const width = ref(0)
const rect = reactive({width: 0, height: 0})

watch([width, rect], () => {
    /** todo something */
})

watchEffect(() => {
    console.log(rect.value)
})
react 复制代码
const width = useRefState(0)
const rect = useReactive({width: 0, height: 0})

useEffect(() => {
    /** todo something */
}, [width.value, rect.watcher])

useEffect(() => {
    console.log(rect.value)
}, [rect.watcher])

// 当需要手动控制 rect 的刷新时机时,把 watcher 替换成 refresh
// 在合适的时机更新 refresh 的任意值即可
useEffect(() => {
    console.log(rect.value)
}, [rect.refresh])
相关推荐
y先森2 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy2 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189112 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿3 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡4 小时前
commitlint校验git提交信息
前端
虾球xz5 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇5 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒5 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员5 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐5 小时前
前端图像处理(一)
前端