마지막으로 우리가만든 리액트 소스파일 들이 스프링 부트가 패키징 되며 합쳐져야 합니다. 빌드를 하면서 합쳐질 수 있도록 아래 설정을 추가해주도록 하겠습니다. 맨 처음 frontendDir 변수를 선언할 때 마지막 경로 부분은 본인이 생성하신 작업 폴더를 지정해주셔야해요!
하지만 그 데이터들을 전부 표시할것은 아닙니다. 중요한 정보들을 간추리고 계산해서 꼭 필요한 내용들만 출력될 수 있도록 해줘야 해요. 오늘은 그 작업을 해보겠습니다.
정보 추출
이제 정보를 추출해야하는데요 한 가지 문제가 생겼습니다. 초월의 정보를 예로 들면 우리는 방어구, 무기에 초월 정보를 가져와야 합니다. 그래서 저는 예상으로는 ArmoryEquipment 라는 방어구 객체에 투구,어깨 등 장비 객체가 들어가 있고 그 안에 "초월" 필드가 있어 그 수치를 가져와야 겠다. 라고 생각했는데요 아래는 초월 정보가 들어있는 문자 입니다.
"Element_010": {
"type": "IndentStringGroup",
"value": {
"Element_000": {
"contentStr": {
"Element_000": {
"bPoint": false,
"contentStr": "민첩 +5880"
},
"Element_001": {
"bPoint": false,
"contentStr": "모든 장비에 적용된 총 126개"
},
"Element_002": {
"bPoint": false,
"contentStr": "5 - 장착중인 모든 장비의 초월 등급 당 최대 생명력이 80씩, 아군 공격력 강화 효과가 0.01%씩 증가합니다."
},
"Element_003": {
"bPoint": false,
"contentStr": "10 - 장착중인 모든 장비의 초월 등급 당 힘, 민첩, 지능이 55씩, 아군 공격력 강화 효과가 0.01%씩 증가합니다."
},
"Element_004": {
"bPoint": false,
"contentStr": "15 - 장착중인 모든 장비의 초월 등급 당 무기 공격력이 14씩, 아군 공격력 강화 효과가 0.01%씩 증가합니다."
},
"Element_005": {
"bPoint": false,
"contentStr": "20 - 장착중인 모든 장비의 초월 등급 당 공격력이 6씩, 아군 공격력 강화 효과가 0.01%씩 증가합니다."
}
},
"topStr": "슬롯 효과[초월] 7단계 21"
}
}
},
tooltip 이라는 장비 객체 안에 정말 모든 정보가 그냥 문자열 하나로 들어가 있습니다...
이 정보로 초월 단계에 접근하려면 Element_010 > value > Element_000 > topStr 까지 가야 초월 활성화 수치 21을 가져올 수 있습니다.
문제는
Element_010 이 고정된 값이 아니다.
- 눈치 채신분도 있겠지만 이 문구는 우리가 장비창에서 장비에 마우스를 올렸을 때 보여주는 그 문구들을 전부 문자열로 출력해준것입니다. 그래서 상급재련을 안한 장비는 _010이 009 가 될수도 있고 무기의 경우 에스더 여부에 따라 또 수치가 달라져서 _010이 초월 정보를 가지고 있는 객체라는것을 확신할 수가 없습니다.
그래서 저는 정규식을 사용했습니다. 저 전체의 문구에서 topStr에 해당하는 문구는 딱 1개이기 때문에 patter을 사용해서 저 슬롯 효과[초월] 7단계가 있는 문구를 찾고 거기서 활성화 수치를 가져오는 방법으로 선택했습니다.
제가 예시로 초월을 들었지만 사실상 거의 모든 수치가 저런 하나의 문자열 tooltip으로 제공되고 그 간단한 강화 수치마저 "+16 화려한 구원의 전장 모자" 여기서 16이라는 수치를 추출해서 사용해야합니다.
API가 조금 불친절한게 아닌가 하는 생각을 했습니다 ㅜ.
그래서 이런식으로 특정 고정된 문구를 찾아서 거기서 강화 수치, 등급, 필요 정보들을 뽑아오는것으로 했고 최종적으로 반환할 캐릭터 객체에 정보를 담았습니다.
각인 API
이게 진짜 불쾌한 API 였는데요 아크패시브를 개방하신 분들은 직업각인을 이제 각인 말고 깨달음 수치로 사용을 하게 됩니다. 그래서 아크패시브를 개방하지 않은 캐릭터는 각인 정보에 직업각인이 나와서 그냥 사용할 수 있는데,
아크패시브 사용자는 또 하나로 쓰여진 문자열에서 직업각인 정보를 추출해서 사용해야합니다. 근데 짜증나는게 누구는 1티어에 직업각인을 찍을 수 있고 누구는 2티어 부터 직업각인을 사용 가능합니다. 굳이 왜..??? 대체 왜???
라우팅 이란 간단하게 페이지를 전환해주는거라고 생각하시면 됩니다. 하나의 페이지 안에서 요청한 url에 따른 정보만 가져와 새로 렌더링하는 방식으로 리액트가 제공하는 리액트 라우터가 있습니다.
이를 사용하기위해 react-router-dom의 설치가 필요합니다. 터미널에서 아래 명령어를 실행해서 설치를 진행해주세요
npm install react-router-dom
설치가 완료됐다면 import로 BrowserRouter, Routes, Route 이렇게 3개를 추가해주세요.
BrowserRouter
BrowserRouter는 앱 전체를 감싸는 라우터로서 브라우저의 히스토리 API를 사용해서 페이지를 가져와 렌더링을 맡아주는 역할로 앱에서 라우팅 기능을 사용할 수 있도록 해주는 컨테이너 라고 생각하시면 됩니다.
Routes
Routes는 Route가 여러개 있을 때 묶어서 관리해주는 컨테이너 입니다.
Route
Route는 특정 URL 경로에 해당하는 컴포넌트를 연결하는 역할입니다.
위 App.js를 보게되면 항상 화면 위에 표시될 Header영역이 있고 URL( 캐릭터 검색)에 따라 화면이 다르게 전환돼야 하기 때문에 /loa의 경로는 Search.jsx를 찾아가게 됩니다.
<Header setUserData={setUserData} /> 는 저번 시간에 사용했던 state를 사용한 코드인데요 Header.jsx 파일에 setUserData라는 이름으로 우리가 선언한 setUserData useState값을 인자로 전달해준 것입니다.
<Route path='/loa/:characterName' element={<Search userData={userData} setUserData={setUserData}/>} /> 를 살펴보면 이 Route는 /loa 라는 URL에 응답할 컴포넌트 이고 :characterName은 우리가 캐릭터를 검색하면 localhost/loa/"캐릭터명" 이런식으로 url에 캐릭터명이 붙을텐데 이를 명시해주는 url입니다. 계속해서 이 Route의 element는 Search이고 위 header처럼 인자로 userData와, setUserData를 전달해줍니다.
태그의 Header와 Search는 우리가 최 상단에 import 시켜준 값을 인식하여 파일을 찾아갑니다.
2. 검색 api 수정
jsx?
이번에 파일을 분리하며 .js를 .jsx 파일로 생성했는데요 .jsx는 js문법을 확장한 문법으로 리액트에서 사용이 가능하고, js 파일 안에서 HTML을 같이 작성해서 사용할 수 있도록 지원합니다.
여기서 변경된 부분은 Link, useNavigate와 fetch url을 조금 수정했습니다.
Navigate
자 우리는 서버로부터 정상적으로 API 응답을 받아왔는데요. 받아오기만 하면 끝이 아니겠죠? 이제 해당 url로 이동해서 화면에 출력해줘야 합니다. 그래서 .then 영역에 navigate를 통해 우리가 Route path로 지정했던 /loa/"캐릭터명" 으로 화면을 전환시켜줄겁니다.
Link
.jsx에서는 a 태그의 href가 아닌 Link문법을 사용해서 To="경로"로 이동하는 방법을 사용합니다.
setUserData
여기에 하나의 추가작업이 들어갔습니다. 처음 const Header 부분을 보면 setUserData로 매개변수가 있습니다. 우리가 App.js에서 setUserData을 인자로 줬던것이 생각나시죠? .then에서 가져온 응답을 사용해서 json 객체를 상위 컴포넌트(App.js)로 전달할 수 있도록 사용했습니다. 다음에 설명하겠지만 새로고침을 했을때 내용을 유지할 수 있도록 상위 컴포넌트에서도 JSON 객체의 정보를 가지고 있을 수 있게 처리한거에요!
3. 화면 출력
자 이제 마지막으로 가져온 정보들을 토대로 화면을 만들어 줘야겠죠 분리한 Search.jsx 입니다.
새로고침 작업을 위해 사용했습니다. useParams의 경우 url에 있는 파라미터를 가져오는 명령어 이고 useEffect는 렌더링 후 특정 상태나 값이 변경이될 때 실행하도록 설정하는 것으로 if의 조건문을 실행하는 것인데 [characterName, userData, setUserData]); 부분이 이 if문이 언제 실행될지 적용하는거에요 이 배열안의 값들이 변경될 때마다 실행된다고 생각하시면 됩니다. 그래서 새로고침을 하며 값이 변할때 다시 실행되도록 했습니다.
삼항연산자
userData ? (...) : <div>...</div> 이 부분이 보이실텐데요 우리가 가져온 데이터가 값이 없을수도 있는데 그냥 userData의 값을 뽑아서 써버리면 문제가 생기겠죠?? 삼항연산자를 통해 값을 한번 체크해줍시다. 자 그다음 적절히 값들을 배치해주면?
자 이렇게 우리가 가져온 정보들이 출력된것을 확인할 수 있습니다. 예쁘지 않은건 그냥 넘어가주세요 ㅜㅜ 저는 정말 미적감각이 없어서 정말 저걸 만들면서도 어떻게 배치해야되지...??? 이게 맞아..? 하면서 했답니다..
자 오늘은 우리가 서버에서 전송한 정보들을 화면에 가볍게 출력해봤습니다.
이제 좀 더 많은 정보들을 함축적으로 보여줘야겠죠?? 다음은 서버에서 더 많은 데이터를 가공해보도록 하겠습니다.
1. Cannot deserialize value of type java.lang.Character from Object value (token JsonToken.START_OBJECT)
- 받으려는 데이터를 Character로 생각없이 만들어버렸다. ㅋㅋㅋㅋ Character는 Char를 처리하는 클래스이기 때문에 다른것으로 변경해서 사용하도록 하자.
2. Unrecognized field "ArmoryProfile" (class com.yeop.lostark.vo.LostarkCharacter), not marked as ignorable
- 사실 처음에는 전체 데이터를 받아와서 ArmoryProfile만 뽑아오려 했다. 처음 구조는
루트>ArmoryProfile,등등
이런식이었는데 내가 루트를 가져와서 ArmoryProfile만 뽑아내려니 Jackson이 역직렬화를 할 때 루트 클래스가 매핑이 안되다보니 나온 오류였다.
2-1. 첫 번째 방법으로 루트에 해당하는 Profiles 객체를 만들고 그 안에 armoryProfile을 필드 객체로 추가해서 받아오는 방법을 생각했다. 최종적으로도 이렇게 가려고한다. 하지만 로스트아크 API가 전달하는 ArmoryProfile과 내가 필드로 넣은 armoryProfile이 대소문자가 일치하지 않아 매핑을 하지 못했다. (정말 귀찮다.) 이럴때는 객체 안에 @JsonProperty("필드명") 으로 이름이 다를경우에 맞춰서 매핑이 가능하다.
2-2. 일단은 ArmoryProfile만 받아오자는 생각으로 위 포스팅처럼 진행했다. ArmoryProfile 클래스를 만들고 ArmoryProfile만 제공하는 API로 해당 필드들을 받아오는것으로 했다.
3. 필드 대소문자 불일치
2번과 똑같은 오류인데 2-2 방법으로 진행하면서 이번엔 필드값들이 대소문자가 맞지 않는 오류가 발생했다.
그래서 내가 사용하는 ObjectMapper에 대소문자를 구분하지 않고 매핑하도록 설정을 해줬다.
ObjectMapper().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES,true) 이 옵션으로 가능하다.