响应式状态演进:从 JavaScript Proxy 到 React 与 Vue 的设计博弈
现代前端框架的核心在于状态与视图的同步。Vue 与 React 走向了截然不同的道路:Vue 依赖 JavaScript 的 Proxy 实现细粒度响应式,而 React 则坚守不可变数据驱动粗粒度调和。理解这层底层设计,是写出高性能代码的关键。
JavaScript Proxy:拦截与代理的原生引擎
Vue3 放弃 Object.defineProperty,全面拥抱 Proxy,根本在于 Proxy 能拦截对象的所有操作,而非仅仅属性的读写。
反例:传统对象属性修改无感知
const state = { count: 0 };
state.count++; // 视图如何得知?需要手动触发更新或脏检查
正例:利用 Proxy 构建最小响应式内核
const reactive = (obj) => {
const deps = new Map();
return new Proxy(obj, {
get(target, key) {
if (!deps.has(key)) deps.set(key, new Set());
// 依赖收集:假设 activeEffect 为当前运行的副作用函数
if (activeEffect) deps.get(key).add(activeEffect);
return Reflect.get(target, key);
},
set(target, key, value) {
const result = Reflect.set(target, key, value);
// 派发更新
deps.get(key)?.forEach(effect => effect());
return result;
}
});
};
let activeEffect;
const effect = (fn) => { activeEffect = fn; fn(); activeEffect = null; };
const state = reactive({ count: 0 });
effect(() => console.log('视图更新:', state.count)); // 初始执行: 视图更新: 0
state.count++; // 自动触发: 视图更新: 1
Vue3:基于 Proxy 的细粒度依赖收集
Vue 利用 Proxy 拦截 getter 收集依赖,拦截 setter 触发更新,实现精准渲染。但 Proxy 的代理是针对对象的,基本类型无法拦截,这也是 ref 存在的意义。
反例:错误解构导致响应式丢失
import { reactive } from 'vue';
const state = reactive({ user: { name: 'Alice' } });
// 解构取值脱离了 Proxy 的 get 拦截环境
const { name } = state.user;
name = 'Bob'; // 视图无更新,name 只是一个普通字符串变量
正例:保持 Proxy 链路或使用 toRefs
import { reactive, toRefs } from 'vue';
const state = reactive({ user: { name: 'Alice' } });
// 1. 通过 Proxy 对象访问
state.user.name = 'Bob'; // 触发 setter,视图更新
// 2. 使用 toRefs 为属性创建 ref 包装
const { name } = toRefs(state.user);
name.value = 'Charlie'; // 触发 ref 的 setter,视图更新
React:拥抱不可变数据的粗粒度调和
React 并不追踪属性的读写,而是通过比对前后状态引用是否相同(Object.is)来决定是否重渲染。修改同一份引用,React 将忽略更新。
反例:直接变异(Mutate)State
const [list, setList] = useState([1, 2, 3]);
const addItem = () => {
list.push(4); // 直接修改原引用
setList(list); // 引用未变,React 浅比较判定无变化,拒绝重渲染
};
正例:产生新的引用触发更新
const [list, setList] = useState([1, 2, 3]);
const addItem = () => {
setList([...list, 4]); // 扩展运算符生成新数组,引用改变,触发更新
};
跨框架状态设计的统一范式
当我们在大型应用中需要设计脱离框架层面的通用状态库时,如何同时兼容 Vue 的响应式与 React 的不可变?核心思路是:底层使用 Proxy 监听写操作,但在面向 React 消费时,包装为不可变更新接口。
跨框架状态管理核心实现:
class Store {
#state;
#listeners = new Set();
constructor(initialState) {
// Vue 可直接消费此 reactive 对象
this.#state = reactive(initialState);
}
// 适用于 React 的不可变更新钩子
useReactState() {
const [, setState] = useState(0);
useEffect(() => {
const triggerRender = () => setState(prev => prev + 1);
// 当 Proxy set 派发更新时,触发 React 重渲染
this.#listeners.add(triggerRender);
return () => this.#listeners.delete(triggerRender);
}, []);
// 返回快照,切断直接修改的可能,符合 React 不可变理念
const snapshot = { ...this.#state };
return snapshot;
}
// 统一修改入口
mutate(updater) {
updater(this.#state); // 触发 Proxy set
this.#listeners.forEach(fn => fn()); // 通知 React
}
}
// 使用
const store = new Store({ count: 0 });
store.mutate(state => state.count++); // Vue 自动响应,React 触发重渲染
结语
Vue 的 Proxy 是对 JavaScript 引擎特性的极致压榨,以自动依赖追踪换取开发便利;React 的不可变是对数据流动的强约束,以逻辑可预测性换取并发渲染的安全。理解 Proxy 的边界与不可变数据的威力,才能在框架的规则下游刃有余。
0 评论
评论区
登录 后参与评论