본문 바로가기
React

리액트 기초 2강 정리

by imagineer_jinny 2021. 4. 10.

화면을 예쁘게! - SCSS

 

패키지 설치 해야함

yarn add node-sass@4.14.1 open-color sass-loader classnames

 

화면을 예쁘게! - styled-components

 

패키지 설치

yarn add styled-components
import React from 'react';
// 패키지에서 styled를 불러올게요!
import styled from 'styled-components';

function App() {
  return (
    <div className="App">
      {/* props로 bgColor를 줘볼까요! */}
      <MyStyled bgColor를={"red"}>hello React!</MyStyled>
    </div>
  );
}

// scss처럼 자기 자신을 지칭할 때 &를 쓸 수 있습니다!
// props 주는 방법! 이제 알고 있죠?
const MyStyled = styled.div`
  width: 50vw;
  min-height: 150px;
  padding: 10px;
  border-radius: 15px;
  color: #fff;
  &:hover{
    background-color: #ddd;
  }
  background-color: ${(props) => (props.bgColor를 ? "red" : "purple")};
`;
export default App;

가상돔이란?

 

DOM 트리 중 하나가 수정될 때마다 모든 DOM을 뒤지고, 수정할 걸 찾고, 싹 수정을 한다면? → 필요없는 연산이 너무 많이 일어난다! → 그래서 등장한 게 가상돔!

가상돔은 메모리 상에서 돌아가는 가짜 DOM입니다.

 

가상돔의 동작 방식:

기존 DOM과 어떤 행동 후 새로 그린 DOM(가상 돔에 올라갔다고 표현합니다)을 비교해서

정말 바뀐 부분만 갈아끼워줍니다! → 돔 업데이트 처리가 정말 간결하죠!

돔을 그리고 갈아 끼우는 것을 '렌더링', '리렌더링'이라고 부름

 

어렵지 않죠! 그럼 어떤 행동을 해야 DOM을 새로 그릴까요?

- 처음 페이지 진입했을 때도 그리겠죠!

- 데이터가 변했을 때도 그릴 겁니다!

 

라이프 사이클이란?

 

컴포넌트가 렌더링을 준비하는 순간부터, 페이지에서 사라질 때까지가 라이프 사이클

  • 컴포넌트는 생성되고 → 수정(업데이트)되고 → 사라집니다.
  • 생성은 처음으로 컴포넌트를 불러오는 단계입니다.
  • 수정(업데이트)는 사용자의 행동(클릭, 데이터 입력 등)으로 데이터가 바뀌거나, 부모 컴포넌트가 렌더링할 때 업데이트 됩니다. 아래의 경우죠!
    • props가 바뀔 때
    • state가 바뀔 때
    • 부모 컴포넌트가 업데이트 되었을 때(=리렌더링했을 때)
    • 또는, 강제로 업데이트 했을 경우! (forceUpdate()를 통해 강제로 컴포넌트를 업데이트할 수 있습니다.)
  • 제거는 페이지를 이동하거나, 사용자의 행동(삭제 버튼 클릭 등)으로 인해 컴포넌트가 화면에서 사라지는 단계입니다.

알아두면 좋을 라이프 사이클 함수

라이프 사이클 함수는 클래스형 컴포넌트에서만 사용할 수 있습니다.

라이프 사이클을 아는 건 중요한데 왜 우리는 클래스형 컴포넌트보다 함수형 컴포넌트를 많이 쓰냐구요? 리액트 공식 매뉴얼에서 함수형 컴포넌트를 더 권장하기 때문입니다!

(리액트 16.8버전부터 등장한 React Hooks으로 라이프 사이클 함수를 대체할 수 있거든요.)

 

-constructor()

생성자 함수. 컴포넌트가 생성되면 가장 처음 호출됨.

 

-render()

컴포넌트의 모양을 정의하는 친구입니다!

여기에서도 state, props에 접근해서 데이터를 보여줄 수 있어요.

리액트 요소를 return에 넣어 반환해줬던 거 기억하시죠?

render() 안에 들어갈 내용은 컴포넌트의 모양에만 관여하는 것이 가장 좋습니다.

즉, state나, props를 건드려 데이터를 수정하려고 하면 안됩니다!

 

-componentDidMount()

컴포넌트가 화면에 나타나는 것을 마운트(Mount)한다고 표현합니다.

didMount()는 마운트가 완료 되었다는 소리겠죠? 이 함수는 첫번째 렌더링을 마친 후에만 딱 한 번 실행됩니다. 컴포넌트가 리렌더링할 때는 실행되지 않아요. 보통은 이 안에서 ajax 요청, 이벤트 등록, 함수 호출 등 작업을 처리합니다. 또, 이미 가상돔이 실제돔으로 올라간 후니까 DOM 관련 처리를 해도 됩니다!

 

-componentDidUpdate(prevProps, prevState, snapshot)

DidMount()가 첫 렌더링 후에 호출 되는 함수라면, DidUpdate()는 리렌더링을 완료한 후 실행되는 함수입니다.

이 함수에 중요한 파라미터가 2개 있는데, prevProps와 prevState입니다. 각각 업데이트 되기 전 props, state예요.

이전 데이터와 비교할 일이 있다면 가져다 쓰도록 합시다. DidUpdate()가 실행될 때도 가상돔이 실제돔으로 올라간 후니까 DOM 관련 처리를 해도 됩니다!

 

-componentWillUnmount()

 

알면 덜 스트레스 받는 리액트 상식:

어?! 왜 render도 constructor도 두번씩 찍히는거죠?! src/index.js 파일을 열어보세요!

아마 <React.StrictMode>라는 녀석이 <App/>을 감싸고 있을거예요.

strictmode는 우리가 만든 애플리케이션이 문제는 없는지, 좀 더 깐깐히 검사해주는 모드예요.

우리 로컬 환경에서만 활성화되니, 걱정말기! (보고 싶지 않다면 지우고 <App/>만 남겨두셔도 좋습니다.)

 

Ref! 리액트에서 돔요소를 가져오려면?

 

Ref와 DOM – React (reactjs.org)

 

Ref와 DOM – React

A JavaScript library for building user interfaces

ko.reactjs.org

Ref는 render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공합니다.

 

React.createRef()

이제 가상돔이 뭔지, 돔이 뭔진 알겠죠? 라이프 사이클도 알구요. 그런데 만약에, 내가 어떤 인풋박스에서 텍스트를 가져오고 싶으면 어떻게 접근해야할까요? (render()가 끝나고 가져오면 될까요? 아니면 mount가 끝나고? 아니, 그 전에 가상돔에서 가져오나? 아니면 DOM에서? 😖) → 답은, 리액트 요소에서 가져온다!

 

-React 요소를 가지고 오는 방법: React.createRef()

import React from "react";
import logo from "./logo.svg";
// BucketList 컴포넌트를 import 해옵니다.
// import [컴포넌트 명] from [컴포넌트가 있는 파일경로];
import BucketList from "./BucketList";
import styled from "styled-components";

// 클래스형 컴포넌트는 이렇게 생겼습니다!
class App extends React.Component {
  constructor(props) {
    super(props);
    // App 컴포넌트의 state를 정의해줍니다.
    this.state = {
      list: ["영화관 가기", "매일 책읽기", "수영 배우기"],
    };
    // ref는 이렇게 선언합니다! 
    this.text = React.createRef();
  }

  componentDidMount(){
		// 콘솔에서 확인해보자!
    console.log(this.text);
		console.log(this.text.current);
  }

  // 랜더 함수 안에 리액트 엘리먼트를 넣어줍니다!
  render() {
    
    return (
      <div className="App">
        <Container>
          <Title>내 버킷리스트</Title>
          <Line />
          {/* 컴포넌트를 넣어줍니다. */}
          {/* <컴포넌트 명 [props 명]={넘겨줄 것(리스트, 문자열, 숫자, ...)}/> */}
          <BucketList list={this.state.list} />
        </Container>

        <div>
          <input type="text" ref={this.text}/>
        </div>
      </div>
    );
  }
}

 

constructor 생성자 함수에 ref를 선언해준다.

// ref는 이렇게 선언합니다! 
    this.text = React.createRef();

그다음 렌더에서 보여질 무언가를 만들기 위해 <div>사이에 <input>박스 하나 만들어주고

만든 ref를 써준다!

<div>
	<input type="text" ref={this.text}/>
</div>

 

콘솔에서 확인해주기 위해 componentDidMount()를 만들어준다.

componentDidMount(){
		// 콘솔에서 확인해보자!
    console.log(this.text); //1번
	console.log(this.text.current); //2번
  }

화면에서 확인해보면 인풋 생긴거 알 수 있고 콘솔도 잘 찍힌걸 볼 수 있다.

 

createRef라는 것은 우리가 가지고 오고 싶어 하는 어떤 거를 이 current라는 변수명 안에다 넣어주는 거구나!


새 프로젝트를 만들어보자!

 

아래의 (1)~(3)은 이후 새 프로젝트를 만들 때마다 하실 부분입니다!

 

(1) 새 CRA 만들기

yarn create react-app nemo

(2) index.js에서 <React.StrictMode> 부분을 지우기

(3) App.js를 class형 컴포넌트로 바꾸고 시작! (그냥 연습하기 위해서 바꿔보는 것. 큰 의미 없음)


State 관리 (1) : 클래스형 컴포넌트에서 state 관리 - setState()

 

단방향 데이터 흐름이란?

데이터는 위에서 아래로, 부모에서 자식으로 넘겨줘야 한다는 소리.

 

왜 단방향으로 써야하지?

  • 라이프 사이클과 함께 생각해보기 부모 컴포넌트의 state가 업데이트 되면 자식 컴포넌트도 리렌더링이 일어납니다. 만약 자식 컴포넌트의 state가 바뀐 걸 부모 컴포넌트가 props로 받는다고 생각해봅시다. 그러면 자식 컴포넌트가 업데이트 될 때 부모 컴포넌트도 업데이트 되겠죠? 앗..., 그럼 또 자식 컴포넌트가 리렌더링될거구요. 😢

클래스형 컴포넌트로 addNemo 만들어보기 

//App.js

import React from 'react';
import Nemo from "./Nemo"

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

    this.state={
      count:3,
    };
  
  }

  //state를 고쳐주는 함수를 만들어보자
  addNemo =()=>{
    //state 고치는 함수? setState!!
    this.setState({count:this.state.count +1});//{}안에 state값을 넣어주면 됨
    console.log('add nemo');
  }

  removeNemo =()=>{
    if(this.state.count>0)
    {
      this.setState({count:this.state.count -1});
    }else{
      window.alert('네모가 없어요!');
    }

    console.log('remove nemo');
  }

  //뷰담당
  render(){
    //숫자의 길이만큼 크기를 가진 배열로 반환해주는 배열의 내장함수
    //배열을 담을 변수                  //v는 아무거나 들어가고 이 뒤엔 순서가 들어감
    const nemo_count=Array.from({length: this.state.count},(v,i)=>(i))
    console.log(nemo_count);
    return (
      <div className="App">
        {/* 이 순서대로 맵 돌리면 되겠구나! */}
        {/* 첫번째 들어갈건 숫자 하나하나 값 */}
        {/* 그 다음에 들어갈 건 인덱스 값 */}
        {nemo_count.map((num,idx)=>{
          return(
            // 리턴으로 리액트 요소 반환
            <div
              key={idx} //고유 키 값 넣어주기
              style={{
              width:'150px',
              height:'150px',
              backgroundColor:'#eee',
              margin:'10px'
            }}>
              nemo
            </div>
          );
        })}
        <button onClick={this.addNemo}>하나 추가</button>
        <button onClick={this.removeNemo}>하나 빼기</button>
       
      </div>
    );
  }

}

export default App;

*문법 보충*

Array, map 돌리는 것 잘 모르겠음 

 

State 관리 (2) : 함수형 컴포넌트에서 state 관리 - useState()

import React from 'react';

const Nemo =(props)=>{
  //첫번째에는 state로 쓸 변수 명 적어주고
  //두번째에는 count라는 값을 바꿔줄 함수를 만들어준다.
  //함수 안에 값은 초기값을 뜻함
  //즉, count에 처음부터 들어갈 값을 뜻함!!
  const [count, setCount] =React.useState(3);
  console.log('in nemo');
  console.log(count);
  
  const addNemo =()=>{
  
    setCount(count+1);

  }

  const removeNemo =()=>{
     
    setCount(count>0? count-1:window.alert('네모없음'));

    
  }
  const nemo_count = Array.from({ length: count }, (v, i) => i);
  // 반환할 리액트 요소가 없을 때는 null을 넘겨주세요!
  return (
    <div className="App">
      {nemo_count.map((num, idx) => {
        return (
          <div
            key={idx}
            style={{
              width: "150px",
              height: "150px",
              backgroundColor: "#ddd",
              margin: "10px",
            }}
          >
            nemo
          </div>
        );
      })}

      <div>
        <button onClick={addNemo}>하나 추가</button>
        <button onClick={removeNemo}>하나 빼기</button>
      </div>
    </div>
  );

}
export default Nemo;

 

-버킷리스트에 아이템을 추가해보자! 

 

*문법 보충*

Spread 문법 이용해서 배열에 요소 추가 잘 모르겠음

class App extends React.Component {
  constructor(props) {
    super(props);
    // App 컴포넌트의 state를 정의해줍니다.
    this.state = {
      list: ["영화관 가기", "매일 책읽기", "수영 배우기"],
    };

    this.text= React.createRef();
  }

  componentDidMount(){
    console.log(this.text);
    console.log(this.text.current);
  }

  SendList=()=>{
    let list=this.state.list;
    const new_item=this.text.current.value;
    
    //spread 문법이용해서 배열에 요소 추가
    //하나하나에 아이템이 들어가고 그 옆에 뉴 아이템이라는게 들어간
    //배열이 생김!
    //이건 배열이 새로 생기는 것이군!
    this.setState({list:[...list, new_item]});
   
    
  }

 

-Event Listener

이벤트 참조 | MDN (mozilla.org)

 

이벤트 참조 | MDN

이벤트 참조 DOM 이벤트는 발생한 흥미로운 것을 코드에 알리기 위해 전달됩니다. 각 이벤트는 Event 인터페이스를 기반으로한 객체에 의해 표현되며 발생한 것에 대한 부가적인 정보를 얻는데

developer.mozilla.org

이벤트 리스너란?

이벤트 리스너는 사용자가 어떤 행동(=이벤트)을 하는 지 아닌 지 지켜보다가 알려주는 것입니다. 대표적으로는 마우스 클릭, 터치, 마우스 오버, 키보드 누름 등이 자주 쓰여요!

 

이벤트 리스너는 <div onClick={}>에서처럼 엘리먼트에 직접 넣어줄 수도 있지만, 이번엔 addEventListener를 통해 추가해볼거예요.

 

클래스형 컴포넌트에서 event listener 구독하기

이벤트 리스너는 어디에 위치해야할까요? 클릭을 하건, 마우스를 올리건 DOM 요소가 있어야 이벤트가 발생하는 지 지켜볼 수 있겠죠? → componentDidMount()에 넣어주면 됩니다.

 

이때 주의해야할 것: 컴포넌트가 사라지기 전에는 Event listener 구독을 해제해주기!

어디서? → componentWillUnMount()에서!

 

 

nemo 프로젝트에서 이벤트 리스너 연습 해보자!

 

하고 싶은 것: App이라는 커다란 div에 마우스를 올리면 배경 색이 바뀌는 걸 하고 싶음

행동: 그럼 뭐부터 해야해? App이라는 div에 ref 부터 잡아야한다.

 

 

잡은 다음에?

ref를 연결했으니 addEventListener를 해야 하는데 addEventListener를 하기 위한 준비물 있음

파라미터 준비물 두개:

1) 어떤 이벤트일 때? 어떤 이벤트일 때 걸거냐?

2) 어떤 행동을 해줄거야? 하는 함수 하나

 

EventListener 등록해줘야함. 어디에서 등록? 돔이 일단 다 생겨야함.

componentDidMount 해서 등록해줄거임.

등록해줬으면 해제도 해주기

 <정리>

EventListener를 어떻게 구독하면 좋은지 순서

1. 일단 ref를 잡아주기. 돔에 접근을 해야 하는 거니까.

2. 뭘 해줄건지 정하기. mouseover 이벤트면 mouseover 이벤트가 일어났을 때

어떤걸 해주겠다 하는 함수를 만들어줌

3. 그 다음에 componentDidMount에서 뭘 해줬다? 등록을 해줬다.

4. 그리고 나서 이 컴포넌트가 사라질 때 쓸 데 없는 Event listener가 남아 있는거를 방지하기 위해서 componentWillUnMount에서 구독을 해제해줌

 

 

 

댓글