리액트는 state와 props를 통해 데이터마다 다른 화면을 보여줄 수 있었다. 이번엔 state가 바뀔 때마다 리액트가 렌더링하는 방식을 살펴보겠다.

 

우선 결론적으로 말하자면, state가 바뀔 때마다 그 state를 갖고 있는 컴포넌트 자체를 새로 렌더링한다. 즉 그 컴포넌트를 통째로 화면에  다시그린다는 것. 이 때 쉽게 생각할 수 있는 문제점은 "아니 그럼 컴포넌트를 통째로 다시 그린다면 컴포넌트 내에서 안 바뀌는 부분도 다시 그린다는 건데 이건 좀 낭비 아냐?"이다.

 

이런 문제를 해결하기 위해, React에선 Virtual DOM(가상 돔)이란 것을 활용한다. 리액트 내에서 DOM Tree를 본따서 만든 것인데, 이를 통해 Element를 새로 렌더링할 때 실제 DOM Tree에 바로 반영하는 게 아니라 일단은 Virtual DOM에 적용한다. 이후 state 변경 전의 Virtual DOM과 변경 후의 Virtual DOM을 비교해, 실제로 바뀐 부분만 찾아낸 다음에 비로소 그 부분들만 실제 DOM Tree에 적용한다. 똑똑하게도 바로 화면을 바꾸는 게 아니라 화면을 바꿀 준비를 하고 어디어디가 바뀌는지 파악한 다음 그 때 바꾸는 것.

 

이런 방식으로 렌더링하는 것은 크게 2가지의 좋은 점이 있다.

 

1. 개발자가 직접 DOM을 신경쓸 필요가 없으니 단순하고 깔끔한 코드를 작성할 수 있다는 것. 무슨 데이터를 어떻게 보여줄 지만 신경쓰면 된다.

2. 변경사항들을 리액트가 적당히 모아 처리할 수 있다는 것. 브라우저의 한정적인 자원을 효율적으로 이용할 수 있다는 것이다.

 

참고로 이 Vitrual DOM은 리액트에만 있는 개념은 아니다. 그냥 여기선 Virtual DOM을 사용하면 효율적인 화면 처리가 가능하다는 것만 알아두면 될 것 같다. 

 

 

state?

: 리액트에서 데이터를 다룰 때 사용하는 것으로, props가 부모 컴포넌트에서 자식 컴포넌트로 값을 전달할 때 사용하는 것이었다면 state는 컴포넌트 자기 자신이 갖는 값을 의미한다. props의 주인은 부모이지만 state의 주인은 나! 인 느낌.

 

이 state의 값이 바뀔 때마다 얘를 갖고 있는 컴포넌트들이 새로 렌더링되며, state는 다음과 같이 만들 수 있다.

import { useState } from 'React';

function Component(){
	const [num, setNum] = useState(1);
    
	return(
    	<>
        </>
	)
}

export default Component

Component란 컴포넌트에서 num이란 이름의 state를 만든 모습이다. 보통 이렇게 Destrucrturing 문법으로 작성한다. 초기엔 useState를 통해 초깃값을 할당하는데 파라미터로 준 값이 초깃값이 된다. setNum은 setter함수라고 부르며 보통 state이름 앞에 set을 붙이고 camel case로 명명한다. state를 변경하는 것은 기존에 하던 것처럼 num = 5 이런 식으로 하면 안되고, 이 setter함수를 이용해야 한다. 예를 들어 setNum(5)를 해야 num이란 state가 5로 바뀐다. 

 

 

참조형 state?

js를 공부하다보면 참조형 변수라는 걸 만나게 된다. 기본형 변수는 변수에 값을 그대로 할당하는 반면 참조형 변수는 값의 주소를 할당한다는 점(즉 참조한다)에 차이가 있다. 참조형의 예로는 객체, 배열 등이 있다.

 

예를 들어 let a = 1; 을 하면 a라는 변수를 위한 공간이 메모리의 100번지에 마련되고, 100번지에 해당하는 공간에 1이란 값이 들어간다. 즉 값이 그대로 할당되는 것. 그러나 let b = [1, 2, 3]을 하면 b라는 변수를 위한 공간이 메모리의 101번지에 마련되지만 이 101번지에 해당하는 공간에 [1, 2, 3]이 아니라 메모리 어딘가에 만들어둔 [1, 2, 3]의 주소가 할당되는 것이다.

 

즉 js에선

let b = [1, 2, 3];
let c = b;
c[2] = 5;

console.log(b);
console.log(c);

 

출력되는 두 결과가 [1, 2, 5]로 같다. 

 

암튼, 리액트도 js를 기반으로 만들어진 놈이니 state를 다룰 때 state를 배열같은 걸로 해준다면, 참조형을 다루는 것이므로 예기지 못한 상황에 직면할 수 있다.  

 

다음과 같은 state를 만들었는데, 배열을 사용하는 놈이라고 하자.

const [list, setList] = useState([]);

이 때 다음 코드를 실행한 결과는 무엇이 될까?

list.push(5)
setList(list)

당연히 이 코드를 작성한 사람이 원한 것은 list에 5를 추가하는 것인데, setter함수로만 list를 조작할 수 있으니 push를 해주고 setList를 통해 list를 다시 설정해주는 것. 그러나 이렇게 하면 list state에는 아무런 변화가 없다..!

list는 배열이니까 주소를 갖고 있는데, push하던 뭘하던 주소엔 변화가 없으니까 리액트는 상태가 바뀌었다고 판단하지 않는 것.

 

그래서 참조형 변수를 state로 활용할 때 state를 변경하고 싶다면 새로운 참조형 변수를 만들어야 한다. 위와 같은 상황에서 가장 쉬운 방법은 Spread문법을 활용하는 것.

setList([...list, 5]);

 

 

※ spread문법

펼치다 라는 뜻을 갖는 문법.

let a = [1, 2, 3, 4];가 있다고 하자. a는 배열이다. 그럼 a를 펼치면 뭐가 되는가? 1, 2, 3, 4가 된다.

...은 그런 '펼치는' 역할을 해준다.

list라는 배열이 있고 [1, 2, 3, 4]일 때, 여기에 5를 push한 새 배열을 만들고 싶다. 그러면 [...list, 5]를 해주면 된다. list를 펼친 1, 2, 3, 4가 그대로 ...list에 들어간다고 생각하면 됨.

 

객체에서도 spread문법을 활용가능하다. 다음처럼. 얘도 똑같~~이 펼치다 라는 뜻에 주목해서 보면 됨.

const obj1 = {
    name : "JSH",
    age : 24
}

const obj2 = {
   ...obj1
   gender : "man"
}

// obj2는 name, age, gender프로퍼티를 가지며 value는 각각 "JSH", 24, "man"

props에 대해 정리해보자.

JSX문법에서 컴포넌트 작성 시 컴포넌트에 지정한 속성들을 props라 부른다. properties의 약자이며, 컴포넌트에 속성을 지정해주면 얘네들이 하나의 객체로 모여서 컴포넌트를 지정한 함수의 첫 번째 파라미터로 전달된다! 이 때 파라미터 이름은 통상 props로 해주는 게 깔끔. 이 때 props로 전달하는 것은 함수도 가능하다.

 

ex)

import React from 'react';
import Jofe from './jofe';

function App(){
    const func = function(){
        alert("Hi!");
    }
    return (
        <>
            <Jofe name="lje" age={23} func={func}/>
        </>
    );
}

export default App;

 

이렇게 App.js에서 Jofe라는 컴포넌트를 사용하는데 이런 식으로 props들을 Jofe컴포넌트로 전달가능하며, 이 녀석들은 다음과 같은 형태 즉 객체의 형태로 묶여서 전달됨.

{
    name : "lge",
    age : 23,
    func : func
}

 

그럼 여기서 children이란 걸 알아본다. props에서 특별취급하는 prop으로, JSX 문법에서 컴포넌트 작성 시 컴포넌트를 단일 태그가 아닌 여는태그 + 닫는태그의 형태로 작성하면, 이 태그 사이에 작성된 코드가 자동으로 아무것도 해주지 않아도 children이란 값에 담겨 컴포넌트로 전달된다.

import React from 'react';
import Jofe from './jofe';

function App(){
    return (
        <>
            <Jofe name="lje" age={23}>안녕안녕children</Jofe>
        </>
    );
}

export default App;

즉 여기선  안녕안녕children이란 문자열이 chidren에 담겨 Jofe컴포넌트에 전달된다. children이란 값에 담겨 전달되니 Jofe컴포넌트를 지정한 함수에선 children이란 이름으로 접근해 사용가능.

import React from 'react';

function Jofe({name, age, children}){
    return (
        <>
            <p>my name is {name} and age = {age}, children = {children}</p>
        </>
    );
}

export default Jofe;

 

그럼 children을 쓰면 뭐가 좋은가?

 

1. 화면에 보여질 모습을 좀 더 직관적인 코드로 쓰고자 할 때 활용 가능

2. 컴포넌트 안에 컴포넌트 작성 또는 컴포넌트 안에 복잡한 태그들 작성 가능. children을 쓸려면 단일 태그로 컴포넌트를 작성하는 게 아니라 여는 태그 + 닫는 태그로 컴포넌트를 작성하게 되므로!

 

props : 컴포넌트가 어떤 값을 전달받아 사용해야 할 때 이용된다. 컴포넌트에 속성들을 지정하는 형태로 전달하며,  객체 형태로 전달된다! 무슨 말인지는 예시 코드를 보자.

 

import React from 'react';
import Jofe from './jofe';

function App(){
    return (
        <>
            <Jofe />
        </>
    );
}

export default App;

 

이런 코드가 있다고 하자. App에서 Jofe라는 컴포넌트를 사용하는 모습이다. 이 때 Jofe컴포넌트에 "jsh"를 name이란 이름으로 전달하고 싶다면?

import React from 'react';
import Jofe from './jofe';

function App(){
    return (
        <>
            <Jofe name="jsh"/>
        </>
    );
}

export default App;

이렇게 태그의 속성을 지정하는 형태로 컴포넌트에 전달할 수 있다는 얘기다. 객체 형태로 전달된다는 것은, {name : "jsh"}이런 형태로 전달된다는 말이고.

 

Jofe 컴포넌트에선 이를 props라는 파라미터 이름으로 전달받아 사용가능하다. prrops말고 다른 이름도 쓸 수 있지만 다들 의미를 알기 쉽게 할려고 props라는 이름으로 쓴다고 한다.

import React from 'react';

function Jofe(props){
    return (
        <>
            <p>my name is {props.name}</p>
        </>
    );
}

export default Jofe;

 

- Props를 여러 개 전달하기

다음과 같이 여러 개의 props를 전달하는 것도 물론 가능하다.

import React from 'react';
import Jofe from './jofe';

function App(){
    return (
        <>
            <Jofe name="jsh" age={24}/>
        </>
    );
}

export default App;

props를 전달할 때 자바스크립트 숫자를 전달하기 위해선 {}로 감싸서 해야 한다고 한다. 문자열같은 건 ""로 하면 되고. 또한 Jofe 컴포넌트에선 props라는 이름으로 파라미터를 받는 게 아니라 다음과 같은 형태로 받을 수도 있다.

import React from 'react';

function Jofe({name, age}){
    return (
        <>
            <p>my name is {name} and age = {age}</p>
        </>
    );
}

export default Jofe;

이렇게 하면 매번 props.name이나 props.age 등 번거롭게 props라는 이름을 달아줄 필요가 없다. 또한 props를 전달받지 않는 상황에 대비해 디폴트 props를 지정가능하다.

import React from 'react';

function Jofe({name = "jsh", age = 24}){
    return (
        <>
            <p>my name is {name} and age = {age}</p>
        </>
    );
}

export default Jofe;

다음과 같은 방법으로도 디폴트 props를 지정 가능.

import React from 'react';

function Jofe({name, age}){
    return (
        <>
            <p>my name is {name} and age = {age}</p>
        </>
    );
}

Jofe.defaultProps = {
    name: 'jsh',
    age: 24
}

export default Jofe;

 

- 함수도 전달할 수 있다

함수도 props로 전달가능하다. 

import React from 'react';
import Jofe from './jofe';

function App(){
    const func = function(){
        alert("Hi!");
    }
    return (
        <>
            <Jofe name="lje" age={23} func={func}/>
        </>
    );
}

export default App;

alert를 이용해 hi라는 알림창을 띄우는 함수 func를 Jofe컴포넌트에 전달하는 모습. Jofe컴포넌트에선 다음과 같이 활용가능하다.

import React from 'react';

function Jofe({name, age, func}){
    return (
        <>
            <p onClick={func}>my name is {name} and age = {age}</p>
        </>
    );
}

export default Jofe;

p태그의 onclick이벤트로 등록해줬다. 실제로 저 글을 클릭하면 다음과 같이 알림창이 뜬다.

jsx문법으로 작성된 코드를 javascript변수에 담아줄 수도 있다. 예를 들면, 

import ReactDOM from 'react-dom';

const element = <h1>hello, my name is JSH</h1>;

ReactDOM.render(
  element, document.getElementById('root')
);

이렇게 할 수 있다는 것. element를 콘솔에 찍어보면(console.log를 활용해서) 자바스크립트 객체라고 나오며, 이때 이 element를 react element라고 한다. 공식 문서의 내용에 따르면 react element는 react의 가장 작은 단위이며, 화면에 표시되는 내용이 되고, 컴포넌트를 구성하는 요소가 된다. 또한 이 react element를 함수 형태로 작성하면 jsx문법을 쓸 때 custom한 태그처럼 쓸 수도 있다. 예를 들면,

import ReactDOM from 'react-dom';

function func(){
  return <h1>hello, my name is JSH</h1>;
}

const element = (
  <>
    <func />
    <func />
  </>
)

ReactDOM.render(
  element, document.getElementById('root')
);

이렇게 쓸 수도 있다는 말. <func />는 func()가 리턴하는 녀석이 된다. 이때, 이 코드에서 func와 같은 함수를 컴포넌트라고 부른다! 리액트를 앞으로 공부하면서 이렇게 react element 또는 이들의 묶음을 컴포넌트로 만들어서 활용하는 일들이 많다.

 

이를 활용한다면, index.js가 아니라 다른 데서 컴포넌트들을 만들고 이를 index.js에서 import하여 사용할 수도 있다. 아니면 A라는 컴포넌트를 작성할 때 B라는 컴포넌트를 import해서 A에 사용하는 등 활용법은 무궁무진하다. 그 샘플을 예로 들어보자.

 

index.js가 있는 위치 즉 src폴더에 jofe.js라는 javascript파일을 만들고 다음과 같이 Jofe라는 컴포넌트를 만들었다.

function Jofe(){
    return (
        <>
            <h1>jofe component</h1>
            <p>hello, i am studying react</p>
        </>
    );
}

export default Jofe;

참고로 컴포넌트 이름은 항상 첫글자는 대문자로 시작해야 한다고 함. 그렇게 정했다네요.

마지막 줄에선 다른 데서 이 컴포넌트를 쓸 수 있도록 export.

그럼 이 Jofe라는 컴포넌트를 index.js에서 다음과 같이 활용 가능하다.

import ReactDOM from 'react-dom';
import Jofe from './jofe'

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

터미널에 npm start를 입력해서 나오는 모습을 보면,

이렇게 잘 나온 것을 확인가능하다.

+ Recent posts