프론트엔드에서 다루는 변수, 객체 들은 대표적으로 두개로 나뉜다.
- 서버로부터 받는 정보 (프론트엔드에서 함부로 건들이면 안되는 것이 원칙)
- 유저로부터 받는 정보 (Input, Button 등 유저의 행동에 의해 변경될 수 있는 변수)
그래서 M-V-VM의 데이터 모델링 규칙에서 두가지 변수를 어떻게 모델링 할것인지 정리해보려고 한다.
개인적으로 디자인패턴이라는 것은 더 나은길을 제시해 줄 뿐이며, 정답없는 이정표같은 느낌이다.
혹시라도 잘못 사용하는 것 같다면 얼마든지 피드백을 받을 준비가 되어있기에 많은 관심을 기다린다.
1. DTO
서버로 부터 받는 정보는 DTO로 모델링하고 다음과 같이 사용한다.
프론트엔드에서 특별한 상황이 아니고서는 임의변경을 하면 안되기에 readonly로 각 변수를 선언해준다.
import { Expose } from "class-transformer";
export default class TestDto {
@Expose({ name: "id" })
public readonly id: number = 0;
@Expose({ name: "name" })
public readonly name: string = '';
@Expose({ name: "phone_number" })
public readonly phoneNumber: string = '';
}
위처럼 class-transformer를 사용하여 key가 'phone_number'로 들어오더라도 'phoneNumber'로 변경해 사용할 수 있다.
사용할땐 다음과같이 ViewModel에서 사용한다.
export default class TestViewModel extends DefaultViewModel {
public testData: TestDto = new TestDto();
...variables
constructor(props: IDefaultProps) {
super(props);
...mobX
makeObservable(this, {
testData: observable,
getData: action,
});
}
...actions
getData = async () => {
await this.api
.get('/apiurl')
.then((result: AxiosResponse<TestDto>) => {
runInAction(() => {
this.testData = plainToInstance(TestDto,result.data);
});
})
.catch((error: AxiosError) => {
console.log("error : ", error);
...error handle
return false;
});
};
}
class로 만들어진 모델링은 타입으로도 사용가능하고 객체로도 사용 가능하다.
또 의도대로 key값이나 type을 정의된 대로 사용하며 모델링객체에 담으려면 plainToInstance를 사용해서 저장해주면 된다.
그럼 결과를 보자.
이렇게까지 하는 이유는 무엇일까?
그냥 변수에 저장하고 보내주는대로 사용하고 변경안하려면 변경코드를 작성하지 않으면 되는 것 아닌가?
물론 맞다. 하지만 만약 내가 아닌 개발자가 이어받는다면? 그런 규칙따윈 신경 안쓸가능성도 있다.
개발 의도가 달라져버린다는 말이다.
예측이 가능하고 엄격하게 관리되는 코드는 유지보수에서도 다른개발자의 가독성면에서도 뛰어날 수 밖에 없다.
2. Model
유저가 입력하는 로그인정보, 버튼클릭에 따른 토글상태, 날짜 등등 여러가지 상황에 의해서 변경되는 변수를 Model로 지정했다.
export default class AccountModel {
public account: string = "";
public password: string = "";
}
간단하다 변경이 되어야하기때문에 readonly는 당연히 없고 서버에 보내야하는 모델이라면 변수명만 잘 맞추어놓으면 된다.
export default class TestViewModel extends DefaultViewModel {
public loginModel: AccountModel = new AccountModel();
...variables
constructor(props: IDefaultProps) {
super(props);
...mobX
makeObservable(this, {
loginModel: observable,
handleChangeLogin: action,
});
}
...actions
handleChangeLogin = (event: ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
runInAction(() => {
this.loginModel = plainToInstance(AccountModel,{ ...this.loginModel, [name]: value });
});
};
}
이처럼 사용하는 방법은 Dto와 크게 다를것은 없다.
결과는?
Model 또한 마찬가지다.
Dto처럼 아니 오히려 변경해도 되는 변수라면 그냥 사용해도 되지 않는가?
역시나 유지보수와 가독성이 훨씬뛰어나다.
Type을 위해서 어차피 interface를 만들거라면 Model을 사용해서 더 보기좋게 만들면 좋지않은가!
TypeScript를 사용한다면 간편한 타입지정으로 위와같이 자동완성을 위한 추천단어로 모델링 된 객체의 키들을 보여준다.
당연히 Type또한 의도한 값으로 입력되도록 개발을 유도하며 이는 예측가능한 코드를 만들게한다.
같은 Model을 사용하는 컴포넌트, 뷰, 뷰모델이 많다면 이 Model, Dto를 재사용해서 사용할 수 있다는 점도 좋다.
Model에 초기값을 로그인이기때문에 빈문자열로 해놓았지만 기본값을 지정해놓을 수도 있다.
예를들면
이런식으로 기본값을 설정해놓으면 이 객체는 new 객체()가 되어 새 객체로 생성될때마다 기본값이 들어가게 되어있다.
오늘은 Dto와 Model에 대해서 적어보았는데
사실 더 나은 방식이 있을것같아 지금도 연구중이고 혹시라도 잘못사용하는 부분이 있지않을까 항상 고민하고있다.
글을보고 실수나 더나은방식을 캐치한것이 있다면 댓글부탁드립니다.