๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
WEB/REACT

Ref๋ฅผ ์ข€ ๋” ์ž˜ ์จ๋ณผ๊นŒ? (forwardRef์™€ useImperativeHandle)

by mingzoo 2024. 6. 28.

๋“ค์–ด๊ฐ€๋ฉฐ: React์˜ Ref ์ „๋‹ฌ ๋ฌธ์ œ

 

React์˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ref๋ฅผ ์ „๋‹ฌํ•˜๋ ค๊ณ  ํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

// ๐Ÿšจ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ์ฝ”๋“œ
const Input = (props) => {
  return <input ref={props.inputRef} />;
};

// ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ
const Parent = () => {
  const inputRef = useRef(null);
  return <Input inputRef={inputRef} />; // ref๊ฐ€ ์ „๋‹ฌ๋˜์ง€ ์•Š์Œ
};



์ด๋Š” React๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ref๋ฅผ ์ผ๋ฐ˜ prop์œผ๋กœ ์ „๋‹ฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฐ ์ œํ•œ์ด ์žˆ๋Š” ์ด์œ ๋Š”:
1. ์บก์Šํ™” ์œ ์ง€: ๋‚ด๋ถ€ ๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ์„ ๋ณดํ˜ธ
2. ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ๋ณด์žฅ
3. ๋ถˆํ•„์š”ํ•œ ๋…ธ์ถœ ๋ฐฉ์ง€

 

forwardRef: React์˜ ๊ณต์‹์ ์ธ ref ์ „๋‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜


React ๊ณต์‹ ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด, `forwardRef`๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค:

1. DOM ์š”์†Œ๋ฅผ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— ๋…ธ์ถœํ•ด์•ผ ํ•  ๋•Œ
2. ๊ณ ์ฐจ ์ปดํฌ๋„ŒํŠธ(HOC)์—์„œ ref๋ฅผ ์ „๋‹ฌํ•  ๋•Œ
3. ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ฐœ๋ฐœ ์‹œ

// โœ… forwardRef๋ฅผ ์‚ฌ์šฉํ•œ ์˜ฌ๋ฐ”๋ฅธ ๊ตฌํ˜„
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
  return <input ref={ref} {...props} />;
});

// ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ
const Parent = () => {
  const inputRef = useRef<HTMLInputElement>(null);
  
  useEffect(() => {
    inputRef.current?.focus();
  }, []);
  
  return <Input ref={inputRef} />;
};



### forwardRef์˜ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ํ™œ์šฉ

// ๋ช…ํ™•ํ•œ ํƒ€์ž… ์ •์˜๋กœ ์•ˆ์ „์„ฑ ํ™•๋ณด
interface InputProps {
  label?: string;
  onChange?: (value: string) => void;
}

const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
  const { label, onChange, ...rest } = props;
  
  return (
    <div>
      {label && <label>{label}</label>}
      <input
        ref={ref}
        onChange={e => onChange?.(e.target.value)}
        {...rest}
      />
    </div>
  );
});



useImperativeHandle

`useImperativeHandle`์€ ref๋ฅผ ํ†ตํ•ด ๋…ธ์ถœํ•  ๊ฐ’์ด๋‚˜ ๋ฉ”์„œ๋“œ๋ฅผ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ Hook์ž…๋‹ˆ๋‹ค.

1. ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

interface CustomInputHandle {
  focus: () => void;
  getValue: () => string;
  setValue: (value: string) => void;
}

const CustomInput = forwardRef<CustomInputHandle, InputProps>((props, ref) => {
  const inputRef = useRef<HTMLInputElement>(null);
  const [value, setValue] = useState('');

  useImperativeHandle(ref, () => ({
    // ์ปค์Šคํ…€ ๋ฉ”์„œ๋“œ ์ •์˜
    focus: () => inputRef.current?.focus(),
    getValue: () => value,
    setValue: (newValue) => setValue(newValue)
  }), [value]); // ์˜์กด์„ฑ ๋ฐฐ์—ด ์ฃผ์˜

  return <input ref={inputRef} value={value} onChange={e => setValue(e.target.value)} />;
});

 


### 2. ์‹ค์ „ ํ™œ์šฉ ํŒจํ„ด

#### 2.1 ์„ ํƒ์  ๋ฉ”์„œ๋“œ ๋…ธ์ถœ

```typescript
interface GridHandle {
  // ํ•„์ˆ˜ ๋ฉ”์„œ๋“œ
  required: {
    refresh: () => void;
    clear: () => void;
  };
  // ์„ ํƒ์  ๋ฉ”์„œ๋“œ
  optional?: {
    export?: () => void;
    print?: () => void;
  };
}

const Grid = forwardRef<GridHandle, Props>((props, ref) => {
  useImperativeHandle(ref, () => ({
    required: {
      refresh: () => { /* ๊ตฌํ˜„ */ },
      clear: () => { /* ๊ตฌํ˜„ */ }
    },
    optional: props.enableExport ? {
      export: () => { /* ๊ตฌํ˜„ */ }
    } : undefined
  }));
});
```

#### 2.2 ์„ฑ๋Šฅ ์ตœ์ ํ™”

```typescript
const Grid = forwardRef((props, ref) => {
  const methodsRef = useRef({
    refresh: () => { /* ๊ตฌํ˜„ */ },
    clear: () => { /* ๊ตฌํ˜„ */ }
  });

  // ๋ฉ”์„œ๋“œ ์žฌ์ƒ์„ฑ ๋ฐฉ์ง€
  useImperativeHandle(ref, () => methodsRef.current, []);
});
```

#### 2.3 ์กฐ๊ฑด๋ถ€ ๊ธฐ๋Šฅ ๋…ธ์ถœ

```typescript
const Form = forwardRef<FormHandle, FormProps>((props, ref) => {
  const { mode = 'basic' } = props;

  useImperativeHandle(ref, () => ({
    // ๊ธฐ๋ณธ ๊ธฐ๋Šฅ
    submit: () => { /* ๊ตฌํ˜„ */ },
    reset: () => { /* ๊ตฌํ˜„ */ },
    
    // ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ
    ...(mode === 'advanced' && {
      validate: () => { /* ๊ตฌํ˜„ */ },
      transform: () => { /* ๊ตฌํ˜„ */ }
    })
  }), [mode]);
});
```

### 3. useImperativeHandle์˜ ์ฃผ์˜์‚ฌํ•ญ

1. **์˜์กด์„ฑ ๋ฐฐ์—ด ๊ด€๋ฆฌ**
```typescript
// ๐Ÿšจ ์ž˜๋ชป๋œ ์‚ฌ์šฉ
useImperativeHandle(ref, () => ({
  getData: () => data // data๊ฐ€ ์˜์กด์„ฑ ๋ฐฐ์—ด์— ์—†์Œ
})); 

// โœ… ์˜ฌ๋ฐ”๋ฅธ ์‚ฌ์šฉ
useImperativeHandle(ref, () => ({
  getData: () => data
}), [data]); // data๋ฅผ ์˜์กด์„ฑ ๋ฐฐ์—ด์— ํฌํ•จ
```

2. **๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€**
```typescript
const Component = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => {
    const heavyResource = createExpensiveResource();
    
    return {
      useResource: () => heavyResource
    };
  }, []); // ๋ฆฌ์†Œ์Šค๊ฐ€ ํ•œ ๋ฒˆ๋งŒ ์ƒ์„ฑ๋˜๋„๋ก ํ•จ
  
  // cleanup ๋กœ์ง ์ถ”๊ฐ€
  useEffect(() => {
    return () => {
      // ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ
    };
  }, []);
});
```

## ์‹ค์ „ ์‚ฌ๋ก€: RealGrid์—์„œ์˜ ํ™œ์šฉ

RealGrid ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์ด๋Ÿฌํ•œ ํŒจํ„ด๋“ค์„ ๋ณตํ•ฉ์ ์œผ๋กœ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค:

```typescript
const RealGrid = forwardRef<RealGridRef, RealGridProps>((props, ref) => {
  const gridRef = useRef<HTMLDivElement>(null);
  const [isReady, setIsReady] = useState(false);
  
  useImperativeHandle(ref, () => ({
    // DOM ์š”์†Œ ์ ‘๊ทผ
    grid: gridRef.current,
    
    // ์ƒํƒœ ํ™•์ธ
    isReady,
    
    // ๋ฐ์ดํ„ฐ ์กฐ์ž‘ ๋ฉ”์„œ๋“œ
    getChangedRows: <T = any>() => {
      if (!isReady) return null;
      return /* ๊ตฌํ˜„ */;
    },
    
    // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
    validateRequireColumns: (fields?: string[]) => {
      if (!isReady) return false;
      return /* ๊ตฌํ˜„ */;
    },
    
    // ์•ก์…˜ ๋ฉ”์„œ๋“œ
    commitChange: () => {
      if (!isReady) return;
      /* ๊ตฌํ˜„ */
    }
  }), [isReady]);
  
  return <div ref={gridRef}>/* ๊ตฌํ˜„ */</div>;
});
```

## Best Practices ๋ฐ ๊ถŒ์žฅ ํŒจํ„ด

1. **๋ช…์‹œ์  ํƒ€์ž… ์ •์˜**
   ```typescript
   interface ComponentRef {
     method1: () => void;
     method2: (param: string) => boolean;
   }
   
   const Component = forwardRef<ComponentRef, Props>((props, ref) => {
     // ๊ตฌํ˜„
   });
   ```

2. **์•ˆ์ „ํ•œ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ**
   ```typescript
   const Parent = () => {
     const componentRef = useRef<ComponentRef>(null);
     
     const handleClick = () => {
       // ์˜ต์…”๋„ ์ฒด์ด๋‹์œผ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ํ˜ธ์ถœ
       componentRef.current?.method1();
     };
   };
   ```

3. **์ ์ ˆํ•œ ์บก์Šํ™”**
   ```typescript
   interface PublicAPI {
     // ์™ธ๋ถ€์— ๋…ธ์ถœํ•  ๋ฉ”์„œ๋“œ๋งŒ ์ •์˜
   }
   
   interface PrivateAPI extends PublicAPI {
     // ๋‚ด๋ถ€ ๊ตฌํ˜„์— ํ•„์š”ํ•œ ์ถ”๊ฐ€ ๋ฉ”์„œ๋“œ
   }
   ```

## ๋งˆ์น˜๋ฉฐ

`forwardRef`์™€ `useImperativeHandle`์€ React์˜ ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์›์น™์„ ํ•ด์น˜์ง€ ์•Š์œผ๋ฉด์„œ๋„, ํ•„์š”ํ•  ๋•Œ ์ปดํฌ๋„ŒํŠธ ๊ฐ„์˜ ๋ช…๋ นํ˜• ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Ÿฐ ๊ธฐ๋Šฅ๋“ค์€ ๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์‹ ์ค‘ํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

---
์ฐธ๊ณ  ์ž๋ฃŒ:
- [React ๊ณต์‹ ๋ฌธ์„œ - forwardRef](https://react.dev/reference/react/forwardRef)
- [React ๊ณต์‹ ๋ฌธ์„œ - useImperativeHandle](https://react.dev/reference/react/useImperativeHandle)

728x90