simpleSymbols = [
{"PAUSE"},
{"ENTER"},
{},
{"MOUSE", "SLASH"},
... ,
{}
]
각 단어마다 어떤 기호가 포함되어 있는지 저장하기 위해 위와 같은 방법을 사용한 이유는 다음과 같다.
- 기호들을 reducer로 묶으면서, 같은 로직을 기호의 이름만 달리 해서 실행할 수 있어야 했다.
- 사용자가 같은 기호를 여러 번 적용하더라도 중복 없이 하나만 저장해야 한다.
- 각 기호별로 단어 수만큼의 길이를 가진 배열을 저장하는 것보다 저장공간이 절약된다.
- 새로운 기호를 추가하더라도 기존 코드를 변경 할 필요 없이 해당 기호의 이름만 추가하면 된다.
- 기호들을 렌더링 할 때 각 단어마다 전체 기호 수 만큼의 비교 연산을 수행할 필요가 없다(set에 있는 단어들을 그냥 모두 출력).
그러나 이 방법을 사용하려면 서버에 전송할 때 JSON.stringify()가 set에 제대로 작동하지 않기 때문에 형변환을 시켜줘야 한다. 서비스 특성상 기호를 서버와 주고받는 일이 매우 자주 발생하는데 데이터를 매번 변환하여 서버에 넘겨주는게 불필요한 자원을 쓴다고 생각해서 다른 방법을 고민해 봤다.
pause: {1, 5}
enter: {7, 9}
우선 이렇게 각 기호별로 적용된 단어의 인덱스를 저장하는 방법을 생각해 봤다. 그러나 이 방법은 결국 처음 방법(pauseSymbol = [true, false, .. ])이랑 다를게 거의 없다. 저장공간은 줄어들겠지만 기호를 렌더링 할 때는 어차피 각 기호 배열에 인덱스로 접근하기 때문에 시간복잡도는 같다. reducer로 묶어서 관리하기 어렵고 렌더링하는 구문을 반복해야 한다는 점에서 처음 방법에서 나아질게 없었다.
그 다음 생각한 방법은
{
1: {"PAUSE"}
10: {"ENTER"}
}
이렇게 모든 기호를 한 번에 관리하는 객체를 만들고 key는 적용된 기호가 있는 단어의 인덱스, value는 적용된 기호 이름의 set으로 저장하는 방법이었다. 그러나 이 방법은 렌더링 할 때 매번 key 배열 뽑아서 기호가 적용되어 있는지 확인하는 등의 비교 연산이 꼭 필요하다.
결국 처음으로 생각한 해결책인 각 단어별로 어떤 기호가 적용되어 있는지를 저장하는 것이 앞서 말한 다섯 가지 이유로 가장 효율적이라는 결론을 내렸다. 다만 서버 전송 과정에서 매번 배열로 변환하는 cost를 줄이기 위해 처음부터 set이 아닌 배열로 저장했다.
simpleSymbols = [
["PAUSE"],
["ENTER"],
[],
["MOUSE", "SLASH"],
... ,
[]
]
여전히 기호는 중복되면 안되기 때문에 set은 중복 체크를 위해서만 활용했다.
const simpleSymbolsReducer = (state, action) => {
switch (action.type) {
case "INIT":
return action.payload;
case "ADD":
return state.map((symbol, i) => {
if (i == action.idx) {
return [...new Set([...symbol, action.symbol])];
} else {
return symbol;
}
});
case "REMOVE":
return state.map((symbol, i) => {
if (i == action.idx) {
// return new Set();
return [];
} else {
return symbol;
}
});
default:
throw new Error("Unhandled action");
}
};
기호를 렌더링 하는 부분도 별다른 고민 없이 map을 사용하면 된다.
simpleSymbols[i].map((symbol) => (
<img
src={symbolIcons[symbol]}
alt={symbol}
key={symbol}
/>
))
동작이 비슷한 기호들을 리듀서로 묶으면서 생각보다 많은 부분을 수정했다. 예를 들어 기호 적용을 위해 커서를 바꿔줘야 하는데, 그 커서에 따라 리듀서에도 다른 액션을 전해줘야 하다 보니 기존에 불필요하게 추가되었던 "현재 선택된 기호의 인덱스"를 삭제하고 기호의 이름으로 구분 방식을 통일했다. 이제 보니 처음 설계때부터 커서와 기호를 같이 관리했어야 하는게 맞는데 그러지 못하고 다른 상태를 또 추가하고 추가해서 괜히 복잡한 코드를 짰다는 것을 알게 됐다.
프로젝트 초기에 계획을 완벽히 세우지 못하고 기능을 중간에 추가하거나, 시간이 없어 급하게 작성하게 되는 경우 어쩔 수 없이 코드가 복잡하고 가독성이 떨어진다. 지금 나 뿐만 아니라 백엔드 팀원들도 아예 코드를 처음부터 짜고 있다는데...
처음부터 완벽한 계획을 세우고 그 계획대로만 가는 것은 현실적으로 어렵다. 프로젝트를 진행하며 실력이나 이해도가 느는 것을 반영할 수도 없다. 그렇다고 작은 기능 하나를 위해 뒷일은 생각하지 않고 냅다 코드부터 짜는 것도 나중에 후회할 일이다. (리팩터링을 하면서 처음부터 잘 할걸이라는 후회가 들다가도 그 때는 어쩔 수 없었다는 사실을 떠올리는게 반복되고 있다.)
기능 개발보다 재미는 없지만 코드를 왜 이렇게 짰었는지 스스로 돌아보게 된다는 점에서 실력은 많이 늘 것 같다. 아직 모르는게 아는 것보다 훨씬 많으니까 테스트 코드도 짜고 리팩터링도 계속 해보면서 열심히 해봐야겠다!
'React' 카테고리의 다른 글
useReducer로 useState 리팩터링 하기 (2) (0) | 2023.09.15 |
---|---|
useReducer로 useState 리팩터링 하기 (1) (0) | 2023.09.15 |
useReducer (0) | 2023.09.12 |
useEffect로 state 변경 바로 감지하기 (4) | 2023.08.08 |
State 작동 방식 (0) | 2023.08.08 |