๋ค์ด๊ฐ๋ฉฐ: 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)
'WEB > REACT' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[โ๏ธํธ๋ฌ๋ธ์ํ ] useEffect ๋ด setState ๋ฌดํ๋ฃจํ (0) | 2023.10.05 |
---|---|
react-query๋ฅผ ์งง๊ฒ ์ฌ์ฉํด๋ณธ ํ๊ณ ..๋๊น...? (0) | 2023.04.04 |
๋ฆฌ์กํธ์์ setState๋ฅผ ํตํด์ state๋ฅผ ๋ณ๊ฒฝํด์ฃผ๋ ์ด์ / setState์ ๋น๋๊ธฐ์ฑ (0) | 2022.10.21 |
Redux (0) | 2021.07.13 |
[React] useEffect (0) | 2021.07.06 |