중요하지만 헷갈리는 리액트 개념 이해하기

fromzoo·2020년 12월 9일
0
post-thumbnail

📘 중요하지만 헷갈리는 리액트 개념 이해하기

✅ 리액트를 사용한 코드의 특징

const [todoList,  setTodoList] = useState([]);
// todoList -> 첫번째 아이템은 상태값
// setTodoList -> 두번째 아이템은 상태값 변경함수
  • useState를 사용하면 컴포넌트의 상태값을 추가할 수 있다. 매개변수는 상태값의 초기값 의미 (투두에서는 빈배열을 초기값으로 넣어줌)

  • useState 는 배열을 반환한다.

  • 상태값 변경함수를 호출해서 상태가 변경되면 리액트는 UI에 반영해준다.

  • 배열을 표현할때는 key 값을 입력해줘야한다. 리액트가 화면을 효율적으로 업데이트 할 수 있다.

  • 배열비구조화문법
    비구조화문법은 각아이템을 변수로 만들 수 있다.

const person = ['mike',23]
const [name,age] = person
  • 스프레드 연산자
    모든 아이템을 나열해주는 것과 같은 효과
const arr1 = [1,2,3]
const arr2 = [...arr1]
// arr2 = [arr1[0],arr1[1],arr1[2]] 처럼 나열하는 것과 같음

자바스크립트 todolist vs 리액트 todolist

  • 리액트는 비즈니스 로직과 UI 코드가 분리되어 있다.
    - 리액트는 데이터만 변경하는 코드가 있음
  • 리액트에서 UI 코드는 JSX 부분에서 한번만 작성해주면 된다.
  • js는 명령형 프로그래밍 / 리액트는 선언형 프로그래밍
  • 리액트는 UI가 어떤 모습일지 한눈에 보인다.
  • js는 DOM api가 구체적이고 DOM 환경이 아닌 곳에서는 사용하기 힘들다는 단점이 있다.
  • 리액트는 무엇을 그리는지만 나타내서 다양한 방식으로 그릴 수 있고,돔환경뿐 만아니라 모바일 네이티브의 UI도 표현할 수 있다.
    - 물론 돔 환경에 맞는 마크업을 사용했지만, Titile, List 같은 형태로 바꿔줘도 된다.
  • 이런 선언형 프로그래밍은 명령형보다 추상화단계가 높다.
    - 추상화단계가 높을수록 비즈니스 로직에 더 집중할 수 있다는 장점이 있다.

✅ 컴포넌트의 속성값과 상태값

리액트 컴포넌트에서 UI 데이터 다루는 방법

리액트 컴포넌트에서는 UI 데이터를 속성값이나 상태값으로 관리해야한다.

리액트가 값의 상태 변경 사실을 알기위해서는 상태값으로 관리해줘야한다.

상태값 설정

리액트에서 상태값을 설정하는 방법

const [color,setColor] = useState('red')

초기값은 red로 설정

function  onClick() {
	setColor('blue');
}

값을 변경할때는 setColor함수를 호출해준다.

속성값 변경

Title.js => 자식 컴포넌트

import React from  'react';

export default function Title(props) { 
	return <p>{props.title}</p>
}
  
function  Title({title}) {
	console.log('title render');
	return  <p>{title}</p>
}

export  default  React.memo(Title)
  • props 는 부모가 전달해주는 속성값
  • React.memo 속성값 title이 변경될때만 이 컴포넌트가 렌더링 된도록 도와주는 함수

counter.js =>Title에게 값을 전달해주는 부모 컴포넌트

import React, { useState } from  'react';
import Title from  './Title'

export default function Counter() {
	const [count,setCount] = useState(0);
	function onClick() {
		setCount(count + 1)
	}
	return (
		<div>
			<Title title={`현재 카운드:${count}`}/>
			<button onClick={onClick}>증가</button>
		</div>
	)
}
  • counter 에서 title이라는 속성값을 내려주고 있다.
    - 이때 count라는 상태값을 기반으로 title값을 계산한다.
  • count값이 변경되면 counter 컴포넌트는 다시 렌더링이 되고 title컴포넌트도 다시 렌더링이 된다.

부모컴포넌트가 렌더링 될 때 마다 자식도 렌더링 된다.

  • 만약 자식의 속성값이 변경되지 않았는데 굳이 렌더링할 필요 없을 때는?
    - 속성값이 변경될 때만 자식 컴포넌트가 렌더링 되도록 하고 싶다면 react.memo를 사용한다.
  • 같은 컴포넌트를 여러번 사용할 수도 있다.
    - 이때 같은 컴포넌트는 상태값을 위한 자신만의 메모리 공간이 있어서 같은 컴포넌트라고 하더라도 자신만의 상태값이 존재한다.
    - 각자 사용된 곳에서 상태값을 유지한다.

객체를 불변변수로 관리하는 방법

  • 속성값불변변수
    - 값을 변경하면 에러발생
    - 자식 컴포넌트에 전달되는 속성값은 상위 컴포넌트에서 관리하기 때문에 수정하지 못하도록 막혀있다.
    - 변경하려고 시도하면 read only라는 에러 발생
    - 데이터를 변경하려면 상위컴포넌트에서 관리하는 상태값 변경 함수를 이용해야한다.
export  default  function  Counter() {
	const [count,setCount] = useState({value1:0,value2:0,value:0});
	function  onClick() {
		setCount({ ...count, value:count.value  +  1 }); 
		// count를 객체에 있는 모든 속성을 풀어놓고, 변경하고자 하는 값만 할당해주는 것
	}
	return (
		<div>
			<Title  title={`현재 카운드:${count.value}`}/>
			<button  onClick={onClick}>증가</button>
		</div>
	)
}
  • 상태값불변변수가 아님
    - but 상태값도 불변변수로 관리하는게 좋다.
    - 객체를 불변변수로 관리하는 방법은 스프레드 연산자를 사용하는것

✅ 컴포넌트 함수의 반환값

컴포넌트에서는 기본적으로 리액트 요소를 반환할 수 있다.

export  default  function  App() {
	return <div> 안녕하세요</div>
}

물론 컴포넌트도 리액트 요소로 반환할 수 있다.

export  default  function  App() {
	return <Counter/>
}

단순히 문자열, 숫자, 배열도 반환할 수 있다.

export  default  function  App() {
	return 'abc'
}

배열로 반환할때는 리액트 요소가 key를 갖고있어야 한다.

export  default  function  App() {
	return [<p key={1}>안녕</p>,<p key={2}>하세요.</p>]
}

key는 렌더링을 효율적으로 하기위해서 필요한 값이다.
리액트가 이 값을 이용해서 virtualDom에서의 연산을 효율적으로 할 수 있다.

Fragment

컴포넌트가 반환할 수 있는 값 중 Fragment가 있다.

export  default  function  App() {
	return (
		<React.Fragment>
			<p>안녕</p>
			<p>하세요.</p>
		</React.Fragment>
	)
}
  • 이때 배열과 다르게 key가 없어도 에러가 나지 않는다.
  • Fragment는 요소의 순서가 key 역할을 한다.
  • Fragment는여러개 요소를 반환할때 유용하게 사용한다.
  • 비교적 최근에 추가된 문법
    - 그전에는 div로 감싸줬지만 불필요한 div가 생성되는 상황이 있을때가 있다.
  • Fragment를 사용하면 실제 DOM에는 반영이 안된다.
export  default  function  App() {
	return (
		<>
			<p>안녕</p>
			<p>하세요.</p>
		</>
	)
}

축약형으로도 사용. 아무것도 입력하지 않아도 동작한다.

export  default  function  App() {
	return (
		<>
			<p>안녕</p>
			{null}
			{true}
			{false}
		</>
	)
}
  • null이나 boolean 값도 반환할 수 있다.
  • 이러한 값들이 무시된다. (아무것도 출력이안됨)
  • 조건부 렌더링할 떄 유용하게 사용한다.
return (
	<div>
	{count.value  >  0  &&  <Title  title={`현재 카운드:${count.value}`}/>  }
	<button  onClick={onClick}>증가</button>
	</div>
)
  • 양의 정수만 출력하도록 조건부 렌더링을 주는 코드
  • &&은 왼쪽의 조건이 모두 만족이 돼야 뒤에 있는 것이 렌더링 된다는 뜻

Potal

컴포넌트에서는 리액트 Potal을 반환할 수 있다.

public > index.html

<div  id="root"></div>
<div  id="somthing"></div>

root가 말고 다른 멀리 떨어진 엘리먼트를 렌더링하고 싶을때 사용한다.

Potal을 사용하기 위해서 react-dom에 있는 함수를 사용해야 한다.

import ReactDOM from 'react-dom'
export  default  function  App() {
	return (
		<>
			<Counter></Counter>
			{ReactDOM.createPortal(
				<div>
					<p>안녕하세요.</p>
					<p>실전 리액트 프로그래밍입니다.</p>
				</div>,
				document.getElementById('something'),
			)}
		</>
	)
}
  • 두번째 매개변수는 html에 있는 요소를 입력하면 된다.
  • Potal은 보통 모달을 위해서 사용되기도 한다.

✅ 리액트 요소와 가상돔1

리액트 요소는 리액트가 UI를 표현하는 수단

  • 리액트는 렌더링 성능을 위해서 가상돔이라는 것을 활용
    - 빠른 렌더링을 위해서 돔 변경을 최소화 해줘야 함
  • 리액트는 메모리에 가상돔을 올려놓고 이전과 이후의 가상돔을 비교한다.
    - 변경된 부분만 실제돔에 반영하는 전략

리액트 요소로부터 가상돔을 만들어서 실제 돔에 반영할 변경사항을 찾는 과정을 알아보자.

const element = (
	<a  key="key1"  style={{width:100}}  href="http://google.com">click here</a>
)

console.log(element);

위의 리액트 요소를 출력한 결과

const consoleLogResult = {
	type: 'a', // a 태그
	key: 'key1', // key 속성을 넣어줬기떄문
	ref: null,
	props: {
		href:'http://google.com',
		style:{
			width:100,
		},
		children:'click here',
	}
	// ...
}
  • 객체 형식의 트리 구조 형태로 출력
  • DOM 요소이기 때문에 문자열이 입력이 됐지만 컴포넌트 같은 경우에는?

컴포넌트로 확인해보는 리액트 요소

function  Title({title,color}) {
	return  <p  style={{ color }}>  {title}  </p>
}

DOM 요소와는 다르게 type속성에 Title이라는 컴포넌트 함수가 출력되는 것이 확인된다.

const element = <Title  title="안녕하세요"  color="blue"></Title>

const consoleLogResult = {
	type : Title, 
	props : {title:'안녕하세요',color:'blue'}
}
  • 이 함수를 이용해서 리액트는 렌더링을 위한 충분한 정보를 얻을 수 있다.
  • Title함수를 리액트가 실행하면 함수 안에 있는 p태그의 값을 얻는다.

불변객체

const element = <a  href="http://google.com">click here</a>
element.type  =  'b'  // 에러발생
// 리액트 요소는 변경할 수가 없다.
  • 리액트 요소는 불변객체기 때문에 변경하려고 하면 에러가 발생한다.
  • 리액트 요소는 변경할 수가 없다.

변경된 부분만 실제 DOM에 반영된다?

App.js

export  default  function  App() {
	const [seconds,  setSeconds] = useState(0);
	useEffect(()=>{
		setTimeout(()  => {
			setSeconds(v=>v+1) // 1초에 한번씩 seconds 라는 상태값을 증가 시킴
		},  1000);
	});
	
	return (
	<div>
		<h1 style={{color:seconds  %  2  ?  'red'  :  'blue' }>안녕하세요.</h1>
		<h2>지금까지 {seconds}초가 지났습니다.</h2>
	</div>
	)
}
  • 브라우저에서 Elements 창을 확인해봤을때 {seconds}부분만 값이 변경되는 것을 확인할 수 있다.
  • 색을 변경했을때도 변경되는 부분만 색이 바뀐다.

만약 DOM요소의 key를 변경해본다면??

<div key={seconds}>

  • 상위에 있는 div의 key 값을 변경되는 {seconds} 값으로 변경해보았다.
  • 해당 dom요소가 삭제됐다가 추가됐다가하는 현상을 볼 수 있다.
  • 이렇게 key 를 변경하면 리액트는 이것은 다른 요소라고 판단해서 이전 것은 삭제하고 새로 만들어서 붙이기 때문에 이런 현상이 발생하는 것이다.

컴포넌트요소의 key를 변경한다면?

Counter 컴포넌트를 만든뒤

export  default  function  Counter() {
	const [count,setCount] = useState(0);
	function  onClick() {
		setCount(count  +  1)
	}
	return (
		<div>
			<p>{`현재 카운트: ${count}`}</p>
			<button  onClick={onClick}>증가</button>
		</div>
	)
}

<CounterChange key={seconds}/>

  • 위 코드에서 div의 key 값을 제거하고, CounterChange컴포넌트의 key 값에 적용해보았다.
  • 증가 버튼을 눌렀더니 숫자가 1초마다 0으로 계속 초기화가 된다.
  • 해당 컴포넌트는 삭제됐다가 다시 추가가 되는것이다.
  • 이렇게 컴포넌트가 삭제되는 것 을 unmount라고 부르고 컴포넌트가 추가되는 것을 mount라고 부른다.
  • 컴포넌트가 mount 됐을 때는 useState에서 첫번째 매개변수로 입력된 초기값이 상태값으로 할당된다.
    - 따라서 1초에 한번씩 0이 할당되는 것을 볼 수 있다.
  • 이렇게 컴포넌트에서 key를 변경하면 컴포넌트는 unmountmount 를 반복한다.

조건부렌더링

조건부렌더링도 비슷한 효과를 볼 수 있다.

return (
<div>
	{seconds  %  2  ===  0  &&  <CounterChange/>}
	<h1  style={{color:seconds  %  2  ?  'red'  :  'blue' }}>안녕하세요.</h1>
	<h2>지금까지 {seconds}초가 지났습니다.</h2>
</div>
)
  • 컴포넌트가 조건부렌더링을 적용하니 보였다가 안보였다가 한다.
  • 또한 unmount되면서 현재 카운트가 0으로 초기화 되는 것을 볼 수 있다.

counter.js

export  default  function  Counter() {
	const [count,setCount] = useState(0);
	function  onClick() {
		setCount(count  +  1)
	}

	return (
		<div>
			<p>{`현재 카운트: ${count}`}</p>
			<button  onClick={onClick}>증가</button>
		</div>
	)
}

Count 컴포넌트를 생성해서 App.js에 연결한다.

✅ 리액트 요소와 가상돔2

jsx 문법코드 -> 객체 트리 구조

  • 하나의 화면을 표현하기 위해서 여러개의 리액트 요소가 트리구조로 구성된다.
  • 리액트에서 데이터 변경에 의한 화면 업데이트는 렌더단계커밋단계를 거친다.
    - 렌더단계 는 실제 DOM에 반영할 변경사항을 파악하는 단계
    - 변경사항을 파악하기 위해서 가상돔을 이용함
    - 가상돔 리액트 요소로 부터 만들어짐
    - 리액트는 렌더링을 할때마다 가상돔을 만들고 이전의 가상돔과 비교함
    - 이것은 실제 돔의 변경사항을 최소화하기 위한 과정이다.
    - 커밋단계는 파악된 변경사항을 실제 DOM에 반영하는 단계

실제 DOM이 만들어지는 과정

두개의 컴포넌트가 있다 ( 코드생략 )

function Todo() {
	const [priority,setPriority] = userState('high;)
	// Todo에서는 priority라는 상태값 사용
}

const Title = React.memo()
// Title 컴포넌트는 memo 함수 사용했기 때문에 Title의 속성값이 변경될때만 렌더링 된다.
  • 이렇게 Todo 컴포넌트로 만들어진 리액트 요소는 객체 트리구조형태를 갖는다 (생략)

  • type에는 Todo 컴포넌트 함수가 있을 것이다.

  • 리액트는 렌더링 결과를 얻기 위해서 Todo함수를 호출한다.
    - 그 결과 또한 객체 트리구조형태를 가진다
    - 호출된 Todo함수 객체 트리구조를 살펴봤을때 Title 컴포넌트가 존재한다. 때문에 해당 리액트 요소는 실제 돔으로 만들 수 없다.
    - 리액트 요소 트리가 실제 돔으로 만들어지기 위해서는 모든 리액트 요소의 타입 속성 값이 문자열이여야 한다.

  • Title 컴포넌트를 렌더링한 결과 모든 리액트 요소의 타입 속성은 문자열이 된 것을 알 수 있다. -> 실제 돔을 만들 수 있다.

  • 이와 같이 실제 돔을 만들 수 있는 리액트 요소 트리를 가상돔이라고 할 수 있다.

  • 최초의 리액트 요소 트리로부터 가상돔을 만들고 이전 가상돔과 비교해서 실제 돔에 반영할 내용을 결정하는 단계를 렌더단계라고 부른다.

  • 참고로 최종 리액트 요소 트리를 만들기 위해서 치환되는 중간에 Todo나 Title같은 컴포넌트의 리액트 요소도 메모리에 저장돼서 렌더단계의 효율을 높이는데 사용된다.

  • 가상돔은 UI에서 변경된 부분을 빨리 찾기위한 개념이므로 컴포넌트의 리액트 요소(Title,Todo)도 가상돔의 일부라고 생각할 수 있다.

  • 리액트는 화면을 업데이트할때 이전의 가상돔과 현재가상돔을 비교해서 변경된 부분만 실제 돔에 반영한다.

  • 지금 작성한 코드는 최초 렌더링 결과이므로 이대로 실제 돔에 반영된다.
    - 이후에 다시 렌더링 될때 지금 만들어진 가상돔과 비교해서 변경된 부분만 실제돔에 반영

  • 이렇게 중요하지만 다소 번거로운 작업을 리액트가 내부적으로 해주는 것

  • 렌다단계는 렌더함수를 호출(ReactDOM.render())하거나 또는 컴포넌트 내부에서 상태값 변경함수를 호출해서 시작될 수 있다.

상태값 변경에 의해 수행되는 렌더단계

  • 실제 돔에서 변경된 부분만 반영
  • 사용자의버튼 클릭으로 Todo 상태값이 변경되고 두번째 렌더단계가 실행되고 새로운 가상돔 만들어졌다.
    - 이때 이전의 가상돔과 비교해서 변경된 부분만 실제 돔에 반영

🔍 강의출처
실전 리액트 프로그래밍

profile
프론트엔드 주니어 개발자 🚀

0개의 댓글

관련 채용 정보