邪修 :让 React 体验更接近 Vue3

510 阅读2分钟

Vue与React的核心差异

Vue3采用组合式API(Composition API),提供了setup()函数和明确的响应式系统,以及清晰的生命周期函数如onMountedonBeforeMount等。而React则采用函数式组件和Hooks模式,状态管理通过useState,副作用通过useEffect处理。

创建useVue自定义Hook

下面是一个模拟Vue3开发体验的useVue。使用 useVue,使其更接近 Vue3 的开发体验。添加计算属性、监听器、方法、生命周期钩子等功能,并优化状态管理方式。

import { useState, useEffect, useRef, useCallback, useContext, createContext } from 'react';
// 创建上下文用于依赖注入
const VueContext = createContext();
export function useVue(options) {
  // 状态管理
  const [state, setState] = useState({});
  const stateRef = useRef({});
  const mountedRef = useRef(false);
  const watchersRef = useRef({});
  const computedRef = useRef({});
  
  // 处理data选项
  if (options.data) {
    const data = typeof options.data === 'function' ? options.data() : options.data;
    stateRef.current = { ...data };
    setState({ ...data });
  }
  
  // 处理computed计算属性
  if (options.computed) {
    Object.keys(options.computed).forEach(key => {
      const computedFn = options.computed[key];
      if (typeof computedFn === 'function') {
        computedRef.current[key] = computedFn;
      } else if (computedFn.get) {
        computedRef.current[key] = computedFn.get;
      }
    });
  }
  
  // 处理methods方法
  const methodsRef = useRef({});
  if (options.methods) {
    Object.keys(options.methods).forEach(key => {
      methodsRef.current[key] = options.methods[key].bind(stateRef.current);
    });
  }
  
  // 处理watch监听器
  if (options.watch) {
    Object.keys(options.watch).forEach(key => {
      watchersRef.current[key] = options.watch[key];
    });
  }
  
  // 处理provide
  if (options.provide) {
    const provideValue = typeof options.provide === 'function' 
      ? options.provide(stateRef.current) 
      : options.provide;
    
    // 使用React的Context提供值
    const Provider = ({ children }) => (
      <VueContext.Provider value={provideValue}>
        {children}
      </VueContext.Provider>
    );
    
    stateRef.current.Provider = Provider;
  }
  
  // 处理inject
  if (options.inject) {
    const injectedValues = useContext(VueContext);
    if (Array.isArray(options.inject)) {
      options.inject.forEach(key => {
        stateRef.current[key] = injectedValues[key];
      });
    } else {
      Object.keys(options.inject).forEach(key => {
        const injectKey = options.inject[key];
        stateRef.current[key] = injectedValues[injectKey];
      });
    }
  }
  
  // 创建ref引用
  const refsRef = useRef({});
  const createRef = useCallback((name) => {
    return (el) => {
      refsRef.current[name] = el;
    };
  }, []);
  
  // 生命周期钩子处理
  useEffect(() => {
    // onMounted
    if (options.onMounted && !mountedRef.current) {
      options.onMounted.call(stateRef.current);
      mountedRef.current = true;
    }
    
    // onUpdated
    if (options.onUpdated && mountedRef.current) {
      options.onUpdated.call(stateRef.current);
    }
    
    // 监听器处理
    Object.keys(watchersRef.current).forEach(key => {
      const oldValue = stateRef.current[key];
      const newValue = state[key];
      
      if (oldValue !== newValue) {
        watchersRef.current[key].call(stateRef.current, newValue, oldValue);
      }
    });
    
    // 更新引用
    stateRef.current = { ...stateRef.current, ...state };
  }, [state]);
  
  // onBeforeUnmount
  useEffect(() => {
    return () => {
      if (options.onBeforeUnmount) {
        options.onBeforeUnmount.call(stateRef.current);
      }
    };
  }, []);
  
  // 创建响应式状态更新函数
  const setStateValue = useCallback((key, value) => {
    setState(prev => ({ ...prev, [key]: value }));
  }, []);
  
  // 创建批量状态更新函数
  const setStateBatch = useCallback((newState) => {
    setState(prev => ({ ...prev, ...newState }));
  }, []);
  
  // 暴露API
  const vueApi = {
    // 状态访问
    $state: state,
    $data: stateRef.current,
    
    // 状态更新
    $setState: setStateValue,
    $setBatch: setStateBatch,
    
    // 计算属性
    $computed: computedRef.current,
    
    // 方法
    $methods: methodsRef.current,
    
    // 引用
    $refs: refsRef.current,
    $ref: createRef,
    
    // 生命周期
    $onMounted: options.onMounted,
    $onUpdated: options.onUpdated,
    $onBeforeUnmount: options.onBeforeUnmount,
    
    // 上下文
    $provide: options.provide,
    $inject: options.inject,
    Provider: stateRef.current.Provider,
  };
  
  // 返回合并后的API和状态
  return {
    ...state,
    ...methodsRef.current,
    ...computedRef.current,
    ...vueApi,
  };
}
// 生命周期钩子辅助函数
export const onMounted = (fn) => ({ onMounted: fn });
export const onUpdated = (fn) => ({ onUpdated: fn });
export const onBeforeUnmount = (fn) => ({ onBeforeUnmount: fn });
// 计算属性辅助函数
export const computed = (getter) => ({ computed: { _: getter } });
// 监听器辅助函数
export const watch = (source, callback) => ({ watch: { [source]: callback } });
// 方法辅助函数
export const methods = (methodsObj) => ({ methods: methodsObj });
// 数据辅助函数
export const data = (dataObj) => ({ data: dataObj });
// 依赖注入辅助函数
export const provide = (provideObj) => ({ provide: provideObj });
export const inject = (injectKeys) => ({ inject: injectKeys });

使用示例

可以用类似Vue3的方式编写React组件:

import React from 'react';
import { useVue, onMounted, onUpdated, onBeforeUnmount, computed, watch, methods, data, provide, inject } from './useVue';
function ParentComponent() {
  const vue = useVue(
    data(() => ({
      count: 0,
      message: 'Hello Vue in React!',
    })),
    computed({
      doubleCount() {
        return this.count * 2;
      },
    }),
    methods({
      increment() {
        this.count++;
      },
      decrement() {
        this.count--;
      },
      reset() {
        this.count = 0;
      },
    }),
    watch('count', (newVal, oldVal) => {
      console.log(`Count changed from ${oldVal} to ${newVal}`);
    }),
    provide({
      sharedMessage: 'This is a shared message',
    }),
    onMounted(() => {
      console.log('Component mounted!');
    }),
    onUpdated(() => {
      console.log('Component updated!');
    }),
    onBeforeUnmount(() => {
      console.log('Component will unmount!');
    })
  );
  return (
    <div>
      <h1>{vue.message}</h1>
      <p>Count: {vue.count}</p>
      <p>Double Count: {vue.doubleCount}</p>
      <button onClick={vue.increment}>Increment</button>
      <button onClick={vue.decrement}>Decrement</button>
      <button onClick={vue.reset}>Reset</button>
      
      <vue.Provider>
        <ChildComponent />
      </vue.Provider>
    </div>
  );
}
function ChildComponent() {
  const vue = useVue(
    inject(['sharedMessage']),
    data(() => ({
      childMessage: '',
    })),
    onMounted(() => {
      this.childMessage = this.sharedMessage + ' (from child)';
    })
  );
  return (
    <div>
      <h2>Child Component</h2>
      <p>{vue.childMessage}</p>
    </div>
  );
}

优化点说明

  1. 更接近Vue3的API设计
    • 使用data()computed()methods()等辅助函数定义组件
    • 生命周期钩子使用onMountedonUpdatedonBeforeUnmount
    • 支持watch监听器
    • 支持provide/inject依赖注入
  2. 状态管理优化
    • 自动合并状态和方法,可以直接通过this访问
    • 提供$setState$setBatch方法进行状态更新
    • 状态更新自动触发重新渲染
  3. 响应式系统模拟
    • 计算属性自动计算并缓存结果
    • 监听器自动检测状态变化并触发回调
  4. 模板引用支持
    • 提供$ref方法创建引用,类似Vue的ref属性
  5. 依赖注入
    • 使用React的Context实现Vue风格的provide/inject
  6. 性能优化
    • 使用useRef保存持久化数据,避免不必要的重新渲染
    • 合理使用useCallback优化函数引用

缺点

  1. 性能开销:封装层会增加一定的性能开销
  2. 不完全等价:React和Vue的响应式系统底层实现不同(Vue3使用Proxy,React使用状态更新触发重渲染)
  3. 社区支持:这种自定义方式无法获得React社区的广泛支持和最佳实践

使用建议

  1. 逐步过渡:先使用useVue作为学习工具,然后逐步尝试React原生Hooks
  2. 理解底层原理:了解React和Vue在响应式系统、渲染机制上的差异
  3. 团队协作:在团队项目中,确保其他成员理解这种封装方式
  4. 性能考虑:对于复杂应用,可能需要评估这种封装的性能影响