쿼리스트링은 key-value 쌍으로 되어있는 일종의 토큰이다. 원리는 단순하지만 이걸 마음껏 조작하기는 불편하다.
때문에, 객체를 쿼리스트링으로, 다시 쿼리스트링을 객체로 바꿔주는 라이브러리들도 존재한다.
이 쿼리스트링 조작함수를 아주 원시적인 형태 수준으로 직접 구현해보자
http://website.com/aaa/bbb?ccc=ddd&eee=fff&ggg=hhh
이렇게 생긴 url 이 있다 치자
물음표 기호 기준으로 오른쪽만 꺼내자. 그냥 split 메서드를 쓰면 된다.
const parseQueryString = (url) => {
const [_, query] = url.split('?')
}
url 에서 ? 기호는 유일무이 하다. 다시말해 딱 한번만 존재한다. 어떻게 확신하냐고? 그냥 규칙이 그렇다.
이렇게 만든 query
변수는 문자열이거나 undefined 이다. 물음표 기호가 없다면 배열에 요소가 하나밖에 안 만들어지고, _
에 담긴다. 물음표 기호가 있을때만 query
변수에 값이 생기는거다.
쿼리가 없다면 빈 객체를 리턴하도록 하자
const parseQueryString = (url) => {
const [_, query] = url.split('?')
if (!query) return {}
}
이제 query
변수엔 이런 형태의 문자열이 담긴다.
ccc=ddd&eee=fff&ggg=hhh
각각의 쿼리 쌍은 &
기호로 연결되어있다. 저걸 분리하자. 마찬가지로 split 을 쓰면 된다.
const parseQueryString = (url) => {
const [_, query] = url.split('?')
if (!query) return {}
const queries = query.split('&')
}
queries 엔 다음과 같은 배열이 담긴다.
[ 'ccc=ddd', 'eee=fff', 'ggg=hhh' ]
이제 =
기호 기준으로 각각 왼쪽은 키고 오른쪽은 값이 되도록 하면 되겠다. 저 배열을 map 으로 반복시키면서 다시 split 하자
const parseQueryString = (url) => {
const [_, query] = url.split('?')
if (!query) return {}
const queries = query.split('&')
const queries2 = queries.map((e) => {
const [key, value] = e.split('=')
return { key, value }
})
}
우리는 각 요소를
=
기준으로 split 했을때 무조건 요소가 두개씩 나올거라는걸 알고있다.
=
기준으로 왼쪽은 키고 오른쪽은 값이니깐.그러니 비구조화할당 해서 각각 key, value 라는 변수명으로 만들고 객체로 리턴하자.
결과는 다음처럼 나온다.
[
{ key: 'ccc', value: 'ddd' },
{ key: 'eee', value: 'fff' },
{ key: 'ggg', value: 'hhh' }
]
이제 조금 어려운게 등장했다.
우리는 이미 우리가 어떤 형태의 객체를 원하는지 알고있다.
{ ccc: 'ddd', eee: 'fff', ggg: 'hhh' }
이렇게 나와야 하는것이다.
가장 원시적인 방법으로 이 객체를 만드는 방법은, 빈 객체를 만든 뒤 for 문으로 배열을 반복시키면서 만들어둔 배열에 속성을 할당하는 것이다.
const queries2 = [
{ key: 'ccc', value: 'ddd' },
{ key: 'eee', value: 'fff' },
{ key: 'ggg', value: 'hhh' }
]
const queries3 = {}
for (let i = 0; i < queries2.length; i++) {
const current = queries2[i]
queries3[current.key] = current.value
}
하지만 ES6의 computed property, object spread property 문법과 배열의 map 메서드를 활용하면 더 간단하게 만들 수 있다.
const queries3 = queries2.reduce((acc, curr) => {
const {key, value} = curr
return { ...acc, [key]: value }
}, {})
우리가 만들고 있던 함수에 넣으면 이렇게 된다.
const parseQueryString = (url) => {
const [_, query] = url.split('?')
if (!query) return {}
const queries = query.split('&')
const queries2 = queries.map((e) => {
const [key, value] = e.split('=')
return { key, value }
})
const queries3 = queries2.reduce((acc, curr) => {
const { key, value } = curr
return { ...acc, [key]: value }
}, {})
return queries3
}
끝난건가...?
하지만 우리는 이 함수를 좀 더 줄일 수 있다.
map과 reduce 는 배열의 메서드이다. 그리고 split과 map과 reduce는 배열을 리턴한다.
이 두개의 사실을 이용해 우리는 메서드 체이닝을 할 수 있다
const parseQueryString = (url) => {
const [_, query] = url.split('?')
if (!query) return {}
return query.split('&')
.map((e) => {
const [key, value] = e.split('=')
return { key, value }
})
.reduce((acc, curr) => {
const { key, value } = curr
return { ...acc, [key]: value }
}, {})
}
배열을 map을 돌리고 또 다시 그걸 reduce 하지 않고 그냥 reduce 안에 로직을 다 때려넣을수 있다.
const parseQueryString = (url) => {
const [_, query] = url.split('?')
if (!query) return {}
return query.split('&').reduce((acc, e) => {
const [key, value] = e.split('=')
return { ...acc, [key]: value }
}, {})
}
완성이다.
사실 실사용하기위해선 좀더 여러가지 기능들이 들어가야 하지만, 시간이 없으니 그만 알아보기로 하자.