이전 포스트에서 모던 자바스크립트에 대한 간단한 문법과 특징들에 대해 배웠다. 이번엔 리엑트가 무엇이고 컴포넌트, state, props 등이 무엇인지에 대해 알아보자.
리액트를 공부하다 보니 최근에 import, export 에 대해 알아 봤던게 계속 떠오른다.
const App = () => {
return (
<h1>hi</h1>
)
}
위와 같은 컴포넌트를 모듈이라고 생각해보자. 다른 파일에서 이 모듈을 사용하려면 export, import 를 해줘야 한다.
const App = () => {
return (
<h1>hi</h1>
)
}
export defualt App;
import App from './App';
자잘하게 리엑트에 대해 알아야 할 점들
리액트는 자바스크립트 기반이라 확장자가 .js 이나 컴포넌트는 .jsx 를 사용하기도 한다.
이벤트는 js 와 달리 카멜케이스를 사용한다.
onClick
/ onChange
css 속성명도 카멜케이스를 사용한다.
fontSize
/ marginTop
JSX 안에서 JS 는 {} 로 감싸서 작성한다.
리엑트에서 인라인 스타일로 스타일을 지정해줄때 {} 를 두번 쓰는게 왜 그런가 싶으면서도 번거로움이 느껴졌었다.
이는 리엑트에서 css 의 각 요소가 자바스크립트 객체로 기술하기에 style = {}
내에 css 요소를 객체로 넣어야 해서 중괄호를 이중으로 사용하는 형태가 된 것이다.
<p style={{ color: "blue" }}>안녕하세요</p>
const customStyle = {
color: "red",
fontSize: "20px"
}
<p style={customStyle}>안녕하세요</p>
docs 의 내용을 번역하며 추가로 찾아본 내용을 정리했다.
React 를 사용하면 마크업, CSS, JS 를 앱의 재사용 가능한 UI 요소인 사용자 지정 컴포넌트로 결합할 수 있다. 컴포넌트는 UI (유저 인터페이스)의 구성요소로 고유한 논리와 모양을 갖는다. 컴포넌트는 아주 작은 버튼에서부터 아주 큰 페이지까지 될 수 있다.
<PageLayout>
<NavigationHeader>
<SearchBar />
<Link to="/docs">Docs</Link>
</NavigationHeader>
<Sidebar />
<PageContent>
<TableOfContents />
<DocumentationText />
</PageContent>
</PageLayout>
리엑트 컴포넌트는 자바스크립트 함수로 마크업을 반환한다.
마크업 : React 컴포넌트에서 반환하는 자바스크립트 함수가 생성하는 구조화된 코드를 의미한다. 이는 일반적으로 JSX 문법을 사용하여 작성되며 화면에 표시될 요소들의 구조와 모양을 정의하는 역할을 한다.
같은 파일내라면 export 를 할필요 없이 생성한 컴포넌트를 다른 컴포넌트에 nest
중첩해줄수 있다.
const TableHeader = () => { ... }
const TableBody = () => { ... }
const Table = () => {
return (
<table>
<TableHeader />
<TableBody />
</table>
);
}
위와 같이 컴포넌트는 다른 컴포넌트에 중첩될 수 있다.
React 에서의 컴포넌트는 HTML 태그가 반드시 lowercase 로 작성하는 것과 달리 반드시 대문자로 시작한다.
<MyButton/>
<p></p>
주의점
return 의 경우 한줄일때 줄바꿈을 하면 안된다. 만약 그럴시엔 괄호로 감싸줘야 한다.
❌
export default function Profile() {
return
<img src="https://i.imgur.com/jA8hHMpm.jpg" alt="Katsuko Saruhashi" />;
}
🅾️
export default function Profile() {
return <img src="https://i.imgur.com/jA8hHMpm.jpg" alt="Katsuko Saruhashi" />;
}
export default function Profile() {
return (
<img
src="https://i.imgur.com/jA8hHMpm.jpg"
alt="Katsuko Saruhashi"
/>
);
}
컴포넌트, interface, type 별칭에는 파스칼 케이스를 사용한다.
// React component
const LeftGridPanel = () => {
...
}
// Typescript interface
interface AdminUser {
name: string;
id: number;
email: string;
}
// Typescript Type Alias
type TodoList = {
todos: string[];
id: number;
name: string;
}
변수, 배열, 객체, 함수 등 JavaScript 데이터 타입에는 카멜 케이스를 사용한다.
const getLastDigit = () => { ... }
const userTypes = [ ... ]
function Profile() {
return (
<img
src="https://i.imgur.com/lICfvbD.jpg"
alt="Aklilu Lemma"
/>
);
}
export default function Profile() {
return (
<img
src="https://i.imgur.com/lICfvbD.jpg"
alt="Aklilu Lemma"
/>
);
}
그냥 export 를 쓸수는 없을까?
그냥 컴포넌트는 export 될 수 없고 앞에 export default 가 붙어야 한다.
확인해보니 그냥 export 를 붙였다고 항상 저 에러가 뜨는게 아니고 조건이 있었다.
출처 : react.dev docs - component
Syntax | Export statement | Import statement |
---|---|---|
Default | export default function Button() {} | import Button from './Button.js'; |
Named | export function Button() {} | import { Button } from './Button.js'; |
컴포넌트를 export default
로 export 할 경우 import 할때
import Button from './Button.js';
방식으로 해야 하고
컴포넌트를 export
로 export 할 경우 import 할때
import { Button } from './Button.js';
방식으로 해야 한다.
이게 지켜지지 않았을때 위와 같은 오류가 뜬다.
추가로 클래스나 함수를 내보낼때는 세미콜론을 붙이지 않는다. 위에선 잘 모르고 붙였었는데 세미콜론을 붙인다고 함수 선언 방식이 함수 선언문에서 함수 표현식으로 바뀌지 않는다. JS 스타일 가이드에서도 함수나 클래스 선언 뒤에 세미콜론을 붙이지 않는 것을 권장한다.
import React, { useState, useEffect, useCallback } from "react";
import Typography from "@material-ui/core/Typography";
import Divider from "@material-ui/core/Divider";
import Title from "../components/Title";
import Navigation from "../components/Navigation";
import DialogActions from "@material-ui/core/DialogActions"
import { getServiceURL } from '../../utils/getServiceURL";
import Grid from "@material-ui/core/Grid";
import Paragraph from "../components/Paragprah";
import { sectionTitleEnum } from "../../constants";
import { useSelector, useDispatch } from "react-redux";
import Box from "@material-ui/core/Box";
import axios from 'axios';
import { DatePicker } from "@material-ui/pickers";
import { Formik } from "formik";
import CustomButton from "../components/CustomButton";
react 에서 컴포넌트를 가져와 쓰다보면 어느새 수없이 많이 import 한 컴포넌트, 모듈, 패키지들을 볼 수 있게 된다. 어느순간 뭐가 뭔지 헷갈리게 될수 있어서 그룹으로 묶어두는 것을 권장하기도 한다.
import React, { useState, useEffect, useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
import { Formik } from "formik";
import axios from 'axios';
import Typography from "@material-ui/core/Typography";
import Divider from "@material-ui/core/Divider";
import Box from "@material-ui/core/Box";
import DialogActions from "@material-ui/core/DialogActions";
import Grid from "@material-ui/core/Grid";
import { DatePicker } from "@material-ui/pickers";
import { getServiceURL } from '../../utils/getServiceURL";
import { sectionTitleEnum } from "../../constants";
import CustomButton from "../components/CustomButton";
import Title from "../components/Title";
import Navigation from "../components/Navigation";
import Paragraph from "../components/Paragraph";
이처럼 내부 및 외부 import 문을 분리하기도 하고
내장('react' 같은) -> 외부 (서드파티 모듈) -> 내부 순으로 정리하기도 한다.
import 를 정렬하는 것은 JS 파일을 다루는데 큰 장점이 있는데 먼저 각각의 패키지들을 import 한 것을 보기 쉽게 만들어주고 어떤 import 가 서드파티 패키지인지 지역 import 인지 쉽게 구분할 수 있게 해준다.
ESLint 를 사용한다면 이에 대해 큰 도움이 될 것이다.
ESLint 는 import 를 정렬하는 규칙이 내장되어 있다. 만약 내가 원하는 바를 충분히 충족시키지 않는다면 eslint-plugin-import 를 사용해보자.
# inside your project's working tree
npm install eslint-plugin-import --save-dev
{
"import/order": [
"error",
{
"groups": [
// import 되는 순서를 정의, 그룹 내 각 요소의 순서와 동일
// builtin / external / internal / unknown / parent / sibling / index / object / type
"builtin",
"external",
"internal",
// 'sibling', 'parent', 'index' 타입은 상대 경로의 파일로부터 import 되는 모듈을 뜻한다.
["sibling", "parent", "index"],
"type",
// 생략된 'object' 타입과 함께 가장 마지막에 import 된다.
"unknown"
],
"pathGroups": [
// 추가적으로 옵션을 설정하고 싶은 경로를 별칭과 함께 다시 그룹화
{
"pattern": "react",
"group": "external",
"position": "before" // before / after
},
{
"pattern": "@saas-fe/**/*.style",
"group": "unknown"
}
],
"pathGroupsExcludedImportTypes": ["react", "unknwon"],
// pathGroup 에서 설정에 의해 처리되지 않는 import type 을 정의한다.
// 즉, import 문을 다른 그룹과 분리하여 순서 규칙에서 제외시 사용한다.
// 보통 외부 라이브러리나 특정 패키지들을 다른 그룹으로 분리하고자 할 때 이 옵션을 사용한다.
"newlines-between": "always",
// 그룹 사이에 줄바꿈을 강제화 하거나 금지하는 옵션이다.
// ignore / always / always-and-inside-groups / never
"alphabetize": {
// 그룹 내에서 알파벳을 기준으로 정렬 순서를 결정한다.
"order": "asc",
"caseInsensitive": true
}
}
]
}
groups
: group 의 순서 설정pathGroups
: pattern 에 의해 설정한 경로에 의해 그룹화할 수 있다.pathGroupsExcludedImportTypes
: import type 을 정의한다. 위의 경우 react 는 다른 타입들처럼 다뤄질 것이기에 다른 외부 패키지들과 분리할 수 있다.newline-between
: 각 그룹을 새 라인을 사이에 두고 구분한다.alphabetize
: 그룹을 어떻게 정렬할지를 정한다.오류가 발생했는데 parser 에 대한 설정을 잘못 했던게 원인이라 다음과 같이 수정했다.
... 왜일까? 적용이 전혀 안되고 있다... 플러그인도 설치하고 extension 도 적용했는데...
이 부분에 대해선 추가로 확인후 따로 eslint 에 관한 포스트를 정리할 예정이다.
JavaScript 의 확장 문법으로 HTML 과 유사하나 JavaScript 코드로 컴파일되어 동작한다. 리엑트에서 사용중인 마크업 문법이다.
<br />
const heading = <h1 className="site-heading">Hello, React</h1>
// 위와 동일
const heading = React.createElement('h1', { className: 'site-heading' }, 'Hello, React!')
웹은 HTML, CSS 및 JS 를 기반으로 구축되었다. 오랜시간동안 콘텐츠는 HTML, 디자인은 CSS, 로직은 JS 로 구성했다. 콘텐츠는 HTML 내에서 마크업되었으나 로직은 JS 에서 별도로 존재했다.
그런데 웹이 점점 더 상호작용하게 되며 논리가 콘텐츠를 결정하게 되었고 JS 가 HTML 을 담당하게 되면서 리엑트에서 렌더링 로직과 마크업이 같은 위치인 컴포넌트에 함께 존재하게 되었다.
렌더링 로직과 마크업을 함께 유지하면 수정할 때마다 서로 동기화가 유지되는데 관련되지 않은 내용들은 서로 분리되어 있어 각각을 따로 변경하는 것이 좋다. React 컴포넌트는 React 가 렌더링하는 일부 마크업을 포함할 수 있는 JS 함수이다. 컴포넌트는 JSX 라는 구문 확장을 사용해서 마크업을 나타낸다.
JSX 를 사용시 JavaScript 파일 내에 HTML 과 유사한 마크업을 작성해서 렌더링 로직과 콘텐츠를 같은 위치에 유지할 수 있다. 때에 따라 JS 로직을 추가하거나 마크업 내에서 동적 속성을 참조하고 싶을 수 있는데 이럴때 JSX 에서 중괄호를 사용해서 JS 창을 열 수 있다.
export default function TodoList() {
const name = 'Gregorio Y. Zara';
return (
<h1>{name}'s To Do List</h1>
);
}
<h1>{name}'s To Do List</h1>
: OK<{tag}>Gregorio Y. Zara's To Do List</{tag}>
: NO=
등호 뒤에 따라오는 어트리뷰트에 중괄호를 사용할 수 있다. 그러나 따옴표로 감싼 중괄호는 문자로서 취급된다.src={avatar}
: OKsrc="{avatar}"
: NOconst person = {
name: 'Gregorio Y. Zara',
theme: {
backgroundColor: 'black',
color: 'pink'
}
};
export default function TodoList() {
return (
<div style={person.theme}>
<h1>{person.name}'s Todos</h1>
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
);
}
주의점
바로 앞의 예시코드에서<h1>{person.name}'s Todos</h1>
의 경우<h1>{person}'s Todos</h1>
처럼 사용할 수 없다.
위와 같은 방식으로 children 일부에 객체가 올 수는 없다. text 로 이루어진 요소노드엔 문자열을 넣어줘야 한다.
리엑트에서 CSS 클래스는 className
으로 추가한다.
리엑트에서 따로 조건식을 위한 특별한 문법이 있지 않다. 대신 JS 에서 사용하듯 같은 조건식을 사용하면 된다.
그냥 if else 와 같은 조건식을 사용하거나 3항 연산자를 사용할 수 있다.
<div>
{isLoggedIn ? (
<AdminPanel />
) : (
<LoginForm />
)}
</div>
else 를 필요로 하지 않을 경우 && 논리곱 연산자를 사용할 수 있다.
<div>
{isLoggedIn && <AdminPanel />}
</div>
이런 조건식은 attribute 를 명세하는데에도 같은 방식으로 사용된다.
정리
{cond ? <A /> : <B />}
는 만약 cond 조건이라면 <A />
를 렌더링하고 아니라면 <B />
를 렌더링하라는 것을 의미한다.{cond && <A />}
는 만약 cond 을 만족하면 <A />
를 렌더링하고 그렇지 않으면 아무것도 렌더링하지 않는다.for loop
이나 array map()
함수를 이용하여 컴포넌트 리스트를 렌더링할 수 있다.
예를 들어 어떤 제품에 대한 정보를 담은 배열이 있다고 할때 이 제품들을 map 을 통해 JSX 문법으로 치환하여 리스트에 넣어줄 수 있다.
const products = [
{ title: 'Cabbage', id: 1 },
{ title: 'Garlic', id: 2 },
{ title: 'Apple', id: 3 },
];
const listItems = products.map(product =>
<li key={product.id}>
{product.title}
</li>
);
return (
<ul>{listItems}</ul>
);
주의점
- React 에서
<li>
와 같은 리스트를 사용할때 반드시 유일한 숫자나 문자로 이루어진 식별자 (예를들어 key)를 부여해야 한다.
react.dev 의 renderling-lists 에 나오는 내용입니다.
바로가기
<ul>
<li>Creola Katherine Johnson: mathematician</li>
<li>Mario José Molina-Pasquel Henríquez: chemist</li>
<li>Mohammad Abdus Salam: physicist</li>
<li>Percy Lavon Julian: chemist</li>
<li>Subrahmanyan Chandrasekhar: astrophysicist</li>
</ul>
위의 리스트 아이템들은 contents 만 다르다. 우리는 이런 데이터를 JS 객체나 배열에 저장하고 map
이나 filter
같은 메서드를 사용하여 컴포넌트 리스트를 렌더링할 수 있다.
const people = [
'Creola Katherine Johnson: mathematician',
'Mario José Molina-Pasquel Henríquez: chemist',
'Mohammad Abdus Salam: physicist',
'Percy Lavon Julian: chemist',
'Subrahmanyan Chandrasekhar: astrophysicist'
];
const listItems = people.map(person => <li>{person}</li>);
listItem
을 <ol>
혹은 <ul>
로 감싼다.return <ul>{listItems}</ul>;
const people = [{
id: 0,
name: 'Creola Katherine Johnson',
profession: 'mathematician',
}, {
id: 1,
name: 'Mario José Molina-Pasquel Henríquez',
profession: 'chemist',
}, {
id: 2,
name: 'Mohammad Abdus Salam',
profession: 'physicist',
}, {
name: 'Percy Lavon Julian',
profession: 'chemist',
}, {
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrophysicist',
}];
profession 이 'chemist' 인 사람들을 보이고 싶어하는 상황이다. 이때 filter 메서드를 사용할 수 있다.
const chemists = people.filter(person =>
person.profession === 'chemist'
);
const listItems = chemists.map(person =>
<li>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
known for {person.accomplishment}
</p>
</li>
);
return <ul>{listItems}</ul>;
계속 렌더링이란 말이 나왔는데 처음 화면을 표시하는 것을
렌더링
이라 하고 변경을 감지하고 컴포넌트를 다시 처리하는 것을재렌더링
이라 부른다. 렌더링과 재렌더링의 차이로 리엑트의 라이프 사이클에 관한 것을 다음 포스트에서 다뤄보자
Pitfall
- 화살표함수에서 => 다음에 오는 것은 반환값이니 중괄호 없이 사용할 수 있다.
- 단, 중괄호를 사용할 경우 return 을 꼭 명시해줘야 하며 이 경우엔 한줄 이상일 경우 권장된다.
Pitfall 예시코드
const listItems = chemists.map(person =>
<li>...</li> // Implicit return!
);
const listItems = chemists.map(person => { // Curly brace
return <li>...</li>;
});
function MyButton() {
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
이벤트는 뒤에 괄호가 붙지 않았다는 것에 주의하자. 리엑트는 유저가 이벤트를 동작시킬때 이벤트 핸들러를 부른다.
props
는 컴포넌트에 전달하는 인수와 같은 것으로 컴포넌트는 전달받은 props 에 따라 표시하는 스타일과 내용을 변경한다.
전에 만들어밨던 버튼 컴포넌트가 대략 아래와 같았다.
import styled, { css } from "styled-components";
import colors from "../../config/Colors";
const StyledButton = styled.button`
width: ${(props) => props.width || "100%"};
${(props) =>
props.input &&
css`
color: ${colors.gray};
`};
${(props) =>
props.login &&
css`
onClick: ${props.onClick};
`};
${(props) =>
props.disabled &&
css`
background: ${colors.bg_disabled};
color: ${colors.white};
`};
`;
function Button({ children, text, ...props }) {
return <StyledButton {...props}>{text || children}
</StyledButton>;
}
export default Button;
버튼 사용시
<Button input text="입력버튼"/>
<Button login />
<Button disabled />
컴포넌트를 사용하다보면 위에서 사례로 든 버튼처럼 기본 버튼, 비활성화 버튼, 로그인 버튼, 입력 버튼 등 큰 범위로 보면 공통요소가 있으나 세부적인 차이가 조금씩 있는 경우가 있다. 그런데 그들을 DefaultButotn, DisabledButton, LoginButton, InputButton ... 등 만들다 보면 컴포넌트가 너무 많아져 재사용하기 어려워진다. 이때 어느 정도 동적으로 컴포넌트를 재사용할 수 있게 props 로 조건을 전달할 수 있다.
props 는 컴포넌트 태그 안에 임의의 이름을 붙여 props 를 전달할 수 있다. 그러면 컴포넌트는 파라미터로 props 을 전달받아 조건을 확인할 수 있다.
그 외에 특별한 props 로 children
이 있다.
컴포넌트도 HTML 태그와 마찬가지로 임의의 요소를 감싸서 사용할 수 있는데 이 둘러싸인 부분이 props 에 설정된다.
<Button>
<span>안녕하세요</span>
</Button>
위의 경우 안녕하세요 가 Button 의 children props 가 된다. 사용시엔 props.children
으로 사용할 수 있다.
react.dev 의 passing-props-to-a-component 챕터의 예시코드다.
App.js
import Avatar from './Avatar.js';
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
export default function Profile() {
return (
<Card>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
</Card>
);
}
import Avatar from './Avatar.js';
를 사용해서 import 한 것을 보면 Avatar 는 export default 를 사용했음을 추측할 수 있다.
Avatar.js
import { getImageUrl } from './utils.js';
export default function Avatar({ person, size }) {
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={size}
height={size}
/>
);
}
utils.js
export function getImageUrl(person, size = 's') {
return (
'https://i.imgur.com/' +
person.imageId +
size +
'.jpg'
);
}
string props 에는 중괄호를 피하자. strig 이 아니라 JS 표현식을 사용하고 싶다면 중괄호를 사용해야 한다.
컴포넌트가 특정 정보들을 기억하도록 하기 위해서는 state
라는것이 필요하다. 리액트 개발에서는 화면에 표시하는 데이터나 길이가 변하는 상태 등을 모두 state
로 관리한다.
state
: 컴포넌트의 상태를 나타내는 값
화면 상태로
등을 예로 들 수 있다.
함수 컴포넌트에서 리액트 훅이라 말하는 기능중 useState 라는 함수를 사용해 state 를 다룬다.
import { useState } from 'react';
useState 를 개발자모드로 조회해보면 useState 의 반환값이 배열 형태로 첫 번째 인수가 현재 상태를 의미하는 state 이고 두 번째 인수가 function 인 것을 볼 수 있다. 이 함수는 state 를 업데이트하기 위한 함수가 설정되어 주로 다음과 같은 형태로 사용한다.
const [mode, setMode] = useState();
useState의 인자로는 초깃값을 설정해 줄 수 있다.
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={handleClick}>
Clicked {count} times
</button>
);
}
수치 외에도 자바스크립트에서 변수로 다루는 문자열, 논리값, 배열, 객체등 무엇이든 state 로 관리할 수 있다.
그런데 사실 위의 코드는 다음과 같이 사용하는게 엄밀하게는 더 올바른 방법이다.
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount((prev) => prev + 1); // 바꾼 부분
}
return (
<button onClick={handleClick}>
Clicked {count} times
</button>
);
}
첫 번째 코드
두 번째 코드
function handleClick() {
setCount(count + 1);
setCount(count + 1);
}
function handleClick() {
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
}
전자의 경우 각 호출에서 사용되는 count 값이 동일한 값을 가지기에 두 번 실행해도 상태가 한번만 증가한다.
후자의 경우 각 호출에서 이전 상태 값을 기반으로 하여 새로운 값을 계산하므로 두 번 실행시 상태가 두 번 증가한다.
컴포넌트를 여러번 사용시 각 컴포넌트는 각자의 상태를 고유하게 갖는다.
let animals = [
{
name:'Lion',
category: 'carnivore'
},
{
name:'dog',
category:'pet'
},
{
name:'cat',
category:'pet'
},
{
name:'wolf',
category:'carnivore'
}
]
set 은 반복을 제거해준다.
let category = animals.map((animal)=>animal.category);
console.log(category); //["carnivore" , "pet" , "pet" , "carnivore"]
그냥 map 을 사용하면 중복 여부를 체크하지 않는다고 할때 (조건식 여부 제외하고 그냥 모든 값을 보고 반환하는 상황)
//wrap your iteration in the set method like this
let category = [...new Set(animals.map((animal)=>animal.category))];
console.log(category); //["carnivore" , "pet"]
set 을 사용하면 위와 같이 중복을 제거할 수 있다.
reduce 는 매우 강력한 배열 함수이다. 이는 filter 와 find 메서드를 대체할 수 있고 대용량 데이터를 다룰때 map 과 filter 를 사용하는 것보다 좀더 용이하다.
map 과 filter 를 연달아 사용시 작업을 두번 하게 된다. 먼저 모든 각각의 값을 필터링하고 그 뒤에 남은 값들을 매핑한다.
반면에 reduce 는 필터링과 매핑을 한번에 할 수 있게 한다.
reduce 는 배열을 반복하면서 map, filter, find 등과 같이 콜백 함수를 얻는다. 차이점으로는 reduce 는 배열을 단일 값(숫자, 배열, 객체) 으로 줄인다.
다음의 예시에서 reduce 는 두개의 인자를 갖는데 첫 번째 인자는 계산의 총합이고 두 번째 인자는 현재 반복 값이다.
let staffs = [
{ name: "Susan", age: 14, salary: 100 },
{ name: "Daniel", age: 16, salary: 120 },
{ name: "Bruno", age: 56, salary: 400 },
{ name: "Jacob", age: 15, salary: 110 },
{ name: "Sam", age: 64, salary: 500 },
{ name: "Dave", age: 56, salary: 380 },
{ name: "Neils", age: 65, salary: 540 }
];
const salaryInfo = staffs.reduce(
(total, staff) => {
let staffTithe = staff.salary * 0.1;
total.totalTithe += staffTithe;
total['totalSalary'] += staff.salary;
return total;
},
{ totalSalary: 0, totalTithe: 0 }
);
console.log(salaryInfo); // { totalSalary: 2150 , totalTithe: 215 }
자세히 알아볼까?
reduce 메서드는 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출한다. 그리고 콜백 함수의 반환값을 다음 순회시 콜백 함수의 첫 번째 인수로 전달하면서 콜백 함수를 호출하여 하나의 결과값을 만들어 반환하며 원본 배열은 변경되지 않는다.
reduce 메서드는 첫 번째 인자로 콜백 함수, 두 번째 인수로 초기값을 전달받는다. reduce 메서드의 콜백함수는 4개의 인수를 갖는데 초기값 또는 이전 반환값, reduce 메서드를 호출한 배열의 요소값과 인덱스, reduce 메서드를 호출한 배열 그 자체인 this 가 전달된다.
const sum = [1, 2, 3, 4].reduce((accumulator, currentValue, index, array) => accumulator + currentValue, 0);
console.log(sum); // 10
reduce 를 사용하는 사례로 평균 구하기, 최대값 구하기, 요소의 중복 횟수 구하기, 중첩 배열 평탄화, 중복 요소 제거등이 있다.
그중 중첩 배열 평탄화는 다음과 같이 구현할 수 있다.
const values = [1, [2, 3], 4, [5, 6]];
const flatten = values.reduce((acc, cur) => acc.concat(cur), []);
// [1] => [1, 2, 3] => [1, 2, 3, 4] => [1, 2, 3, 4, 5, 6]
console.log(flatten); // [1, 2, 3, 4, 5, 6]
물론 위의 경우는 ES2019 에서 도입된 Array.prototpe.flat 메서드가 더 직관적이다.
[1, [2, 3], 4, [5, 6]].flat(); // [1, 2, 3, 4, 5, 6]
[1, [2, 3, [4, 5]].flat(2); // [1, 2, 3, 4, 5], 인수 2 는 중첩 배열을 평탄화하기 위한 깊이 값이다.
docs
react docs ⭐️
book
모던 자바스크립트로 배우는 리엑트 입문
모던 자바스크립트 Deep Dive
blog
react tutorial
FreeCodeCamp - React 배우기 전에 알아야 할 상위 JS 개념
log4me - ESLint
DEKU - ESLint
lakelouise - ESLint
helloinyoung - ESLint
column
Sorting your imports correctly in React
FreeCodeCamp - 리엑트 모범 사례 - 2022 더 나은 리액트 코드 작성을 위한 팁 ⭐️
문서를 찾다 보면 역시 공식문서 docs 가 가장 잘 설명되어 있다. 책, docs, 정리가 잘 된 블로그 들을 비교하며 정리하고 있으며 그중 이해가 되지 않는 부분에 대해서 chatGPT 의 도움을 받고 있다.
그냥 크롤링한 코드를 복붙하는 개발자보다 왜 동작하는지 원리와 개념에 대해 확실히 아는 개발자가 되고 싶다.
추가로 robinwieruch - React Folder Structure in 5 Steps - 2022 에서 보면 react 의 구조에 대해 궁금했었는데 react 폴더 구조에 관한 글을 볼 수 있었다. 흥미로운 내용이니 한번 참고해보자.