TIL 22-04-10

thisisyjin·2022년 4월 10일
0

TIL 📝

목록 보기
18/113

React.js

Today I Learned ... react.js

🙋‍♂️ React.js Lecture

🙋‍ My Dev Blog


React Lecture CH 4

1 - 리액트 조건문
2 - setTimeout (반응속도게임)

3 - 성능체크
4 - 반응속도게임 (Hooks)
5 - return 내부에 for, if 사용(제어문)-

지난시간 Review

  • render 부분이 복잡해지면?
    -> 따로 함수로 빼준다.
render() {
    const { state, message } = this.state;
    return (
      <>
        <div id="screen" className={state} onClick={this.onClickScreen}>
          {message}
        </div>
        {this.renderAverage()}  // 👈 이부분
      </>
    );
  }
// this.renderAverage
renderAverage = () => {
    const { results } = this.state;
    return results.length === 0 ? null : ( // ❗️ return 붙여줘야함.
      <>
        평균 시간:
        {results.reduce((prev, cur) => prev + cur) / results.length}
        ms
        <button onClick={this.onReset}>리셋</button>
      </>
    );
  };
  • 사실은 함수로 빼는것도 좋지만, 컴포넌트로 분리해주는 것이 더 좋음.

지난시간 Try 컴포넌트로 빼줬던 것 처럼!


reset 버튼 추가

  • 평균 시간을 리셋하는 기능의 button 추가하기.
    -> results 배열의 평균값을 구하는 것 이므로
    results 를 setState로 빈배열[] 로 변경해준다.
 renderAverage = () => {
    const { results } = this.state;
    return results.length === 0 ? null : (
      <>
        평균 시간:
        {results.reduce((prev, cur) => prev + cur) / results.length}
        ms
        <button onClick={this.onReset}>리셋</button>
      </>
    );
  };
onReset = () => {
    this.setState({
      results: [],
    });
  };

성능체크

  • 쓸 데 없는 렌더링이 발생하는지 체크함.
  • chrome dev tool의 highlight 기능 이용.
  • 리셋 버튼을 누르면 평균 시간 부분이 사라지는데, 동시에 screen 부분이 리렌더링 된다.
    -> 아래부분만 사라지면 되므로, 불필요한 렌더링이 발생한 것.
...
  onReset = () => {
    this.setState({
      results: [],  // state 변경 - render() 실행 
    });
  };

  renderAverage = () => {
    const { results } = this.state;
    return results.length === 0 ? null : (
      <>
        평균 시간:
        {results.reduce((prev, cur) => prev + cur) / results.length}
        ms
        <button onClick={this.onReset}>리셋</button>  
      </>
    );
  };

// 버튼 onClick시 - onReset 실행 - setState - render() 실행 - 전체가 다시 렌더링됨

  render() {
    const { state, message } = this.state;
    return (
      <>
        <div id="screen" className={state} onClick={this.onClickScreen}>
          {message}
        </div>
        {this.renderAverage()}
      </>
    );
  }
}

export default ResponseCheck;

따라서, screen 부분과 renderAverage 부분은 따로 별개의 컴포넌트로 구분해야한다.

✅ 참고

함수형 컴포넌트는 전체가 다시 실행되지만,
클래스형 컴포넌트는 render()부분만 다시 실행됨.
-> 함수형에서는 불필요한 함수의 재실행을 막기 위해
useCallback, useMemo 등을 이용해야함.


Hooks로 변경

// Hooks

import React, { useState, useRef } from 'react';

const ResponseCheck = () => {
  const [state, setState] = useState('waiting');
  const [message, setMessage] = useState('클릭해서 시작하세요');
  const [results, setResults] = useState([]);

  const timeout = useRef(null);
  const startTime = useRef();
  const endTime = useRef();

  const onClickScreen = () => {
    if (state === 'waiting') {
      setState('ready');
      setMessage('초록색이 되면 클릭하세요.');
      timeout.current = setTimeout(() => {
        setState('now');
        setMessage('지금 클릭');
        startTime.current = new Date();
      }, Math.floor(Math.random() * 1000) + 2000); // 2sec-3sec 사이 랜덤으로
    } else if (state === 'ready') {
      clearTimeout(timeout.current);
      setState('waiting');
      setMessage('너무 빠릅니다. 초록색이 된 후에 클릭하세요.');
    } else if (state === 'now') {
      endTime.current = new Date();
      setState('waiting');
      setMessage('클릭해서 시작하세요.');

      setResults((prevResults) => [
        ...prevResults,
        endTime.current - startTime.current,
      ]);
    }
  };

  const onReset = () => {
    setResults([]);
  };

  const renderAverage = () => {
    return results.length === 0 ? null : (
      <>
        평균 시간:
        {results.reduce((prev, cur) => prev + cur) / results.length}
        ms
        <button onClick={onReset}>리셋</button>
      </>
    );
  };

  return (
    <>
      <div id="screen" className={state} onClick={onClickScreen}>
        {message}
      </div>
      {renderAverage()}
    </>
  );
};

export default ResponseCheck;

KEY POINT

  • React.useState로 state 구현
  • this 모두 제거 - hooks에서는 안쓰임.
  • ⭐️ useRef()의 또다른 용도
    -> 1. DOM에 접근하는 용도 (ex> focus() 등)
    -> 2. ❗️ 렌더링이 일어나지 않는 변하는 값.
  • 우리가 클래스형 컴포넌트에서도
    timeout, startTime, endTime 은 state가 아닌 클래스 몸체에 선언했었다.
class ResponseCheck extends Component {
  state = {
    state: 'waiting',
    message: '클릭해서 시작하세요',
    results: [],
  };


// 이 부분 🔻
  timeout;
  startTime;
  endTime;
...
}
  • 클래스 몸체에 프로퍼티로 선언하여 렌더링이 일어나지 않도록 할 수 있다.
  • 즉, 값이 변할때 렌더링이 일어나야 하면 - state
  • 값이 변해도 렌더링이 일어나지 않아야 하면 - 위와 같이 선언한다.
    = this.timeout, this.startTime, this.endTime

✅ 그렇다면, Hooks 에서는?
  • Hooks에서는 클래스가 아니므로, 프로퍼티로 선언할 수는 없다. (this.___ 불가)

  • Hooks에서는 대신 useRef()를 사용하면 된다.

useRef()

  • useRef는 DOM에 접근 및 저장하여 focus 등을 하는데 사용했다.
let inputRef = useRef(null);

<input ref={inputRef} />

// 사용시
inputRef.current.focus();

참고 - 클래스 컴포넌트에서도 useRef()와 유사하게 사용 가능한 createRef()

  • useRef는 클래스 몸체에 선언한 프로퍼티와 같은 동작을 한다.
    -> 즉, 값이 변경되어도 리렌더링이 되지 않는다!

❗️ 주의

  • useRef()를 사용할때 or 값을 변경할때는 반드시
    current를 붙여줘야 한다!

참고 - current 프로퍼티 (react.org document)

render() 안에서 ref가 엘리먼트에게 전달되었을 때, 그 노드를 향한 참조는 ref의 current 어트리뷰트에 담기게 됩니다.

const node = this.myRef.current;

ref의 값은 노드의 유형에 따라 다릅니다.

ref 어트리뷰트가 HTML 엘리먼트에 쓰였다면, 생성자에서 React.createRef()로 생성된 ref는 자신을 전달받은 DOM 엘리먼트를 current 프로퍼티의 값으로서 받습니다.

ref 어트리뷰트가 커스텀 클래스 컴포넌트에 쓰였다면, ref 객체는 마운트된 컴포넌트의 인스턴스를 current 프로퍼티의 값으로서 받습니다.

함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref 어트리뷰트를 사용할 수 없습니다.


useRef()의 활용

  • JS로 DOM 요소에 focus 하기, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 때.
  • 애니메이션을 직접적으로 실행시킬 때.
  • 서드 파티 DOM 라이브러리를 React와 같이 사용할 때.

-> '비제어 컴포넌트'를 제어할 때.

'비제어 컴포넌트' 란?

  • 리액트가 제어하지 않는 컴포넌트.
  • 순수 JS로만 제어함.


+) return 안에서 if, for 사용하기

  • React의 return() 안에서는 조건문(if) / 반복문(for)을 사용할 수 없었다.
  • 따라서 if-else 대신 삼항연산자( ? : ) 를 사용하고,
  • 반복문 대신에는 배열의 map 메서드를 사용했다.
// map을 이용한 반복문
return(
  <ul>
  {tries.map((v, index) => (
          <Try key={`${index}차시도`} tryInfo={v} />
        ))}
  </ul>
)


// 삼항연산자를 이용한 조건문
return loading ? null : (
      <>
        <Contents />
      </>
  )
  • 사실은, return 안에서도 if나 for을 사용할 수 있다!
  • 단, 거의 이렇게 쓸 일은 없고 코드도 매우 지저분해진다.

즉시실행함수(IIFE) 안에 if, for문을 작성할 수 있다.

return (
    <>
      <div id="screen" className={state} onClick={onClickScreen}>
        {message}
      </div>
      {renderAverage()}

      {(() => {
        if (results.length === 0) {
          return null;
        } else {
          return (
            <>
              평균 시간:
              {results.reduce((prev, cur) => prev + cur) / results.length}
              ms
              <button onClick={onReset}>리셋</button>
            </>
          );
        }
      })()}
  • 함수 안에는 if,for을 사용할 수 있는 성질을 이용함.
  • 단, 함수가 즉시 호출(=실행)되도록 IIFE 를 이용.

반복문의 경우에는, 각 요소에 해당하는 JSX를 저장할 array 변수를 선언하여 push한 후, 해당 array를 리턴해야 한다.

(기존에는 배열 메서드인 map으로 알아서 순회했지만, for문으로 바꿔야 하므로)

{(() => {
          const array = [];
          for (let i = 0; i < tries.length; i++) {
            array.push(<Try key={`${index}차시도`} tryInfo={v} />)
          }
          return array;
        })()}

-> ✅ 이와 같이 배열 안에 JSX를 담아 리턴하는 것도 유효한 문법이다!
예>

// 배열 리턴도 가능! 
// ❗️ 단, key를 적어줘야한다.
return [
    <div key="apple">사과</div>,
    <div key="banana">바나나</div>,
    <div key="strbr">딸기</div>,
    <div key="grape">포도</div>,
    <div key="kiwi">키위</div>,
  ];

-> 잘 안씀. 여러 태그를 묶어줄 떄에는 <></>를 더 많이 쓴다.

주의

  • 왠만해서는 if, for을 쓸거면 이런식으로 return 안에 쓰지 말고,
    함수로 빼던가 자식 컴포넌트로 분리해주자.
profile
기억은 한계가 있지만, 기록은 한계가 없다.

0개의 댓글