简单介绍一下 Javascript 中响应式的实现原理,从 Vue2/3 的响应式机制到 React Hooks 的响应式实现…
介绍 今天我们来深入探讨一下 Javascript 中响应式系统的实现原理。响应式编程是一种面向数据流和变更传播的编程范式,这意味着当底层数据发生变化时,相关的计算和视图会自动更新。在前端框架中,响应式系统是核心之一,它让我们可以更直观地编写声明式的代码。
今天是国庆假期,一边看阅兵一边写代码,感觉挺有意思的。看着国家的强大,也激发我深入探索技术的热情。废话不多说,我们进入今天的主题!
Vue 响应式系统分析 Vue2.x 的响应式实现 Vue2.x 使用 Object.defineProperty() 来劫持各个属性的 getter 和 setter,当数据发生变化时通知视图更新。这种方法有一定的局限性,比如无法检测到对象属性的添加或删除,也无法检测到数组索引的变化。
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 Object .defineProperty (obj, key, { enumerable : true , configurable : true , get : function reactiveGetter ( ) { dep.depend (); } return val; }, set : function reactiveSetter (newVal ) { if (newVal === val) { return ; } val = newVal; } }); } constructor ( ) { this .subs = []; } static target = null ; addSub (sub ) { this .subs .push (sub); } depend ( ) { if (Dep .target ) { Dep .target .addDep (this ); } } notify ( ) { const subs = this .subs .slice (); for (let i = 0 , l = subs.length ; i < l; i++) { subs[i].update (); } } } constructor (vm, expOrFn, cb ) { this .vm = vm; this .getter = typeof expOrFn === 'function' ? expOrFn : this .parsePath (expOrFn); this .cb = cb; this .value = this .get (); } get ( ) { Dep .target = this ; const value = this .getter .call (this .vm , this .vm ); Dep .target = null ; return value; } update ( ) { const oldValue = this .value ; this .value = this .get (); this .cb .call (this .vm , this .value , oldValue); } addDep (dep ) { dep.addSub (this ); } parsePath (path ) { const segments = path.split ('.' ); return function (obj ) { let ret = obj; for (let i = 0 ; i < segments.length ; i++) { if (!ret) return ret; ret = ret[segments[i]]; } return ret; }; } }
Vue2 响应式的局限性 无法检测属性添加或删除 :
无法检测数组索引变化 :
1 vm.items [indexOfItem] = newValue;
Vue3.x 的响应式实现 Vue3.x 使用了 ES6 的 Proxy 来替代 Object.defineProperty(),从根本上解决了 Vue2.x 的响应式限制。
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 return target; } return proxy; } get (target, key, receiver ) { const res = Reflect .get (target, key, receiver); return reactive (res); } return res; }, set (target, key, value, receiver ) { const oldValue = target[key]; } return result; }, deleteProperty (target, key ) { const hadKey = hasOwn (target, key); const result = Reflect .deleteProperty (target, key); if (hadKey) { trigger (target, 'delete' , key, undefined ); } return result; } }; function track (target, type, key ) { if (!activeEffect) { return ; } let depsMap = targetMap.get (target); if (!depsMap) { targetMap.set (target, (depsMap = new Map ())); } let dep = depsMap.get (key); if (!dep) { depsMap.set (key, (dep = new Set ())); } if (!dep.has (activeEffect)) { dep.add (activeEffect); activeEffect.deps .push (dep); } } const depsMap = targetMap.get (target); if (!depsMap) { return ; } const effects = new Set (); const add = (effectsToAdd ) => { if (effectsToAdd) { effectsToAdd.forEach (effect => { if (effect !== activeEffect) { effects.add (effect); } }); } }; add (depsMap.get (key)); } effect (); }; effects.forEach (run); }
Vue3 响应式优势 可以拦截对象的所有属性操作 :
1 2 const reactiveObj = reactive ({});reactiveObj.newProp = '新属性' ;
可以拦截数组索引和 length 的变化 :
1 2 const arr = reactive ([]);arr.push (1 );
更好的性能 :
不需要预先遍历对象所有属性 懒观察,只有访问时才建立依赖关系 React Hooks 响应式机制 React 通过 Hooks 提供了一种不同的响应式体验。虽然不像 Vue 那样自动追踪依赖,但通过 useState、useEffect 等 Hooks 提供了精确的响应式控制。
useState 与响应式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import React , { useState, useEffect } from 'React' ;function Counter ( ) { document .title = `计数器: ${count} ` ; }, [count]); <div > <p > 计数: {count}</p > <button onClick ={() => setCount(count + 1)}> 增加 </button > </div > ); }
自定义响应式 Hook 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 function useComputed (getter, deps ) { const [value, setValue] = useState (() => getter ()); useEffect (() => { setValue (getter ()); }, deps); return value; } const [a, setA] = useState (1 ); const [b, setB] = useState (2 ); return ( <div > <p > a: {a}, b: {b}</p > <p > sum: {sum}</p > <button onClick ={() => setA(prev => prev + 1)}>增加 a</button > <button onClick ={() => setB(prev => prev + 1)}>增加 b</button > </div > ); }
useReducer + Context 实现状态管理 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 import React , { createContext, useContext, useReducer } from 'React' ; return ( <StateContext.Provider value ={useReducer(reducer, initialState )}> {children} </StateContext.Provider > ); } user : null , basket : [] }; const reducer = (state, action ) => { switch (action.type ) { case 'SET_USER' : return { ...state, user : action.user }; case 'ADD_TO_BASKET' : return { ...state, basket : [...state.basket , action.item ] }; default : return state; } }; const [{ user, basket }, dispatch] = useStateValue (); return ( <div > <h1 > 欢迎, {user?.name || '访客'}</h1 > <p > 购物车: {basket.length} 件商品</p > </div > ); }
Vue 与 React 响应式对比 设计理念差异 特性 Vue React 响应式类型 响应式数据绑定 函数式响应 更新机制 自动依赖追踪 手动状态管理 模板语法 模板 + 指令 JSX 学习曲线 较平缓 中等
性能对比 初始渲染 :
1 2 3 4 5 6 7 8 <template> <div > {{ message }}</div > </template> return <div > {message}</div > ; }
更新性能 :
Vue:细粒度依赖追踪,精准更新 React:虚拟 DOM 比较,批量更新 实现简单的响应式系统 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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 constructor (data ) { this .data = data; this .effectStack = []; this .depMap = new Map (); this .walk (); } walk ( ) { Object .keys (this .data ).forEach (key => { this .convert (key, this .data [key]); }); } convert (key, val ) { if (typeof val === 'object' && val !== null ) { new SimpleReactive (val); } const dep = new Dep (); this .depMap .set (key, dep); Object .defineProperty (this .data , key, { get : () => { if (this .effectStack .length ) { dep.add (this .effectStack [this .effectStack .length - 1 ]); } return val; }, set : (newVal ) => { if (newVal !== val) { if (typeof newVal === 'object' && newVal !== null ) { new SimpleReactive (newVal); } val = newVal; dep.notify (); } } }); } effect (fn ) { const effectFn = ( ) => { this .effectStack .push (effectFn); fn (); this .effectStack .pop (); }; effectFn (); } } class Dep { constructor ( ) { this .subs = new Set (); } add (sub ) { this .subs .add (sub); } notify ( ) { this .subs .forEach (sub => sub ()); } } count : 0 , name : 'Vue' }); console .log ('count 变化:' , reactiveData.data .count ); });
响应式最佳实践 Vue 响应式最佳实践 避免直接修改数组索引 :
使用计算属性优化性能 :
1 2 3 4 5 computed : { return this .list .filter (item => item.active ); } }
React 响应式最佳实践 正确使用依赖数组 :
1 2 3 4 5 6 7 8 9 useEffect (() => { }, [dependency]); useEffect (() => { document .title = `Count: ${count} ` ; }, []);
使用 useCallback 优化函数 :
1 2 3 const handleClick = useCallback (() => { console .log (count); }, [count]);
总结 响应式系统是现代前端框架的核心,它让我们可以专注于业务逻辑而非手动更新视图。 Vue 通过自动依赖追踪提供声明式的数据绑定,而 React 通过函数式编程提供精确的状态管理。 Vue3 的 Proxy 方案从根本上解决了 Vue2 的限制,提供了更强大的响应式能力。 React 的 Hooks 机制为函数式组件带来了状态管理和生命周期的响应式能力。 无论使用哪种方案,理解响应式的工作原理都能帮助我们更好地利用它们,避免常见的陷阱。 晚上出去和家人一起看烟花,感觉今天学习的这些技术知识就像是为思维点亮的烟花,绚烂而有意义!
参考资料 Vue 响应式原理 React Hooks ECMAScript Proxy 深入理解 Vue 响应式系统