13. 이벤트 기반 웹뷰 프레임워크 설계와 플러그인 생태계 만들기
https://www.youtube.com/watch?v=pEPOGDPDU-U
1. 화면전환을 사용해서 유저 경험 개선하기
- 이전화면이 슬쩍 보이는 애니메이션을 웹에서 만들어보기?
- 웹에 내장되어있는 history api 사용하기 시도
- 현재 시점에 대한 상태값 뿐만 아니라 이전 화면에 대한 상태값도 필요함
- history api 는 이전 화면에 대한 상태값을 가져올 수 없음
- history api 를 감싸서 이전 화면에 대한 상태값 저장
문제점
- history api 에 의존하면서 테스트하기 어려움.
- 상태 동기화에 버그가 발생함
- history api 와 리액트 내부 상태를 동기화하기 쉽지않음
- 리액트 환경 자체에 종속되어버림
- 확장하기 어려움
- 동작 자체가 react-router dom에 의존해서
2. 리팩토링
- Transition, Naviation, URL은 문제의 본질이 아니었음.
- 실제 문제의 본질은 애니메이션을 표현하는 Stack 이었음
- 따라서 아무것도 의존하지 않아도 되었음.
Stack -> 이벤트 기반으로 생각해보기
- a.k.a Action, Message, Transition
- 이벤트 기반이란?
- 유저가 행동데이터를 발행하면, 해당 데이터가 로직에 들어가서 현재 상태를 계산하는 방식
- 만약 유저가 행동을 더 수행하면, 행동 여러개가 로직을 통하게 되고 계산된 상태가 변경됨
- 결국 상태는 로직에 행동 여러개를 넣은 값
- state = aggregate
- 행동 = event
const state = aggregate(events, currentTime)
어떤 이벤트가 필요할까?
- init (스택 초기화)
- pushed
- pop
동작 모델링
- 유닛테스트만으로 동작 문서화하기
test("푸시하면 스택에 추가되고, 팝하면 맨 위 화면이 비활성화 된다", () => {
const evnets = [
makeEvent("Init"),
makeEvent("Pushed"),
makeEvent("Pushed"),
makeEvent("Popped")
];
const state = aggregate(events, nowTime());
expect(state).toStrictEqual({/**기대되는 값 **/})
})
- 외부 의존성이 없어서 테스트 가능
리액트와 어떻게 통합?
- 리액트의 useSyncExternalStore 사용
플러그인 인터페이스로 쉬운 확장성 제공하기
확장성?
-
...했을때..을 수행한다.
-
callback, action
-
라이브러리는 자신이 정의한 시점에 callback 함수를 등록할 수 있는 인터페이스를 노출하여 확장성을 간단히 유저에게 줄 수 있음
-
상상력을 펼칠 수 있도록 미리 재료를 준비하기!
- 그런데, 너무 많은 콜백을 노출하면 과도한 설계가 될 수 있음
-
Effect 추가하기
-
사용자 입장에서는 아래와 같은 인터페이스를 만남
- 설계와 외부 인터페이스가 일관됨
makeCoreStore({
onInit();
onBeforePush(){},
onBeforePop(){},
onPushed(){},
onPopped(){}
})
- 옵션이 많아져서 개발자가 이해해야하는 것이 많아짐
- 라이프사이클 훅이 많아질 수록, 유저는 복잡하게 이해하기 힘들다고 느낄 수 있음
확장성은 유지하되, 유저가 쉽게 이해할 수 있도록 만들자!
- 아이디어 : GraphQL Envelop
- envelop은 유저가 구체적인 인터페이스는 모르더라도, 플러그인을 통해서 쉽게 확장기능을 사용할 수 있음
const getEnveloped = envelop({
plugins:[
useSchema(schema),
useParserChache(),
useValidationCache(),
]
})
- 아래와 같이 변경
makeCoreStore({
plugins:[
{
onInit(){},
onBeforePush(){},
onBeforePop(){},
onPushed(){},
onPopped(){}}
]
})
리팩토링 결과
- 코어 로직에 의존성을 제거하여, 쉽게 테스트 가능
- 이벤트 스냅샷을 통해 쉽게 디버깅
- 코어로직을 통하여 다른 생태계에도 쉽게 적용 가능
- 라이프사이클과 훅 플러그인 인터페이스를 통해 확장 용이