관리 메뉴

Mini

24. 10. 2. 개발일지 // 프론트컨트롤러 v3, 동적렌더링, mapToObj 본문

JS/boostCamp

24. 10. 2. 개발일지 // 프론트컨트롤러 v3, 동적렌더링, mapToObj

Mini_96 2024. 10. 2. 20:41

* 프론트컨트롤러 v3 구현

  • 기존의 단점 : 컨트롤러에 req, res등 서블릿 기술에 의존적임.
  • 단점2 : 아래와같이 viewPath부분의 중복이 많음.
public process(req : Request , res : Response) : MyView {
    const viewPath : string = path.join(process.cwd(), 'dist','views','members.html');
    return new MyView(viewPath);
}

  • ModelView
    • 여기의 map으로 new-form 등을 매칭해줄거임.
      • model에 < 뷰의 논리이름, 실제객체 >를 넣어준다!!
export class ModelView {
    private viewName : string;
    private model : Map<String, Object>= new Map();

    constructor(viewName : string) {
        this.viewName = viewName;
    }

    public getViewName(){
        return this.viewName;
    }

    public setViewName(viewName : string): void {
        this.viewName = viewName;
    }

    public getModel() : Map<String, Object>{
        return this.model;
    }

    public setModel(model : Map<String, Object>): void{
        this.model = model;
    }

}
  • 컨트롤러 설계
    • ModelView를 반환해준다.
import {ModelView} from "../ModelView";

export interface ControllerV3{

    process (paramMap : Map<String, Object>) : ModelView;
}
  • 컨트롤러 구현
    • 논리적인 이름만 넘긴다.
    • 물리적이름 만들기는 프론트컨트롤러의 viewResolver에서 담당한다.
import {ControllerV3} from "../ControllerV3";
import {ModelView} from "../../ModelView";

export class MemberFormControllerV3 implements ControllerV3{

    process(paramMap: Map<String, Object>): ModelView {
        return new ModelView("new-form");
    }
}
  • model에 < 뷰의 논리이름, 실제객체 >를 넣어준다!!
import {ControllerV3} from "../ControllerV3";
import {ModelView} from "../../ModelView";

interface Member {
    id: number;
    loginId: string;
    name: string;
}

export class MemberListControllerV3 implements ControllerV3{
    process(paramMap: Map<String, Object>): ModelView {
        //todo : repository에서 member 모두 찾아서 넣기
        const members: Member[] =  [
            { id: 1, loginId: 'user1', name: '홍길동' },
            { id: 2, loginId: 'user2', name: '김철수' },
            { id: 3, loginId: 'user3', name: '이영희' }
        ];
        const mv : ModelView = new ModelView("members");
        mv.getModel().set("members",members);
        
        return mv;
    }
}

  • 버그수정
    • 버그 : viewsmembers 이런식으로 / 가 생략되어 파일을 못찾음
    • path 모듈이용해서 수정
/**
 * 논리이름을 (members)
 * 물리이름으로 변환 (~~~/dist/views/members.html)
 * @param viewName
 * @private
 */
private viewResolver(viewName: string):MyView {
    const viewPath: string = path.join(process.cwd(), 'dist', 'views',viewName+'.html');
    const view = new MyView(viewPath);
    return view;
}

 

v3로 test 한 결과

 

* 동적렌더링 구현

  • 현재는 그냥 html을 그대로 그려주고있다.
  • 이를 동적으로 바꾸고자 한다.
<div class="container">
    <h1>회원 목록</h1>
    <table>
        <thead>
        <tr>
            <th>ID</th>
            <th>로그인 ID</th>
            <th>이름</th>
        </tr>
        </thead>
        <tbody>

        <tr>
            <td>1</td>
            <td>chovy</td>
            <td>정지훈</td>
        </tr>

        <tr>
            <td>2</td>
            <td>faker</td>
            <td>이상혁</td>
        </tr>

        <tr>
            <td>3</td>
            <td>tsla</td>
            <td>일론머스크</td>
        </tr>
        </tbody>
    </table>
</div>
</body>
</html>

  • map.forEach 주의점
    • val, key순으로 callback을 조회해야한다.
    • 보통 key, value 순으로 생각했는데 특이하다??

value가 첫번째 파라미터인듯?

  • 뭔가 크롬에서 html이 렌더링되지않고 파일이 다운로드 되버리는 버그가 있다.
    • 캐시 삭제, 강력 새로고침하니 해결되었다.
  • 현재 데이터를 강제로 넣어준경우 동적렌더링은 작동한다.
// 사용 예시
const pageData = {
    title: '회원 목록',
    members: [
        { id: 1, loginId: 'user1', name: '홍길동' },
        { id: 2, loginId: 'user2', name: '김철수' },
        { id: 3, loginId: 'user3', name: '이영희' }
    ]
};
const renderedHtml = await this.renderEjsTemplate(viewPath, pageData);

  • 이제 할일은 data를 pageData의 형태로 바꿔주는 일이다.
  • 하려고봤더니 또 버그가생겼다.
    • 바로 ejs는 내용이 변하지 않는다는 점이다.
    • 그래서 내용을 바꿔도 ejs의 해시값은 똑같기때문에 , 304 Not Modified가 되면서 과거의 내용이 보여진다.
    • 일단 임시로 etag 기능을 삭제한다.
    • 템플릿엔진의경우, etag나 last-modified 기능을 못쓰는것인지? 의문이다

임시로 삭제할 부분

  • 이제 진짜 할일은 data를 pageData의 형태로 바꿔주는 일이다.

현재 pageData에는 이런것들이 담겨있다.
목표

  • 먼저 jsp의 동작방식을 살펴보자.
    • render 함수에서 model을 다시 request로바꾼후 속성을 set해줘야 jsp에서 접근이 가능한 구조다.

  • 이부분을 좀 수정해야 될것같다.
    • model에는 현재 < "members" , member 객체 > 가 담겨있다.
    • 이 모델을 객체로 바꿔서 넘기면 될것같다.
public async renderEjs(model: Map<String, Object>, req : Request, res : Response) : Promise<void> {
    let pageData = {};
    model.forEach((val, key) =>{
        pageData = { ...pageData, val};
    });
    await res.forwardEjs(req,res,this.viewPath, pageData);
}

{ key : [value] }로 만들면 될듯?

 

* map to obj

  • 일단 String이 아니라 string으로 Map을 바꿔준다.
  • map < members , member 객체 > 를 obj로 바꿔주고 넘기면 된다.
  • obj[key] = value; 이런식으로 값을 set 할수있다.
mapToObject(map: Map<string, object>): { [key: string]: object } {
    const obj: { [key: string]: object } = {};

    for (const [key, value] of map) {
        obj[key] = value;
    }

    return obj;
}
public async renderEjs(model: Map<string, Object>, req : Request, res : Response) : Promise<void> {
    const pageData = this.mapToObject(model);

    await res.forwardEjs(req,res,this.viewPath, pageData);
}

결과

  • 이제 각각의 컨트롤러에서 ModelAndView에 절대경로, 모델객체만 잘 넘겨주면 알아서 렌더링된다.
  • 이제 개발자는 비즈니스로직에만 집중할 수 있다.