重複コードの再パッケージ(VanillaJS)


開始します.🌈
私のコードを見たとき、私はいつも心配していました.
  • というコードは、これからも使えますか?
  • は繰り返す感じ?!
  • は性能面で悪いのではないでしょうか?!
  • ということで、今回も私のコードを見て、イベント面で何度か重複している部分を見ました.
    そして始まります.ボタンコードを再包装!
    本題📖
    私が再包装することにしたコードはsidebar部分です.
    import {
      _removeAllChildNodes,
      _createElemWithAttr,
      _renderPosts,
    } from '@/utils/customDOMMethods';
    import names from '@/utils/classNames';
    import createPost from '@/apis/route/post/createPost';
    import Modal from '@/components/common/Modal';
    import { push } from '@/apis/router';
    import { ERROR_STATUS } from '@/utils/constants';
    import deletePost from '@/apis/route/post/deletePost';
    import getPostList from '@/apis/route/post/getPostList';
    import Button from '@/components/common/Button';
    import checkState from '@/utils/checkState';
    
    /*
      {
        username,
        documents: []
      }
    */
    
    export default function SideBar({ $target, initialState, onClick }) {
      const {
        postsItem,
        postsBlock,
        sideBarItem,
        sideBarContainer,
        sideBarButtonBox,
        sideBarCreatePostBtn,
        postBlock,
        postToggleBtn,
        postNext,
        postLink,
        postNextNew,
        postRemoveBtn,
      } = names;
      this.state = initialState;
    
      const $sideBar = _createElemWithAttr('nav', [sideBarContainer]);
      const $posts = _createElemWithAttr('section', [sideBarItem, postsBlock]);
      const $sideBarButtonBox = _createElemWithAttr('div', [sideBarButtonBox]);
      new Button({
        $target: $sideBarButtonBox,
        attributes: { classNames: [sideBarCreatePostBtn], text: '페이지 생성' },
        onClick: () => {
          const $app = document.querySelector('#app');
          const modal = new Modal({
            $target: $app,
            head: '생성할 페이지의 제목을 입력해주세요!',
            isInput: true,
            onConform: async title => {
              try {
                const result = await createPost(this.state.username, {
                  title,
                  parent: null,
                });
                push(`/posts/${result.id}`);
              } catch (e) {
                console.error(e);
                alert(ERROR_STATUS, e);
              }
            },
          });
          modal.render();
        },
      });
      $sideBar.appendChild($sideBarButtonBox);
    
      this.setState = nextState => {
        if (!checkState(this.state, nextState)) {
          _removeAllChildNodes($posts);
          this.state = nextState;
          const { documents } = this.state;
          const $fragment = new DocumentFragment();
          _renderPosts($fragment, documents);
          $posts.appendChild($fragment);
          $sideBar.appendChild($posts);
        }
        this.render();
      };
    
      this.render = () => {
        console.log($sideBar, $target);
        if (!$target.querySelector(`.${sideBarContainer}`))
          $target.append($sideBar);
      };
    
      $sideBar.addEventListener('click', e => {
        if (
          !e.target.classList.contains(postsItem) &&
          !e.target.classList.contains(postLink)
        ) {
          return;
        }
        const postId = e.target.closest(`.${postsItem}`).getAttribute(['data-id']);
        onClick(postId);
      });
    
      $posts.addEventListener('click', e => {
        const { target } = e;
        if (!target.classList.contains(postToggleBtn, 'post__link')) return;
        const closestPostId = target.closest(`.${postBlock}`).dataset.id;
        const $nextItem = $posts.querySelector(
          `.${postNext}[data-id="${closestPostId}"]`,
        );
        $nextItem.classList.toggle('invisible');
        target.classList.toggle('toggle');
      });
    
      $sideBar.addEventListener('click', e => {
        const closestPostNextNew = e.target.closest(`.${postNextNew}`);
        if (!closestPostNextNew) return;
        const $app = document.querySelector('#app');
        const closestPostNext = e.target.closest(`.${postNext}`);
        const modal = new Modal({
          $target: $app,
          head: '생성할 페이지의 제목을 입력해주세요!',
          isInput: true,
          onConform: async title => {
            try {
              const result = await createPost(this.state.username, {
                title,
                parent: closestPostNext.dataset.id,
              });
              push(`/posts/${result.id}`);
            } catch (e) {
              console.error(e);
              alert(ERROR_STATUS, e);
            }
          },
        });
        modal.render();
      });
    
      $sideBar.addEventListener('click', e => {
        if (!e.target.classList.contains(postRemoveBtn)) return;
        const closestPostNext = e.target.closest(`.${postsItem}`);
        const modal = new Modal({
          $target: document.querySelector('#app'),
          head: '정말로 삭제하시겠어요?',
          isInput: false,
          onConform: async () => {
            try {
              const { id } = closestPostNext.dataset;
              await deletePost(this.state.username, id);
              const posts = await getPostList(this.state.username);
              this.setState({
                documents: posts,
              });
              if (id === window.location.pathname.split('/')[2]) {
                push('/');
              }
            } catch (e) {
              console.error(e);
              alert(ERROR_STATUS, e);
            }
          },
        });
        modal.render();
      });
    }
    どうですか.繰り返したような気がしますか?!
    私の場合、ボタンがクリックされると、モードを実行させる一連のステップもフォーマットできると思います.
    第一に、共通の部分を選ぶ.
    そこで、私たちはまず共通の部分を選びました.
    その結果、ある月の活動を実行する部分で、次のような結論が得られました.
  • 活動は多くの場所で使用でき、関数形式よりaddEventListener形式で残るほうが説得力があるので触らないでください.
  • の場合、まずmodalを実行します.必要なパラメータは、modalのタイトルとinputがあるかどうか、およびtryがあるかどうかです.
  • そこで、この3つをパラメータの関数として、以降の重複コードで再使用しましょう.したがって、これらのモデルを作成するコードについては、utilsというディレクトリに配置し、後でコンポーネントレンダリングで役立つ関数を収集します.renderComponentMethods.jsというモジュールが作成されました.
    renderComponentMethods.js
    import Modal from '@/components/common/Modal';
    import names from '@/utils/classNames';
    import { ERROR_STATUS } from '@/utils/constants';
    
    export const renderModalByEvent = ({ head, isInput, tryFunc }) => {
      const { container } = names;
      const $app = document.querySelector('#app');
      const modal = new Modal({
        $target: document.querySelector('#app'),
        head,
        isInput,
        onConform: async content => {
          try {
            await tryFunc(isInput && content);
          } catch (e) {
            console.error(e);
            alert(ERROR_STATUS, e);
          } finally {
            if ($app.querySelector(`.${container}`)) {
              $app.removeChild(modal.$container);
            }
          }
        },
      });
      modal.render();
    };
    たくさん摘んだみたい!
    こんなに早く一つ一つのコードを省略しようとすると興奮しますか?!
    では、sidebarに移動して適用します.
    sidebar.js
    ここで私は彼に関数を作った.
    これは、いずれにしても、ボタンをクリックしたときに現れるtryFuncが、この素子の中でほとんど似ているからである.
    そこで,openCreatePageModalというアプリケーション関数を作成した.
    sidebar.js/openCreatePageModal
      const openCreatePageModal = async $elem => {
        renderModalByEvent({
          head: INPUT_TITLE_MESSAGE,
          isInput: true,
          tryFunc: async title => {
            const result = await createPost(this.state.username, {
              title,
              parent: $elem?.dataset.id ?? null,
            });
            push(`/posts/${result.id}`);
          },
        });
      };
    でも本当は不満もあるcheck以降->作成モードの過程でも、この過程は強い繰返し感があります.
    したがって、check->openCreatePageModelを実行する関数も作成されます.
    sidebar.js/renderModal
      const renderModal = async ({ eventTarget, isValid, closestSelectorName }) => {
        if (!isValid) return;
        const $elem = eventTarget.closest(`.${closestSelectorName}`);
        await openCreatePageModal($elem);
      };
    どのように簡単ですか.
    最後にどれだけ減らせるか見てみましょう.
      new Button({
        $target: $sideBarButtonBox,
        attributes: { classNames: [sideBarCreatePostBtn], text: '페이지 생성' },
        onClick: () => {
          const $app = document.querySelector('#app');
          const modal = new Modal({
            $target: $app,
            head: '생성할 페이지의 제목을 입력해주세요!',
            isInput: true,
            onConform: async title => {
              try {
                const result = await createPost(this.state.username, {
                  title,
                  parent: null,
                });
                push(`/posts/${result.id}`);
              } catch (e) {
                console.error(e);
                alert(ERROR_STATUS, e);
              }
            },
          });
          modal.render();
        },
      });
    こんなハーモニー!
      new Button({
        $target: $sideBarButtonBox,
        attributes: {
          classNames: [sideBarCreatePostBtn],
          text: BUTTON_COMPONENT_TEXT,
        },
        onClick: () => openCreatePageModal(),
      });
    本当にやり終えて、ずいぶん簡略化しましたね.
    改造後
    export default function SideBar({ $target, initialState, onClick }) {
      const {
        sideBarContainer,
        postsItem,
        postsBlock,
        sideBarItem,
        sideBarButtonBox,
        sideBarCreatePostBtn,
        postBlock,
        postToggleBtn,
        postNext,
        postLink,
        postNextNew,
        postCreateBtn,
        postRemoveBtn,
      } = names;
    
      const $sideBar = _createElemWithAttr('nav', [sideBarContainer]);
      const $posts = _createElemWithAttr('section', [sideBarItem, postsBlock]);
      this.state = initialState;
    
      const $sideBarButtonBox = _createElemWithAttr('div', [sideBarButtonBox]);
      new Button({
        $target: $sideBarButtonBox,
        attributes: {
          classNames: [sideBarCreatePostBtn],
          text: BUTTON_COMPONENT_TEXT,
        },
        onClick: () => openCreatePageModal(),
      });
      $sideBar.appendChild($sideBarButtonBox);
    
      this.setState = nextState => {
        if (!checkState(this.state, nextState)) {
          _removeAllChildNodes($posts);
          this.state = nextState;
          const { documents } = this.state;
          const $fragment = new DocumentFragment();
          _renderPosts($fragment, documents);
          $posts.appendChild($fragment);
          $sideBar.appendChild($posts);
        }
        this.render();
      };
    
      this.render = () => {
        if (!$target.querySelector(`${sideBarContainer}`)) {
          $target.appendChild($sideBar);
        }
      };
    
      $sideBar.addEventListener('click', e => {
        const { classList } = e.target;
        if (!classList.contains(postsItem) && !classList.contains(postLink)) return;
        const postId = e.target.closest(`.${postsItem}`).getAttribute(['data-id']);
        onClick(postId);
      });
    
      $posts.addEventListener('click', e => {
        const { target } = e;
        if (!target.classList.contains(postToggleBtn, 'post__link')) return;
        const closestPostId = target.closest(`.${postBlock}`).dataset.id;
        const $nextItem = $posts.querySelector(
          `.${postNext}[data-id="${closestPostId}"]`,
        );
        $nextItem.classList.toggle('invisible');
        target.classList.toggle('toggle');
      });
    
      $sideBar.addEventListener('click', e => {
        renderModal({
          eventTarget: e.target,
          isValid: e.target.closest(`.${postNextNew}`),
          closestSelectorName: postNext,
        });
      });
    
      $sideBar.addEventListener('click', e => {
        renderModal({
          eventTarget: e.target,
          isValid: e.target.classList.contains(postCreateBtn),
          closestSelectorName: postsItem,
        });
      });
    
      $sideBar.addEventListener('click', e => {
        if (!e.target.classList.contains(postRemoveBtn)) return;
        const closestPostsItem = e.target.closest(`.${postsItem}`);
        renderModalByEvent({
          head: MODAL_DELETE_QUESTION,
          isInput: false,
          tryFunc: async () => {
            await deletePost(this.state.username, closestPostsItem.dataset.id);
            const posts = await getPostList(this.state.username);
            this.setState({
              documents: posts,
            });
          },
        });
      });
    
      const renderModal = async ({ eventTarget, isValid, closestSelectorName }) => {
        if (!isValid) return;
        const $elem = eventTarget.closest(`.${closestSelectorName}`);
        await openCreatePageModal($elem);
      };
    
      const openCreatePageModal = async $elem => {
        renderModalByEvent({
          head: INPUT_TITLE_MESSAGE,
          isInput: true,
          tryFunc: async title => {
            const result = await createPost(this.state.username, {
              title,
              parent: $elem?.dataset.id ?? null,
            });
            push(`/posts/${result.id}`);
          },
        });
      };
    }
    結局こうして、
  • 再構成関数を除いて、コードは約20%短縮され、
  • モードの関数イベントを生成するコードがより直感的になりました!
  • 終了時👏
    カバーした瞬间に悩みがいっぱい
    でも終わってから.
  • 以降は突然何か変化がありますか?
  • 再製作が必要な場合は便利に再利用できるので心配なし!
  • 皆さん、きれいなコードと一緒にプロジェクトを頑張りましょう.🖐
    あまり良いコードではありませんが、この文章が誰かに参考になることを望んでいます.😆 以上
    💡 より簡潔な再構築スキームがある場合は、フィードバックを惜しまないでください:)