javascript 로 SPA 구현하기

피아노위고양이·2023년 2월 28일
0

사진: UnsplashJames Harrison

폴더구조


src
└───api
	└─── api.js ++
└───route          
│	└─── route.js ++
└───storage          
│	└─── storage.js ++
└───page          
	└───page1
    │	│   SubPage.js ++ 
│	└─── Page1.js ++
│   App.js ++          
│   index.js ++		
index.html ++
style.css

index.html

<html>
  <head>
    <title>타이틀</title>
    <link rel="stylesheet" href="./style.css">
  </head>
  <body>
    <main class="App">
    </main>
    <script src="./index.js" type="module"></script>
  </body>
</html>

src/route/route.js

const ROUTE_CHANGE_EVENT = 'ROUTE_CHANGE'

export const init = (onRouteChange) => {
    window.addEventListener(ROUTE_CHANGE_EVENT, () => {
        onRouteChange()
    })
}

export const routeChange = (url, params) => {
    history.pushState(null, null, url)
    window.dispatchEvent(new CustomEvent(ROUTE_CHANGE_EVENT, params))
}

src/api/api.js

export const API_END_POINT = 'http://localhost:3000'

const request = async (url) => {
    const res = await fetch(url)

    if(res.ok) {
        const json = await res.json()
        return json
    }
    throw new Error('요청 실패')
}

export const fetchPage1 = async () => {
    return await request(`${API_END_POINT}/page1`)
}

src/storage/storage.js

export const storage = localStorage

export const getItem = (key, defaultValue) => {
    try {
        const value = storage.getItem(key)
        return value ? JSON.parse(value) : defaultValue
    } catch (error) {
        console.log(error)
        return defaultValue
    }
}

export const setItem = (key, value) => {
    try {
        storage.setItem(key, JSON.stringify(value))
    } catch (error) {
        console.log(error)
    }
}

export const removeItem = (key) => {
    try {
        storage.removeItem(key);
    } catch (error) {
        console.log(error)
    }
}

src/index.js

import App from './App.js'

new App({$target: document.querySelector('.App')})

src/App.js

import { init } from './route/route.js'
import Page1 from './page/Page1.js'

export default function App ({$target}){
  	// data
	this.state = {
        prop1: null,
    }

    this.setState = (newState) => {
        this.state = {
            ...this.state,
            ...newState
        }
    }
    // route
    this.route = () => {
        const { pathname } = location 

        $target.innerHTML = ``

        if(pathname === '/web/'){
            const Page1 = new Page1({$target, initialState: {
                prop1: this.state.prop1
            }}).render()
        }
      
    // 뒤로가기, 앞으로가기 발생 시 popstate 이벤트가 발생합니다.
    window.addEventListener('popstate', this.route)
    
    init(this.route)

    this.route()
}

src/page/Page1.js

import { fetchPage1 } from "../api/api.js"
import { routeChange } from "../route/route.js"

import SubPage1 from './page1/SubPage1.js'
export default function Page1 ({$target, initialState}) {

    // component
    let subPage1 = null;

    // template

    this.$element = document.createElement('div')
    this.$element.className = 'page1'

    $target.appendChild(this.$element)

    this.render = () => {
      this.$element.innerHTML = ``
      this.$element.innerHTML = `
        <li data-product-id="1">
        <span> text </span>
        </li>
        <div class="subPage1"></div>
		`    
      subPage1 = new SubPage1({$target: this.$element.querySelector('.subPage1') , initialState: { text: this.state.text} })
    }
    // data
    this.state = {
        text: null,
        ...initialState,
    }

    this.setState = (newState) => {
        this.state = {
            ...this.state,
            ...newState
        }

        this.render()
    }

    // eventListener
    this.$element.addEventListener('click', (e) => {
         const $li = e.target.closest('li')
         const { productId } = $li.dataset
    
         if (productId) {
           routeChange(`/web`)
         }
        this.setState({text: 'subText'})
      })
  	// 참고 할 다른 이벤트 들
   	// this.$element.addEventListener('submit', (e) => {
    //     e.preventDefault()
    // })
    // this.$element.addEventListener('keyup', (e => {
    //     if(['Enter','ArrowUp', 'ArrowDown','ArrowLeft','ArrowRight'].includes(e.key)) return;
    //     onchange(e.target.value)
    // }))

    // method
    this.getPage1 = async () => {
        await fetchPage1()
    }

    // mounted
    this.getPage1();

    this.render()
}

src/page/page1/SubPage.js

import { routeChange } from "../../route/route.js"
export default function SubPage1 ({$target, initialState}) {
    // template
    this.$element = document.createElement('ul')

    $target.appendChild(this.$element)

    this.render = () => {
        if(this.state && this.state.text){
            this.$element.innerHTML = `
        <li>
            <span> ${this.state.text} </span>
        </li>
        `    
        }
        
    }
    // data
    this.state = {
        ...initialState
    }

    this.setState = (newState) => {
        this.state = {
            ...this.state,
            ...newState
        }

        this.render()
    }

    // eventListener

    this.render()
}

0개의 댓글