응애 컴포넌트

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