리액트에서 자주 사용하는 기초 JS 정리
배열의 주요 메서드
map
💡 원본 배열에서 각 요소를 가공한 새로운 배열을 리턴하는 함수
1. 배열 내 모든 요소 각각에 대해
2. 주어진 함수를 호출한 결과를 모아
3. 새로운 배열을 반환
* 원본을 바꾸지 않고 새로운 배열을 반환한다는 것이 중요!! > 무조건 return 써주기!! (안쓰면 undefined가 출력됨)
예시 코드
const numbers = [1, 2, 3, 4, 5];
const squared = numbers.map(function (num) {
return num * num;
});
console. log (squared);
// [1, 4, 9, 16, 25] 출력
filter
주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환
예시 코드
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
const movieList = [
{
id: 1,
title: "movie01",
img: "img01.jpg",
},
{ id: 2, title: "movie02", img: "img02.jpg" },
{ id: 3, title: "movie03", img: "img03.jpg" },
{ id: 4, title: "movie02", img: "img04.jpg" },
];
const filterdMovieList = movieList.filter(function(movie) {
return movie.title === "movie02";
});
// movie02를 가진 두 객체가 배열에 들어가게 됨
// [
// { id: 2, title: "movie02", img: "img02.jpg" },
// { id: 4, title: "movie02", img: "img04.jpg" },
// ]
reduce
배열의 각 요소에 대해 주어진 함수를 실행하고 결과물을 누적시켜 반환 = 누적기
예시 코드
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 15
sort
배열의 요소를 적절한 위치에 정렬하고 배열을 반환
원본 배열 자체를 바꾸기 때문에 불변성이 바뀜
기본적으로 문자로 계산하기 때문에 숫자도 기본 옵션이 없다면 문자화 시켜서 변환됨.
예시 코드
// 기본 사용법
const fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.sort();
console.log(fruits); // ["Apple", "Banana", "Mango", "Orange"]
// 숫자 배열 정렬
const numbers = [40, 100, 1, 5, 25];
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 5, 25, 40, 100]
// 숫자를 그냥 정렬하면 문자화로 인해 순서가 이상해짐
const numbers = [40, 100, 1, 5, 25];
numbers.sort();
console.log(numbers); // [1, 100, 25, 40, 5]
Destructuring
객체나 배열의 속성을 보다 쉽게 추출할 수 있는 문법
객체의 Destructuring
여기가 배열보다 중요!
1-1. 다중 속성 추출
const { name, price } = item;
console.log(name); // 커피
console.log(price); // 4000
1-2. 함수 매개변수에서의 사용
리액트 컴포넌트에서 많이 사용
obj 안에 key를 그대로 사용하여 분해한다는 것도 중요한 개념
function greet({ name, age }) {
console.log(`안녕하세요, 제 이름은 ${name}이고 나이는 ${age}살입니다.`);
}
greet({ name: "르순이", age: 28 });
배열의 Destructuring
let colors = ["red", "green", "blue"];
let [firstColor, secondColor] = colors;
console.log(firstColor); // red
console.log(secondColor); // green
let [, , thirdColor] = colors;
console.log(thirdColor); // blue
Spread operators
배열이나 객체를 개별 요소로 확장 (복사하거나 합칠 때) 할 때 사용
불변성이 깨지는 현상이 생김 : 바라보는 주소가 같기 때문
1. 객체에서의 사용 (spread operators)
1-1. 객체 복사 및 확장
객체의 속성을 쉽게 다른 객체로 복사하거나 확장
const originalUser = { name: "길동이", age: 28 };
const updatedUser = { ...originalUser, location: "한국" };
console.log(updatedUser); // { name: "길동이", age: 28, location: "한국" }
// 불변성이 깨짐 (그래서 위와 같이 해야함 얕은복사)
const originalUser2 = { name: "르탄이", age: 28 };
const updatedUser2 = originalUser2
updatedUser2.name = "길순이"
console.log(originalUser2); // { name: "길순이", age: 28 }
console.log(updatedUser2); // { name: "길순이", age: 28 }
2. 배열에서의 사용 (spread operators)
2-1. 배열 합치기
두 배열을 간편하게 합칠 때 많이 사용
const first = [1, 2, 3];
const second = [4, 5, 6];
const combined = [...first, ...second];
console.log(combined); // [1, 2, 3, 4, 5, 6]
rest operator
함수의 매개변수에서 사용되거나, 객체 리터럴이나 배열 리터럴에서 남은 부분을 하나의 변수로 그룹화할 때 사용
여러 인수를 배열로 그룹화하거나, 객체 분해 할당에서 특정 속성을 제외한 나머지 속성들을 새 객체로 그룹화할 때 사용
1-1. 함수 매개변수
배열로 넣은 적은 없지만 ...에 의해 배열로 들어가게 됨
function sum(...numbers) {
return numbers.reduce((acc, current) => acc + current, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
1-2. 객체 분해 할당
const person = {
name: "John",
age: 30,
country: "USA",
occupation: "Developer"
};
const { country, ...rest } = person;
console.log(rest); // { name: "John", age: 30, occupation: "Developer" }
논리합 연산자 (||)
좌변이 truthy 값인 경우 결과값을 반환 > 우변은 평가되지 않음
논리곱 (&&) 연산자
좌변이 truthy 값인 경우 우변 평가 > 조건에 따라 특정 코드를 실행할때 유용
Optional Chaining (?.)
객체의 속성에 접근할때 오류가 아닌 undefined를 반환 > 객체의 중첩된 속성에 안전하게 접근
const user = {
profile: {
name: "르탄이",
details: {
age: 30,
location: "서울시 강남구"
}
}
};
console.log(user.profile?.details?.age); // 출력: 30
원래 profile이나 detail 값이 undefined나 null인 경우 에러를 발생하는데 .?를 활용하면 문제 예방 가능
Null 병합 연산자 (??)
좌변이 null이나 undefined 일때 우변 평가
// 사용자의 위치 설정이 없으면 기본 위치를 제공
let userLocation = null;
console.log(userLocation ?? 'Unknown location'); // 출력: Unknown location
userLocation = 'Seoul';
console.log(userLocation ?? 'Unknown location'); // 출력: Seoul
// 사용자 입력이 0인 경우에도 0을 유효한 값으로 취급
const temperature = 0;
console.log(temperature ?? 25); // 출력: 0
논리 Or 연산자와 null 병합 연산자의 차이
truthy한 값이냐 아니냐 vs null이냐 undefined이냐 아니냐
ES6 Modules 기본
재사용 가능한 코드 조각을 캡슐화
다른 자스 파일에서 쉽게 재사용 가능
일반적인 코드
// math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
// app.js
import { add, multiply, ONE, sample } from './math.js';
console.log(add(2, 3)); //5
console.log(multiply(2, 3)); //6
모듈을 사용해야하는 이유
1. 명확한 종속성 관리
기존 <script> 태그를 사용해서 로드하는 방식은 로딩 순서를 수동으로 관리 > 종속성을 추적 관리 어렵고 버그 발생 가능성이 높아짐
모듈 시스템을 사용하면 개발자는 파일을 로드하는 순서에 신경 쓸 필요가 없음 (종속성을 내부적으로 선언)
<!-- 전통적인 스크립트 로딩 방식 -->
<script src="jquery.js"></script>
<script src="plugin.js"></script> <!-- 이 스크립트는 jquery.js에 의존 -->
<script src="app.js"></script> <!-- 이 스크립트는 plugin.js와 jquery.js에 의존 -->
// ES6 모듈 사용 예시
import $ from 'jquery';
import plugin from 'plugin'; // 자동으로 jQuery에 대한 의존성을 처리
import app from 'app'; // 모든 의존성이 충족되면 실행
2. 코드 캡슐화와 충돌 방지
- 모듈은 자체적으로 스코프를 가짐, 모듈 외부에서 모듈 내부의 변수에 직접 접근 할 수 없음 > 전역변수 오염, 이름 충돌 방지
- 여러 스크립트에서 동일한 함수 이름을 사용해도 괜찮아짐
// module1.js
export function conflictFunction() {
console.log('Module 1의 함수');
}
// module2.js
export function conflictFunction() {
console.log('Module 2의 함수');
}
// app.js
import { conflictFunction as function1 } from './module1';
import { conflictFunction as function2 } from './module2';
function1(); // "Module 1의 함수"
function2(); // "Module 2의 함수"
3. 효율적인 코드 로딩
필요한 기능한 선택적으로 불러오기 가능 > 초기 로딩 시간 단축
코드 스플리팅을 사용하면 필요한 코드만 동적으로 로드 가능
// 동적으로 모듈 로드하기 (예: 사용자가 특정 기능을 활성화했을 때)
button.addEventListener('click', event => {
import('./heavyModule.js')
.then(module => {
module.heavyFunction();
})
.catch(err => {
console.error("모듈 로딩에 실패했습니다.", err);
});
});
이름 바꾸기, 기본 내보내기
별칭사용
import 시 특정 요소에 별칭을 지정하여 충돌을 방지 > 명확성을 높일 수 있음
import { square as sqr } from './utils.js';
console.log(sqr(4)); // 16
기본 내용 내보내기, 가져오기
모듈당 하나의 주요 기능을 내보낼 때 유용
export default를 통해 내보내진 모듈은 import시 이름변경이 가능
이름 명시하지 않아도 됨
// math.js
export default function multiply(x, y) {
return x * y;
}
// app.js
import multiply from './math.js';
console.log(multiply(6, 7)); // 42
// export default 시 모듈이름 변경은 자유!
// utils.js
export default function square(x) {
return x * x;
}
// main.js
import mySquare from './utils.js';
console.log(mySquare(4)); // 출력: 16
전체 모듈 내용 가져오기
// app.js
import * as MathFunctions from './math.js';
console.log(MathFunctions.add(10, 5)); // 15
console.log(MathFunctions.multiply(10, 5)); // 50
Async/Await
복잡한 프로미스 체인을 간결하고 동기적인 흐름으로 작성할 수 있게 도와줌
Promise
비동기 작업의 최종 완료 또는 실패를 나타내는 객체
주로 서버나 데이터를 요청하고 받아오는 HTTP 요청 처리에 사용
.then(), .catch(), finally() 메소드를 이용해 연속적으로 처리 가능 > 콜백 지옥 피하고 코드 가독성을 높임
const myPromise = new Promise(function(resolve, reject) {
// 비동기 작업을 수행하고
if (/* 작업 성공 */) {
resolve('Success!');
} else {
reject('Error!');
}
});
myPromise.then(function(value) {
// 성공(resolve)한 경우 수행
console.log(value); // 'Success!'
}).catch(function(error) {
// 실패(reject)한 경우 수행
console.error(error); // 'Error!'
});
async
promise를 반환
async function fetchData() {
return 'Data loaded';
}
// 아래 코드와 같아요.
// async function fetchData() {
// return Promise.resolve('Data loaded');
// }
fetchData().then(console.log); // 'Data loaded'
await
프로미스의 완료를 기다리는 동안 함수의 실행을 잠시 중단
프로미스가 해결되면 자동으로 함수의 실행을 재개
> 비동기 코드를 동기적으로 표현이 가능
async function fetchData() {
try {
const data = await fetch('https://api.example.com/data');
const json = await data.json();
console.log(json);
} catch (error) {
console.error("Data loading failed", error);
}
}
fetchData();