[처음 만난 리액트(React)]::section6, 7, 8

오젼·2022년 7월 13일
0

Section 6. State and Lifecycle

State와 Lifecycle의 정의

State

  • State: 리액트 Component의 상태, 리액트 Component의 변경 가능한 데이터
  • state는 개발자가 정한다.
  • 렌더링이나 데이터 흐름에 사용되는 값만 스테이트에 포함시켜야 한다 --> 스테이트가 변경될 경우 컴포넌트가 재렌더링 되는데 렌더링과 관련 없는 값을 포함시키게 되면 불필요한 렌더링이 일어나게 된다. => 성능저하
  • state는 1. JavaScript 객체이다 2. 직접 수정할 수 없다 setter 사용해야함.

Lifecycle

  • Lifecycle: 리액트 클래스 Component의 생명주기
  • constructor / render
  • mounting / updating / unmounting
  • 컴포넌트는 시간의 흐름에 따라 생성되고 업데이트 되다가 사라진다.

(실습)state 사용하기

Section 7. Hooks

Hooks의 개념과 useState, useEffect

  • function component: state 사용 불가. Lifecycle에 따른 기능 구현 불가
  • class component: 생성자에서 state 정의. setState()로 state 업데이트. lifecycle methods 제공
  • function component의 기능 부족을 지원하기 위해 Hooks 탄생. 훅을 이용하면 함수 컴포넌트도 클래스 컴포넌트와 동일한 기능을 사용 가능
  • use땡땡 으로 선언해서 사용

useState()

  • const [변수명, set함수명] = setState(초기값)
  • 클래스 컴포넌트는 setState 하나로 모든 state를 업데이트할 수 있었지만
  • 함수 컴포넌트는 변수 각각 setState가 따로 존재

useEffect()

  • side effect를 수행하기 위한 hook
  • useEffect(이펙트 함수, 의존성 배열)
  • 의존성 배열 안에 있는 값 중 하나라도 변경되면 effect함수가 실행됨
  • useEffect(이펙트 함수, []) : 빈 배열. Effect function이 mount, unmont 시 단 한 번만 실행
  • useEffect(이펙트 함수): 의존성 배열 생략 시 컴포넌트가 업데이트 될 때마다 호출 됨
useEffect(() => {
    // 컴포넌트가 마운트 된 이후,
    // 의존성 배열에 있는 변수들 중 하나라도 값이 변경되었을 때 실행됨
    // 의존성 배열에 빈 배열([])을 넣으면 마운트와 언마운트시에 단 한 번씩만 실행됨
    // 의존성 배열 생략 시 컴포넌트 업데이트 시마다 실행됨
 	// class component의 componentDidMount, componentDidUpdate와 비슷하게 작동.
    ...
    return () => {
  		// 컴포넌트가 마운트 해제되기 전에 실행됨
      	// class component의 componentDidUnmount와 비슷하게 작동.
      	...
	}
}, [의존성 변수1, 의존성 변수2, ...]);     

useMemo, useCallback, useRef

useMemo

  • Memoized value를 리턴하는 hook
  • Memoization: 연산량이 많은 함수의 호출 결과를 저장해뒀다가 같은 함수를 사용할 때 새로 함수를 호출하지 않고 이전에 저장해뒀던 값을 사용
  • 흠 useMemo는 렌더링이 일어날 때 실행됨. 그렇기 때문에 렌더링이 일어나는 동안 작동하면 안 되는 코드를 넣어서는 안 됨. 후자의 경우는 useEffect를 사용해야 함
const memoizedValue = useMemo(
  () => {
    // 연산량이 높은 작업을 수행하여 결과를 반환
    return computeExpensiveValue(의존성 변수1, 의존성 변수2);
  },
  [의존성 변수1, 의존성 변수2]
);

useCallback

  • useMemo()와 유사하지만 값이 아닌 함수를 반환
const memoizeddCallback = useCallback(
  () => {
    doSomething(의존성 변수1, 의존성 변수2);
  },
  [의존성 변수1, 의존성 변수2]
);

useRef

  • reference를 사용하기 위한 훅
  • 리액트에서 reference란: 특정 컴포넌트에 접근할 수 있는 객체
const refContainer = useRef(초기값);
  • useRef는 내부의 데이터가 변경되었을 때 별도로 알리지 않는다.
  • 돔 노드의 변화를 알기 위해선 Callback ref를 사용해야 함

Hook의 규칙과 Custom Hook 만들기

Hook 규칙

  1. Hook은 무조건 최상위 레벨에서만 호출해야 한다.

    • 반복문이나 조건문, 중첩된 함수 안에서 호출되면 안 된다.
    • Hook은 컴포넌트가 렌더링될 때마다 매번 같은 순서로 호출되어야 한다.
  2. 리액트 함수 컴포넌트에서만 Hook을 호출해야 한다.

잘못된 훅 사용법

function MyComponent(props) {
  const [name, setName] = useState('jihoh);
  if (name !== '') {
    useEffect(() => {
      ...
    });
  }
}
  • 조건문의 값이 참인 경우에만 useEffect가 실행된다.
  • 이 경우 조건문에 따라 hook의 호출이 달라진다. 이럼 안됨

Custom Hook 만들기

  • 이름이 use로 시작하고 내부에서 다른 Hook을 호출하는 하나의 자바스크립트 함수.
  • 각 custom hook은 분리된 state를 얻게 된다.
  • custom hook의 호출 또한 완전히 독립적이다.
  • 그렇다면 훅들 사이에서 데이터를 공유하려면? : 커스텀훅의 파라미터로 state를 전달

(실습)Hooks 사용해보기

// useCounter.jsx
import React, { useState } from "react";

function useCounter(initialValue) {
	const [count, setCount] = useState(initialValue);

	const increaseCount = () => setCount((count) => count + 1);
	const decreaseCount = () => setCount((count) => Math.max(count - 1, 0));
	
	return [count, increaseCount, decreaseCount];
}

export default useCounter;
// Accommodate.jsx
import React, { useState, useEffect } from "react";
import useCounter from "./useCounter";

const MAX_CAPACITY = 10;

function Accommodate(props) {
	const [isFull, setIsFull] = useState(false);
	const [count, increaseCount, decreaseCount] = useCounter(0);

	useEffect(() => {
		console.log("=====================");
		console.log("useEffect() is called")
		console.log(`isFull: ${isFull}`);
	});

	useEffect(() => {
		setIsFull(count >= MAX_CAPACITY);
		console.log(`Current count value: ${count}`);
	}, [count]);

	return (
		<div style={{ padding: 16 }}>
			<p>{`${count}명 수용했습니다.`}</p>

			<button onClick={increaseCount} disabled={isFull}>
				입장
			</button>
			<button onClick={decreaseCount}>퇴장</button>

			{isFull && <p style={{ color: "red" }}>정원이 가득찼습니다.</p>}
		</div>
	);
}

export default Accommodate;
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import Library from './chapter_03/Library';
import Clock from './chapter_04/Clock';
import CommentList from './chapter_05/CommentList';
import NotificationList from './chapter_06/NotificationList';
import Accommodate from './chapter_07/Accommodate';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <React.StrictMode>
    <Accommodate />
  </React.StrictMode>,
  document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

  • count에 의존성을 갖고 있는 hook은 입장 인원이 가득 차 count값이 더 이상 변하지 않았기 때문에 마지막엔 실행되지 않았다.

8. Handling Events

Event의 정의 및 Event 다루기

  • DOM의 이벤트 핸들링과 react의 이벤트 핸들링 표기법이 좀 다름
  • react는 camelCase로 이벤트 적고 {}로 함수 사용
  • 자바스크립트에선 기본적으로 클래스 함수들이 바운드 되지 않음. 그렇기 때문에 bind를 이용해서 바인딩을 해줘야 한다. 바인딩 안 하면 글로벌 스코프에서 함수 찾음.
  • 일반적으로 함수 이름 뒤에 괄호 없이 사용하려면 해당 함수를 바인드 해줘야 한다.
  • 바인딩을 하지 않고 class fields syntax 사용할 수도 있음

class component의 event handler

// bind 사용해야 하는 예
class Toggle extends React.Component {
  constructor(props) {
	super(props);
    this.state = { isToggleOn: true };
    
    // callback에서 'this'를 사용하기 위해선 바인딩 필수
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }
  
  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? '켜짐' : '꺼짐'}
      </button>
    );
  }
}
// class fields syntax 사용
class MyButton extends React.Component {
  // class fields
  handleClick = () => {
    console.log('this is:', this);
  }
  
  rendef() {
    return (
      <button onClick={this.handleClick}>
        클릭
      </button>
    );
  }
}
// 이벤트 핸들러에 arrow function을 넣는 방식
class MyButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }
  
  render() {
    // 이렇게 하면 'this'가 바운드됩니다.
    // 마이버튼 컴포넌트가 렌더링 될 때마다 다른 콜백함수가 생성된다는 문제가 있음
	// 이 콜백 함수가 하위 컴포넌트의 props으로 넘겨지게 되면 하위 컴포넌트에서 추가적인 렌더링이 발생
	// 성능 문제를 피하기 위해선 binding이나 class fields syntax를 사용하는 게 좋음
    return (
      <button onClick={() => this.handleClick()}>
        클릭
      </button>
    );
  }
}
  • 지금은 클래스 컴포넌트를 거의 사용하지 않기 때문에 이 내용들은 참고로만 알고 있음 됨

function component의 event handler

function Toggle(props) {
  const [isToggleOn, setIsToggleOn] = useState(true);
  
  // 방법 1. 함수 안에 함수로 정의
  function handleClick() {
    setIsToggleOn((isToggleOn) => !isToggleOn);
  }
  
  // 방법 2. arrow function을 사용하여 정의
  const handleClick = () => {
    setIsToggleOn((isToggleOn) => !isToggleOn);
  }
  
  return (
    // 함수 컴포넌트는 이벤트를 넣어줄 때 this를 사용하지 않고 곧바로 정의한 이벤트 핸들러를 전달하면 됨
    <button onClick={handleClick}>
      {isToggleOn ? "켜짐" : "꺼짐"}
    </button>
  );

class component의 argument 전달

// 1. arrow function 사용
// 명시적으로 react의 event 객체를 두 번째 매개변수로 전달
<button onClick={(event) => this.deleteItem(id, event)}>삭제하기</button>

// 2. bind 사용
// 리액트가 알아서 event를 id 이후의 매개변수로 전달
<button onClick={this.deleteItem.bind(this, id)}>삭제하기</button>
  • 클래스 컴포넌트 방식은 지금은 거의 사용하지 않음

function component의 argument 전달

function MyButton(props) {
  const handleDelete = (id, event) => {
    console.log(id, event.target);
  };
  
  return (
    <button onClick={(event) => handleDelete(1, event)}>
      삭제하기
    </button>
  );
}

(실습)클릭 이벤트 처리하기

class component bind 이용한 이벤트 핸들러

import React from "react";

class ConfirmButton extends React.Component {
	constructor(props) {
		super(props);

		this.state = {
			isConfirmed: false,
		}
		this.handleConfirm = this.handleConfirm.bind(this);
	};

	handleConfirm() {
		this.setState((prevState) => ({
			isConfirmed: !prevState.isConfirmed,
		}));
	}

	render() {
		return (
			<button
				onClick={this.handleConfirm}
				disabled={this.state.isConfirmed}
			>
				{this.state.isConfirmed ? "확인됨" : "확인하기"}
			</button>
		);
	}
}

export default ConfirmButton;

class component class fields syntax 이용한 이벤트 핸들러

import React from "react";

class ConfirmButton extends React.Component {
	constructor(props) {
		super(props);

		this.state = {
			isConfirmed: false,
		};
	}

	handleConfirm = () => {
		this.setState((prevState) => ({
			isConfirmed: !prevState.isConfirmed,
		}));
	}

	render() {
		return (
			<button
				onClick={this.handleConfirm}
				disabled={this.state.isConfirmed}
			>
				{this.state.isConfirmed ? "확인됨" : "확인하기"}
			</button>
		);
	}
}

export default ConfirmButton;

function component 이벤트 핸들러

import React, { useState } from "react";

function ConfirmButton(props) {
	const [isConfirmed, setIsConfirmed] = useState(false);

	const handleConfirm = () => {
		setIsConfirmed((prevIsConfirmed) => !prevIsConfirmed);
	};

	return (
		<button onClick={handleConfirm} disable={isConfirmed}>
			{isConfirmed ? "확인됨" : "확인하기"}
		</button>
	);
}

export default ConfirmButton;

0개의 댓글

관련 채용 정보