();
+
+ // 새로운 자식들을 순회하며 매칭
+ newChildren.forEach((newChild, newIndex) => {
+ if (!newChild) {
+ updatedChildren.push(null);
+ return;
+ }
+
+ const matchedOldChild = findMatchingChild(newChild, newIndex, oldChildren, usedOldIndices);
+
+ // 경로 생성: 매칭된 인스턴스가 있고 타입이 같으면 기존 경로 유지, 없으면 새 경로 생성
+ const childPath =
+ matchedOldChild && matchedOldChild.node.type === newChild.type
+ ? matchedOldChild.path
+ : createChildPath(parentPath, newChild.key, newIndex, newChild.type, newChildren);
+
+ // 타입이 다르거나 key가 다르면 기존 인스턴스를 null로 처리하여 새로 마운트
+ const oldChildForReconcile =
+ matchedOldChild && matchedOldChild.node.type === newChild.type ? matchedOldChild : null;
+
+ // key 불일치 처리 (HOST/TEXT의 경우에만 필요)
+ if (handleKeyMismatch && newChild.key !== null && (!matchedOldChild || matchedOldChild.node.key !== newChild.key)) {
+ handleKeyMismatch(newChild, matchedOldChild);
+ }
+
+ const childInstance = reconcile(parentDom, oldChildForReconcile, newChild, childPath);
+ updatedChildren.push(childInstance);
+ });
+
+ // 사용되지 않은 기존 자식들 언마운트
+ for (let i = 0; i < oldChildren.length; i++) {
+ if (!usedOldIndices.has(i) && oldChildren[i]) {
+ reconcile(parentDom, oldChildren[i], null, oldChildren[i]!.path);
+ }
+ }
+
+ return updatedChildren;
+};
+
+/**
+ * 노드 타입에 따라 kind를 결정합니다.
+ */
+const getNodeKind = (vNode: VNode): NodeType => {
+ if (vNode.type === TEXT_ELEMENT) return NodeTypes.TEXT;
+ if (vNode.type === Fragment) return NodeTypes.FRAGMENT;
+ if (typeof vNode.type === "function") return NodeTypes.COMPONENT;
+ return NodeTypes.HOST;
+};
+
+/**
+ * 컴포넌트를 마운트합니다.
+ */
+const mountComponent = (parentDom: HTMLElement, node: VNode, path: string): Instance | null => {
+ if (typeof node.type !== "function") return null;
+
+ const renderedVNode = node.type(node.props);
+ if (renderedVNode === null) return null;
+
+ const childPath = createChildPath(path, null, 0, renderedVNode.type);
+ const childInstance = reconcile(parentDom, null, renderedVNode, childPath);
+
+ return {
+ kind: NodeTypes.COMPONENT,
+ dom: getFirstDom(childInstance),
+ node,
+ children: childInstance ? [childInstance] : [],
+ key: node.key,
+ path,
+ };
+};
+
+/**
+ * 텍스트 노드를 마운트합니다.
+ */
+const mountText = (parentDom: HTMLElement, node: VNode, path: string): Instance => {
+ const dom = document.createTextNode(node.props.nodeValue as string);
+ parentDom.appendChild(dom);
+
+ return {
+ kind: NodeTypes.TEXT,
+ dom,
+ node,
+ children: [],
+ key: node.key,
+ path,
+ };
+};
+
+/**
+ * Fragment를 마운트합니다.
+ */
+const mountFragment = (parentDom: HTMLElement, node: VNode, path: string): Instance => {
+ const childInstances: (Instance | null)[] = [];
+ const children = node.props?.children || [];
+
+ children.forEach((child, index) => {
+ const childPath = createChildPath(path, child.key, index, child.type, children);
+ const childInstance = reconcile(parentDom, null, child, childPath);
+ childInstances.push(childInstance);
+ });
+
+ return {
+ kind: NodeTypes.FRAGMENT,
+ dom: getFirstDomFromChildren(childInstances),
+ node,
+ children: childInstances,
+ key: node.key,
+ path,
+ };
+};
+
+/**
+ * HOST 요소를 마운트합니다.
+ */
+const mountHost = (parentDom: HTMLElement, node: VNode, path: string): Instance => {
+ const dom = document.createElement(node.type as string);
+ setDomProps(dom, node.props);
+ parentDom.appendChild(dom);
+
+ const newInstance: Instance = {
+ kind: NodeTypes.HOST,
+ dom,
+ node,
+ children: [],
+ key: node.key,
+ path,
+ };
+
+ // 자식들을 재귀적으로 마운트
+ const children = node.props?.children || [];
+ const childInstances: (Instance | null)[] = [];
+
+ children.forEach((child, index) => {
+ const childPath = createChildPath(path, child.key, index, child.type, children);
+ const childInstance = reconcile(dom, null, child, childPath);
+ childInstances.push(childInstance);
+ });
+
+ newInstance.children = childInstances;
+ return newInstance;
+};
+
+/**
+ * HOST 또는 TEXT 인스턴스를 업데이트합니다.
+ */
+const updateHost = (instance: Instance, node: VNode, path: string): Instance => {
+ if (instance.dom) {
+ updateDomProps(instance.dom as HTMLElement, instance.node.props, node.props);
+
+ // 텍스트 노드 업데이트
+ if (instance.kind === NodeTypes.TEXT && node.props.nodeValue !== undefined) {
+ (instance.dom as Text).nodeValue = String(node.props.nodeValue);
+ }
+ }
+
+ // key 불일치 처리 함수
+ const handleKeyMismatch = (newChild: VNode, matchedOldChild: Instance | null) => {
+ if (!matchedOldChild || matchedOldChild.node.key === newChild.key) return;
+
+ // 이전 자식 중 같은 타입이지만 key가 다른 인스턴스 찾기
+ const oldChildren = instance.children || [];
+ for (let i = 0; i < oldChildren.length; i++) {
+ const oldChild = oldChildren[i];
+ if (
+ oldChild &&
+ oldChild.node.type === newChild.type &&
+ oldChild.node.key !== newChild.key &&
+ oldChild.node.key !== null
+ ) {
+ cleanupInstance(oldChild);
+ removeInstance(instance.dom as HTMLElement, oldChild);
+ break;
+ }
+ }
+ };
+
+ // 자식 재조정
+ const oldChildren = instance.children || [];
+ const newChildren = node.props?.children || [];
+ const updatedChildren = reconcileChildren(
+ instance.dom as HTMLElement,
+ path,
+ oldChildren,
+ newChildren,
+ handleKeyMismatch,
+ );
+
+ // DOM 노드를 올바른 순서로 재배치
+ reorderDomNodes(instance.dom as HTMLElement, updatedChildren);
+
+ instance.children = updatedChildren;
+ instance.node = node;
+ return instance;
+};
+
+/**
+ * 컴포넌트 인스턴스를 업데이트합니다.
+ */
+const updateComponent = (parentDom: HTMLElement, instance: Instance, node: VNode, path: string): Instance | null => {
+ if (typeof node.type !== "function") return instance;
+
+ // 컴포넌트 함수 재실행
+ const renderedVNode = node.type(node.props);
+
+ if (renderedVNode === null) {
+ removeInstance(parentDom, instance);
+ return null;
+ }
+
+ // 렌더된 VNode를 재조정
+ const childPath = createChildPath(path, null, 0, renderedVNode.type);
+ const childInstance = reconcile(parentDom, instance.children[0] || null, renderedVNode, childPath);
+
+ instance.children = [childInstance];
+ instance.node = node;
+ instance.dom = getFirstDom(childInstance);
+ return instance;
+};
+
+/**
+ * Fragment 인스턴스를 업데이트합니다.
+ */
+const updateFragment = (parentDom: HTMLElement, instance: Instance, node: VNode, path: string): Instance => {
+ const oldChildren = instance.children || [];
+ const newChildren = node.props?.children || [];
+ const updatedChildren = reconcileChildren(parentDom, path, oldChildren, newChildren);
+
+ // DOM 노드를 올바른 순서로 재배치
+ reorderDomNodes(parentDom, updatedChildren);
+
+ instance.children = updatedChildren;
+ instance.node = node;
+ instance.dom = getFirstDomFromChildren(updatedChildren);
+ return instance;
+};
/**
* 이전 인스턴스와 새로운 VNode를 비교하여 DOM을 업데이트하는 재조정 과정을 수행합니다.
@@ -27,12 +402,62 @@ export const reconcile = (
node: VNode | null,
path: string,
): Instance | null => {
- // 여기를 구현하세요.
// 1. 새 노드가 null이면 기존 인스턴스를 제거합니다. (unmount)
- // 2. 기존 인스턴스가 없으면 새 노드를 마운트합니다. (mount)
- // 3. 타입이나 키가 다르면 기존 인스턴스를 제거하고 새로 마운트합니다.
- // 4. 타입과 키가 같으면 인스턴스를 업데이트합니다. (update)
- // - DOM 요소: updateDomProps로 속성 업데이트 후 자식 재조정
- // - 컴포넌트: 컴포넌트 함수 재실행 후 자식 재조정
- return null;
+ if (node === null) {
+ if (instance) {
+ // 언마운트되는 컴포넌트는 visited에 포함하지 않아야 cleanupUnusedHooks가 정리할 수 있음
+ // cleanup 실행 및 DOM 제거
+ cleanupInstance(instance);
+ removeInstance(parentDom, instance);
+ }
+
+ return null;
+ }
+
+ context.hooks.componentStack.push(path);
+ context.hooks.visited.add(path);
+
+ try {
+ // 2. 기존 인스턴스가 없으면 새 노드를 마운트합니다. (mount)
+ if (instance === null) {
+ const kind = getNodeKind(node);
+
+ switch (kind) {
+ case NodeTypes.COMPONENT:
+ return mountComponent(parentDom, node, path);
+ case NodeTypes.TEXT:
+ return mountText(parentDom, node, path);
+ case NodeTypes.FRAGMENT:
+ return mountFragment(parentDom, node, path);
+ case NodeTypes.HOST:
+ return mountHost(parentDom, node, path);
+ default:
+ return instance;
+ }
+ }
+
+ // 3. 타입이나 키가 다르면 기존 인스턴스를 제거하고 새로 마운트합니다.
+ if (instance.node.type !== node.type || instance.key !== node.key) {
+ // cleanup 실행 (key 변경 시 이전 인스턴스의 cleanup 필요)
+ cleanupInstance(instance);
+ removeInstance(parentDom, instance);
+ // 새로 마운트
+ return reconcile(parentDom, null, node, path);
+ }
+
+ // 4. 타입과 키가 같으면 인스턴스를 업데이트합니다. (update)
+ switch (instance.kind) {
+ case NodeTypes.HOST:
+ case NodeTypes.TEXT:
+ return updateHost(instance, node, path);
+ case NodeTypes.COMPONENT:
+ return updateComponent(parentDom, instance, node, path);
+ case NodeTypes.FRAGMENT:
+ return updateFragment(parentDom, instance, node, path);
+ default:
+ return instance;
+ }
+ } finally {
+ context.hooks.componentStack.pop();
+ }
};
diff --git a/packages/react/src/core/render.ts b/packages/react/src/core/render.ts
index 79c4bbb8..381cd5f9 100644
--- a/packages/react/src/core/render.ts
+++ b/packages/react/src/core/render.ts
@@ -1,18 +1,66 @@
import { context } from "./context";
-import { getDomNodes, insertInstance } from "./dom";
import { reconcile } from "./reconciler";
import { cleanupUnusedHooks } from "./hooks";
import { withEnqueue } from "../utils";
+import { HookTypes } from "./constants";
+import type { EffectHook } from "./types";
/**
* 루트 컴포넌트의 렌더링을 수행하는 함수입니다.
* `enqueueRender`에 의해 스케줄링되어 호출됩니다.
*/
+/**
+ * 예약된 이펙트들을 실행합니다.
+ * 렌더링이 끝난 후 비동기로 실행됩니다.
+ */
+const executeEffects = (): void => {
+ const effectsToRun = [...context.effects.queue];
+ context.effects.queue = [];
+
+ effectsToRun.forEach(({ path, cursor, effect }) => {
+ // path에 대한 훅 배열 가져오기
+ const hooks = context.hooks.state.get(path);
+ if (!hooks) return;
+
+ // 현재 커서에 해당하는 effect hook 가져오기
+ const hook = hooks[cursor] as EffectHook | undefined;
+ if (!hook || hook.kind !== HookTypes.EFFECT) return;
+
+ // 이전 클린업 함수 실행
+ if (hook.cleanup) hook.cleanup();
+
+ // effect함수 실행후 return 값으로 new cleanup 함수 생성
+ const cleanup = effect();
+
+ // 클린업 함수 저장
+ const newHooks = [...hooks];
+ const updatedHook: EffectHook = {
+ ...hook,
+ cleanup: cleanup || null,
+ };
+ newHooks[cursor] = updatedHook;
+ context.hooks.state.set(path, newHooks);
+ });
+};
+
export const render = (): void => {
- // 여기를 구현하세요.
// 1. 훅 컨텍스트를 초기화합니다.
+ context.hooks.cursor.clear();
+ context.hooks.visited.clear();
+ context.hooks.componentStack = [];
+
// 2. reconcile 함수를 호출하여 루트 노드를 재조정합니다.
- // 3. 사용되지 않은 훅들을 정리(cleanupUnusedHooks)합니다.
+ context.root.instance = reconcile(
+ context.root.container as HTMLElement,
+ context.root.instance,
+ context.root.node,
+ "0", // 루트 경로
+ );
+
+ // 3. 예약된 이펙트들을 실행합니다 (렌더링 후 비동기)
+ if (context.effects.queue.length > 0) queueMicrotask(executeEffects);
+
+ cleanupUnusedHooks();
};
/**
diff --git a/packages/react/src/core/setup.ts b/packages/react/src/core/setup.ts
index 03813995..0120abe9 100644
--- a/packages/react/src/core/setup.ts
+++ b/packages/react/src/core/setup.ts
@@ -1,7 +1,6 @@
import { context } from "./context";
import { VNode } from "./types";
import { removeInstance } from "./dom";
-import { cleanupUnusedHooks } from "./hooks";
import { render } from "./render";
/**
@@ -11,9 +10,23 @@ import { render } from "./render";
* @param container - VNode가 렌더링될 DOM 컨테이너
*/
export const setup = (rootNode: VNode | null, container: HTMLElement): void => {
- // 여기를 구현하세요.
// 1. 컨테이너 유효성을 검사합니다.
+ if (!container) throw new Error("Container is required");
+ if (rootNode === null) throw new Error("Root node is required");
+
// 2. 이전 렌더링 내용을 정리하고 컨테이너를 비웁니다.
+ if (context.root.instance) {
+ removeInstance(container, context.root.instance);
+ }
+ // 컨테이너를 완전히 비웁니다 (removeInstance로 제거되지 않은 노드들)
+ while (container.firstChild) {
+ container.removeChild(container.firstChild);
+ }
+
// 3. 루트 컨텍스트와 훅 컨텍스트를 리셋합니다.
+ context.root.reset({ container, node: rootNode });
+ context.hooks.clear();
+
// 4. 첫 렌더링을 실행합니다.
+ render();
};
diff --git a/packages/react/src/core/types.ts b/packages/react/src/core/types.ts
index d88c5714..26e6a729 100644
--- a/packages/react/src/core/types.ts
+++ b/packages/react/src/core/types.ts
@@ -55,7 +55,7 @@ export interface HooksContext {
}
export interface EffectsContext {
- queue: Array<{ path: string; cursor: number }>;
+ queue: Array<{ path: string; cursor: number; effect: () => (() => void) | void }>;
}
export interface Context {
diff --git a/packages/react/src/hocs/deepMemo.ts b/packages/react/src/hocs/deepMemo.ts
index 9f0177d6..649be1d0 100644
--- a/packages/react/src/hocs/deepMemo.ts
+++ b/packages/react/src/hocs/deepMemo.ts
@@ -8,5 +8,8 @@ import type { FunctionComponent } from "../core";
export function deepMemo(Component: FunctionComponent
) {
// 여기를 구현하세요.
// memo HOC와 deepEquals 함수를 사용해야 합니다.
- return memo(Component, deepEquals);
+
+ const MemoizedComponent = memo(Component, deepEquals);
+
+ return MemoizedComponent;
}
diff --git a/packages/react/src/hocs/memo.ts b/packages/react/src/hocs/memo.ts
index 24569ce4..6818a84f 100644
--- a/packages/react/src/hocs/memo.ts
+++ b/packages/react/src/hocs/memo.ts
@@ -12,10 +12,17 @@ import { shallowEquals } from "../utils";
*/
export function memo
(Component: FunctionComponent
, equals = shallowEquals) {
const MemoizedComponent: FunctionComponent
= (props) => {
+ const prevPropsRef = useRef
(null);
+ const component = useRef(null);
+
+ if (prevPropsRef.current === null || !equals(prevPropsRef.current, props)) {
+ prevPropsRef.current = props;
+ component.current = Component(props);
+ }
// 여기를 구현하세요.
// useRef를 사용하여 이전 props와 렌더링 결과를 저장해야 합니다.
// equals 함수로 이전 props와 현재 props를 비교하여 렌더링 여부를 결정합니다.
- return Component(props);
+ return component.current;
};
MemoizedComponent.displayName = `Memo(${Component.displayName || Component.name})`;
diff --git a/packages/react/src/hooks/useAutoCallback.ts b/packages/react/src/hooks/useAutoCallback.ts
index 19d48f72..a4733ea6 100644
--- a/packages/react/src/hooks/useAutoCallback.ts
+++ b/packages/react/src/hooks/useAutoCallback.ts
@@ -9,7 +9,20 @@ import { useRef } from "./useRef";
* @returns 참조가 안정적인 콜백 함수
*/
export const useAutoCallback = (fn: T): T => {
- // 여기를 구현하세요.
- // useRef와 useCallback을 조합하여 구현해야 합니다.
- return fn;
+ // useRef로 최신 함수를 저장
+ const fnRef = useRef(fn);
+
+ // 매 렌더링마다 최신 함수로 업데이트
+ fnRef.current = fn;
+
+ // useCallback으로 안정적인 참조를 가진 래퍼 함수 생성
+ // 이 함수는 항상 ref에 저장된 최신 함수를 호출
+ const stableCallback = useCallback(
+ ((...args: Parameters) => {
+ return fnRef.current(...args);
+ }) as T,
+ [],
+ );
+
+ return stableCallback;
};
diff --git a/packages/react/src/hooks/useCallback.ts b/packages/react/src/hooks/useCallback.ts
index c0043993..a36bdaa4 100644
--- a/packages/react/src/hooks/useCallback.ts
+++ b/packages/react/src/hooks/useCallback.ts
@@ -1,3 +1,4 @@
+import type { AnyFunction } from "../types";
import { DependencyList } from "./types";
import { useMemo } from "./useMemo";
@@ -9,8 +10,9 @@ import { useMemo } from "./useMemo";
* @param deps - 의존성 배열
* @returns 메모이제이션된 콜백 함수
*/
-export const useCallback = any>(callback: T, deps: DependencyList): T => {
- // 여기를 구현하세요.
+export const useCallback = (callback: T, deps: DependencyList): T => {
// useMemo를 사용하여 구현할 수 있습니다.
- return callback;
+ const callbackRef = useMemo(() => callback, deps);
+
+ return callbackRef;
};
diff --git a/packages/react/src/hooks/useDeepMemo.ts b/packages/react/src/hooks/useDeepMemo.ts
index f968d05a..54eefc32 100644
--- a/packages/react/src/hooks/useDeepMemo.ts
+++ b/packages/react/src/hooks/useDeepMemo.ts
@@ -8,5 +8,6 @@ import { useMemo } from "./useMemo";
export const useDeepMemo = (factory: () => T, deps: DependencyList): T => {
// 여기를 구현하세요.
// useMemo와 deepEquals 함수를 사용해야 합니다.
- return factory();
+ const memo = useMemo(() => factory(), deps, deepEquals);
+ return memo;
};
diff --git a/packages/react/src/hooks/useMemo.ts b/packages/react/src/hooks/useMemo.ts
index c275d0e1..f21760ff 100644
--- a/packages/react/src/hooks/useMemo.ts
+++ b/packages/react/src/hooks/useMemo.ts
@@ -12,8 +12,12 @@ import { shallowEquals } from "../utils";
* @returns 메모이제이션된 값
*/
export const useMemo = (factory: () => T, deps: DependencyList, equals = shallowEquals): T => {
- // 여기를 구현하세요.
// useRef를 사용하여 이전 의존성 배열과 계산된 값을 저장해야 합니다.
// equals 함수로 의존성을 비교하여 factory 함수를 재실행할지 결정합니다.
- return factory();
+ const ref = useRef<{ value: T; deps: DependencyList } | null>(null);
+ if (ref.current === null || !equals(ref.current.deps, deps)) {
+ ref.current = { value: factory(), deps };
+ }
+
+ return ref.current.value;
};
diff --git a/packages/react/src/hooks/useRef.ts b/packages/react/src/hooks/useRef.ts
index d5521ca1..9efac9d2 100644
--- a/packages/react/src/hooks/useRef.ts
+++ b/packages/react/src/hooks/useRef.ts
@@ -8,7 +8,8 @@ import { useState } from "../core";
* @returns `{ current: T }` 형태의 ref 객체
*/
export const useRef = (initialValue: T): { current: T } => {
+ const [ref] = useState(() => ({ current: initialValue }));
// 여기를 구현하세요.
// useState를 사용하여 ref 객체를 한 번만 생성하도록 해야 합니다.
- return { current: initialValue };
+ return ref as { current: T };
};
diff --git a/packages/react/src/utils/enqueue.ts b/packages/react/src/utils/enqueue.ts
index a4957d53..c08bb3b0 100644
--- a/packages/react/src/utils/enqueue.ts
+++ b/packages/react/src/utils/enqueue.ts
@@ -5,7 +5,7 @@ import type { AnyFunction } from "../types";
* 브라우저의 `queueMicrotask` 또는 `Promise.resolve().then()`을 사용합니다.
*/
export const enqueue = (callback: () => void) => {
- // 여기를 구현하세요.
+ queueMicrotask(callback);
};
/**
@@ -13,7 +13,16 @@ export const enqueue = (callback: () => void) => {
* 렌더링이나 이펙트 실행과 같은 작업의 중복을 방지하는 데 사용됩니다.
*/
export const withEnqueue = (fn: AnyFunction) => {
- // 여기를 구현하세요.
// scheduled 플래그를 사용하여 fn이 한 번만 예약되도록 구현합니다.
- return () => {};
+ let scheduled = false;
+
+ return () => {
+ if (scheduled) return;
+ scheduled = true;
+
+ enqueue(() => {
+ fn();
+ scheduled = false;
+ });
+ };
};
diff --git a/packages/react/src/utils/equals.ts b/packages/react/src/utils/equals.ts
index 31ec4ba5..7ed63090 100644
--- a/packages/react/src/utils/equals.ts
+++ b/packages/react/src/utils/equals.ts
@@ -5,7 +5,26 @@
export const shallowEquals = (a: unknown, b: unknown): boolean => {
// 여기를 구현하세요.
// Object.is(), Array.isArray(), Object.keys() 등을 활용하여 1단계 깊이의 비교를 구현합니다.
- return a === b;
+ if (a === b) return true;
+ if (typeof a !== typeof b) return false;
+
+ // 배열 비교
+ if (typeof a === "object" && Array.isArray(a) && typeof b === "object" && Array.isArray(b)) {
+ if (a.length !== b.length) return false;
+ if (a.some((value, index) => value !== b[index])) return false;
+ return true;
+ }
+
+ //객체 비교
+ if (typeof a === "object" && a !== null && typeof b === "object" && b !== null) {
+ if (Object.keys(a).length !== Object.keys(b).length) return false;
+ if (Object.keys(a).some((key) => a[key as keyof typeof a] !== b[key as keyof typeof b])) return false;
+ return true;
+ }
+
+ // 함수 비교
+ if (typeof a === "function" && typeof b === "function") return a.toString() === b.toString();
+ return Object.is(a, b);
};
/**
@@ -15,5 +34,18 @@ export const shallowEquals = (a: unknown, b: unknown): boolean => {
export const deepEquals = (a: unknown, b: unknown): boolean => {
// 여기를 구현하세요.
// 재귀적으로 deepEquals를 호출하여 중첩된 구조를 비교해야 합니다.
- return a === b;
+ if (typeof a !== typeof b) return false;
+ if (typeof a === "object" && Array.isArray(a) && typeof b === "object" && Array.isArray(b)) {
+ if (a.length !== b.length) return false;
+ if (a.some((value, index) => !deepEquals(value, b[index]))) return false;
+ return true;
+ }
+
+ if (typeof a === "object" && a !== null && typeof b === "object" && b !== null) {
+ if (Object.keys(a).length !== Object.keys(b).length) return false;
+ if (Object.keys(a).some((key) => !deepEquals(a[key as keyof typeof a], b[key as keyof typeof b]))) return false;
+ return true;
+ }
+ if (typeof a === "function" && typeof b === "function") return a.toString() === b.toString();
+ return Object.is(a, b);
};
diff --git a/packages/react/src/utils/validators.ts b/packages/react/src/utils/validators.ts
index da81b3dd..386bbfbb 100644
--- a/packages/react/src/utils/validators.ts
+++ b/packages/react/src/utils/validators.ts
@@ -6,6 +6,6 @@
* @returns 렌더링되지 않아야 하면 true, 그렇지 않으면 false
*/
export const isEmptyValue = (value: unknown): boolean => {
- // 여기를 구현하세요.
- return false;
+ if (value === null || value === undefined || value === false) return false;
+ return true;
};