- 자식컴포넌트의 메서드 호출할 수 있도록 한다.
- 폭포수 같은 리액트에서 역류가 가능..
- 일종의 프록시
forwardRef()
와 함께 사용한다.
응애 컴포넌트
import React, {useState, useImperativeHandle, forwardRef } from 'react'
// props와 ref를 인자로 받음
function ChildCompo(props,ref){
const [name, setName]=useState('');
const [age, setAge]=useState(0);
// 1번인자 : ref
// 2번인자 : 현재 컴포의 값을 부모 컴포가 접근할수 있는 CB 함수를 전달
useImperativeHandle(ref, () => ({
addAge : value => setAge(age + value),
getNameLength : ()=> name.length,
}))
return (
<div>
<p>{`name is ${name}`}></p>
<p>{`age is ${age}`}></p>
// ...
</div>
)
}
// 여기서 forwardRef를 씌워줌으로 ref 매개변수를 사용할수 있게됨
// 부모컴포넌트에서 useRef()를 사용하고 자식의 useImprerativeHandle에 전달한 객체를 사용해 값 수정 가능
export default forwardRef(ChildCompo)
엄마 컴포넌트
function ParentCompo () {
const childCompoRef = useRef();
const onClick = () => {
if (childCompoRef.current) {
console.log('current name length:', childCompoRef.current.getNameLength();)
childCompoRef.current.addAge(5);
}
}
return (
<div>
<Profile ref={profileRef} />
<button onClick={onClick}>add age 5</button>
</div>
)
}
예제1
const FormControl = React.forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
getValue(){
return inputRef.current.value
}) }, [inputRef]); // deps도 추가가능하다.
return (
<div className="form-control">
<input type="text" ref={inputRef} />
</div>
);
})
const App = () => {
const inputRef = React.useRef();
return (
<div className="App">
<FormControl ref={inputRef} />
<button
onClick={() => {
console.log(inputRef.current.getValue());
}}>
focus
</button>
</div>
);
}
예제2
export default function useInputRef<T extends HTMLInputElement | HTMLTextAreaElement | InputRef> (
ref: React.ForwardedRef<InputRef>,
options?: InputRefOptions
) {
const inputRef = useRef<T>();
const {validate, clear, number} = options || {};
useImperativeHandle(ref, () => ({
get value () {
return number ? filterFloat(inputRef.current.value) : inputRef.current.value;
},
set value (value: string) {
inputRef.current.value = value;
},
validate: () => validate?.(inputRef.current.value),
focus: () => inputRef.current.focus(),
blur: () => inputRef.current.blur(),
clear: () => {
inputRef.current.value = '';
(inputRef.current as InputRef).clear?.();
clear?.();
},
}), [inputRef.current, ref]);
return inputRef;
}
const UnitInput = forwardRef<InputRef, UnitInputProps>((props: UnitInputProps, ref) => {
const inputRef = useInputRef<InputRef>(ref, {number: true});
return (
<NumberInput
{...props}
ref={inputRef}
errorDisabled
confirmDisabled
className="inp_unit"
SideComponent={<span className="txt_count">{props.unit}</span>}
/>
);
});
const unitRef = useRef<InputRef>();
...
...
<UnitInput
ref={unitRef}
validates={[
value => ({
isError: !value,
message: '입력해주세요.',
}),
value => ({
isError: !/^(\\d{1,3})(,\\d{3})*(.\\d+)?$/.test(value),
message: '숫자를 입력해주세요.',
}),
]}
confirmedMessage="입력 완료."
unit="개"
onChange={value => console.log(value)}
/>
///
예제3
export default function useValidate<T> (validates: Validate<T>[] = []) {
const [error, setError] = useState<InputError>(DEFAULT_INPUT_ERROR);
const [isConfirmed, setIsConfirmed] = useState<boolean>(false);
const clear = () => {
setError(DEFAULT_INPUT_ERROR);
setIsConfirmed(false);
};
const validate: Validate<T> = useCallback(value => {
let resultError = DEFAULT_INPUT_ERROR;
const isAllValidated = validates.every(func => {
const inputError = func(value);
if (inputError.isError && inputError.message !== error.message) {
setError(inputError);
resultError = inputError;
}
return !inputError.isError;
});
if (isAllValidated) {
setIsConfirmed(true);
setError(DEFAULT_INPUT_ERROR);
} else {
setIsConfirmed(false);
}
return resultError;
}, [validates]);
return {
isConfirmed,
error,
clear,
validate,
};
}
const {error, validate, clear} = useValidate([textLengthValidate(maxLength), ...validates]);
const inputRef = useInputRef<HTMLTextAreaElement>(ref, {validate, clear});
참고
React Hooks #9 useImperativeHandle()
[React Hook] useImperativeHandle