[리액트] typescript, mobx 에서 private state를 처리하는 방법에 대하여
mobx 상에 store를 만들어 사용할 때 일반적으로 observable를 통해 만들게 됩니다. 간단한 예를 들어 설명하겠습니다.
일반적으로 클래스형 store에서는 간단하게 makeObservable()를 통해 간단히 만들곤 합니다.
import { action, makeObservable, observable } from 'mobx'
class Count {
number: number = 0
constructor() {
makeObservable(this, {
number: observable,
increase: action,
decrease: action,
})
}
increase = () => {
this.number++;
}
decrease = () => {
this.number--;
}
}
const countStore = new Count()
export default countStore
최근 mobx 에는 makeObservable()를 좀더 간단하게 만들어주는 것도 등장을 했습니다.
| 바로 makeAutoObserable() 입니다.
import { makeAutoObservable } from 'mobx'
class Count {
number: number = 0
constructor() {
makeAutoObservable(this)
}
increase = () => {
this.number++
}
decrease = () => {
this.number--
}
}
const countStore = new Count()
export default countStore
보시면 constructor 부분에 action, observable 등을 달 필요 없이 자동(auto)으로 관찰 가능한 state를 만들어주는 syntactic sugar입니다. 저도 일일이 action, observable를 달지 않아도 된다는 편리함 때문에 자주 사용하곤 합니다.
하지만 이런 makeAutoObserable() 에도 단점이 있는데요. 바로 super를 사용하는 곳에서는 사용할 수 없다는 점입니다. typescipt를 통해 최신 자바스립트를 사용할수 있게 되면서 생긴 장점 중에 하나라고 생각하는 점은 class의 도입과 상속입니다.
하지만 makeAutoObserable의 경우 상충되는 부분이 생겨 사용할 수 없게 됩니다.
이러한 일을 발생하는 이유는 super로 상속되는 경우 어떤 store에 종속되어 있는지 모호해지기 때문이라고만 생각하고 있습니다.(정확한 이유는 추가적인 공부를 하고 확실히 알고 난 후에 수정하겠습니다. ㅜㅜ)
따라서 우회 방한은 결국 다시 makeObserable를 사용하는 것입니다.
아래는 Mobx 공식 홈페이지(mobx.js.org/observable-state.html) 에 나와있는 설명입니다.
limitation 3, 5번을 보시면
3번 : 기본적인 타입 스크립트에서는 private field를 허용하지 않는다. 그래서 generic argument로 우회해라
5번 : 자바스크립트는 private field는 지원하지 않는다. 하지만 타입스크립트 쓸 때는 private를 추천한다.
| 결론은 private를 쓰고 generic으로 우회하라는 말입니다.
아래는 실제 사용하는 코드를 테스트 코드로 바꿔본 예제입니다.
예제 코드의 경우 크게
TestParentModel 이라는 인터페이스를 TestChildModel이 상속합니다.
또한 TestParentStore 라는 스토어를 TestChildStore가 상속합니다.
이때 name, id, userInfo 는 private로 설정하고
userInfo의 경우 name, id를 파라미터로 서버로 보내 받은 정보를 저장하는 변수라고 하겠습니다.
import { makeAutoObservable, makeObservable, observable, action, computed } from "mobx";
export interface TestChildModel extends TestParentModel {
name: string;
id : string;
updateName: (name: string) => void;
}
export class TestChildStore extends TestParentStore implements TestChildModel {
private __name: string;
private __id: string;
private __userInfo : string;
constructor(info: TestType) {
super();
this.__name = info.name;
this.__id = info.id;
this.__userInfo = {};
makeObservable<TestChildModel | "__userInfo">(this, {
name: computed,
id: computed,
__userInfo: observable,
userInfo: computed,
updataName: action,
});
}
updataName(name: string) {
if (name.length == 0) {
console.log("wrong name!");
return;
}
/*ID와 NAME 정보로 서버에서 userInfo를 가져오는 로직*/
const response : string = axios..... // 대충 이런 비동기로 가져온다고 합시다
response.then((result)=> {
this.makeInfoByResponse(result);
})
}
makeInfoByResponse(result: any) {
this.__userInfo = result;
}
get name() {
return this.__name;
}
get id() {
return this.__id;
}
get userInfo() {
return this.__userInfo;
}
set userInfo(info: any) {
this.__userInfo = info;
}
}
코드를 보시면 다른 클래스와 다를 바가 없는 코드입니다. 다만 constructor 부분이 눈에 띕니다.
constructor(info: TestType) {
super();
this.__name = info.name;
this.__id = info.id;
this.__userInfo = {};
makeObservable<TestChildModel | "__userInfo">(this, {
name: computed,
id: computed,
__userInfo: observable,
userInfo: computed,
updataName: action,
});
}
makeObservable이 아주 더럽게 되어있습니다.
TestChildModel 는 위에 선언한 인터페이스여서 알겠는데 "__userInfo" 는 대체 무엇인가.. 하니 store 내부에서 생성되고 사용하는 private변수입니다.
위에서 설명드렸다시 비 userinfo의 경우 name, id를 통해 서버에서 가져온 값을 저장합니다. 따라서 constructor에서 들어온 info값으로 초기화하지 않고 빈 객체로 초기화한 후에 updateName 안에서 변경이 일어나게 됩니다.
따라서 userInfo도 obserable로 만들어줘야 합니다. 이런 경우에 다음 예시 테스트 코드를 사용하시면 됩니다.
| 글을 마치며
오늘 4시간 동안 삽질한 것을 정리하기 위해 글을 작성했는데, 결국 document를 제대로 읽지 않아 발생한 문제였습니다 ㅜㅜ.
또한 다른 Mobx, types 예시의 경우, decorate를 통해 @ 문법을 사용하는데 20년 9월부터 Mobx6에서는 decorate가 deprecated가 되고 make(Auto)Obsevable를 사용하길 권장하고 있습니다. 그래서 예시가 없어서 더 고생했습니다.
추가적으로 현재 makeAutoObsevable의 경우 super와 같이 사용할 수 없다고 되어는 있지만, makeAutoObsevable가 makeObsevable를 편하게 사용하기 위한 syntactic sugar인 만큼 조만간 간단하게 사용할 수 있는 방법이 업데이트되지 않을까 생각 은하고 있습니다. 그전까진 어쩔 수 없이 저런 문법을 사용하긴 해야 할 듯합니다.
다음 react 포스트에서는 외부 라이브러리인 Plotly, tabulator, sun editor를 react, types 환경에서 사용하는 방법에 대해 알아보도록 하겠습니다.
내용이 부족한 점, 잘못된 점이 있으면 댓글로 참 교육 부탁드립니다. 감사합니다.