From d53280bdf950b8b22d42df0e56fd0c65a5c66ec4 Mon Sep 17 00:00:00 2001 From: JinMu Go Date: Wed, 21 Feb 2024 12:19:23 +0900 Subject: [PATCH] feat(hooks): add useCallback similar with useMemo --- srcs/hooks/core/hooksDispatcher.js | 3 ++ srcs/hooks/index.js | 3 +- srcs/hooks/useCallback/useCallback.js | 8 ++-- srcs/hooks/useCallback/useCallbackImpl.js | 48 +++++++++++++++++++++++ srcs/hooks/useEffect/useEffectImpl.js | 2 +- srcs/hooks/useReducer/useReducerImpl.js | 10 +++-- 6 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 srcs/hooks/useCallback/useCallbackImpl.js diff --git a/srcs/hooks/core/hooksDispatcher.js b/srcs/hooks/core/hooksDispatcher.js index ed8deb0..925d32f 100644 --- a/srcs/hooks/core/hooksDispatcher.js +++ b/srcs/hooks/core/hooksDispatcher.js @@ -5,6 +5,7 @@ import { mountReducer, updateReducer } from "../useReducer/useReducerImpl"; import { mountEffect, updateEffect } from "../useEffect/useEffectImpl"; +import { mountCallback, updateCallback } from "../useCallback/useCallbackImpl"; /** * @description This object is dispatcher for mount hooks. @@ -18,6 +19,7 @@ export const hookDispatcherOnMount = { useReducer: mountReducer, useEffect: mountEffect, // useMemo: mountMemo, + useCallback: mountCallback, }; /** @@ -32,4 +34,5 @@ export const hookDispatcherOnUpdate = { useReducer: updateReducer, useEffect: updateEffect, // useMemo: updateMemo, + useCallback: updateCallback, }; diff --git a/srcs/hooks/index.js b/srcs/hooks/index.js index 42097f2..de00ed1 100644 --- a/srcs/hooks/index.js +++ b/srcs/hooks/index.js @@ -1,4 +1,5 @@ import useReducer from "./useReducer/useReducer.js"; import useEffect from "./useEffect/useEffect.js"; +import useCallback from "./useCallback/useCallback.js"; -export { useReducer, useEffect }; +export { useReducer, useEffect, useCallback }; diff --git a/srcs/hooks/useCallback/useCallback.js b/srcs/hooks/useCallback/useCallback.js index e6336d5..a30172d 100644 --- a/srcs/hooks/useCallback/useCallback.js +++ b/srcs/hooks/useCallback/useCallback.js @@ -3,7 +3,7 @@ * @description This module defines the useCallback function. */ -import c from "../core/core"; +import hookCore from "../core/hookCore"; /** * @description This function is useCallback hook. @@ -11,6 +11,8 @@ import c from "../core/core"; * @argument {Array} deps * @returns {Function} */ -export const useCallback = (callback, deps) => { - return c.RfsCurrentDispatcher.current.useMemo(() => callback, deps); +const useCallback = (callback, deps) => { + return hookCore.RfsCurrentDispatcher.current.useCallback(callback, deps); }; + +export default useCallback; diff --git a/srcs/hooks/useCallback/useCallbackImpl.js b/srcs/hooks/useCallback/useCallbackImpl.js new file mode 100644 index 0000000..bb89eda --- /dev/null +++ b/srcs/hooks/useCallback/useCallbackImpl.js @@ -0,0 +1,48 @@ +import { + mountWorkInProgressHook, + updateWorkInProgressHook, +} from "../core/workInProgressHook"; + +import { areHookDepsEqual } from "../useEffect/useEffectImpl"; + +/** + * + * @param {Function} callback + * @param {Array} deps + * @description - useMemo와 동일한 코드입니다. + * 하나 다른 점은 callback을 실행하지 않고 callback그 자체를 저장한다는 것입니다. + * @returns + */ +export const mountCallback = (callback, deps) => { + const hook = mountWorkInProgressHook(); + const nextDeps = deps === undefined ? null : deps; + hook.memoizedState = [callback, nextDeps]; + return callback; +}; + +/** + * + * @param {Function} callback + * @param {Array} deps + * @description - 만약 deps가 같다면, 이전에 저장된 callback을 반환합니다. + * 만약 deps가 다르다면, 새로운 callback을 저장하고 반환합니다. + * 이때 callback은 deps에 의존하기 때문에 deps가 변경되었을 때, + * 해당 deps를 반영한 새로운 callback을 반환합니다. + * @returns + */ +export const updateCallback = (callback, deps) => { + const hook = updateWorkInProgressHook(); + const nextDeps = deps === undefined ? null : deps; + const prevState = hook.memoizedState; + + if (nextDeps !== null) { + const prevDeps = prevState[1]; + if (prevDeps !== null) { + if (areHookDepsEqual(nextDeps, prevDeps)) { + return prevState[0]; + } + } + } + hook.memoizedState = [callback, nextDeps]; + return callback; +}; diff --git a/srcs/hooks/useEffect/useEffectImpl.js b/srcs/hooks/useEffect/useEffectImpl.js index 3732e6c..18548a1 100644 --- a/srcs/hooks/useEffect/useEffectImpl.js +++ b/srcs/hooks/useEffect/useEffectImpl.js @@ -51,7 +51,7 @@ const createFunctionComponentUpdateQueue = () => { * useEffect의 deps 배열을 비교하여 같다면 true, 다르다면 false를 반환합니다. * @returns */ -const areHookDepsEqual = (prevDeps, nextDeps) => { +export const areHookDepsEqual = (prevDeps, nextDeps) => { for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { if (is(prevDeps[i], nextDeps[i])) { continue; diff --git a/srcs/hooks/useReducer/useReducerImpl.js b/srcs/hooks/useReducer/useReducerImpl.js index 75b050e..435cefd 100644 --- a/srcs/hooks/useReducer/useReducerImpl.js +++ b/srcs/hooks/useReducer/useReducerImpl.js @@ -17,10 +17,12 @@ import { createHookUpdate, createUpdateQueue } from "../constructor/index.js"; * @param {THookUpdateQueue} queue * @param {THookUpdate} update * @description - This function enqueues an update. - * react Source에서는 enqueueRenderPhaseUpdate라는 이름으로 사용되었음. - * enqueueConcurrentHookUpdate는 우선순위를 위한 것이기 때문에 사용하지 않음. + * hook.queue에 update를 추가합니다. + * 만약 어떤 update도 없다면, pending에 update를 추가하고, circular list를 만듭니다. + * pending이 존재한다면 update를 enqueue하고 현재 queue.pending을 해당 update로 변경합니다. + * 즉, pending은 circular list의 마지막 update를 가리키게 됩니다. */ -const enqueueUpdate = (queue, update) => { +const enqueueRenderPhaseUpdate = (queue, update) => { const pending = queue.pending; if (pending === null) { // This is the first update. Create a circular list. @@ -53,7 +55,7 @@ const enqueueUpdate = (queue, update) => { */ const dispatchReducerAction = (fiber, queue, action) => { const update = createHookUpdate(action, false, null, null); - enqueueUpdate(queue, update); + enqueueRenderPhaseUpdate(queue, update); const lastRenderedReducer = queue.lastRenderedReducer; const currentState = queue.lastRenderedState; const eagerState = lastRenderedReducer(currentState, action);