본문 바로가기

IT/Coding Act

리액트 튜토리얼 - 초보자를 위한 React18 기반의 리액트 기초

 2022 버전 React18를 베이스로 리액트를 처음 접하는 이들을 위해 가장 기초적인 리액트 사용법을 담았다. jsx, props, state라는 리액트만의 고유한 특징과 리액트에서 사용되는 CSS 및 자바스크립트 특징까지 리액트 기본을 배울 수 있다. 


1장 리액트 시작

  리액트를 접할 때 가장 먼저 알아두어야 할 점은 리액트는 무언가가 변할 때, 즉 데이터가 변할 때 기존 뷰를 전부 버리고 처음부터 새로 렌더링 하는 방식이라는 것이다. 

  이는 기존에 HTML, CSS, JavaScript(또는 제이쿼리)로 웹페이지를 만들었던 이들을 가장 당혹하게 만드는 부분이다. 하지만 이렇게 처음부터 새로 렌더링 하는 방식을 사용한다는 점이 오히려 리액트가 매력적인 이유이기도 하다. 


  1) 리액트를 꼭 사용해야 하나?

   리액트는 웹을 만드는 라이브러리이다. 기존에  HTML, CSS, JavaScript로 홈페이지(웹)을 만든 결과물과 어떠한 차이도 존재하지 않는다. 단지 만드는 방식에 차이가 있을 뿐이다. 

  일반적으로 개인용 홈페이지나 소규모 웹페이지를 만드는 게 목적이라면 기존의 방식대로 HTML, CSS, JavaScript를 사용해 만드는 게 더 효율적이고 효과적이다. 리액트는 조금 규모가 큰 프로젝트 즉, 동적인 변화가 많은 웹페이지를 만들 때 유리한 라이브러리일 뿐이다. 

  여기서 동적인 변화가 많다는 부분은 쉽게 말해서 웹페이지를 만들 때 자바스크립트 코드를 많이 사용하는 경우라고 간주해도 과언이 아니다. 웹페이지를 만들 때 상태가 변하는 부분, 즉 동적인 변화는 자바스크립트로 코드를 구성하기 때문이다.  이러한 경우에 자바스크립트 특성상 코드를 작성할 때 예기치 않은 오류가 발생하거나 과도하게 중복되는 코드를 작성하기도 한다. 바로 이런 자바스크립트의 단점을 보완한 게 리액트 라이브러리의 장점이다. 

 

  2) 그러면 자바스크립트를 사용하지 않아도 되나? 

  결론을 먼저 말하면 리액트는 자바스크립트를 사용한다. HTML과 CSS도 사용한다. 기존의 웹을 만들 때 사용하는 HTML, CSS, JavaScript를 모두 활용하므로 HTML, CSS, JavaScript를 모르고는 리액트를 배울 수 없다. 따라서 HTML, CSS, JavaScript까지 배운 후에 리액트에 도전해야 한다. 

  3) 기존 뷰를 버리고 새로 렌더링 하는 방식이란? 

   리액트는 자바스크립트 라이브러리로 사용자 인터페이스를 만드는 데 사용한다.  구조가 MVC, MVW 등인 프레임워크와 달리, 오직 V(View)만 신경 쓰는 라이브러리이다.

  메타(페이스북) 개발 팀은 하나의 아이디어를 고안해 냈는데, 어떤 데이터가 변할 때마다 어떤 변화를 줄지 고민하는 것이 아니라 그냥 기존 뷰를 날려 버리고 처음부터 새로 렌더링하는 방식이다.

  이와 관련하여 조금 더 깊게 알고 싶다면, Virtual DOM 을 이해하면 된다.
 리액트는 Virtual DOM 방식을 사용하여 DOM 업데이트를 추상화함으로써 DOM 처리 횟수를 최소화하고 효율적으로 진행한다.
  리액트에서 데이터가 변하여 웹 브라우저에 실제 DOM을 업데이트할 때는 다음 세 가지 절차를 밟는다.

4) 리액트 프로젝트 만들기

  우선 기본적인 에디터를 사용하고  Node.js를 알고 있어야 한다.

  에디터는 무엇을 사용해도 상관없지만 비주얼 스튜디오 코드(VS Code)를 개인적으로 추천한다. 무료이고 마이크로소프트가 지원하고 있어 안정적인 운영을 기대할 수 있기 때문이다. ( 리액트 역시 메타(페이스북)에서 지원하고 있어 안정적인 운영을 기대할 수 있다는 점도 빼놓을 수 없다. )

  Node.js는 크롬 V8 자바스크립트 엔진으로 빌드한 자바스크립트 런타임이다. 이것으로 웹 브라우저 환경이 아닌 곳에서도 자바스크립트를 사용하여 연산할 수 있다. Node.js를 설치하면 Node.js 패키지 매니저 도구인 npm이 설치된다. (사실 리액트 프로젝트를 할 때 npm만 필요하므로, Node.js를 깊게 알 필요는 없다.) 

  리액트 홈페이지에 처음으로 프로젝트를 생성하는 방법이 있다. 모두 다 이런 식으로 리액트 프로젝트를 생성한 후에 작업을 수행한다고 생각해도 된다. 실제로 특별한 경우가 아니고는, 그런 특별한 경우가 있는지도 모르겠지만, 하여튼 리액트 공식 홈페이지(https://reactjs.org/)에 나온 코드를 에디터에 적으면 프로젝트 생성은 끝난다. 

 

리액트 프로젝트 생성 살펴보기

2장 JSX

  const element = <h1> Hello, World! </h1>
 이처럼 html과 자바스크립트가 함께 쓰인 듯 보이는 게 JSX이다. 리액트 컴포넌트 파일에서 XML 형태로 코드를 작성하면 babel이 JSX를 JavaScript로 변환해준다.

 1)JSX 란?

  JSX는 HTML과 비슷하지만 완전히 똑같지는 않다. 코드로 보면 XML 형식이지만 실제로는 자바스크립트 객체이며, 용도도 다르고 문법도 조금씩 차이가 난다. 즉, JSX는 자바스크립트의 확장 문법이며 XML과 매우 비슷하게 생겼다.

 2) JSX 문법

JSX 내부에서 주석을 작성할 때는 {/* … */}와 같은 형식으로 작성한다.

 3) JSX와 자바스크립트 

  자바스크립트로만 표현할 때는 자바스크립트 문법을 사용한다. 하지만 html 즉, 태그와 함께 자바스크립트를 사용할 때는 JSX를 사용한다. return() 밖에서는 순수 자바스크립트 문법 사용 가능하고, return() 안에서는 JSX 사용한다고 생각하면 된다.

 

3장 컴포넌트

  리액트는 컴포넌트로 구성되어 있다.
 개념적으로 컴포넌트는 자바스크립트 함수와 유사하며, 컴포넌트 이름은 항상 대문자로 시작해야 한다.

 1) 컴포넌트 선언하는 2가지 방식 

    컴포넌트를 선언하는 방식은 두 가지이다. 하나는 함수형 컴포넌트이고, 또 다른 하나는 클래스형 컴포넌트이다. 원래 클래스형 컴포넌트가 주류였지만, React 버전 16.8부터 리액트 훅이 추가되면서 이제는 함수형 컴포넌트가 대세로 자리 잡는 추세이다.

 2)  모듈 내보내기 및 불러오기(export import)

   export default는 기본으로 export 한다는 의미이고, export는 그냥 네이밍으로 export 한다는 의미이다.
 export default ContentA                   export ContentB

 이처럼 한 파일에서 2개의 컴포넌트를 만들어 export 했다면, 이를 사용할 파일에서 import 할 때에는 import ContentA, { ContentB } from ‘./파일 경로’ 해야 한다.
 즉, export default가 아닌 경우에는 중괄호 안에 넣어야 한다.

3)  컴포넌트 생성 및 export

4) 컴포넌트 임폴트 및 활용

 

5)함수형 컴포넌트 생성 및 export 3가지 방법

  import React from 'react'

6) 컴포넌트 임포트 및 활용 방법 3가지

    만든 컴포넌트를 사용할 파일에서 임포트 한다.
 예) import Hello from './component/Hello';

 4장 props

  props는 properties를 줄인 표현으로 컴포넌트에 값을 전달할 때 사용하는 요소이다.
 컴포넌트를 임폴트한 컴포넌트를 부모 컴포넌트라 하고, 컴포넌트를 익스폴트한 컴포넌트를 자식 컴포넌트라 한다.

1) 부모 컴포넌트에서 자식 컴포넌트에 데이터를 전달할 때 props를 사용한다.

  부모컴포넌트에서 <자식컴포넌트명 속성명=“값” />
  자식컴포넌트에서
 const 자식컴포넌트명 = (props) => {return (<> {props.속성명} </>}

  2) defaultProps

   부모컴포넌트에서 props로 넘긴 게 없을 때, 자식컴포넌트가 스스로 기본 Props를 설정할 수 있다.
 const 자식컴포넌트명 = (props) => { return (<> {props.name} </>}
 자식컴포넌트명.defaultProps = {name:'기본이름’};
 exprots default 자식컴포넌트명;

  3) 태그 사이의 내용을 보여 주는 children

  부모컴포넌트에서 자식컴포넌트를 단일 태그로 닫지 않고, 그 사이에 자식이 있으면 자식컴포넌트에서 중괄호 안에 props.childeren으로 데이터를 받아올 수 있다. 

  4) 비구조화 할당 문법을 통해 props 내부 값 추출하기

  props 대신에 속성명을 { } 중괄호 안에 넣어 사용할 수도 있다.
 - 부모컴포넌트에서 <컴포넌트명 속성명=“값” /> 또는 <컴포넌트명 속성명={함수명} />
 - 자식컴포넌트에서
 const Child = (props) => { return (<p>{props.속성명}</p> }
 const Child = ( {속성명} ) => { return (<p> { 속성명 } </p> }

 

5장 state

  state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다.

1) state 없이 상태값을 변수를 활용해서 변경하는 방법

 * 컴포넌트에 넣어준다.
 <div id="name">{name}</div>
 <button onClick={changeName}>Change name </button>
 전형적인 자바스크립트의 변수를 활용한 속성값을 변경하는 방법이다.

  2) 리액트 훅에서 제공하는 useState()를 통해 속성값을 변경하는 방법

* state를 사용하기 위해 useState를 반드시 임폴트해야 한다.
 import {useState} from "react";
 * 또한 useState는 리액트 훅의 일환이므로 반드시 컴포넌트 안에 작성해야 한다.
Welcome 컴포넌트 안에 작성하고 있다.
 function Welcome() { const [name, setName] = useState('nabi');
 구조분해할당 사용했다. useState()는 배열을 반환한다.
 첫 번째는 state로 변수명이라 생각하면 되고, 두 번째는 state를 변경해주는 함수이다.
 useState('초기값');

 * 이 부분이 컴포넌트 안에 return () 위에 위치하는 속성 변경 함수 부분이다.
 function changeName() {
 const newName = name === "nabi" ? "sphinx" : "nabi";
 setName(newName)
 위 2개의 구문을 아래처럼 한 개의 구문으로 정리가 가능하다.
 setName(name === "nabi" ? "sphinx" : "nabi");
 결국 위에서 const [name, setName] = useState('nabi');를 통해 name이란 변수에 'nabi'를 할당한 값을 변경할 때에는 setName() 함수를 통해서 변경해야 하고 (여기에 변경할 값을 주면 된다.)
 * 이후 return() 안에 작성한 구문에 함수명만 적어주면 된다.
 <div id="name">{name}</div>
 <button onClick={changeName}>Change name </button>

 이를 통해 {name} 이란 변수에 들어갈 속성값이 {changeName} 함수를 통해 변경되게 된다. 따라서 document.querySelector("#name").textContent = name; 사용할 필요가 없다.

  3) 2의 방법을 다른 방식으로 조금 더 단순화하면

  *useStste를 임폴트하고 import {useState} from "react"; * 컴포넌트 안에 useState()의 기본값과 변경함수를 설정하고 const [name, setName] = useState('nabi');
* 따로 함수를 만들지 않고 useState()가 배열로 반환하는 변경함수 자체를 활용해 return() 안에 작성한 구문에 그대로 넣어도 동일하게 작동한다.
 <button onClick={ () => { setName(name === "nabi" ? "sphinx" : "nabi") } }> Change name </button> 

  4) useState() 훅 추가 설명

 5) props와 state

  * 부모 컴포넌트와 자식 컴포넌트 간의 데이터(문자, 변수, 함수 등) 전달은 props
 컴포넌트 내에서의 값이 변경될 때는 state
 * 기본적으로 props는 부모가 자식에게 데이터(함수도 가능)를 전달하고 변경하지 않는 데이터이다.
 * props를 사용한다고 해서 값이 무조건 고정적이지는 않다. 부모 컴포넌트의 state를 자식 컴포넌트의 props로 전달하고, 자식 컴포넌트에서 특정 이벤트가 발생할 때 부모 컴포넌트의 메서드를 호출하면 props도 유동적으로 사용할 수 있다.
 즉, 데이터를 변경하려면 state를 사용해야 한다. 자식컴포넌트에서 props로 받은 데이터를 useState(props.속성명)을 활용해 이벤트 핸들러와 결합해 데이터를 바꿀 수 있다.

 

 * 이처럼 데이터 변경은 state가 담당하며 효율적인 state(상태) 관리를 위해 Redux 라이브러리가 활용된다.
 * state가 변경되면 화면은 자동으로 리렌더링된다. 이 부분을 인지하고 있어야 한다. 폼의 버튼은 자동으로 리렌더링하므로 event.preventDefault()를 사용한다.
 더불어 리액트는 값이 바뀐다고 자동으로 리렌더링되는 게 아니라 state가 변할 때만 자동으로 리렌더링된다.

6장 이벤트 핸들링

  사용자가 웹 브라우저에서 DOM 요소들과 상호 작용하는 것을 이벤트(event)라고 한다.
 이벤트 이름은 카멜 표기법으로 작성한다.
 onclick은 리액트에서는 onClick으로, onkeyup은 onKeyUp으로 작성한다.
 이벤트에 실행할 자바스크립트 코드를 전달하는 것이 아니라, 함수 형태의 값을 전달한다.

 

 1) onClick={ 이 안에 직접 코드를 입력하는 방법 }

 예) <button onClick={ () => { console.log(30); } } > Show age </button>
 (이때 onClick={ console.log(30)} 이 아니라
 onClick={ () => { console.log(30); } } 이런 식으로 함수 전체를 넣어야 한다.
 물론 onClick={function(){console.log(35);}}도 가능하나, 가급적이면 화살표 함수를 넣는 게 유용하다.

 2) onClick={ 이 안에 함수명만 입력하고 함수는 따로 작성하는 방법}

  예) <button onClick={showName}> Soow name </button>
 (이때 {여기 안에 showName()이 아니라 showName이다. })
 return() 외부에 함수를 만들기.
 function showName() { console.log("aattitude");}
 물론 화살표 함수도 가능하다.
 const showName = () => {console.log(`sphinx`);}

3) 1과 2의 방법 조합해 함수 생성

  우선 1의 방법을 <button onClick={()=>{ showAge(20); } } > Show age </button>처럼{안에 매개변수가 있는 함수 형태로 만들고} 2의 방법처럼 외부에 함수를 만들어준다. function showAge(age) { console.log(age); }
 그러면 1의 방법을 다른 곳에서도 2의 함수를 그대로 활용해서 매개변수 값만 변경해서 여러 번 사용할 수 있다는 장점이 있다.
 매개변수가 있는 함수는 함수명만 기입하는 방식이 아니라 반드시 완전한 함수의 완전꼴로 작성해야 한다. () => 함수명(매개변수)

4)매개변수가 있는 함수 표현법

 매개변수가 없는 이벤트는 함수명만 적어도 된다.
 onClick={ clearnum}처럼 또는 onClick={ () => clearnum()} 해도 똑같이 작동한다.
 하지만 매개변수가 있는 이벤트는 축약이 아닌 전체 함수명 표시방법을 사용한다.
 즉, onClick={()=> deletenum(index)}처럼 사용해야 한다.
 이것은 onClick={ deletenum} 또는 onClick={deletenum(index)}로 표시하면 작동하지 않는다.
 * 예시
 버튼 클릭 시 index인자를 지닌 deletenum(index) 함수가 실행된다.
 <ul> {recordnums.map( (number, index) =>
 <li key={index}> <span> {index +1 } 번 : {number} </span>
 <button onClick={()=> deletenum(index)}>삭제 </button> </li> )} </ul>
 외부에 deletenum 함수를 생성하고 index를 매개변수에 넣는다.
 const deletenum = (index) =>{
 setRecordnums(recordnums.filter((ele, _index) => {return _index !=index}));}

 

 => 버튼 태그에서 index를 인자로 넘겼기에 외부 함수에서 index를 매개변수로 받아 사용할 수 있는 함수의 기본원리이다.
 여기서 중요한 포인트는 버튼 태그에서 클릭 시 실행하는 구문이 일반적으로 함수명만 기입하고 함수명을 외부에 따로 작성하는데, 매개변수가 있을 경우에는 애로우 함수 전체를 적은 후에 함수명을 외부에 따로 작성해야 한다.
 <button onClick={()=> deletenum(index)}>삭제 </button>

5)input 이벤트

 onChange가 지닌 event를 활용한다.
 <input type="text" onChange={showText}></input>
 이후 외부에 함수를 만들어준다.
 function showText(e) { console.log(e.target.value); }

 //onChange가 지닌 event를 e로 받아서, 이벤트의 타깃인 input 자체의 value 값을 보여주는 방식이다.
 물론 외부 함수 작성 없이 안에서 그냥 작성해도 된다.
 <input type="text" onChange={ e=>{ console.log(e.target.value); }}></input>
 여기서 조금 더 응용하면
 <input type="text" onChange={ e=>{ const txt = e.target.value; showText(txt) }}></input>
 외부 함수는
 function showText(txt) {document.querySelector("#subText").textContent = txt;}

 7장 리액트 CSS 및 자바스크립트

기본 CSS 시스템(index.css 또는 app.css)을 사용하는 것만으로도 충분하다.

1) 리액트 CSS 다양한 사용 방법

  • 컴포넌트 이름-클래스 형태(예: App-header).
  • BEM 네이밍(BEM Naming) 방식(예: .card__title-primary).
     = 해당 클래스가 어디에서 어떤 용도로 사용되는지 명확하게 작성하는 방식
  • Sass(Syntactically Awesome Style Sheets)(문법적으로 매우 멋진 스타일시트)
     - Sass에서는 두 가지 확장자 .scss와 .sass를 지원한다. Sass가 처음 나왔을 때는 .sass 확장자만 지원되었으나 나중에 개발자들의 요청에 의해 .scss 확장자도 지원하게 되었다. .scss의 문법과 .sass의 문법은 꽤 다르다.
  • CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값, 즉 [파일 이름]_[클래스 이름]__[해시값] 형태로 자동으로 만들어서 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해 주는 기술이다.
     import styles from ‘./CSSModule.module.css’;
     <div className={styles.wrapper}>

2) 변수(props)와 스타일 응용하기

  상류에 state가 설정되어 있다.
 const [isDark, setIsDark] = useState(false);
 이때 하류에서 props로 받아서 스타일에 넣어 사용할 경우에 아래 둘은 똑같다.

 

3) 자바스크립트 Tip

  1. 자바스크립트 배열의 map() 함수
     자바스크립트 배열 객체의 내장 함수인 map 함수를 사용하여 반복되는 컴포넌트를 렌더링할 수 있다. map 함수는 파라미터로 전달된 함수를 사용해서 배열 내 각 요소를 원하는 규칙에 따라 변환한 후 그 결과로 새로운 배열을 생성한다.
     arr.map(callback, [thisArg])
  2. 배열의 join메서드를 사용해 구분자를 넣을 수 있다.
     {checkedItem.join(', ')}

옵션 개수에 따라 기본값 자동으로 채우기 new Array(options.length).fill(false)
 const [optionCheckeds, setOptionCheckeds] =
 useState(new Array(options.length).fill(false) );