
평소 즐겨보던 유튜브 채널에서 흥미있는 내용을 알려주어,, 실제로 구현을 해본 경험이 있어요.! 영상 링크 남겨두겠습니다. 한번 보시길 추천드려요.! 길지 않고 재밌고 유익합니다:)
요약하자면, 데이터를 url 에 저장함으로서 database 비용을 줄일 수 있고, 공유가 더 편리해지는 장점이 있다고 소개합니다. 크고 작은 프로젝트에서 잘 사용하는 방법이라고 하네요 ㅎㅎ
개인적인 생각을 더 추가하자면 저장한 데이터를 이전 상태로 복구해야할 경우, web-storage 에 저장했을 때보다 더 간단한 것 같아요. 뒤로가기 기능을 잘 활용하면 되니까요.!
오늘 포스트에서는 위의 영상으로부터 영감을 받아, url 을 통해 데이터 저장/조회/수정/삭제 기능을 구현한 경험과 방법에 대해 적어보겠습니다.!
(* 참고로, 저의 작업 환경은 react 입니다.)
우선 주소창에 접근하는 방법을 알아야 합니다.
react-router-dom 라이브러리는 react application 의 page 이동과 관련된 편리한 메서드를 많이 제공하고 있습니다.
해당 라이브러리에서 제공하는 useSearchParams() 를 이용할 경우, query-string 형태로 데이터를 저장할 수 있습니다. (단, 페이지 경로를 react-router-dom 에 미리 등록한 상태임을 전제합니다. RouterProvider 에 대해 찾아보시면 좋을 거 같아요. 👍)
useSearchParams()사용법에 대해 간단히 소개합니다. :)
Web-API 인 URLSearchParams 객체를 반환하여, query-parameter 를 자유롭게 가공할 수 있는 hook 입니다. 다음과 같이 선언하여 사용할 수 있어요.const [param, setParams] = useSearchParams()간략하게 제가 자주 사용하는 기본적인 메서드만 적어두겠습니다. 사용방법과 관련된 자료가 인터넷에 많으니, 여기서 일일히 풀진 않겠습니다. 😅
/** 데이터 저장 */ params.set("key","value") setParams(params)/** key 를 통해 값을 조회 */ params.get("key")/** key 에 대한 값의 유무 확인 */ params.has("key")
주소창에서 데이터를 관리하기 위해선, 저장하려는 값의 타입이 string 이어야 합니다.
JSON.stringify() 메서드를 이용하면, 아무리 복잡한 구조의 객체나 배열도 JSON 문자열로 직렬화하여 string 타입으로 만들 수 있어요. JavaScript의 내장 함수이므로 별도의 라이브러리를 설치할 필요 없이 쉽게 사용할 수 있습니다.!
const userObj = {
name: "Jeff",
age: 27,
};
const userStringify = JSON.stringify(userObj)
console.log(typeof userStringify) // ✅ 출력결과 : string
console.log(userStringify) // ✅ 출력결과 : {"name":"Jeff","age":27}
다시 객체 타입으로 되돌리기 위해선, JSON.parse() 메서드를 사용하면 됩니다.
const revertObj = JSON.parse(userStringify)
console.log(typeof revertObj) // ✅ 출력결과 : object
이 상태로 url 에 저장하는 것도 좋습니다.👍
하지만 다루는 데이터가 주소창에 그대로 노출되는 것은 막고 싶은 생각이 듭니다.. (서비스 설계에 따라 다르겠지만, 이름과 나이가 주소창에 대놓고 드러내는 페이지에는 별로 접근하고 싶지 않겠죠..? 😓)
아까 소개해드린 영상에서도 그렇고, 보통 json 문자열을 encoding 하는 방법으로 base64 를 추천하더라구요. 이미지와 같은 binary data 를 보낼 때에 base64 encoding 후, 한번에 전송하는 게 일반적이라고 합니다. 아래와 같이 사용할 수 있습니다.! (이 역시 외부 라이브러리 설치 없이 사용할 수 있어요.)
const userObj = {
name: "Jeff",
age: 27,
};
// 1. 객체 직렬화
const userStringify = JSON.stringify(userObj)
// 2. base64 encoding
const encodedData = btoa(userStringify)
console.log(encodedData); // ✅ 출력 결과: eyJuYW1lIjoiSmVmZiIsImFnZSI6Mjd9
이를 decoding 하는 방법은 아래와 같습니다.
// 1. base64 decoding
const decodedData = atob(encodedData)
// 2. object 타입으로 parsing
const userObjectify = JSON.parse(decodedData)
console.log(typeof userObjectify) // ✅ 출력 결과: object
console.log(userObjectify); // ✅ 출력 결과: { name: 'Jeff', age: 27 }
"b to a", "a to b" 이름을 아주 흥겹게 지어셨네요..
const originalText = "안녕하세요."
const base64Encoded = btoa(originalText) // 🚨 에러 발생!!
const base64Decoded = atob(base64Encoded)

base64 는 기본적으로 binary-data(0 과 1로 이뤄진 데이터) 를 ASCII 문자집합으로 바꿔줍니다. ASCII 코드는 1 바이트로 표현할 수 있는 언어/기호를 표현하기 위해 만들어 졌습니다 (영어+숫자). 한글, 한자 등을 모두 표현하기 위해선 더 많은 바이트가 필요하기 때문에 ASCII 코드에 기반한 base64 는 한글을 인식할 수 없어 이런 오류가 발생합니다. (세계의 더 많은 언어를 모두 표현하기 위해 ASCII 코드 이후 만들어진 표준체계가 유니코드입니다.)
UTF-8(Unicode Transformation Format - 8-bit) encoding 방식 등과 같이, ASCII 와 호환되는 변환과정을 더해주면 됩니다 👍
const originalText = "안녕하세요."
// encoding
const utf8Encoded = encodeURIComponent(originalText) // 👈 base64-encoding 전에 먼저 변환
const base64Encoded = btoa(utf8Encoded)
console.log(base64Encoded) // ✅ 출력 결과: JUVDJTk1JTg4JUVCJTg1JTk1JUVEJTk1JTk4JUVDJTg0JUI4JUVDJTlBJTk0Lg==
// decoding
const base64Decoded = atob(base64Encoded)
const utf8Decoded = decodeURIComponent(base64Decoded) // 👈 base64-decoding 후에 변환
console.log(utf8Decoded) // ✅ 출력 결과: 안녕하세요.
UTF-8 encoding 을 하더라도, 영어나 숫자 같이 ASCII 코드로 표한 가능한 문자는 아무 영향이 없습니다 :)
이제 위의 방법을 적절히 활용해서, data encoding 후, url에 저장 하는 기능과 url에서 data 를 받아, decoding 하는 기능을 구현하면 됩니다.
저는 (여러 인적 정보로 이뤄진) 객체 배열을 url에 저장하기 위해서, 배열을 ecoding, decoding 할 수 있는 util 함수를 선언했습니다.
// src > utils > crypto.ts
// encoder
export const base64ArrayEncoder = <T>(origin: Array<T>): string
=> btoa(encodeURIComponent(JSON.stringify(origin)));
// decoder
export const base64ArrayDecoder = <T>(encoded: string): Array<T>
=> JSON.parse(decodeURIComponent(atob(encoded)));
이제 위 함수의 결과를 저장, 조회하는 로직을 구현해야 합니다. 주소창에서 데이터를 다루기 위해, 저는 useSearchParams() 를 활용하겠다고 했어요. 관심사 분리를 위해 useSearchCryptoArray() 라는 hook 함수를 선언했습니다.
하나의 hook 이 하나의 배열을 관리하길 바랬습니다. 그래서, query-parameter 의 키를 useSearchCryptoArray() 의 실행부에서 입력받도록 했어요.!
export const useSearchCryptoArray = <T>(urlParamKey: string) => {
const [params, setParams] = useSearchParams();
}
조회 기능을 위해선, 주소창에 있는 ecoding 된 문자열을 decoding 해주는 로직이 필요합니다. (말하기가 조금 어려워서 그렇지, 단순한 과정입니다..)
// src > hooks > useSearchCryptoArray.ts
export const useSearchCryptoArray = <T>(urlParamKey: string) => {
const [params, setParams] = useSearchParams();
/** 조회하기 */
const getArray = (): T[] => {
// key 에 대한 값이 없다면, 빈 배열 반환 (url 에 아무 데이터도 없는, 초기 상태에 대비)
if(params.has(urlParamKey)) return []
// encoding 된 배열을 받아오기
const encodedArray = params.get(urlParamKey);
// decoding 해서 반환하기
return base64Decoder<T>(encodedArray);
};
return {getArray}
}
이제 배열에 새로운 원소를 추가하는 기능을 구현합니다.
// src > hooks > useSearchCryptoArray.ts
export const useSearchCryptoArray = <T>(urlParamKey: string) => {
const [params, setParams] = useSearchParams();
/** 추가하기 */
const appendElement = (element: T) => {
// 조회 메서드의 결과와 새로운 입력받은 원소를 합쳐 새로운 배열 생성
const newArray: Array<T> = [...getArray(), element];
// 새로운 배열을 encoding
const encodedArray = base64Encoder<T>(newArray);
// update
params.set(urlParamKey, encodedArray);
setParams(params);
};
/** 조회하기 */
const getArray = (): T[] => {
if(params.has(urlParamKey)) return []
const encodedArray = params.get(urlParamKey);
return base64Decoder<T>(encodedArray);
};
return {getArray, appendElement}
}
사실 지금까지 만든 함수를 기반해서 더 많은 기능을 구현할 수 있지만, 핵심 개념은 다 작성한 것 같으니, 이 이후는 생략하겠습니다. 😅
🖥️ 실행화면
(저는 바보같이 착각했어서,,) 혹시나 하는 마음에 남깁니다.ㅎㅎ
배열에 새로운 원소를 추가한 뒤 encoding 한 다고 해서 문자열 길이가 그대로 남아있진 않습니다.
(*길이가 1인 배열의 encoding 결과)
JTVCJTdCJTIyaWQlMjIlM0EyNTcwMDU0JTJDJTIybmFtZSUyMiUzQSUyMiVFRCU4NSU4QyVFQyU4QSVBNCVFRCU4QSVCODElMjIlMkMlMjJwaG9uZSUyMiUzQSUyMjEyMzQ1Njc4MTIzJTIyJTJDJTIyYmlydGhkYXklMjIlM0ElMjIyMDIzLTA0LTI5JTIyJTJDJTIyY3JlYXRlZEF0JTIyJTNBJTIyMjAyNC0wNC0yMCUyMiU3RCU1RA%3D%3D
(*길이가 2인 배열의 encoding 결과)
JTVCJTdCJTIyaWQlMjIlM0EyNTcwMDU0JTJDJTIybmFtZSUyMiUzQSUyMiVFRCU4NSU4QyVFQyU4QSVBNCVFRCU4QSVCODElMjIlMkMlMjJwaG9uZSUyMiUzQSUyMjEyMzQ1Njc4MTIzJTIyJTJDJTIyYmlydGhkYXklMjIlM0ElMjIyMDIzLTA0LTI5JTIyJTJDJTIyY3JlYXRlZEF0JTIyJTNBJTIyMjAyNC0wNC0yMCUyMiU3RCUyQyU3QiUyMmlkJTIyJTNBMjU3MTQxMCUyQyUyMm5hbWUlMjIlM0ElMjIlRUQlODUlOEMlRUMlOEElQTQlRUQlOEElQjgyJTIyJTJDJTIycGhvbmUlMjIlM0ElMjIwMTA5ODc2NTQzMiUyMiUyQyUyMmJpcnRoZGF5JTIyJTNBJTIyMjAyMi0wNy0yMSUyMiUyQyUyMmNyZWF0ZWRBdCUyMiUzQSUyMjIwMjQtMDQtMjAlMjIlN0QlNUQ%3D
이 당연한 걸,, 저는 왜 그렇게 생각했는지 모르겠는데, encoding 을 하면 일정한 길이의 문자열이 생성되는지 알았습니다.!
위에서 따로 소개하진 않았지만, base64 결과로, 원래의 데이터 크기(= 길이)보다 더 큰 크기의 값을 얻게 된다고 합니다. 보통 컴퓨터에서 다루는 데이터 단위는 8bit 가 기본이지만, base64는 텍스트를 6bit 씩 잘라 암호화하기 때문에 약 33% 용량 증가가 발생한다고 하네요.
url 에 데이터를 인코딩하여 저장할 수 있는 것은 좋은 기술입니다만, 암호화된 문자열이 너무 길어져버린다면 (= url 길이가 너무 길어져버리면),, 브라우저에서 에러가 발생하기도 합니다. 저도 실제로 겪었습니다.😅
처음 보는 에러 당황했지만, 열심히 Searching 해서ㅎㅎ 문자열 압축 알고리즘으로 해결했습니다. 이 내용은 구체적인 base64 암호화 방법과 함께 다음 2편에서 소개하도록 하겠습니다.!