Developer/React

React , 예제로 살펴보는 useRef /Ref 요소선택 배열사용 2

단님 2024. 6. 15. 23:02
728x90

 

익명의 배열을 활용한 useRef

 

ref 속성에 함수나 , 함수 객체를 참조하여 전달할 때 지정한 매개변수는
우리가 이벤트 핸들러에서 이벤트 객체를 얻고 target 속성을 이용해 해당 요소의 참조를 얻는 것과
동일한 효과가 발생된다.

target의 속성을 이용할 필요 없이 지정한 매개변수가 그의 효과를 발생시키기 때문에,
매개변수 자체가 해당 요소를 직접 참조하게 된다.

예제를 보며 설명해보겠다.
 const inputEle = useRef(new Array(2));

익명의 2개의 공간이 있는 useRef 저장 공간을 inputEle가 참조받아 저장공간의 이름이 생겼다.

 

첫 마운트 시점에는 이 값이 정해지지 않은 상태로 return 의 엘리먼트 들이 랜더링 된다.

    return (
        <>
            <div>
                <label>
                    <input type="text" ref={ele => inputEle.current[0] = ele} />
                </label>
                <button onClick={registeredName}>등록</button>
            </div>

            <div>
                <label>
                    전화번호
                    <input type="text" ref={ele => inputEle.current[1] = ele} />
                </label>
                <button onClick={registeredPhoneNum}>등록</button>
            </div>
        </>
    );

input ref 속에 있는 콜백함수들은 첫 마운팅이 된 후 바로 실행되어 inputEle의 배열에 각각 값이 저장된다.

 

즉 , 리액트 컴포넌트의 흐름은 다음과 같다.

1. 컴포넌트 함수가 실행된다

2. 함수들이 HTML 돔 요소를 정의하고 그린다.(마운팅)

(이때 input요소에 ref속성을 지녔으나 아직 함수는 실행되지 않은 상태.)

3.가상의 리액트 DOM이  실제 HTML DOM 요소로 업데이트가 일어난다. (업데이트)

4.각 요소들이 HTML 요소에 추가된후 콜백 함수가 실행된다.

 

이 부분에서 공부하면서 재밌었던 부분은 함수의 생애 주기를 느낄 수 있었다.

 첫로드에 콘솔로그를 찍으면 undefined 가 뜨는데 ,

setTimeout 함수로 1초뒤에 값에 접근하면 input 요소가 뜨는것이 눈으로 보인다.

    console.log(inputEle.current[0]);
    setTimeout(() => {
        console.log(inputEle.current[0]);  
    }, 1000);


 

리랜더링 상황의 Ref 의 콜백함수 파헤쳐 보기

 

Ref 는 이전에 봤듯 , 첫 마운트를 지나고 업데이트가 되고 실행되는것을 볼 수 있었다.

만약 상태값이 바뀌는 useState로 인해 리랜더링이 일어나는 경우가 발생한다면 어떻게 될까 ?

 

ref 콜백이 인라인 함수로 선언되있다면 

ref 콜백은 업데이트 과정 중에 처음에는 null로, 그 다음에는 DOM 엘리먼트로, 총 두 번 호출된다.

 

이러한 현상은 매 렌더링마다 ref 콜백의 새 인스턴스가 생성되므로

React가 이전에 사용된 ref를 제거하고 새 ref를 설정해야 하기 때문에 일어난다.

 

즉 , 리액트가 ref 콜백 함수를 두 번 호출하는 이유는 다음과 같다.

1. 리렌더링 과정에서 기존 DOM 요소의 참조를 정리하기 위해 null 값으로 ref 콜백 함수를 한 번 호출.

2.새로운 DOM 요소가 생성되면, 이를 참조하기 위해 다시 ref 콜백 함수를 호출.

 

이로 인해 ref 콜백 함수는 상태 변화로 인한 리렌더링 시 두 번 호출되며,

첫 번째 호출에서는 null, 두 번째 호출에서는 실제 DOM 요소를 반환합니다.

 

 

최초 마운트 시점에는 요소가 반환되지만 , 

재랜더링시마다 함수가 두번씩 호출되는 것을 볼 수 있다.

 

단 , ref 속성에 함수의 참조가 아닌 , 직접 useRef 에 할당된 참조를 컴포넌트에 할당하는 경우에는 제외된다.

 <input type="text" ref={getElement} data-idx='0' />
    function getElement(ele) {
        console.log(ele);
    }

 

 

ref 를 통해 속성값을 가지고 와서,
그 값을 통해 state 값을 바꿔 UI 그릴건데 
왜 안되 ??????


위의 리랜더링시 ref 가 콜백함수라면 2번 호출되는것을 볼 수 있었는데,

 

- ref  함수

 const inputEle = useRef(new Array(2));
 function getElement(ele) {
        inputEle.current[+ele.dataset.idx] = ele;
    }

이 요소의 dataset-idx 값을 useRef 로 저장할거야.

 

- 클릭이벤트 함수

 	const [data, setData] = useState({
        	data1: '',
        	data2: '',
    	});

	function registeredName() {
        inputEle.current[1].focus();
        setData({
            ...data,
            data1: inputEle.current[0].value,
        });
    }

클릭하면 , useRef 에 저장된 위치값으로 이동할거고 ,

data1에 useRef 저장된 값을 통해 state 값을 변경할거야.

 

근데 왜안됨 ??!!!!

나는 그렇게 하고 싶은데 왜 안됨 ?!!

 

바로 그 이유가 클릭하면 상태값을 바꿀것인데,

getElement(ele) → ele 얘가 null 이잖아요....
 
ele.dataset.idx 를 읽을 수 없지요...

 

그럼 null을 방어 하면 풀리겠네요

    function getElement(ele) {

        if (ele) {
            inputEle.current[+ele.dataset.idx] = ele;
        }
    }

if 문을 통해 ele이 참일때 , 즉 undefined 나 null 이 아닐때로 조건을 걸어 방어한다면 해결된다.

 


 

그럼 어떻게 하는 것이 좋은 코드 일까 ?

 

1.가독성과 명시성을 고려할것.

 

ref  함수가 하는 역할이 비슷하다면 , 합쳐서 사용 가능할 수 있도록 만들것.

 

-변경전-

                <label>
                    <input type="text" ref={ele => inputEle.current[0] = ele} />
                </label>
                <button onClick={registeredName}>등록</button>
            </div>

            <div>
                <label>
                    전화번호
                    <input type="text" ref={ele => inputEle.current[1] = ele} />
                </label>
                <button onClick={registeredPhoneNum}>등록</button>

 

-변경후 - ( 함수를 하나로 합쳐 콜백)

function getElement(ele) { inputEle.current[+ele.dataset.idx] = ele; }
          <div>
                <label>
                    이름
                    <input type="text" ref={getElement} data-idx='0' />
                </label>
                <button onClick={registeredName}>등록</button>
            </div>

            <div>
                <label>
                    전화번호
                    <input type="text" ref={getElement} data-idx='1' />
                </label>
                <button onClick={registeredPhoneNum}>등록</button>
            </div>
        </>

 

2.확장성을 고려할 것.

 

array 형식의 저장공간에 index를 확장성을 고려하여 사용할 수 있다.

즉. ref의 값을 들고오는 방식을 활용하여 변수화 할수 있다.

 

- 변경전-

ref={ele => inputEle.current[1] = ele}

 

-변경 후-

    function getElement(ele) {
        inputEle.current[+ele.dataset.idx] = ele;
    }
    //-------------------------------------------------
    <input type="text" ref={getElement} data-idx='1' />

 

 

ref 와 콜백 두번 실행의 요약 /
리액트에서 ref 콜백 함수가 두 번 호출되는 이유

 

리렌더링 과정에서 기존 DOM 요소를 정리하고 새로운 DOM 요소를 생성하는 과정 때문.

 

첫 번째 호출은 기존 DOM 요소를 정리하기 위해 ele에 null을 전달하고,

두 번째 호출은 새로운 DOM 요소를 참조하기 위해 ele에 실제 DOM 요소를 전달한다.

 

 

느낀점

 

ref를 통해 생애주기의 중요성과 콜백함수의 두번실행을 알게되었다.

생각보다 복잡한 진행을 가지고 있는 것 같아 참 많은 자료를 보게 되었다.

ref 에 대해서 속시원한 자료가 부족했고 ,

useRef 에 대한 간결한 자료들이 많아 찾아 보기 힘들어

chatGPT에게 설명을 다시해줘 다시해줘 하지만 같은 답을 들으니 조금 답답했다.

콘솔 로그를 찍어보며 아 이래서 디버깅이 중요한가보다 싶었다.

 

항상 느끼지만 컴퓨터가 똑똑한게 아니라 개발하는 분들이 참 똑똑하다고 느낀다.

열심히 발자취를 따라가겠슴다 필씅

 

 

 

 

 

이어서 보는 Ref 예제

 

여기를 클릭해 주세요

React , 예제로 살펴보는 useRef /Ref 요소 선택 기본 사용 1

React , 예제로 살펴보는 useRef /Ref 요소선택 배열사용 2