20210322 React Hooks02 useEffect, useRef, (Nooks : useTabs, useTitle, useClick), nomadcoder

5 분 소요

React Hooks




지난 시간

  • hook은 기본적으로 react에서 제공하는 hook이 있고 개인이 custom 하여 만들 수도 있다.
  • 지난 시간에는 React hook인 useState와 nico’s hook(Nooks)인 useInput을 공부하였다.
  • 이번에는 useState를 사용하는 nooks useTabs를 알아볼 예정
  • 그리고 기본 hook인 useEffect와 useRef와 이를 활용한 여러 nooks를 알아볼 예정

useTabs (nico’s hook using useState)

  • useState hook로 만들어진 useTabs객체가 들은 배열 : [{},{},..]과 배열안의 객체의 인덱스 번호에 따라서 해당하는 값을 자유자제로 활용하게 해주는 함수이다.


  • 활용할 객체가 들은 배열 (Array)
    • 배열은 content, 안에 들어간 객체는 section이고 section들은 각각 tab, content라는 item으로 이루어짐
    • content 배열은 객체를 꺼낼때 []를 통해 인덱싱으로 접근해야한다.
const content = [
  {
    tab: "Section 1",
    content: "I'm the content of the Section 1"
  },
  {
    tab: "Section 2",
    content: "I'm the content of the Section 2"
  }
];




  • useTabs
    • useTabs 함수는 currentIndex 변수는 initialTab이라는 값을 불러오고, setCurrentIndex 함수는 initialTab이라는 값을 변경시키는데 이 둘로 이루어진 useState로 이루어 져있다.
    • if를 통해 함수에 들어오는 매개변수에 대한 유효성을 검증하여 필터링 시켜 활용하고자 하는 값을 반환한다.
    • 반환하는 값은 currentItem 변수, changeItem 함수으로서 currentItem은 현재 인덱스 값을 반영한 객체를 반환하고, changeItem함수는 setCurrentIndex 함수로 index로 지정할 값을 받아 반영하게 한다.
const useTabs = (initialTab, allTabs) => {
  const [currentIndex, setCurrentIndex] = useState(initialTab);
  if (!allTabs || !Array.isArray(allTabs)) {
    return;
  }
  return {
    currentItem: allTabs[currentIndex],
    changeItem: setCurrentIndex
  };
};





  • 기본 Component(render할 App component)
    • 처음에 useTabs 함수를 사용해서 content 배열의 index 0을 넣어 currentItem 변수, changeItem 함수을 활용할 수 있게 가져온다.({}활용하기 위해서 객체형으로 가져옴)
    • 그리고 return할때는 content를 map함수를 통해서 객체(item)을 하나씩 꺼내오는데 callback 함수에 item(section)과, index를 매개변수로 활용하기 위해서 설정하고 callback 함수에서 JSX를 보여지게 한다. JSX는 button 요소로 onClick시 콜백 함수가 changeItem 함수를 호출하여 map에서 받아온 현재 객체의 index를 값으로 설정하게 한다. 그리고 button요소에 content로 표시될 내용은 각 item의 tab property이다.
    • 마지막으로 useTabs를 통해 받아온 currentItem의 content property이 render 되게 함
function App() {
  const {currentItem, changeItem} = useTabs(0, content); 
  // 그냥 tabs로 받아오면 tabs.currentItem으로 접근해야 되서 변수명을 더 쓰게 됨
  return (
    <div className="App">
      {content.map((section, index) => (
      <button onClick={() => changeItem(index)}>{section.tab}</button> 
      // changeItem 은 setCurrentIndex이므로 안에 무언가 넣으면 그값으로 useState에 넣어 변경함
      ))}
      <div>{currentItem.content}</div>
    </div>
  );
};


export default App;




  • 오늘의 Key Point
    • setState는 re-Render 시켜 줌 (render 함수가 없어도 됨!) 즉, 호출 되었을 때 기본적으로 값을 변경 시키고 render까지 시킨 다는 것임.




useEffect (Hook from React)

  • useEffect는 아주 많은 use를 가지고 있음


  • componentWillUnmount, componentDidMount, componentWillUpdate의 기능을 함
  • 초기에 render 되면 component가 mount 되자마자 실행됨 = componentDidMount
  • deps(두번째 인자)가 없으면 어떤 변수이건 간에 useState의 set함수가 실행되어 render되면 useEffect가 실행됨 = componentDidUpdate


  • useEffect가 받는 2개의 인자중 첫번째는 function으로서 effect임
    • (즉, 실행할 callback함수라는 이야기 임)
  • 두번째 인자는 dependencies(deps)인데 deps가 있으면 effect는 deps리스트에 있는 값일때만 값이 변하도록 활성화 할것임
    • (즉,deps(Array)안에 있는 특정 useState의 값이 변할때만 실행한다는 이야기)
    • componentWillUpdate
  • 반대로 어떤 useState에 변화를 주던지 상관 없이 모두 실행시키고 싶지 않을 때는 두번째 인자 deps를 []로 빈칸으로 남겨두면 됨(한번 초기에 render 될때만 발생하고 변화주어도 실행X)


  • useEffect는 componentDidmount 상태 및 componentDidUpdate에서 함수나 여러 코드들을 작동하고, componentWillUnmount일 때는 return 값을 실행 함

  • 즉, return 을 기준으로 DId, Update 인지 WillUnmount에 따른 함수를 결정


import React, {useState, useEffect} from "react";
import './App.css';

function App() {
  const sayHello = () => {console.log("Hello")}
  const [number, setNumber] = useState(0);
  const [aNumber, setAnumber] = useState(0);
  useEffect(sayHello, [number]);
  return (
    <div className="App">
      <div>Hi</div>
      <button onClick={() => setNumber(number + 1)}>{number}</button>
      <button onClick={() => setAnumber(aNumber + 1)}>{aNumber}</button>
    </div>
  );
};


export default App;


  • 정리
    • useEffect(function, deps)
      • function : 실행할 콜백 함수

      • deps : 특정 useState 변수의 값이 set함수로 인해서 변경될 때 실행하기 위해서 어떤 useState 변수를 조건으로 할 것인가.

        • deps 인자가 아예 없을 때 : 모든 useState 기준으로 실행(처음 render 포함)
        • deps 인자가 []로 빈칸 및 일치하는 useState가 없을 때 : 모든 useState 기준으로 실행 안함(첫번째 render시에만 실행)
        • deps 인자가 있고 useState가 들어 있을 때 : 해당 useState 기준으로 실행(처음 render은 기본적으로 실행됨)


  • useState와 useEffect의 정의된 순서가 중요함(일단 useEffect는 기본적으로 useState를 통해 조건적으로 활용할 수 있기 때문에 useState가 먼저 정의 되어야 그것을 활용할 수 있음)





useTitle (Nico’s hook using useEffect)

  • 문서의 제목을 업데이트 시켜주는 걸 담당하는 hooks
    • 사이트 title을 말하는 것임


  • useTitle
    • useTitle hook의 경우에는 initialTitle을 매개변수로 해서 입력된 값을 useState가 사용되었기 때문에 title 변수로 호출이 가능하고, setTItle함수를 통해 수정이 가능하다.
    • useTitle안에는 updateTitle 함수가 있는데 htmlTitle 변수에 title element를 가져와서 담아 innerText함수를 통해서 해당 element를 useState의 title변수를 할당하는 함수인데 update를 위해서는 useEffect가 필요함
    • 그래서 useTitle 실행시 useEffect hook이 실행되어 title의 값이 변할 때 updateTitle를 수행하여 업데이트 함
    • 그리고 마지막으로 setTitle 함수를 return 함
      • useTitle() = 화면 변경 + setTitle()
import React, {useState, useEffect} from "react";
import './App.css';

const useTitle = (initialTitle) => {
  const [title, setTitle] = useState(initialTitle);
  const updateTitle = () => {
    const htmlTitle = document.querySelector("title");
    htmlTitle.innerText = title;
  }
  useEffect(updateTitle, [title]);
  return setTitle;
}


  • 기본 Component(render할 App component)
    • titleUpdater 변수에 usetitle(“Loading…“)을 받아 titleUpdater은 초기 값이 존재하는 함수가 됨
    • setTimeout 함수를 통해서 콜백 지연을 줌(데이터를 받아오는 시간이라고 상상하고)
      • 5초가 지나면 titleUpdater를 호출하여 “Home”을 넣어 title을 변경하고 화면을 update 함
function App() {
  const titleUpdater = useTitle("Loading...");
  setTimeout(() => titleUpdater("Home"), 5000);
  return (
    <div className="App">
      <div>Hi</div>
    </div>
  );
};

export default App;




useClick (Nico’s hook using useEffect)

useRef() (hook from React)**

  • reference는 우리의 component의 어떤 부분을 선택할 수 있는 방법 임.
  • document.getElementByID()를 사용한것과 동등 함.
  • react에 있는 모든 element들은 reference props를 가지고 있음
import React, {useState, useEffect, useRef} from "react";
import './App.css';

function App() {
  const input = useRef();
  setTimeout(() => {input.current?.focus()}, 5000)
  return (
    <div className="App">
      <div>Hi</div>
      <input ref={input} placeholder="la"  />
    </div>
  );
};

export default App;
  • input자체는 객체 형태로 나타나지고, 하위 property인 current로 사용해야 element로 작동
  • 문제는 mount가 너무 빨라서 기존의 chining인 . 대신에 ?.을 넣어야 한다고 함




useClick

  • 누군가가 element를 클릭했을 때 function을 실행 (element.addEventListener("click", function))
import React, {useState, useEffect, useRef} from "react";
import './App.css';

const useClick = (onClick) => {
  const ref = useRef();
  
  useEffect(() => {
    // componentDIdmount, componentDidUpdate([]옵션으로 udpate는 사용X) 상태시 실행 (Event 추가)
    const element = ref.current;
    if(element){
      element.addEventListener("click", onClick);
    }
    // componentWIllunmout시 실행 (Event 정리)
    return () => {
      if(element) {
        element.removeEventListener("click", onClick); 
      };
    };
  }, [onClick]); 
  // [] 옵션을 통해서 무언가 update 되어도 다시 update가 안되게 함
  
  if (typeof onClick !== "function") {
    return;
  }
  return ref;
};

function App() {
  const sayHello = () => console.log("say hello");
  const title = useClick(sayHello);
  return (
    <div className="App">
      <h1 ref={title}>Hi</h1>
    </div>
  );
};


export default App;


  • Key Point
    • useRef 사용시 useRef를 return jsx에서 해당 요소의 ref props에 할당하여 연결시켜줘야 한다는 것!

    • useEffect는 componentDidmount 상태 및 componentDidUpdate에서 함수나 여러 코드들을 작동하고, componentWillUnmount일 때는 return 값을 실행 함

    • 즉, return 을 기준으로 DId, Update 인지 WillUnmount인지 결정 그래서 뭔가 return으로 받았다면 그건 unmout때 받은 거임




Warning

  • ` React Hook “useEffect” is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?`
  • Hook이 if 조건문 안에서 또는 조건문 보다 늦게 호출 되면 안됨.
  • 변수에 할당하면 변수자체가 hook이 된다는 걸 기억하자.