-
Reactjs hooks 를 통한 비교 이해물망초. 2022. 5. 30. 00:37
Reactjs 구현체에서 활용 중인 비교와 구현 코드
- package/react-conciler/src/ReactFiberHooks.new.js/areHookInputsEqual
... 아래는 실질 비교부 for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { if (is(nextDeps[i], prevDeps[i])) { continue; } return false; } return true; }
- is? Object.is
// Object.is 폴리필 function is(x: any, y: any) { return ( (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare ); } const objectIs: (x: any, y: any) => boolean = typeof Object.is === 'function' ? Object.is : is; export default objectIs;
Object.is('foo', 'foo'); // true Object.is(window, window); // true Object.is('foo', 'bar'); // false Object.is([], []); // false var test = { a: 1 }; Object.is(test, test); // true Object.is(null, null); // true // 특별한 경우 Object.is(0, -0); // false Object.is(-0, -0); // true Object.is(NaN, 0/0); // true
- React hooks 구현체
function updateEffectImpl(fiberFlags, hookFlags, create, deps): void { const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; let destroy = undefined; if (currentHook !== null) { const prevEffect = currentHook.memoizedState; destroy = prevEffect.destroy; if (nextDeps !== null) { const prevDeps = prevEffect.deps; if (areHookInputsEqual(nextDeps, prevDeps)) { hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps); return; } } } currentlyRenderingFiber.flags |= fiberFlags; hook.memoizedState = pushEffect( HookHasEffect | hookFlags, create, destroy, nextDeps, ); } function mountEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void { if ( __DEV__ && enableStrictEffects && (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode ) { return mountEffectImpl( MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect, HookPassive, create, deps, ); } else { return mountEffectImpl( PassiveEffect | PassiveStaticEffect, HookPassive, create, deps, ); } }
// useCallback mount function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T { const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; hook.memoizedState = [callback, nextDeps]; return callback; } // useCallback 갱신 부 function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T { const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const prevState = hook.memoizedState; if (prevState !== null) { if (nextDeps !== null) { const prevDeps: Array<mixed> | null = prevState[1]; if (areHookInputsEqual(nextDeps, prevDeps)) { // 상단에 비교 로직 return prevState[0]; } } } hook.memoizedState = [callback, nextDeps]; return callback; }
// useMemo mount function mountMemo<T>( nextCreate: () => T, deps: Array<mixed> | void | null, ): T { const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const nextValue = nextCreate(); hook.memoizedState = [nextValue, nextDeps]; return nextValue; } // useMemo 갱신 부 function updateMemo<T>( nextCreate: () => T, deps: Array<mixed> | void | null, ): T { const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const prevState = hook.memoizedState; if (prevState !== null) { // Assume these are defined. If they're not, areHookInputsEqual will warn. if (nextDeps !== null) { const prevDeps: Array<mixed> | null = prevState[1]; if (areHookInputsEqual(nextDeps, prevDeps)) { return prevState[0]; } } } const nextValue = nextCreate(); hook.memoizedState = [nextValue, nextDeps]; return nextValue; }
실제 비교는 무엇이 있지?
Referential equality → === 또는 Objest.is
const group1 = { name: 'itzy' }; const group2 = { name: 'twice' }; group1 === group1; // true group1 === group2; // false group1 == group1; // true group1 == group2; // false Object.is(group1, group1); // true Object.is(group1, group2); // false
Manual comparison → 직접 비교로직 작성
function isGroupEqual(object1, object2) { return object1.name === object2.name; } const group1 = { name: 'itzy' }; const group2 = { name: 'itzy' }; const group3 = { name: 'RocketPunch' }; isGroupEqual(group1, group2); // => true isGroupEqual(group1, group3); // => false
Shallow equality → 비교 대상이 많은 속성을 가지거나 런타임 중에 객체의 구조가 결정되는 경우 (1뎁스 비교에 주의)
function shallowEqual(object1, object2) { const keys1 = Object.keys(object1); const keys2 = Object.keys(object2); if (keys1.length !== keys2.length) { return false; } for (let key of keys1) { if (object1[key] !== object2[key]) { return false; } } return true; } const group1 = { name: 'itzy', realName: 'Yuna' }; const group2 = { name: 'itzy', realName: 'Yuna' }; const group3 = { name: 'RocketPunch' }; shallowEqual(group1, group2); // => true shallowEqual(group1, group3); // => false const group1 = { name: 'itzy', song: { title: 'wannabe' } }; const group2 = { name: 'itzy', song: { title: 'wannabe' } }; shallowEqual(group1, group2); // => false
Deep equality → 비교 대상이 중첩된 객체로 이루어져 심층 동등성을 명확하게 확인해야하는 경우
function deepEqual(object1, object2) { const keys1 = Object.keys(object1); const keys2 = Object.keys(object2); if (keys1.length !== keys2.length) { return false; } for (const key of keys1) { const val1 = object1[key]; const val2 = object2[key]; const areObjects = isObject(val1) && isObject(val2); if ( areObjects && !deepEqual(val1, val2) || !areObjects && val1 !== val2 ) { return false; } } return true; } function isObject(object) { return object != null && typeof object === 'object'; } const group1 = { name: 'itzy', song: { title: 'wannabe' } }; const group2 = { name: 'itzy', song: { title: 'wannabe' } }; deepEqual(group1, group2); // => true
결론
- Reactjs 는 Object.is 기반의 reference 비교를 통해 갱신한다.
- deps 의 무한의 가까운 값을 지닐 수 있음을 염두하여 효율성을 위해서 Reactjs 는 shallow equality 를 적용하였다.
참고 문서
- https://dmitripavlutin.com/how-to-compare-objects-in-javascript/
- https://github.com/facebook/react/blob/main/packages/shared/objectIs.js
- https://github.dev/facebook/react/tree/main/packages
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/is
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Equality_comparisons_and_sameness
'물망초.' 카테고리의 다른 글
이상적인 코드 스플릿팅 (0) 2023.11.27 Typescript - 기본타입 (0) 2022.04.24 Dockerfile 명령어 (0) 2022.01.17 redux] async 를 어떻게 처리 하는게 좋을까? (0) 2021.11.14 text-decoration is not woking? (0) 2021.10.07