Medium - Simplify your JavaScript – Use .map(), .reduce(), and .filter()
in Trend
Trend 파악을 Medium 기고문 요약 포스팅 - 당신의 자바스크립트를 단순하게; 맵,리듀스,필터를 쓰세요
자바스크립트를 막 시작했다면 .map(), .reduce(), .filter()에 대해 들어보지 않으셨을 수도 있습니다. 저는 몇 년전까지만 해도 인터넷 익스플로러 8을 지원해야 했기 때문에 시간이 좀 걸렸습니다. 그러나 이런 낡은 브라우저와 호환되어야 할 이유가 없다면 위의 메소드들과 친해져야 합니다.
해당 포스트에 나와있는 개념은 다른 언어에도 존재하는 것이기 때문에 여러분이 무슨 프로그래밍 언어를 쓰고 있던 간에 적용하실 수 있다는 것을 기억해 주세요.
.map()
간단한 예제를 통해서 어떻게 동작하는 지 설명하겠습니다. 하나의 객체가 사람을 표현하는 다수의 객체를 담고있는 배열을 받았다고 생각해봅시다. 결국 끝에가서 여러분이 필요한 것은 각 객체의 id 값입니다.
// What you have
var officers = [
{ id: 20, name: 'Captain Piett' },
{ id: 24, name: 'General Veers' },
{ id: 56, name: 'Admiral Ozzel' },
{ id: 88, name: 'Commander Jerjerrod' }
];
// What you need
[20, 24, 56, 88]
이 작업을 달성하는 방법은 여러가지가 있습니다. 비어있는 배열을 만들고 .forEach(), .for(…of),.for() 등을 사용할 수 있죠. .forEach()를 사용하는 것을 비교해 봅시다!
var officersIds = [];
officers.forEach(function (officer) {
officersIds.push(officer.id);
});
여러분이 비어있는 배열을 만든 것을 기억하세요. .map()을 사용하면 어떻게 되는지 살펴봅시다.
var officersIds = officers.map(function (officer) {
return officer.id
});
ES6에서는 arrow 함수를 사용하여 더욱 간결하게 표현할 수도 있습니다.
const officersIds = officers.map(officer => officer.id);
그래서 .map()이 어떻게 동작하냐구요? 기본적으로 2개의 매개변수를 받습니다. 하나는 콜백이고 하나는 옵셔널 컨텍스트(콜백 네에서 this로 간주됨)입니다. 제가 예제에서 사용하지는 않았지만요. 이 콜백은 배열의 값에 대해 매번 수행되고 매 수행된 새로운 결과 값을 배열의 형태로 리턴합니다.
결과 배열은 원본 배열의 크기와 똑같다는 것을 기억해주세요.
.reduce()
.map()이나 .reduce()와 같이 배열의 모든 요소에 대해서 동작합니다. 다른 점은 reduce는 배열의 한개의 요소를 다른 것과 연산하여 리턴한다는 것입니다. acc는 정수, 문자열, 객체 등 다양한 것이 될 수 있으며 .reduce()를 호출할 때 꼭 초기화 되거나 전달되어야 합니다.
예제를 통해 살펴봅시다.
var pilots = [
{
id: 10,
name: "Poe Dameron",
years: 14,
},
{
id: 2,
name: "Temmin 'Snap' Wexley",
years: 30,
},
{
id: 41,
name: "Tallissan Lintra",
years: 16,
},
{
id: 99,
name: "Ello Asty",
years: 22,
}
];
우리는 모든 파일럿들의 경력이 총 몇 년인지 알고 싶습니다. .reduce()를 사용한다면 굉장히 직관적으로 표현됩니다.
var totalYears = pilots.reduce(function (accumulator, pilot) {
return accumulator + pilot.years;
}, 0);
제가 acc의 시작 값을 0으로 설정한 것을 기억하세요. 필요하다면 존재하는 변수 값을 사용할 수도 있습니다. 배열의 각 요소에 대해서 콜백이 수행되고 나면 reduce는 acc의 최종 결과 값을 리턴합니다.
ES6의 arrow 함수를 사용하면 더욱 짧아집니다.
const totalYears = pilots.reduce((acc, pilot) => acc + pilot.years, 0);
이제 경력이 가장 많은 파일럿을 찾고 싶다고 해봅시다. reduce를 사용해서 다음과 같이 표현할 수 있습니다.
var mostExpPilot = pilots.reduce(function (oldest, pilot) {
return (oldest.years || 0) > pilot.years ? oldest : pilot;
}, {});
acc를 oldest라고 명명했습니다. 제 콜백은 acc와 각 파일럿들을 비교합니다. 파일럿이 oldest보다 경력이 많으면 해당 파일럿이 새로운 oldest가 됩니다.
보시다시피 .reduce를 사용하는 것은 배열에서 하나의 객체를 가져오거나 단일 값을 쉽게 생성할 수 있습니다.
.filter()
여러분이 배열에서 특정 요소만 가져오고 싶을 땐 어떻게 해야할까요? 이럴 때 .filter()를 사용하는 겁니다.
var pilots = [
{
id: 2,
name: "Wedge Antilles",
faction: "Rebels",
},
{
id: 8,
name: "Ciena Ree",
faction: "Empire",
},
{
id: 40,
name: "Iden Versio",
faction: "Empire",
},
{
id: 66,
name: "Thane Kyrell",
faction: "Rebels",
}
];
우리에게 반란군 파일럿과 제국군 파일럿이 있다고 가정합시다. filter로는 쉽게 구분할 수 있습니다.
var rebels = pilots.filter(function (pilot) {
return pilot.faction === "Rebels";
});
var empire = pilots.filter(function (pilot) {
return pilot.faction === "Empire";
});
정말 간단하죠, 그리고 arrow 함수로는 더욱 짧게 표현됩니다.
const rebels = pilots.filter(pilot => pilot.faction === "Rebels");
const empire = pilots.filter(pilot => pilot.faction === "Empire");
기본적으로 콜백 함수가 true를 리턴하면 현재 요소는 결과 배열에 있게되고 false를 리턴한다면 그렇지 않습니다.
Combining .map(), .reduce(), and .filter()
세 개의 메소드가 배열에 대해서 사용되고 .map()과 .filter()가 배열을 리턴하므로 쉽게 조합해서 사용할 수 있습니다.
var personnel = [
{
id: 5,
name: "Luke Skywalker",
pilotingScore: 98,
shootingScore: 56,
isForceUser: true,
},
{
id: 82,
name: "Sabine Wren",
pilotingScore: 73,
shootingScore: 99,
isForceUser: false,
},
{
id: 22,
name: "Zeb Orellios",
pilotingScore: 20,
shootingScore: 59,
isForceUser: false,
},
{
id: 15,
name: "Ezra Bridger",
pilotingScore: 43,
shootingScore: 67,
isForceUser: true,
},
{
id: 11,
name: "Caleb Dume",
pilotingScore: 71,
shootingScore: 85,
isForceUser: true,
},
];
우리의 목적은 force 유저들의 점수의 총합만 얻어내는 것입니다. 먼저 어떤 사용자가 force를 사용하는지 걸러냅시다.
var jediPersonnel = personnel.filter(function (person) {
return person.isForceUser;
});
// Result: [{...}, {...}, {...}] (Luke, Ezra and Caleb)
이제 왼쪽의 결과 배열에는 3개의 요소가 있습니다. 각 요소의 점수 합을 jedi에 넣도록 합시다.
var jediScores = jediPersonnel.map(function (jedi) {
return jedi.pilotingScore + jedi.shootingScore;
});
// Result: [154, 110, 156]
그리고 reduce를 통해 값을 더합니다.
var totalJediScore = jediScores.reduce(function (acc, score) {
return acc + score;
}, 0);
// Result: 420
이제 재밌는 부분입니다. 우리가 원하는 것을 체인을 통해 한줄로 표현할 수 있습니다.
var totalJediScore = personnel
.filter(function (person) {
return person.isForceUser;
})
.map(function (jedi) {
return jedi.pilotingScore + jedi.shootingScore;
})
.reduce(function (acc, score) {
return acc + score;
}, 0);
그리고 arrow 함수를 사용하면 이렇게 깔끔하게 표현됩니다.
const totalJediScore = personnel
.filter(person => person.isForceUser)
.map(jedi => jedi.pilotingScore + jedi.shootingScore)
.reduce((acc, score) => acc + score, 0);
Why not use .forEach()?
저는 반복문이 필요할 때마다 .map()이나 .reduce() 대신에 for를 사용했습니다. 그러나 몇 년후 저는 API에서 많은 데이터를 다루는 작업을 시작했고 이 때부터 .forEach를 사용하지 않을 때 장점을 알게 되었죠.
formatting
사람들의 이름과 직업을 나타내는 목록을 표시해야 한다고 가정합시다.
var data = [
{
name: "Jan Dodonna",
title: "General",
},
{
name: "Gial Ackbar",
title: "Admiral",
},
]
API는 위의 데이터를 건네주겠지만 여러분이 필요한건 title과 사람들의 가족 이름입니다. 여러분은 데이터를 규격화 해야하죠. 그러나 여러분의 앱이 단일 뷰에서 각 사람들을 보여줄 필요가 있다면 반드시 규격화 함수를 작성해서 리스트 뷰와 단일 뷰에서 동작할 수 있도록 해야 합니다.
이 말이 여러분의 포맷팅 함수에서 forEach 반복문을 쓸 수 없거나 함수가 동작하도록 하기 위해서 단일 요소들을 배열로 감싸야 하는 것을 말합니다.
var result = formatElement([element])[0];
// Yeah... that's not right at all
그래서 여러분의 반복문은 함수를 호출하는 것을 감싸게 됩니다.
data.forEach(function (element) {
var formatted = formatElement(element);
// But what then....
});
그러나 .forEach()는 아무것도 리턴하지 않기 때문에 결과를 다른 배열에 집어넣어야 한다는 것입니다.
var results = [];
data.forEach(function (element) {
var formatted = formatElement(element);
results.push(formatted);
});
결과적으로 여러분은 포맷팅 함수와 결과를 배열에 넣는 함수 2개가 필요합니다. 하나의 함수로 표현이 되는데 왜 2개의 함수를 사용해야 할까요?
var results = data.map(formatElement);
Summary
- 반복문을 사용할 때 .map(), .filter(), .reduce()를 사용하는 것이 코드를 간결하게 만들어준다.
- 그래서 가독성이 높아지고 테스트하기 쉬워진다. 직관적인 표현덕에 결과를 예측하기도 쉬워진다.