심리스 - 리액트 공식문서 2일차 : State and Lifecycle, 이벤트 처리하기, 조건부 렌더링, 리스트와 Key

Seuling·2022년 7월 22일
0

FE

목록 보기
37/42
post-thumbnail

State and Lifecycle

함수형 컴포넌트에서 class 컴포넌트로 변환하기

  1. React.Component를 확장하는 동일한 이름의 ES6 class를 생성한다.
  2. render()라고 불리는 빈 메서드를 추가한다.
  3. 함수의 내용을 render() 메서드 안으로 옮긴다.
  4. render() 내용 안에 있는 props를 this.props로 변경한다.
  5. 남아있는 빈 함수 선언을 삭제한다.

실습

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}
class Clock extends React.Component {
 render(){
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
 } 
}

render 메서드는 업데이트가 발생할 때마다 호출되지만, 같은 DOM 노드로 을 렌더링하는 경우 Clock 클래스의 단일 인스턴스만 사용된다.
이것은 로컬 state생명주기 메서드와 같은 부가적인 기능을 사용할 수 있게 해준다.

단일 인스턴스만 사용 ?
인스턴스 ??
인스턴스는 일반적으로 실행 중인 임의의 프로세스, 클래스의 현재 생성된 오브젝트를 가리킨다.
객체(오브젝트)의 인스턴스는 데이터베이스나 SGA, 백그라운드 프로세스등 광범위한 컴퓨터시스템 자원의 접근에 할당된 물리 메모리의 일부를 가리킨다.

클래스에 로컬 State 추가하기

1. render() 메서드 안 : this.props.date > this.state.date 변경

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

2. this.state를 지정하는 class constructor를 추가

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

여기서 어떻게 props를 기본 constructor에 전달하지 ??? 🤔

클래스 컴포넌트는 항상 props로 기본 constructor를 호출해야 한다.

  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

3. 요소에서 date prop을 삭제

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

👇🏻

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

생명주기 메서드를 클래스에 추가하기

많은 컴포넌트가 있는 애플리케이션에서 컴포넌트가 삭제될 때 해당 컴포넌트가 사용 중이던 리소스를 확보하는 것이 중요하다.

마운팅 , componentDidMount():

  • Clock이 처음 DOM에 렌더링 될 때마다 타이머를 설정하는 것
  • 컴포넌트 출력물이 DOM에 렌더링 된 후에 실행된다.
  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

언마운팅, componentWillUnmount() : Clock에 의해 생성된 DOM이 삭제될 때마다 타이머를 해제 하는 것

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

State를 올바르게 사용하기

1. 직접 State를 수정 금지

왜 ?? 다시 렌더링하지 않기에!!

this.state.comment = 'Hello';

대신에 setState()를 사용하기!

// Correct
this.setState({comment: 'Hello'});

this.state를 지정할 수 있는 유일한 공간은 바로 constructor이다.

2. State 업데이트는 비동기적일 수도 있다.

React는 성능을 위해 여러 setState() 호출을 단일 업데이트로 한꺼번에 처리할 수 있다.

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

객체보다는 함수를 인자로 사용하는 다른 형태의 setState()를 사용한다.
그 함수는 이전 state를 첫 번째 인자로 받아들일 것이고, 업데이트가 적용된 시점의 props를 두 번째 인자로 받아들일 것이다.

3. State 업데이트는 병합된다.

setState()를 호출할 때 React는 제공한 객체를 현재 state로 병합한다.

  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }

별도의 setState() 호출로 이러한 변수를 독립적으로 업데이트할 수 있다!

4. 데이터는 아래로 흐릅니다

  • state는 종종 로컬 또는 캡슐화라고 불린다.
  • state가 소유하고 설정한 컴포넌트 이외에는 어떠한 컴포넌트에도 접근할 수 없기에!
  • 일반적으로 “하향식(top-down)” 또는 “단방향식” 데이터 흐름이라고 한다.
  • 모든 state는 항상 특정한 컴포넌트가 소유하고 있으며 그 state로부터 파생된 UI 또는 데이터는 오직 트리구조에서 자신의 “아래”에 있는 컴포넌트에만 영향을 미친다.

이벤트 처리하기

캐멀케이스 사용

JSX를 사용하여 함수로 이벤트 핸들러 전달

기본동작 방지 X

React에서는 false를 반환해도 기본 동작을 방지할 수 없다.
반드시 preventDefault를 명시적으로 호출해야 한다.

addEventListener 호출 필요 X

엘리먼트가 처음 렌더링될 때 리스너를 제공하면 된다.
콜백에 화살표함수 사용하면 됨!

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}

지난 프로젝트 시 생겼던 문제!

리액트에서 함수 연결 할 때는 호출을 하면 안되고 함수 선언을 넘겨야 한다!!
리액트 함수 파라미터 전달 구글링 -> 화살표 함수를 이용하여 원래 부르려던 함수를 감싸고, 해당 함수의 호출부에서 인자를 넘겨주기

const handleDeleteClick = (
    params: number | null,
    e: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    console.log("삭제클릭");
    console.log(params);
    deleteAdList(params);
  };

<Button onClick={(e) => {handleDeleteClick(dailyAd.id, e);}} </Button>

그러나! 여기에도 문제가 있다!!!

이 문법의 문제점은 LoggingButton이 렌더링될 때마다 다른 콜백이 생성된다는 것!!

대부분의 경우 문제가 되지 않으나, 문제 발생하는 경우 ?

  • 콜백이 하위 컴포넌트에 props로서 전달된다면 그 컴포넌트들은 추가로 다시 렌더링을 수행할 수도 있다.
  • 이러한 종류의 성능 문제를 피하고자, 생성자 안에서 바인딩하거나 클래스 필드 문법을 사용하는 것을 권장한다.

조건부 렌더링

  • React에서는 원하는 동작을 캡슐화하는 컴포넌트를 만들 수 있다.
  • 애플리케이션의 상태에 따라서 컴포넌트 중 몇 개만을 렌더링할 수 있다.

엘리먼트 변수

엘리먼트를 저장하기 위해 변수를 사용할 수 있다.
출력의 다른 부분은 변하지 않은 채로 컴포넌트의 일부를 조건부로 렌더링 할 수 있다.

if 로 조건부 렌더링

  if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

논리 && 연산자로 If를 인라인으로 표현하기

JSX 안에는 중괄호를 이용해서 표현식을 포함 할 수 있다.
그 안에 JavaScript의 논리 연산자 &&를 사용하면 쉽게 엘리먼트를 조건부로 넣을 수 있다.

 {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }

true && expression : 항상 expression
false && expression : 항상 false

따라서 && 뒤의 엘리먼트는 조건이 true일때 출력이 된다.
조건이 false라면 React는 무시하고 건너뜀.

조건부 연산자로 If-Else구문 인라인으로 표현하기

condition ? true: false

  The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.

컴포넌트가 렌더링하는 것을 막기

렌더링 결과를 출력하는 대신 null을 반환하는 것으로 해결

리스트와 Key

map()에서 반환하는 새 배열 만들기!

여러개의 컴포넌트 렌더링 하기

엘리먼트 모음을 만들고 중괄호 {}를 이용하여 JSX에 포함 시킬 수 있다.

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);
ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);

기본 리스트 컴포넌트

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li>{number}</li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

이 코드를 실행하면 리스트의 각 항목에 key를 넣어야 한다는 경고가 표시된다.
주로 나오는 문제 ? key!!!
“key”는 엘리먼트 리스트를 만들 때 포함해야 하는 특수한 문자열 어트리뷰트이다.

키 ????

  • Key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕는다.
  • key는 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 한다.
  • Key를 선택하는 가장 좋은 방법은 ??
    • 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것
    • 대부분의 경우 데이터의 ID를 key로 사용한다.
    • 렌더링 한 항목에 대한 안정적인 ID가 없다면 -> 최후의 수단으로 항목의 인덱스를 key로 사용할 수 있다.
    • 리스트 항목에 명시적으로 key를 지정하지 않으면 React는 기본적으로 인덱스를 key로 사용한다! (오호!)

왜 인덱스는 최후의 수단이지 ?

  • 항목의 순서가 바뀔 수 있는 경우 key에 인덱스를 사용하는 것은 권장하지 않는다.
  • 이로 인해 성능이 저하되거나 컴포넌트의 state와 관련된 문제가 발생할 수 있기에!

Key로 컴포넌트 추출하기

키는 주변 배열의 context에서만 의미가 있다.

예를 들어 ListItem 컴포넌트를 추출 한 경우 ListItem 안에 있는 <li> 엘리먼트가 아니라 배열의 <ListItem /> 엘리먼트가 key를 가져야 한다.

경험상 map() 함수 내부에 있는 엘리먼트에 key를 넣어 주는 게 좋다!

Key는 형제 사이에서만 고유한 값이어야 한다.

  • Key는 배열 안에서 형제 사이에서 고유해야 하고 전체 범위에서 고유할 필요는 없다.
  • 두 개의 다른 배열을 만들 때 동일한 key를 사용할 수 있다.

JSX에 map() 포함시키기

JSX를 사용하면 중괄호 안에 모든 표현식을 포함 시킬 수 있으므로 map() 함수의 결과를 인라인으로 처리할 수 있다.

function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />
      )}
    </ul>
  );
}

map() 함수가 너무 중첩된다면 컴포넌트로 추출 하는 것이 좋다!!

profile
프론트엔드 개발자 항상 뭘 하고있는 슬링

0개의 댓글