JS-21 OPP 입문 (23/01/09)

nazzzo·2023년 1월 9일
0

객체지향 설계 맛보기



프론트엔드 객체지향 설계 요점정리

  • 응답 데이터가 바뀌면 화면도 바뀌도록 설계하는 것이 최우선입니다

  • 이벤트는 필요한 경우에 한 번만 생성되도록 설계해야 합니다
    (화면을 새로 그릴 때마다 이벤트가 재생성되지 않도록 해야 해요)

  • 부모 클래스(component)에서는 함수의 실행순서 정리 및 호출만,
    함수의 구체적인 내용은 자식 클래스(app)에서 구현하는 것이 나중에 알아보기 편합니다
    (함수와 변수명은 이름만으로도 역할을 유추할 수 있게끔 구체적으로)

  • 서버에서 직접 데이터(텍스트)를 받아와서 그릴 것인지,
    true / false값만 반환받고 클라이언트측에서 그릴 것인지는 선택사항입니다
    되도록 서버에 가는 부담을 줄이는 편이 나아보이기는 합니다



↓ 예제 및 기록보관용 코드입니다


[Component]

class Component {
  target; // 앞으로 넣을 Element
  state; // 데이터들

  constructor(_target) {
    this.target = _target;
    this.setup();
    this.render();
    this.setEvent(); // setEvent는 render가 끝난 후에(버튼이 생겨난 후에) 호출되어야 함
  }

  // 부모 클래스에서는 함수 호출만
  setup() {} // 자식클래스에서 구현
  template() {} // 자식클래스에서 구현
  render() {
    this.target.innerHTML = this.template(); // render 메서드가 호출될 때 template 함수 실행
  }
  setEvent() {}
  addEvent(type, selector, callback) {
    // type : click, mouseover, mouseleave...
    // selector : 이벤트를 추가할 엘리먼트 선택자

    // console.log(selector)
    // console.log(document.querySelectorAll(selector))
    const children = [...document.querySelectorAll(selector)]

    // 논리연산자 응용
    // const b = null && 10 && undefined // b는 null... false값을 먼저 찾기 때문
    // const c = console.log("hello") || 10 // console.log가 실행되고 c는 10
    // const d = 10 || console.log("world") // 이미 true값을 찾았기 때문에 console.log 실행 x

    const isTarget = (target) => children.includes(target) || target.closest(selector)
    // true or false를 반환하는 함수입니다
    // .btn의 부모요소 div 혹은 내부의 a태그를 선택해도 클릭이벤트가 발동되도록 (포괄적으로)

    this.target.addEventListener(type, (e)=>{
        if (!isTarget(e.target)) return false
        callback(e)
    })
  }

  setState(newState) {
    if (this.state === newState) return; // 데이터가 바뀌면 화면도 바뀌어야 합니다
    this.state = { ...this.state, ...newState };
    this.render(); // 클릭 이벤트로 setState 실행 > render 함수 호출 > 화면이 바뀜
  }
}

export default Component;

[app]

import Component from "/js/core/Component.js";

class App extends Component {
  setup() {
    this.state = {
      list: [
        {
          id: 1,
          userid: "web7722",
          content: "hello1",
          register: "2023-01-09",
          updated: false,
        },
        {
          id: 2,
          userid: "web7722",
          content: "hello2",
          register: "2023-01-09",
          updated: false,
        },
        {
          id: 3,
          userid: "web7722",
          content: "hello3",
          register: "2023-01-09",
          updated: false,
        },
      ],
      user: {
        userid: "web7722",
        username: "ingoo",
      },
    };
  }

  content(content) {
    return `
            <span class="comment-update-btn">${content}</span>
            <span class="comment-delete-btn">❌</span>
        `;
  }

  update(content) {
    return `
            <span>
                <input type="text" class="comment-update-input" value="${content}" data-value="" />
            </span>
            <span class="comment-delete-btn">❌</span>
        `;
  }

  template() {
    const { list } = this.state;

    return `
            <ul class='comment'>
                <li class='comment-form'>
                    <form id='commentFrm'>
                        <h4>
                            댓글쓰기
                            <span></span>
                        </h4>
                        <span class='ps_box'>
                            <input type='text' class='int' name='content' placeholder='댓글입력점.'>
                        </span>
                        <button type='submit' class='btn'>등록</button>
                    </form>
                </li>
                <li id='comment-list'>
                    ${list
                      .map((comment) => {
                        return `<ul class="comment-row" data-index="${
                          comment.id
                        }">
                                    <li class="comment-id">${
                                      comment.userid
                                    }</li>
                                    <li class="comment-content">
                                        ${
                                          comment.updated
                                            ? this.update(comment.content)
                                            : this.content(comment.content)
                                        }
                                    </li>
                                    <li class="comment-date">${
                                      comment.register
                                    }</li>
                                </ul>`;
                      })
                      .join("")}
                </li>
            </ul>
        `;
  }

  setEvent() {
    // ↓ 지양할 코드...render가 실행될 때마다 클릭이벤트가 재생성됩니다
    // this.target.querySelector("#btn").addEventListener("click",()=>{
    //     console.log("click test");
    //     const {list} = this.state
    //     this.setState({ list: [...list, { id: 4, userid: "web7722", content: "hello4", register: "2023-01-09" }] })
    // })

    // ver2 원하지 않는 요소를 클릭해도 이벤트가 발동됩니다
    // this.target.addEventListener('click',(e)=>{
    //     const list = [...this.state.list]
    //     list.push({ id: 4, userid: "web7722", content: "hello4", register: "2023-01-09" })
    //     if (e.target.classList.contains('btn')) {
    //         this.setState({list})
    //     }
    // })

    this.addEvent("submit", "#commentFrm", (e) => {
      e.preventDefault();
      const { list } = this.state;
      this.setState({
        list: [
          ...list,
          {
            id: list.length,
            userid: "web7722",
            content: e.target.content.value,
            register: "2023-01-09",
            updated: false,
          },
        ],
      });
    });

    this.addEvent("click", ".comment-update-btn", (e) => {
      const ul = e.target.closest(".comment-row");
      const { index } = ul.dataset;
      const list = [...this.state.list];
      const newList = list.map((v) => {
        if (v.id === parseInt(index)) v.updated = true;
        return v;
      });
      console.log(newList);
      this.setState({ list: newList });
    });

    this.addEvent("click", ".comment-delete-btn", (e) => {
      const ul = e.target.closest(".comment-row");
      const { index } = ul.dataset;
      // 요청때리고
      // 응답결과
      const list = [...this.state.list].filter((v) => v.id !== parseInt(index));
      this.setState({ list });
    });

    this.addEvent("keypress", ".comment-update-input", (e) => {
      if (e.keyCode !== 13) return;

      const ul = e.target.closest(".comment-row");
      const index = parseInt(ul.dataset.index);

      // 요청
      // 응답

      const list = this.state.list.map((v) => {
        if (v.id === index) {
          v.content = e.target.value;
          v.updated = false;
        }

        return v;
      });

      this.setState({ list });
    });

    // document.querySelector("click", () => {})
  }
}

export default App;

0개의 댓글