프로필

프로필 사진
Popomon
Frontend Developer
(2020/12 ~)

    카테고리

    포스트

    [Frontend/React] 13. 자식 요소에 접근하기

    2020. 10. 3. 22:10

    꿈가게: To Do List - iOS

    꿈가게: To Do List - Android

    언제 자식 요소에 접근하는가?

    리액트로 작업을 하다보면 돔 요소에 직접 접근해야하는 경우가 있습니다. 예를들면, 돔 요소에 포커스를 주거나 돔의 크기나 스크롤 위치 등의 정보를 알고 싶은 경우가 있습니다. 이 때에는 ref 속성값을 이용하면 자식요소에 직접 접근할 수 있습니다.

     

    리액트에서는 이러한 ref 속성값을 사용하기 편리하게 만들어주는 useRef 훅을 제공하고 있습니다. 사용법은 아래와 같습니다.

    userRef 훅을 사용하여 훅을 만든 다음, 연결하기를 원하는 요소에 ref 속성값과 연결해줍니다. ref={inputRef} 

    연결된 ref 객체에 current 속성값을 불러오면 그 돔이 가지고있는 속성들을 사용할 수 있습니다. inputRef.current.focus()

     

    import React, { useRef, useEffect } from 'react';
    
    export default function App() {
      const inputRef = useRef();
      useEffect(() => {
        inputRef.current.focus();
      }, [])
      
      return (
        <div>
          <input type="text" ref={inputRef}/>
          <button>저장</button>
        </div>
      )
    }

     

    ref 속성에는 위와같이 참조를 위한 객체를 바인딩 하는 경우도 있지만 함수를 넣는 경우도 있습니다. 만약에 함수를 넣을 시에는 그 함수는 해당하는 태그가 마운트 될 때와 언마운트 될 때 한번 씩 실행됩니다. 생성될 때(마운트)는 ref 요소가 넘어오기 때문에 setText 함수가 실행되고, 사라질 때(언마운트)는 ref 요소값이 null 로 넘어오기 때문에 아무것도 실행되지 않습니다. 따라서 아래의 코드에서 ref 내부의 함수는 초기에 생성되는 경우에만 초기 텍스트(INITIAL_TEXT) 값을 넣어주는 동작을 하고 있습니다.

     

    import React, { useState } from 'react';
    
    export default function App() {
      const [text, setText] = useState(INITIAL_TEXT);
      const [showText, setShowText] = useState(true);
      
      return (
        <div>
          {showText && (
            <input type="text" 
                   ref={ref => ref && setText(INITIAL_TEXT)}
                   value={text}
                   onChange={e => setText(e.target.value)}
            />
          )}
          <button onClick={() => setShowText(!showText)} >보이기/가리기</button>
        </div>
      )
    }
    
    const INITIAL_TEXT = '안녕하세요';

     

    하지만 이전에 Provider 에서 value 객체가 계속 새로운 객체로 인식되었던 것과 마찬가지로 ref 함수도 App 요소가 렌더링 될 때마다 계속 새로운 함수로 인식하게 됩니다.

     

    리액트는 ref 속성값으로 새로운 함수가 들어오면 이전 함수에 null 인수를 넣어서 호출을 합니다. 그리고 새로운 함수에는 요소의 참조값을 넣어서 다시 호출을 해 줍니다. 따라서, 문자를 입력할 때 계속 컴포넌트가 렌더링 되는데, 그 때마다 새로운 함수가 입력 되면서 초기 텍스트가 계속해서 입력되기 때문에 text 값이 입력한 값으로 제대로 업데이트 되지 않는 것입니다.

     

    요약해서 절차를 정리하면 아래와 같습니다.

     

    초기 렌더링 과정

    1. App 컴포넌트가 렌더링 됩니다.

    2. ref 속성값에 함수가 정의됩니다.

    3. ref 함수에는 요소의 참조값이 들어오고, setText 함수를 호출하여 초기 텍스트를 입력합니다.

     

    반복과정

    1. 사용자가 input 값을 변경합니다

    2. onChange 이벤트로 인해서 text 값이 사용자가 입력한 값으로 변경됩니다

    3. text 값이 변경되었기 때문에 그 값과 관련이 있는 App 컴포넌트가 다시 랜더링 됩니다.

    4. ref 속성 값으로 새로운 함수가 들어옵니다.

    5. 새로운 함수가 들어오면 이전 함수에 null 인수를 넣어서 호출합니다. (아무일도 일어나지 않음)

    6. 그리고 새로운 함수에는 요소의 참조값을 넣어서 다시 호출합니다. 

    7. 사용자가 입력한 값을 지우고 다시 초기 텍스트가 입력됩니다.

     

     

    따라서, 아무리 사용자가 입력을 변경하더라도 초기 텍스트가 보이게 되는 것입니다.

    이러한 현상을 고치기 위해서는 ref 함수를 새로운 함수로 인식하지 않도록 아래와 같이 고정해 둘 필요가 있습니다. 이런 경우에도 리액트는 유용한 훅을 제공하고 있습니다.  useCallback 훅의 메모이제이션 덕분에 한번 생성된 함수를 재사용 할 수 있습니다. 따라서 ref 속성이 계속 새로운 함수로 인식하지 않도록 도와줍니다.

     

    import React, { useState } from 'react';
    
    export default function App() {
      const [text, setText] = useState(INITIAL_TEXT);
      const [showText, setShowText] = useState(true);
      
      const setInitialText = useCallback(ref => ref && setText(INITIAL_TEXT));
      
      return (
        <div>
          {showText && (
            <input type="text" 
                   ref={setInitialText}
                   value={text}
                   onChange={e => setText(e.target.value)}
            />
          )}
          <button onClick={() => setShowText(!showText)} >보이기/가리기</button>
        </div>
      )
    }
    
    const INITIAL_TEXT = '안녕하세요';

     


    여러개의 자식 요소에 접근하기

    접근하고자 하는 돔 요소의 개수가 많을 때는 useRef 를 사용하기가 어렵습니다. 왜냐하면 돔 요소가 수십개 수백개가 될 수도 있는데, 그러한 경우에 각각 돔 마다 uesRef 를 한번씩 호출하여 ref 객체를 관리해 주어야 하기 때문입니다.

     

    이러한 경우에는 해결책으로 위에서 언급했던 함수를 입력하는 방법을 사용하면 됩니다. useRef 라는 속성을 사용하여 반드시 돔과 바인딩을 해줄 필요는 없습니다. 따라서 div 여러개의 돔들을 한번에 관리할 수 있는 boxListRef 객체를 만들고, 오브젝트로 초기화를 합니다. 그 후, BOX_LIST 데이터를 렌더링을 해주면서 동시에 각각의 div 참조 객체를 boxListRef에 넣어서 관리합니다.

     

    import React, { useContext, createContext, useState, useRef } from 'react';
    
    export default function App() {
      const boxListRef = useRef({});
    
      return (
        <div>
          <div
            style={{
              display: 'flex',
              flexWrap:'wrap',
              width: '100%',
              height: '100vh',
            }}
          >
            {BOX_LIST.map(item => (
              <div
                key={item.id}
                ref={ref => (boxListRef.current[item.id] = ref)}
                style={{
                  flex: '0 0 auto',
                  width: item:width,
                  height: 100,
                  backgroundColor: 'yellow',
                  border: 'solid 1px red',
                }}
              >{`box+${item.id}`}</div>
            ))}
          </div>
        </div>
      );
    }
    
    const BOX_LIST = [
      { id: 1, width: 50 },
      { id: 2, width: 30 },
      { id: 3, width: 10 },
      { id: 4, width: 50 },
      { id: 5, width: 70 },
      { id: 6, width: 30 },
      { id: 7, width: 20 }
    ];

     

     

     

     


    출처 : 실전 리액트 프로그리맹 - 인프런 (추천합니다 !!)

     

    실전 리액트 프로그래밍 - 인프런

    [실전 리액트 프로그래밍] 책의 저자 직강! 리액트의 기초부터 실전 활용법까지 익힐 수 있습니다. 초급 프레임워크 및 라이브러리 웹 개발 Front-End React Redux 웹 개발 온라인 강의 리액트(React)를 �

    www.inflearn.com