본문 바로가기

FrontEnd

[Epic React] useLayoutEffect, useImperativeHandle, forwardRef

반응형

useEffect와 LayoutEffect

useLayoutEffect

When?

  • DOM에 (직접) 관찰 가능한 변경을 가하여 브라우저가 업데이트된 dom 페인트해야 하는 경우 LayoutEffect를 사용합니다.
    • 포커싱, 스크롤(top, bottom 이동)
  • 그 외 전부 useEffect
function MessagesDisplay({messages}) {
  const containerRef = React.useRef()
  // update, unmount시마다 호출
  React.useLayoutEffect(() => {
    containerRef.current.scrollTop = containerRef.current.scrollHeight
  })

  return (
    <div ref={containerRef} role="log">
      {messages.map((message, index, array) => (
        <div key={message.id}>
          <strong>{message.author}</strong>: <span>{message.content}</span>
          {array.length - 1 === index ? null : <hr />}
        </div>
      ))}
    </div>
  )
}

useImperativeHandle

when?

  • 위의 useLayoutEffect를 사용해야 하는 경우와 유사
    • 부모 쪽에서 자식의 useLayoutEffect를 발생 시킬 필요가 있는 경우
  • 부모쪽의 ref와 child의 ref 속성 연결
// 1. child 컴포넌트에서
const MessagesDisplay = React.forwardRef(function MessagesDisplay(
  {messages},
  ref,
) {
  const containerRef = React.useRef()
  React.useLayoutEffect(() => {
    scrollToBottom()
  })
  function scrollToTop() {
    containerRef.current.scrollTop = 0
  }
  function scrollToBottom() {
    containerRef.current.scrollTop = containerRef.current.scrollHeight
  }
  // ref로 children의 함수를 넘김
  React.useImperativeHandle(ref, () => ({
    scrollToTop,
    scrollToBottom,
  }))

  return (
    <div ref={containerRef} role="log">
      {messages.map((message, index, array) => (
        <div key={message.id}>
          <strong>{message.author}</strong>: <span>{message.content}</span>
          {array.length - 1 === index ? null : <hr />}
        </div>
      ))}
    </div>
  )
})
// 2. 부모 컴포넌트에서
function App() {
  
  // ref 선언
  const messageDisplayRef = React.useRef()
  const [messages, setMessages] = React.useState(allMessages.slice(0, 8))
  const addMessage = () =>
    messages.length < allMessages.length
      ? setMessages(allMessages.slice(0, messages.length + 1))
      : null
  const removeMessage = () =>
    messages.length > 0
      ? setMessages(allMessages.slice(0, messages.length - 1))
      : null
  
  // childRef에서 메소드 접근
  const scrollToTop = () => messageDisplayRef.current.scrollToTop()
  const scrollToBottom = () => messageDisplayRef.current.scrollToBottom()

 
  return (
    <div className="messaging-app">
      <div style={{display: 'flex', justifyContent: 'space-between'}}>
        <button onClick={addMessage}>add message</button>
        <button onClick={removeMessage}>remove message</button>
      </div>
      <hr />
      <div>
        <button onClick={scrollToTop}>scroll to top</button>
      </div>
      	// 부모와 자식 연결
      <MessagesDisplay ref={messageDisplayRef} messages={messages} />
      <div>
        <button onClick={scrollToBottom}>scroll to bottom</button>
      </div>
    </div>
  )
}

 

 

반응형