✅ React란 무엇일까?
1️⃣ React.js
React는 사용자 인터페이스를 구축하기 위한 선언적이고 효율적이며 유연한 JavaScript라이브러리이다. "컴포넌트"라고 불리는 작고 고립된 코드의 파편을 이용하여 복잡한 UI를 구성하도록 돕는다.
프론트엔드 개발자사이에서는 앵귤러, 뷰와 더불어 가장 많은 인기를 얻고 있다.
SPA를 전제로 하고 있으며, Virtual DOM을 활용하여 업데이트 해야하는 DOM요소를 찾아서 해당 부분만 업데이트 하기 때문에, 리렌더링이 잦은 동적인 모던 웹에서 빠른 퍼포먼스를 낼 수 있다.
2️⃣ 주요 특징
🔹 컴포넌트 기반
스스로 상태를 관리하는 캡슐화된 컴포넌트를 만든다. 그리고 이를 조합해 복잡한 UI를 만들어본다.
컴포넌트 로직은 템플릿이 아닌 JavaScript로 작성된다. 따라서 다양한 형식의 데이터를 앱 안에서 손쉽게 전달할 수 있고 DOM과는 별개로 상태를 관리할 수 있다.
🔹 선언형 뷰
React는 상호작용이 많은 UI를 만들 때 생기는 어려움을 줄여준다. 애플리케이션의 각 상태에 대한 간단한 뷰만 설계한다. 그럼 React는 데이터가 변경됨에 따라 적절한 컴포넌트만 효율적으로 갱신하고 렌더링한다.
선언형 뷰는 코드를 예측 가능하고 디버그하기 쉽게 만들어준다.
🔹 JSX
const element = <h1>Hello, world!</h1>;
JSX는 JavaScript를 확장한 문법이다. JSX라고 하면 템플릿 언어가 떠오를 수 있지만, JavaScript의 모든 기능이 포함되어 있다.
JSX는 React "엘리먼트(element)"를 생성한다.
🔹 Hooks
🔹 State & props
공식 문서 : https://ko.reactjs.org/
✅ React 문법
1️⃣ 화살표 함수
👌 자바스크립트에서 함수 만드는 방법 2가지 : function 키워드 사용 방식, Arrow function
👌 함수 코드
🔹 Function keyword
function sayHello () { return "hello world!" } |
🔹 Arrow function 방식 1
const sayHello = () => { return "hello world!" } |
🔹 Arrow function 방식 2
const sayHello = () => "hello world!" |
🔹 Arrow function 방식 3
코드 줄 안(inline)에서 무명함수로 생성하는 방식
// 예시 1 <App onClick={ ()=>{} } /> // 예시 2 {array.map((item)=> <div>{item}</div>)} |
2️⃣ 객체와 배열 구조분해 할당
👌 구조 분해 할당이란?
객체나 배열을 구조분해하여 나타낼 수 있다.
👌 객체 구조분해 할당 코드 비교
🔹 구조분해 할당을 사용하지 않을 때
const user = {name: "손석구", age: 10}; console.log(user.name) // 손석구 console.log(user.age) // 10 |
🔹 구조분해 할당을 사용했을 때
const { name, age } = user console.log(name) // 손석구 console.log(age) // 10 |
👌 배열 구조분해 할당 코드 비교
🔹 구조분해 할당을 사용하지 않을 때
const games = ['배틀그라운드', '리그오브레전드']; console.log(game[0]) // 배틀그라운드 console.log(game[1]) // 리그오브레전드 |
🔹 구조분해 할당을 사용할 때
// 구조분해 할당 const [battleGround, Lol] = games; console.log(battleGround); // 배틀그라운드 console.log(Lol); // 리그오브레전드 |
3️⃣ 함수 인자에서 구조분해 할당
👌 함수 내 구조분해 할당 비교
함수의 인자가 객체 또는 배열인 경우, 위처럼 구조분해 할당하여 사용할 수 있다.
🔹 구조분해 할당을 사용하지 않을 때
// user가 객체일 때 const getUserName = (user) => { return user.name } // user가 배열일 때 const getUserName = (user) => { return user[0] } |
🔹 구조분해 할당을 사용했을 때
// 객체일 때 const getUserName = ({name, age}) => { return name; }; // 배열일 때 const getUserName = ([name, age]) => { return name }; |
4️⃣ spread operator(전개 구문)
👌 전개 구문이란?
기존에 있는 객체나 배열의 내용을 그대로 가져오거나 새로운 값을 덧붙여서 새로운 객체나 배열을 생성할 수 있다. 기존의 데이터(객체나 배열)에는 전혀 영향을 주지 않고 복사해서 새로운 것을 만든다.
const box = {size: "big", color: "red"}; const newBox = { ...box}; // {size: "big", color: "red"} const newBlueBox = {...box, color: "blue" }; // {size: "big", color: "blue"} |
5️⃣ rest operator(나머지 매개변수 구문)
👌 나머지 매개변수 구문이란?
rest 문법은 spread와 비슷해보이지만 조금 다른 기능을 한다. 객체나 배열에서 구조 분해 할당을 했을 때, 나머지 정보를 묶어서 표현할 수 있다. rest라는 키워드를 꼭 사용하지 않아도 되고 개발자가 편한 키워드를 사용해도 된다.
const user ={id: 1, name: "둘리", age: 10, location: "seoul"}; // rest 사용 const {name, ...rest} = user; // 콘솔에 rest를 찍어보면, 구조분해 할당을 한 정보를 제외한 나머지 값을 보여줍니다. console.log(rest) // {id: 1, age: 10, location: "seoul"} |
✅ Create React App
1️⃣ CRA(Create-react-app)란?
리액트 프로젝트를 하기 위해 필요한 여러 프로그램을 자동으로 설치해주는 도구
✅ Component part1
1️⃣ Component 알아보기
👌 Component란 무엇인가?
🔹 컴포넌트란 블록이고 결국 함수이다.
컴포넌트란 화면을 구성하는 하나의 단위이며, 함수이다.
// App 컴포넌트 function App (){ return <div></div> } |
리액트 세계에서 말하는 컴포넌트(블록)는 즉, 함수이다. 누군가 와서 컴포넌트를 만들어 보세요 한다면, 우리는 html을 return하는 함수를 만들면 된다.
import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App; |
복잡해 보이지만, 결국 App이라는 함수일 뿐이다.
👌 Component 코드 보는 법
컴포넌트(함수)코드를 볼 때는 영역을 나누어서 보면 조금 더 편하다. 컴포넌트 밖에서는 내가 필요한 파일을 import하거나 또는 export default라는 기능을 통해 내가 만든 컴포넌트를 밖으로 내보내는 코드가 있다.
컴포넌트 안에서는 자바스크립트를 쓸 수 있는 부분이 있다.
그리고 return을 기준으로 아랫부분에서는 HTML(정확히 말하면 JSX)을 작성할 수 있다.
👌 반드시 주의해야 할 점
🔹컴포넌트를 만들 때 반드시 가장 첫 글자는 대문자로 만들어야 한다.
🔹 폴더는 소문자로 시작하는 카멜케이스로 작성하고, 컴포넌트를 만드는 대문자로 시작하는 카멜케이스로 이름을 짓는다.
✅ Component Part 2
1️⃣ 부모와 자식관계에 놓인 컴포넌트
👌 컴포넌트 안에 컴포넌트 넣기
컴포넌트는 다른 컴포넌트를 품을 수 있다. 이 때 다른 컴포넌트를 품는 컴포넌트를 부모 컴포넌트라고 부르고 다른 컴포넌트 안에 품어지는 컴포넌트를 자식 컴포넌트라고 부른다.
* App.js
import React from "react"; function Child() { return <div>나는 자식입니다.</div>; } function App() { return <Child />; } export default App; |
- Child라는 새로운 컴포넌트를 만들었고 App 컴포넌트에서 마치 HTML 태그를 쓰듯이 넣었다. 이렇게 한 컴포넌트 안에 다른 컴포넌트를 넣을 수 있다.
- export default App으로 App 컴포넌트가 화면에 보여지고 있는데 App 컴포넌트는 Child 컴포넌트를 자식으로 삼고 있기 때문에, 결국 자식 컴포넌트에 있는 문장이 보여지게 된다. "화면에 보여지게 하다"를 줄여서 Rendering 이라고 부른다.
🔹 JSX : 함수로 만들어진 컴포넌트를 html 태그 사용하듯이 코드를 작성하는 방식
2️⃣ JSX
* App.js
// import [패키지명] from [경로] 이 형식으로 불러온다. import React from 'react'; // js 파일 뿐 아니라 이미지도 가능가능! import logo from './logo.svg'; // css? 가능! import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App; |
🔹 리액트에서는 딱 하나의 html 파일만 존재한다. public 폴더 아래에 있는 index.html
🔹 리액트에서는, App.js 파일에서 보이듯 JSX 문법을 사용해서 React 요소를 만들고 DOM에 렌더링 시켜서 뷰를 그린다.
👌 HTML을 품은 JS === JSX!
아래 같은 HTML태그는 .js 파일 안에서 쓸 수 없다.
<div> <h1>안녕하세요!</h1> <p>시작이 반이다!</p> </div> |
그래서 나온 것이 JSX
자바스크립트 안에서 html 태그같은 마크업을 넣어 뷰(UI)작업을 편하게 할 수 있다.
const start_half = <div> <h1>안녕하세요!</h1> <p>시작이 반이다!</p> </div>; |
JSX에서 쓰는 <div>~~<div>는 DOM 요소가 아니라 정확히는 React 요소이다.
리액트에서 돔을 구성하는 것은 리액트 요소! 돔을 구성하는 것은 돔 요소!
👌 JSX 따라해보기
🔹 JSX에서 javascript 값을 가져오려면 중괄호를 쓴다.
const cat_name = 'perl'; return ( <div> hello {cat_name}! </div> ); |
🔹 값을 가져올 때 뿐만 아니라 map, 삼항연산자 등 자바스크립트 문법을 JSX 안에 쓸 때도 {}를 이용할 수 있다.
import React from 'react'; import logo from './logo.svg'; import './App.css'; function App() { const number = 1; return ( <div className="App"> <p>안녕하세요! 리액트 반입니다 :)</p> <p>{number > 10 ? number+'은 10보다 크다': number+'은 10보다 작다'}</p> </div> ); } export default App; |
🔹 class 대신 className!
<div className="App"> |
JSX로 작성하는 태그 내에서 클래스 명을 정해줄 땐 속성 값을 className으로 사용한다. id는 그대로 id
👌 인라인으로 style 주기
🔹 HTML
<p style="color: orange; font-size: 20px;">orange</p> |
🔹 JSX
// 중괄호를 두 번 쓰는 이유? 딕셔너리도 자바스크립트기 떄문 // 이렇게 쓰거나, <p style={{color: 'orange', fontSize: '20px'}}>orange</p> //혹은 스타일 딕셔너리를 변수로 만들고 쓸 수 있다. function App() { const styles = { color: 'orange', fontSize: '20px' }; return ( <div className="App"> <p style={styles}>orange</p> </div> ); } |
✅ Props Part 1
1️⃣ Props란 무엇인가?
props는 부모 컴포넌트로부터 받아온 데이터
Mother 컴포넌트에서 못 보던 코드가 생겼다. mother의 이름이 생긴 것. 하지만 Child는 지금 Mother의 이름을 모른다. 어떻게 해야 Child 컴포넌트에서 Mother의 이름을 알 수 있을까?
다시 말해서, 어떻게 컴포넌트 간의 정보를 서로 교류할 수 있을까?
* App.js
import React from "react"; function Child() { return <div>연결 성공</div>; } function Mother() { const name = '홍부인'; return <Child />; } function GrandFather() { return <Mother />; } function App() { return <GrandFather />; } export default App; |
👌 props로 값 전달하기
컴포넌트 간의 정보를 교류할 때 Props를 사용한다. 다른 컴포넌트는 생략하고 Mother만 봐보면, 만약 Mother 컴포넌트가 가지고 있는 정보(값)를 child에게 주고 싶을 때는 아래 코드와 같이 한다.
motherName이라는 이름으로 name값을 Child 컴포넌트에게 전달해준 것이다. 이 과정을 "Props로 정보를 전달했다"라고 표현한다.
* App.js
import React from "react"; function Child() { return <div>연결 성공</div>; } function Mother() { const name = '홍부인'; return <Child motherName={name} />; // "props로 name을 전달했다." } function GrandFather() { return <Mother />; } function App() { return <GrandFather />; } export default App; |
Mother가 전달해준 motherName은 Child가 어떻게 받을 수 있을까?
function Child(props){ console.log(props) // 이게 바로 props다. return <div>연결 성공</div> } |
이렇게 props로 받으면 된다.
Mother가 보내준 정보가 object literal 형태로 잘 보인다. 이렇게 컴포넌트는 props를 통해서 정보를 서로 주고 받는다. 리액트가 이것을 가능하게 해주는 것
👌 props로 받은 값을 화면에 렌더링 하기
Mother로부터 전달받은 motherName을 화면에 렌더링하면 된다.
import React from "react"; // div안에서 { } 를 쓰고 props.motherName을 넣어보기 function Child(props) { return <div>{props.motherName}</div>; } function Mother() { const name = "홍부인"; return <Child motherName={name} />; } function GrandFather() { return <Mother />; } function App() { return <GrandFather />; } export default App; |
props는 object literal의 형태이기 때문에 {props.motherName}으로 꺼내서 사용할 수 있다. object literal의 key가 motherName인 이유는 Child로 보내줄 때 motherName = {name} 으로 보내주었기 때문이다.
🔹 object literal : {key: "value"} 데이터 형태 의미
JSX에서는 HTML만 쓸 수 있다고 알고 있었는데, 자바스크립트 코드도 사용할 수 있다. JSX에서도 { } 중괄호를 사용하면 자바스크립트 코드를 사용할 수 있다.
자식 컴포넌트에서는 부모 컴포넌트로 props를 전달할 수 없다. 오직 부모 컴포넌트에서 자식 컴포넌트로만 props를 전달할 수 있다.
✅ Props Part 2
1️⃣ children
👌 부모 컴포넌트로 정보를 전달하는 children
<Component props={props} />와 다른 방식으로 자식 컴포넌트에 정보를 전달해주어도 사용할 수 있는 props가 있다. 바로 children이다.
* App.js
import React from "react"; function User() { return <div></div>; } function App() { return <User>안녕하세요</User>; } export default App; |
App.js에 App 컴포넌트와 User 컴포넌트가 있다.
그리고 App컴포넌트가 User컴포넌트를 자식으로 품고 있고 User 컴포넌트 사이에 "안녕하세요" 라는 문장이 있다. 그런데, yarn start를 해서 화면을 보면 아무것도 화면에 표시되지 않는다.
바로 App 컴포넌트에서 "안녕하세요"라는 정보를 보냈지만, User 컴포넌트에서는 그 정보를 받지 않았기 때문이다.
부모 컴포넌트에서 정보를 보냈을 때, 자식 컴포넌트에서 그 정보를 사용하고 싶다면, props를 통해 정보를 받아와야 한다. 하지만 위 예시에서는 User 컴포넌트에서 그 정보를 받고 있지 않기 때문에 어떠한 문장도 보이지 않고 있는 것이다.
그런데, 위 예시에서는 <User>안녕하세요</User> 이렇게 정보를 보내고 있다. <User hello="안녕하세요"> 이렇게 props를 보내던 방식과는 조금 다르다. 이것이 children props를 보내는 방식이다. 자식 컴포넌트가 정보를 받는 방식은 기존과 동일하다. 대신 그 이름이 children 으로 정해져 있다.
function User(props) { console.log(props.children) return <div></div>; } |
👌 children 값 받아서 렌더링하기
import React from "react"; function User(props) { return <div>{props.children}</div>; } function App() { return <User>안녕하세요</User>; } export default App; |
2️⃣ children 용도
👌 Layout 컴포넌트를 만들 때 자주 사용
import React from "react"; import Layout from "./components/Layout"; function App() { return ( <Layout> <div>여긴 App의 컨텐츠가 들어갑니다.</div> </Layout> ); } export default App; |
Layout 아래에 header라는 컴포넌트가 있다라고 가정한다. <div>여긴 App의 컨텐츠가 들어갑니다.</div> 이 문장이 Layout의 props로 절다되는 것.
결과적으로 header 컴포넌트를 Layout 컴포넌트에서 한번만 작성하면 여러 페이지에서 모두 보여지게 할 수 있다.
✅ Props Part 3
1️⃣ 구조분해 할당과 Props 내부값 추출하기
지금까지 자식 컴포넌트에서 props를 받을 때는
function Todo(props){ return <div>{props.todo}</div> } |
이렇게 썼는데, 문제는 없지만 todo라는 props를 사용하는 모든 곳에서 props. 을 붙여줘야 했다. 이걸 조금 더 짧게 쓰려면 자바스크립트의 구조 분해 할당을 이용하면 된다.
props를 object literal 형태의 데이터이기 때문에, 구조 분해 할당을 이용할 수 있다.
function Todo({ title }){ return <div>{title}</div> } |
만약 여러개의 props를 받는다면, { } 안에 여러개의 props를 그대로 써주면 된다.
function Todo({ title, body, isDone, id }){ return <div>{title}</div> } |
2️⃣ defaultProps
👌 defaultProps 란?
defaultProps는 부모 컴포넌트에서 props를 보내주지 않아도 설정될 초기 값이다.
// components/Child.js import React from 'react'; function Child({ name }){ return <div>내 이름은 {name} 입니다. </div> } export default Child |
Child 컴포넌트 입장에서는 부모 컴포넌트에서 name을 props 정보로 받기 전까지는 name이 없는 상태이다. 그래서 자식 컴포넌트 입장에서는 name이 무엇인지 알 수 없다. 결과적으로 자식 컴포넌트는 화면에 아무것도 표시해주지 못하게 된다(내 이름은 " "입니다.)
그래서 부모 컴포넌트에서 props를 받기 전까지 임시로 사용 할 수 있는 props를 설정할 수 있다. Child 컴포넌트에서 직접하면 된다. 이후 부모 컴포넌트에서 name props가 오게 되면 설정된 defaulProps는 사라지고 내려받은 props로 값이 바뀌게 된다.
👌 defaultProps 지정하기
// components/Child.js import React from 'react'; function Child({ name }){ return <div>내 이름은 {name} 입니다. </div> } // 이렇게 설정한다. Child.defaultProps={ name: '기본 이름' } export default Child |
이렇게 defaultProps를 설정하면, 부모 컴포넌트로부터 아직 props 값을 받지 못했더라도 임시로 props의 값을 채울 수 있다. 그리고 부모 컴포넌트로부터 props가 오게 되면 defaultProps값은 진짜 props값으로 변경된다.
import React from 'react'; // 구조 분해 할당 문법을 사용하면 이렇게도 할 수 있다. function Child({ name = '기본이름' }){ return <div>내 이름은 {name} 입니다. </div> } export default Child |
방법만 다를 뿐 이렇게 defaultProps를 설정할 수도 있다.
✅ State Part 1
1️⃣ State
컴포넌트 내부에서 바뀔 수 있는 값을 의미한다.
위에서 const name = "흥부인"; 이라고 만들었는데, 만약 name이라는 값이 바뀌어야만 하는 정보였다면, state로 생성한다.
👌 State 만들기
State를 만들 때는 useState()를 사용한다.
useState는 state를 만들어주는 리액트에서 제공하는 기능이다. 리액트에만 존재하는 개념이자 기능인데, 이걸 훅이라고 표현한다.
🔹 useState 훅 사용하는 방식
const [ value, setValue ] = useState( 초기값 ) |
const로 선언하고 [ ] 빈 배열을 생성하고 배열의 첫번째 자리에는 이 state의 이름, 그리고 두번째 자리에는 set을 붙이고 state의 이름을 붙인다. 그리고 useState( )의 인자에는 이 state의 원하는 처음값을 넣어준다.
const [name, setName] = useState("김할아"); |
처음값은 initial state라고 부른다. state의 정의처럼, 언제든지 변할 수 있는 값이기 때문에 처음값이라는 개념이 존재하는 것이다.
* App.js
import React, { useState } from "react"; function Child(props) { return <div>{props.grandFatherName}</div>; } function Mother(props) { return <Child grandFatherName={props.grandFatherName} />; } function GrandFather() { const [name, setName] = useState("김할아버지"); // state를 생성 return <Mother grandFatherName={name} />; } function App() { return <GrandFather />; } export default App; |
👌 State 변경하기
state를 변경할 때는 setValue(바꾸고 싶은 값)을 사용한다.
다시 한번, state란 컴포넌트 안에서 변할 수 있는 값이다.
setName("박할아버지")라고 하면, 김할아버지에서 박할아버지로 바뀔 것이다. 버튼까지 같이 생성하면
* App.js
import React, { useState } from "react"; function Child(props) { return ( <div> <button onClick={() => { props.setName("박할아버지"); }} > 할아버지 이름 바꾸기 </button> <div>{props.grandFatherName}</div> </div> ); } function Mother(props) { return ( <Child grandFatherName={props.grandFatherName} setName={props.setName} /> ); } function GrandFather() { const [name, setName] = useState("김할아"); return <Mother grandFatherName={name} setName={setName} />; } function App() { return <GrandFather />; } export default App; |
이렇게 바뀐 값은 브라우저를 새로고침하면 다시 초기값으로 바뀐다. setName을 통해서 바꾼 값은 어디에 저장되는 것이 아니기 때문에 단순히 화면에서만 바뀐 값으로 다시 렌더링이 되는 것이다.
✅ State Part 2
1️⃣ useState + onClick Event
👌 Button과 이벤트 핸들러 구현하기
import React from "react"; function App() { // 버튼을 눌렀을 때 하고 싶은 행동 function onClickHandler() { console.log("hello button"); } return ( <div> <button onClick={onClickHandler}>버튼</button> </div> ); } export default App; |
onClickHandler라는 함수를 만들고 버튼에 onClick에 넣어주기
👌 state 구현하고 이벤트 핸들러와 연결하기
import React, { useState } from "react"; function App() { const [name, setName] = useState("길동이"); function onClickHandler() { setName("누렁이"); } return ( <div> {name} <button onClick={onClickHandler}>버튼</button> </div> ); } export default App; |
초기 name은 길동이로 만들고 setName을 통해서 누렁이로 바꿀 수 있도록 연결
2️⃣ useState + onChange Event
👌 Input과 state 구현하기 + 이벤트 핸들러 구현하고 state와 연결하기
import React, { useState } from "react"; const App = () => { const [value, setValue] = useState(""); const onChangeHandler = (event) => { const inputValue = event.target.value; setValue(inputValue); }; console.log(value) // value가 어떻게 변하는지 한번 콘솔로 보기 return ( <div> <input type="text" onChange={onChangeHandler} value={value} /> </div> ); }; export default App; |
- setValue로 바꿀 값을 inputValue로 만들고 event.target.value라고 해주는 것 잊지 말기
- input에 넣을 때 onChange 값 말고도 value={value}도 넣기
사용자가 입력한 input의 값은 event.target.value로 꺼내 사용할 수 있다.
마지막으로 state인 value를 input의 attribute인 value에 넣어주면 input과 state 연결하기 끝
위에서 사용한 이벤트 객체는 리액트의 개념이 아니며, HTML DOM event의 개념이다.
3️⃣ 불변성
불변성이란 메모리에 있는 값을 변경할 수 없는 것을 말한다. 자바스크립트의 데이터 형태중에 원시 데이터는 불변성이 있고 원시 데이터가 아닌 객체, 배열, 함수는 불변성이 없다.
👌 변수를 저장하면 메모리에 어떻게 저장이 될까?
let number = 1이라고 선언을 하면, 메모리에는 1이라는 값이 저장이 된다. 그리고 number라는 변수는 메모리에 있는 1을 참조한다.
이어서 let secondNumber = 1이라고 다른 변수를 선언했다고 가정하면, 이때도 자바스크립트는 이미 메모리에 생성되어 있는 1이라는 값을 참조한다.
즉, number와 secondNumber는 변수의 이름은 다르지만, 같은 메모리의 값을 바라보고 있는 것이다.
콘솔에 number === secondNumber를 하면 true가 보이게 된다.
하지만, 원시데이터가 아닌 값(객체, 배열, 함수)는 이렇지 않다.
let obj_1 = {name : "kim"} 이라는 값을 선언하면 메모리에 obj_1이 저장이 된다.
let obj_2 = {name : "kim"} 이라는 값을 선언하면, obj_2라는 메모리 공간에 새롭게 저장이 된다.
그래서 obj1 === obj2 는 false가 된다.
👌 데이터를 수정하면 어떻게 될까?
원시데이터로 돌아와서 기존에 1이던 number를 number = 2라고 새로운 값을 할당하면, 원시 데이터는 불병성이 있으므로, 기존 메모리에 저장이 되어 있는 1이라는 값은 변하지 않고 새로운 메모리 저장공간에 2가 생기고 number라는 값을 새로운 메모리 공간에 저장된 2를 참조하게 된다.
그래서 secondNumber를 콘솔에 찍으면 여전히 1이라고 보인다. number와 secondNumber는 각각 다른 메모리 저장공간을 참조하고 있기 때문이다.
obj_1를 obj_1.name = "park" 이라고 새로운 값을 할당하면, 객체는 불변성이 없으므로 기존 메모리 저장 공간에 있는 {name : "kim"} 이라는 값이 {name : "park"}으로 바뀌어 버리는 차이가 있다.
원시데이터는 수정을 했을 때 메모리에 저장된 값 자체는 바꿀 수 없고 새로운 메모리 저장공간에 새로운 값을 저장한다. 원시데이터가 아닌 데이터는 수정했을 때 기존에 저장되어 있던 메모리 저장공간의 값 자체를 바꿔버린다.
👌 왜 리액트에서는 원시데이터가 아닌 데이터의 불변성을 지켜주는 것을 중요시할까?
리액트에서는 화면을 리렌더링 할지 말지 결정할 때 state의 변화를 확인한다. state가 변했으면 리렌더링 하는 것이고 state가 변하기 않았으면 리렌더링을 하지 않는다.
그때 state가 변했는지 변하지 않았는지 확인하는 방법이 state의 변화 전, 후의 메모리주소를 비교하는 것이다. 만약 리액트에서 원시데이터가 아닌 데이터를 수정할 때 불변성을 지켜주지 않고 직접 수정을 가하면 값은 바뀌지만 메모리 주소는 변함이 없게 되는 것이다. 그래서 즉, 개발자가 값은 바꿨지만 리액트는 state가 변했다고 인지하지 못하게 되며 그래서 결국 일어나야 할 리렌더링이 일어나지 않게 된다.
👌 리액트 불변성 지키기 예시
import React, { useState } from "react"; function App() { const [dogs, setDogs] = useState(["말티즈"]); function onClickHandler() { // spread operator(전개 연산자)를 이용해서 dogs를 복사합니다. // 그리고 나서 항목을 추가합니다. setDogs([...dogs, "시고르자브르종"]); } console.log(dogs); return ( <div> <button onClick={onClickHandler}>버튼</button> </div> ); } export default App; |
배열을 setState할 때 불변성을 지켜주기 위해, 직접 수정을 가하지 않고 전개 연산자를 사용해서 기존의 값을 복사하고 그 이후에 값을 수정하는 식으로 구현한다.
✅ 반복되는 컴포넌트 처리하기
1️⃣ 리액트에서의 map
👌 map을 사용하지 않고 컴포넌트 구성했을 때
import React from "react"; const App = () => { const style = { padding: "100px", display: "flex", gap: "12px", }; const squareStyle = { width: "100px", height: "100px", border: "1px solid green", borderRadius: "10px", display: "flex", alignItems: "center", justifyContent: "center", }; return ( <div style={style}> <div style={squareStyle}>감자</div> <div style={squareStyle}>고구마</div> <div style={squareStyle}>오이</div> <div style={squareStyle}>가지</div> <div style={squareStyle}>옥수수</div> </div> ); }; export default App; |
이렇게 해도 되지만, 이렇게 하는 것은 같은 코드가 여러 번 중복이 되고 "토마토"라는 항목이 추가되면 개발자는 화면을 추가로 개발해줘야 한다.
👌 map을 이용해 구현해보기
JSX부분에서 아래와 같이 작성하기
import React from "react"; const App = () => { const style = { padding: "100px", display: "flex", gap: "12px", }; const squareStyle = { width: "100px", height: "100px", border: "1px solid green", borderRadius: "10px", display: "flex", alignItems: "center", justifyContent: "center", }; const vegetables = ["감자", "고구마", "오이", "가지", "옥수수"]; return ( <div style={style}> {vegetables.map((vegetableName) => { return ( <div style={squareStyle} key={vegetableName}> {vegetableName} </div> ); })} </div> ); }; export default App; |
map( )을 이용하기 위해 채소들의 이름을 먼저 배열로 만들어준다.
JSX 부분에서 map( ) 즉, 자바스크립트 코드를 작성할 것이기 때문에 { }로 먼저 감싸고 시작한다.
JSX에서 map( )은 배열의 모든 요소를 화면에 렌더링 해준다. map을 사용하면 중복된 코드가 사라지고 1개의 컴포넌트를 이용하면서 그 안에서 <div>{vegetableName}</div>가 순차적으로 보여진다.
그런데, App 컴포넌트 안에 squareStyle이 같이 있어 코드의 가독성이 좋지 않아,. Square를 컴포넌트로 분리해본다.
👌 Square 컴포넌트 분리해서 구현하기
import React from "react"; // Square 컴포넌트를 분리해서 구현 function Square(props) { const squareStyle = { width: "100px", height: "100px", border: "1px solid green", borderRadius: "10px", display: "flex", alignItems: "center", justifyContent: "center", }; return <div style={squareStyle}>{props.vegetableName}</div>; } function App() { const style = { padding: "100px", display: "flex", gap: "12px", }; const vegetables = ["감자", "고구마", "오이", "가지", "옥수수"]; return ( <div style={style}> {vegetables.map((vegetableName) => { // 분리한 Square 컴포넌트는 이렇게 사용 return <Square key={vegetableName} vegetableName={vegetableName} />; })} </div> ); } export default App; |
2️⃣ 조금 더 복잡한 데이터 다뤄보기
👌 객체가 담긴 배열 다뤄보기
const users = [ { id: 1, age: 30, name: "송중기" }, { id: 2, age: 24, name: "송강" }, { id: 3, age: 21, name: "김유정" }, { id: 4, age: 29, name: "구교환" }, ]; |
user라는 정보이며, 배열안에 object literal 형태의 데이터가 있다.
import React from "react"; function User(props) { const squareStyle = { width: "100px", height: "100px", border: "1px solid green", borderRadius: "10px", display: "flex", alignItems: "center", justifyContent: "center", }; return ( <div style={squareStyle}> <div>{props.user.age}살 - </div> <div>{props.user.name}</div> </div> ); } function App() { const style = { padding: "100px", display: "flex", gap: "12px", }; const users = [ { id: 1, age: 30, name: "송중기" }, { id: 2, age: 24, name: "송강" }, { id: 3, age: 21, name: "김유정" }, { id: 4, age: 29, name: "구교환" }, ]; return ( <div style={style}> {users.map((user) => { return <User user={user} key={user.id} />; })} </div> ); } export default App; |
3️⃣ key?
👌 브라우저 콘솔에 에러가 뜬다면
리액트에서 map을 사용하여 컴포넌트를 반복 렌더링 할 때는 반드시 컴포넌트에 key를 넣어줘야 한다. 넣지 않으면 위에 처럼 오류가 발생한다.
key가 필요한 이유는 React에서 컴포넌트 배열을 렌더링했을 때 각각의 원소에서 변동이 있는지 알아내려고 사용하기 때문이다. 만약 key가 없다면 React는 가상돔을 비교하는 과정에서 배열을 순차적으로 비교하면서 변화를 감지하려 한다. 하지만 key가 있으면 이 값을 이용해서 어떤 변화가 일어났는지 더 빠르게 알아낼 수 있게 된다. 즉, key 값을 넣어줘야 React의 성능이 더 최적화 된다.
map((value, index) => { }) 간혹 이렇게 map에서 지원해주는 index를 사용해서 key를 넣는 경우가 있는데 리액트 공식문서에서 좋지 않은 방식으로 지양하라고 하고 있어, 사용하지 않는 것이 좋다.
'Front-End, CS 스터디 > 항해99 - React 스터디' 카테고리의 다른 글
[리액트 React] 기초반 강의 5주차 정리 (0) | 2022.08.03 |
---|---|
[리액트 React] 숙련 학습 자료 정리 (0) | 2022.07.29 |
[리액트 React] 기초반 강의 4주차 정리 (2) | 2022.07.28 |
[리액트 React] 기초반 강의 3주차 정리 (0) | 2022.07.27 |
[리액트 React] 3주차 입문 주차 팀 과제 정리 (0) | 2022.07.24 |