Frontend/Vue.js
[Vue] Vue에서 어떤 동작을 행했을 때 서버에서 변동 되는 정보 실시간으로 불러오기 (dispatch)
JOKUN
2022. 9. 18. 15:56
사실 이 부분에 대해서 꽤 많은 시간을 들여 고민하고, 해결방법을 찾으려고 했던 것 같다.
근데 계속 한 부분에 꽂혀서 다른 관점으로 바라보지 못했던 것이 있다.
현재 웹에서 RPG 게임의 기능을 구현을 하고 있는데,
가지고 있는 경험치로 경험치 상점에서 체력이나, 마력으로 교환을 했을 때
캐릭터 상태정보에 바로 교환엔 체력과 마력이 올라가는 것을 확인하고 싶었는데
이것을 계속 캐릭터 상태 정보가 변동 사항이 있을 때 자동으로 업데이트되는 것만 생각을 했다.
하지만, 조금만 나아서 생각해보면 경험치 상점에서 교환이 일어났을 때 캐릭터 상태 정보가 업데이트가 되면 되는 것이다.
즉, Vue에서 Spring 서버로 경험치 교환으로 인한 액션 요청이 들어갈 때,
캐릭터 상태 정보 업데이트 요청도 같이 들어가면 되는 것!
그러기 위해서 중요한 것은 아래와 같다.
📌 dispatch
actions 메서드 내부에서 다른 actions 메서드 호출하기
이렇게 actions 기능을 중복되게 사용하여도 동작은 정상적으로 된다.
허나 아래와 같이 dispatch를 사용하면 더욱 깔끔한 코드를 작성할 수 있다.
dispatch에서 두 개의 actions를 동작시키려면
dispatch(a); dispatch(b); 형식 혹은 dispatch(a) 내부에서 다시 dispatch 하면 된다.
🪄Code Review
Frontend
1. 캐릭터 상태 정보 UI
<template>
<div>
<h3>캐릭터 상태 창(Local Component)</h3>
<p>HP: {{ characterStatus.hp }} MP: {{ characterStatus.mp }}
ATK: {{ characterStatus.atk }} Lv: {{ characterStatus.level }} 직업: {{ characterStatus.currentJob }}</p>
<p>STR: {{ characterStatus.str }} INT: {{ characterStatus.intelligence }}
DEX: {{ characterStatus.dex }} VIT: {{ characterStatus.vit }}
DEF: {{ characterStatus.def }} MEN: {{ characterStatus.men }}</p>
<p>경험치: {{ characterStatus.currentLevelBar }} / {{ characterStatus.totalLevelBar }}</p>
<p>소지금: {{ characterStatus.money }}</p>
</div>
</template>
<script>
import {mapActions, mapState} from "vuex";
export default {
name: "CharacterManager",
data() {
return {
}
},
computed:{
...mapState([
'characterStatus',
'characterStatusUpdateFlag'
])
},
methods: {
...mapActions(['requestCharacterStatusFromSpring']),
},
async mounted() { //얘도 메소드 밖에 선언하는 아이
await this.requestCharacterStatusFromSpring()
},
}
</script>
<style scoped>
</style>
2. 경험치 교환 상점 UI
<template>
<div>
<h3>경험치 상점(Local Component)</h3>
<label>
<input type="checkbox" v-model="expShopView">
경험치 교환 목록
</label>
<button v-on:click="doExpExchange()"> 경험치 교환 </button>
<table border="1" v-if="expShopView">
<tr>
<th align="center" width="40">번호</th>
<th align="center" width="100">필요 경험치</th>
<th align="center" width="180">교환 스탯</th>
<th align="center" width="180">설명</th>
<th align="center" width="40">교환</th>
</tr>
<tr v-for="(swap, index) in expSwapList" :key="index">
<th align="center" width="40">{{ index + 1}}</th>
<th align="center" width="100">{{ swap.expValue }}</th>
<th align="center" width="180">{{ swap.name }}</th>
<th align="center" width="180">{{ swap.effect.description }}</th>
<th align="center" width="40">
<label>
<input type="checkbox" v-model="exchangeListValue" :value="index">
</label>
</th>
</tr>
</table>
</div>
</template>
<script>
import {mapActions} from "vuex";
export default {
name: "CharacterManager",
data(){
return {
expExchangeLists: ["hp", "mp", "atk", "str", "dex", "int", "def"],
exchangeListValue: [],
expShopView: false,
expSwapList:[
{name: 'HP', expValue: 10000000, effect:{description: "체력 50 증가"}},
{name: 'MP', expValue: 10000000, effect:{description: "마나 50 증가"}},
{name: 'atk', expValue: 10000000, effect:{description: "공력력 5증가"}},
{name: 'str', expValue: 10000000, effect:{description: "마력 5증가"}},
{name: 'dex', expValue: 10000000, effect:{description: "dex 5증가"}},
{name: 'int', expValue: 10000000, effect:{description: "int 5증가"}},
{name: 'def', expValue: 10000000, effect:{description: "def 5증가"}},
],
}
},
methods: {
...mapActions([
'requestExpExchangeFromSpring'
]),
async doExpExchange(){
await this.requestExpExchangeFromSpring(this.exchangeListValue)
}
}
}
</script>
<style scoped>
</style>
3. 캐릭터 상태 정보, 경험치 교환 서버로 요청
[actions]
import {
REQUEST_CHARACTER_STATUS_LIST,
} from './mutation-types'
// npm install axios --save-dev
import axios from 'axios'
export default {
requestCharacterStatusFromSpring({commit}){
console.log("requestCharacterStatus()")
return axios.get('http://localhost:8888/rpg/character-status')
.then((res) => {
commit(REQUEST_CHARACTER_STATUS_LIST, res.data)
})
},
requestExpExchangeFromSpring({dispatch}, payload){
console.log("requestExpExchangeFromSpring()")
return axios.post('http://localhost:8888/rpg/exchange', payload)
.then((res) => {
if(res.data === true){
dispatch('requestCharacterStatusFromSpring')
}
})
},
}
[mutation-types]
export const REQUEST_CHARACTER_STATUS_LIST = 'REQUEST_CHARACTER_STATUS_LIST'
export const REQUEST_CHARACTER_STATUS_FROM_SPRING = 'REQUEST_CHARACTER_STATUS_FROM_SPRING'
export const REQUEST_CHARACTER_STATUS_UPDATE_FROM_SPRING = 'REQUEST_CHARACTER_STATUS_FROM_SPRING'
[mutations]
import {
REQUEST_CHARACTER_STATUS_LIST,
REQUEST_CHARACTER_STATUS_FROM_SPRING,
REQUEST_CHARACTER_STATUS_UPDATE_FROM_SPRING,
} from './mutation-types'
export default {
[REQUEST_CHARACTER_STATUS_LIST](state, passingData) {
state.characterStatus = passingData
},
[REQUEST_CHARACTER_STATUS_FROM_SPRING](state, passingData) {
state.characterStatus = passingData
},
[REQUEST_CHARACTER_STATUS_UPDATE_FROM_SPRING](state, passingData) {
state.characterStatusUpdateFlag = passingData
},
}
[states]
export default {
characterStatus:[],
characterStatusUpdateFlag: false,
}
Backend
1. 캐릭터 상태 정보 요청이 들어왔을 때 실행되는 컨트롤러
package com.example.demo.controller.vue.rpg;
import com.example.demo.entity.vue.rpg.CharacterStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("/rpg")
@CrossOrigin(origins = "http://localhost:8080/", allowedHeaders = "*")
public class CharacterController {
public static CharacterStatus characterStatus = new CharacterStatus();
@GetMapping("/character-status")
public CharacterStatus requestCharacterStatus(){
log.info("requestCharacterStatus()");
return characterStatus;
}
}
2. 컨트롤러 요청이 들어왔을 때 반환되는 캐릭터 상태 정보
package com.example.demo.entity.vue.rpg;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class CharacterStatus {
//여기도 enum 으로 하면 best
private String currentJob;
private Integer level;
private Integer hp;
private Integer mp;
private Integer itemAtk;
private Integer defaultAtk;
private Integer atk;
private Integer str;
private Integer intelligence;
private Integer dex;
private Integer vit;
private Integer def;
private Integer men;
private Integer totalLevelBar;
private Integer currentLevelBar;
private Integer money;
// 초기 설정값을 따로 final로 지정해주기
private final Integer INIT_LEVEL = 1;
private final Integer INIT_HP = 50;
private final Integer INIT_MP = 30;
private final Integer INIT_ITEM_ATK = 0;
private final Integer INIT_DEFAULT_ATK = 10;
private final Integer INIT_ATK = 10;
private final Integer INIT_STR = 10;
private final Integer INIT_INT = 10;
private final Integer INIT_DEX = 10;
private final Integer INIT_VIT = 10;
private final Integer INIT_DEF = 10;
private final Integer INIT_MEN = 10;
private final Integer INIT_TOTAL_LEVEL_BAR = 10;
private final Integer INIT_CURRENT_LEVEL_BAR = 0;
private final Integer INIT_MONEY = 0;
private final String INIT_CURRENT_JOB = "원딜";
public CharacterStatus() {
level = INIT_LEVEL;
hp = INIT_HP;
mp = INIT_MP;
itemAtk = INIT_ITEM_ATK;
defaultAtk = INIT_DEFAULT_ATK;
atk = INIT_ATK;
str = INIT_STR;
intelligence = INIT_INT;
dex = INIT_DEX;
vit = INIT_VIT;
def = INIT_DEF;
men = INIT_MEN;
totalLevelBar = INIT_TOTAL_LEVEL_BAR;
currentLevelBar = INIT_CURRENT_LEVEL_BAR;
money = INIT_MONEY;
currentJob = INIT_CURRENT_JOB;
}
public CharacterStatus (CharacterStatus characterStatus) {
level = characterStatus.level;
hp = characterStatus.hp;
mp = characterStatus.mp;
itemAtk = characterStatus.itemAtk;
defaultAtk = characterStatus.defaultAtk;
atk = characterStatus.atk;
str = characterStatus.str;
intelligence = characterStatus.intelligence;
dex = characterStatus.dex;
vit = characterStatus.vit;
def = characterStatus.def;
men = characterStatus.men;
totalLevelBar = characterStatus.totalLevelBar;
currentLevelBar = characterStatus.currentLevelBar;
money = characterStatus.money;
currentJob = characterStatus.currentJob;
}
}
3. 경험치 교환 요청이 들어왔을 때 실행되는 컨트롤러
package com.example.demo.controller.vue.rpg;
import com.example.demo.entity.vue.rpg.ExpExchange;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
@Slf4j
@RestController
@RequestMapping("/rpg")
@CrossOrigin(origins = "http://localhost:8080/", allowedHeaders = "*")
public class ExpExchangeController {
private static ExpExchange expExchange = new ExpExchange();
@PostMapping("/exchange")
public boolean requestCharacterStatus (@RequestBody ArrayList<Integer> selectedExchangeList){
log.info("requestCharacterStatus() : " + selectedExchangeList);
return expExchange.exchangeExperience(selectedExchangeList);
}
}
4. 경험치 교환 시스템
들어온 경험치 교환 스탯에 따라 캐릭터 상태 정보 변동
package com.example.demo.entity.vue.rpg;
import com.example.demo.controller.vue.rpg.CharacterController;
import lombok.Getter;
import lombok.ToString;
import java.util.ArrayList;
@ToString
@Getter
public class ExpExchange {
// "hp", "mp", "atk", "str", "dex", "int", "def"
private Integer incrementedHp;
private Integer incrementedMp;
private Integer incrementedAtk;
private Integer incrementedStr;
private Integer incrementedDex;
private Integer incrementedInt;
private Integer incrementedDef;
private final int HP = 0;
private final int MP = 1;
private final int ATK = 2;
private final int STR = 3;
private final int DEX = 4;
private final int INT = 5;
private final int DEF = 6;
private final int INCREMENT_FACTOR = 5;
public Boolean exchangeExperience (ArrayList<Integer> selectedLists) {
System.out.println("여기서 경험치 교환 가능 여부 판정");
CharacterStatus currentCharacterStatus = new CharacterStatus(CharacterController.characterStatus);
for (int i = 0; i < selectedLists.size(); i++) {
switch (selectedLists.get(i)) {
case HP:
CharacterController.characterStatus.setHp(
currentCharacterStatus.getHp() + 50);
break;
case MP:
CharacterController.characterStatus.setMp(
currentCharacterStatus.getMp() + 50);
break;
case ATK:
CharacterController.characterStatus.setAtk(
currentCharacterStatus.getAtk() + INCREMENT_FACTOR);
break;
case STR:
CharacterController.characterStatus.setStr(
currentCharacterStatus.getStr() + INCREMENT_FACTOR);
break;
case DEX:
CharacterController.characterStatus.setDex(
currentCharacterStatus.getDex() + INCREMENT_FACTOR);
break;
case INT:
CharacterController.characterStatus.setIntelligence(
currentCharacterStatus.getIntelligence() + INCREMENT_FACTOR);
break;
case DEF:
CharacterController.characterStatus.setDef(
currentCharacterStatus.getDef() + INCREMENT_FACTOR);
break;
default:
System.out.println("잘못된 요청");
}
}
return true;
}
}