Spring Boot 와 SvelteJS를 이용한 Side Project 진행하기

11 minute read

옛날 성스럽고 현명한 군주들은 모두 가깝게는 자기 자신에게서 원인을 찾아 행동했습니다.
군주가 영명한 까닭은 널리 듣기 때문이고, 군주가 어리석은 까닭은 편협되게 어떤 한 부분만을 믿기 때문입니다.

Spring Boot와 SvelteJS를 이용한 Side Project 진행하기

기본 환경

  • 활용한 라이브러리
    • spring-boot-starter-web
    • spring-boot-starter-data-jpa
    • spring-boot-starter-oauth2-client
    • querydsl-jpa
    • lombok
    • H2DB
    • npm
    • sveltejs

JPA 를 이용해서 Entity를 설계하고, QueryDSL를 이용해서 Query 수준의 자바 코드를 작성하였음.

  • QuerydslRepositorySupport

@Repository
public class NetworkQueryRepositoryImpl extends QuerydslRepositorySupport implements NetworkQueryRepository {

    public NetworkQueryRepositoryImpl() {
        super(Network.class);
    }

    @Override
    public List<Network> searchListTreeNetworks(Long networkNo) {

        QNetwork network = QNetwork.network;
        JPQLQuery<Network> jpaQuery = from(network);
        jpaQuery.where(network.networkNo.eq(networkNo));

        List<Network> networkList = jpaQuery.fetch();

        return networkList;
    }
}

  • JPAQeuryFactory

public class SelectListIdea implements Operation<IdeaMasterRQ, IdeaMasterRS> {

    private final EntityManager entityManager;

    public SelectListIdea(EntityManager entityManager){
        this.entityManager = entityManager;
    }

    @Override
    public IdeaMasterRS operate(IdeaMasterRQ parameter) {
        return BasicOperator.operate(
            parameter,
            functionParam -> {
                    JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);

                    QIdeaEntity ideaEntity = QIdeaEntity.ideaEntity;
                    QIdeaOpinionEntity qIdeaOpinionEntity = QIdeaOpinionEntity.ideaOpinionEntity;

                    List<IdeaEntity> ideaEntities;

                    if(Optional.ofNullable(parameter.getIdeaType()).isPresent() && !parameter.getIdeaType().isEmpty()){
                        ideaEntities = jpaQueryFactory
                                .selectFrom(ideaEntity)
                                .where(ideaEntity.ideaType.eq(parameter.getIdeaType()))
                                .orderBy(ideaEntity.lastCrated.desc())
                                .fetch();
                    }else{
                        ideaEntities = jpaQueryFactory
                                .selectFrom(ideaEntity)
                                .orderBy(ideaEntity.lastCrated.desc())
                                .fetch();
                    }

                    List<IdeaMasterData> returnList = new ArrayList<>();

                    ideaEntities.forEach(item -> {
                        IdeaEntity itemIdeaEntity = item;
                        IdeaMasterData ideaMasterData = new IdeaMasterData();
                        SimpleDateFormat transFormat = new SimpleDateFormat("yyyy-MM-dd");
                        String regiDate = transFormat.format(itemIdeaEntity.getRegDate());

                        ideaMasterData.setTitle(itemIdeaEntity.getTitle());
                        ideaMasterData.setOutline(itemIdeaEntity.getOutline());
                        ideaMasterData.setIdeaType(itemIdeaEntity.getIdeaType());
                        ideaMasterData.setRegistId(itemIdeaEntity.getRegID());
                        ideaMasterData.setRegistName(itemIdeaEntity.getRegID());
                        ideaMasterData.setRegistDate(regiDate);
                        ideaMasterData.setEmail(itemIdeaEntity.getIdeaEmbeddableID().getEmail());
                        ideaMasterData.setIdeaNo(itemIdeaEntity.getIdeaEmbeddableID().getIdeaNo());

                        Long opinionCount = jpaQueryFactory.selectFrom(qIdeaOpinionEntity)
                                .where(qIdeaOpinionEntity.ideaEmbeddableID.ideaNo.eq(itemIdeaEntity.getIdeaEmbeddableID().getIdeaNo()),
                                        qIdeaOpinionEntity.ideaEmbeddableID.email.eq(itemIdeaEntity.getIdeaEmbeddableID().getEmail()) )
                                .fetchCount();

                        ideaMasterData.setOpinionCount(opinionCount);

                        returnList.add(ideaMasterData);
                    });

                    return returnList;
        } , new IdeaMasterRS());
    }
}


h2database를 이용해서 Entity 설계 후 테스트를 수행하였음.

H2DB는 자바 기반의 오픈소스 관계형 데이터 베이스 관리 시스템(RDBMS )입니다. H2DB는 서버(Server) 모드와 임베디드(Embedded) 모드의 인메모리 DB 기능을 지원합니다. 물론 디스크 기반 테이블을 또한 생성할 수 있습니다. 또한 브라우저 기반의 콘솔모드를 이용할 수 있으며, 별도의 설치과정이 없고 용량도 2MB(압축버전) 이하로 매우 저용량 입니다. DB자체가 매우 가볍기 때문에 매우 가볍고 빠르며, JDBC API 또한 지원하고 있습니다.

API 문서 자동화를 위한 Swagger-UI 3.0 적용

Spring Boot 2.4 이상의 yaml

spring boot 2.4 버전부터 애플리케이션 설정 파일( application.properties, application.yml, application.yaml ) 에 대한 구동방식이 변경됐다. 변경된 이유는 k8s 볼륨 마운트 구성때문이라고 하는데 이것에 대해선 논하지 않는다.

2.4 버전부터는 특정 profile document에는 spring.profiles.active, spring.profiles.include 를 사용할 수 없습니다.

특정 profile document란 yaml 파일 내부에 —로 구분된 각 profile 영역

spring.profiles.include는 spring.profiles.group.”profile” 속성으로 변경되었습니다.


# as-is
spring.application.name: "customers"
---
spring.profiles: "production"
spring.profiles.include: "mysql,rabbitmq"
---
spring:
  profiles: "mysql"
  datasource:
    url: "jdbc:mysql://localhost/test"
    username: "dbuser"
    password: "dbpass"
---
spring:
  profiles: "rabbitmq"
  rabbitmq:
    host: "localhost"
    port: 5672
    username: "admin"
    password: "secret"

# to-be
spring:
  application:
    name: "customers"
  profiles:
    group:
      "production": "mysql,rabbitmq"
---
spring:
  config:
    activate:
      on-profile: "mysql"
  datasource:
    url: "jdbc:mysql://localhost/test"
    username: "dbuser"
    password: "dbpass"
---
spring:
config:
  activate:
    on-profile: "rabbitmq"
  rabbitmq:
    host: "localhost"
    port: 5672
    username: "admin"
    password: "secret"

@WebMvcTest를 이용한 단위 테스트 수행

  • MVC를 위한 테스트, 컨트롤러가 예상대로 동작하는지 테스트하는데 사용된다. (To test whether Spring MVC controllers are working as expected, use the @WebMvcTest annotation.)

  • @WebMvcTest 어노테이션을 사용시 다음 내용만 스캔 하도록 제한한다. (보다 가벼운 테스팅이 가능하다.) @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, HandlerInterceptor, WebMvcConfigurer, HandlerMethodArgumentResolver

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing

jsonPath

Json Data로 부터 값을 가져올 때 사용할 수 있는 방식

https://www.baeldung.com/guide-to-jayway-jsonpath

gradle 설정


plugins {
    id 'org.springframework.boot' version '2.4.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
    id "com.github.node-gradle.node" version "3.0.1"
    id 'java'
}

group = 'com.lines.network'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'

    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

    implementation "io.springfox:springfox-boot-starter:3.0.0"

    // JWT
    implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
    runtime 'io.jsonwebtoken:jjwt-impl:0.11.2'
    runtime 'io.jsonwebtoken:jjwt-jackson:0.11.2'


    implementation 'com.querydsl:querydsl-jpa'

    compileOnly 'org.projectlombok:lombok'
    compile group: 'org.modelmapper', name: 'modelmapper', version: '2.4.1'

    developmentOnly 'org.springframework.boot:spring-boot-devtools'

    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'

    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor 'com.querydsl:querydsl-jpa'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

// querydsl
def querydslDir = "$buildDir/generated/querydsl"

querydsl {
    library = "com.querydsl:querydsl-apt:4.4.0"
    jpa = true
    querydslSourcesDir = querydslDir
}

sourceSets {
    main.java.srcDir querydslDir
    test.java.srcDir 'src/test'
}

configurations {
    querydsl.extendsFrom compileClasspath
}

compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}

// npm
node {
    version = '12.13.1'
    download = true
    nodeModulesDir = file("${projectDir}/src/main/resources/static")
}

task copyFrontLib(type: Copy) {
    from "${projectDir}/src/main/resources/static"
    into "${projectDir}/build/resources/main/static/."
}

copyFrontLib.dependsOn npmInstall
compileJava.dependsOn copyFrontLib

기본 패키지 구성

DTO 계층간의 데이터 교환을 위한 객체

  • DB에서 꺼낸 값을 임의로 변경할 필요가 없기 때문에 DTO 클래스에서는 setter가 없다.

Entity 실제 DB의 테이블과 매칭될 클래스

  • Entity 클래스와 DTO 클래스를 분리하는 이유

    • View Layer와 DB Layer의 역할을 철저하게 분리하기 위해서
    • 테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 미칠 수 있지만, 각 객체 간에 데이터를 전달하는 역할을 하는 dto 클래스는 자주 변경되므로 분리해야한다.

Controller

Presentation Layer와 Service(Business) Layer 를 연결하여 사용자의 Action에 반응한다. MVC 패턴에서의 Controller 이며, Model를 통해서 View와의 데이터를 주고 받는다.

Service

Business Logic 를 관리한다.

Repository

Entity에 의해 생성된 DB에 접근하는 메서드(ex) findAll()) 들을 사용하기 위한 인터페이스이다. 위에서 엔티티를 선언함으로써 데이터베이스 구조를 만들었다면, 여기에 어떤 값을 넣거나, 넣어진 값을 조회하는 등의 CRUD(Create, Read, Update, Delete)를 해야 쓸모가 있는데, 이것을 어떻게 할 것인지 정의해주는 계층이라고 생각하면 된다.

OAuth 2.0

OAuth 2.0 관련 링크

  • 실제 가져와서 사용한 코드

https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-5-security-oauth

  • 실 개발시 참고

https://jyami.tistory.com/121 http://yoonbumtae.com/?p=2652

  • 참고 URL

https://godekdls.github.io/Spring%20Security/oauth2/

https://github.com/spring-projects/spring-security/tree/5.3.2.RELEASE/samples/boot/oauth2login#github-login

https://m.blog.naver.com/varkiry05/221865197562
OAuth 2.0을 이용한 로그인 인증 방식

https://stackoverflow.com/questions/66373358/spring-boot-react-failed-to-authorize-filter-invocation

https://m.blog.naver.com/PostView.nhn?blogId=varkiry05&logNo=221313073382&proxyReferer=https:%2F%2Fwww.google.com%2F

  • 구글 SSO 연동 과정

구글 클라우드 콘설에서 API/Service -> Credential 열기
OAuth Create 하며 Client ID 및 Client Scret 발급
Redirect URL 에 대한 설정 - 로그인시 사용자에게 리다렉트할 UI 제공
Spring Boot 의 Spring Security 설정


implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-oauth2-client'
implementation 'org.springframework.security.oauth:spring-security-oauth2:2.4.1.RELEASE'
implementation 'org.springframework.security:spring-security-oauth2-jose:5.4.6’

application.yml 에 client / client scret 설정
서비스 기동 처리

Git의 활용

  • 전역 사용자 명 / 이메일 구성하기

git config - -global user.name {Name} git config - -global user.email {Email}

  • 저장소별 사용자명/이메일 구성하기

git config user.name {Name} git config user.email {Email}

  • 전역 설정 정보 조회

git config -global -list

  • 저장소별 설정 정보 조회

git config -list

  • Git의 출력결과 색상 활성화하기

git config -global color.ui “auto”

  • 새로운 저장소 초기화하기

kdir /path/newDir
cd /path/newDir
git init

  • 저장소 복제하기

git clone {저장소 url}

  • 새로운 원격 저장소 추가하기

git remote add {원격 저장소} {저장소 url}

  • 저장소에서 변경사항을 가져와 현재 브랜치에 합치기

git pull

  • 저장소로 소스 Push

git add {파일} / . git commit -m “{메시지}” git push origin

  • Fork된 자료에 대한 적용사항을 가져와서 반영할 경우

git remote add upstream {Git URL}

git fetch upstream

git merge upstream/develop

Git Flow


dreamui-MacBook-Pro:git-flow dream$ git init
Initialized empty Git repository in /Users/dream/GIT/sample/git-flow/.git/
dreamui-MacBook-Pro:git-flow dream$ ls
main.txt
dreamui-MacBook-Pro:git-flow dream$ git commit -am
error: switch 'm' requires a value
dreamui-MacBook-Pro:git-flow dream$ git commit -a
On branch master
Initial commit
Untracked files:
 	main.txt
nothing added to commit but untracked files present
dreamui-MacBook-Pro:git-flow dream$ git add main.txt
dreamui-MacBook-Pro:git-flow dream$ git commit -am "work 1"
[master (root-commit) 301ae52] work 1
 1 file changed, 1 insertion(+)
 create mode 100644 main.txt
dreamui-MacBook-Pro:git-flow dream$ git tag 0.1

master의 최신 버전은 언제나 실행 가능한 상태를 유지해야한다. 
실행 과정을 만들어가는 과정은 develop brach에서 실행한다. 

git checkout -b develop : develop branch 생성 

dreamui-MacBook-Pro:git-flow dream$ git checkout -b develop
Switched to a new branch 'develop'
dreamui-MacBook-Pro:git-flow dream$ git commit -am 
error: switch 'm' requires a value
dreamui-MacBook-Pro:git-flow dream$ git commit -am "work 2"
[develop bb4e3ac] work 2
 1 file changed, 2 insertions(+), 1 deletion(-)
dreamui-MacBook-Pro:git-flow dream$ git commit -am "work 3"
[develop 1a50a15] work 3
 1 file changed, 2 insertions(+), 1 deletion(-)
dreamui-MacBook-Pro:git-flow dream$ git checkout -b release/0.2
Switched to a new branch 'release/0.2'
dreamui-MacBook-Pro:git-flow dream$ git add release-02.txt 
dreamui-MacBook-Pro:git-flow dream$ git commit -am "release 0.2 1"
[release/0.2 142cf58] release 0.2 1
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 release-02.txt
dreamui-MacBook-Pro:git-flow dream$ git commit -am "release 0.2 2"
On branch release/0.2
nothing to commit, working tree clean
dreamui-MacBook-Pro:git-flow dream$ git commit -am "release 0.2 2"
[release/0.2 114698d] release 0.2 2
 1 file changed, 2 insertions(+)
dreamui-MacBook-Pro:git-flow dream$ git checkout develop
Switched to branch 'develop'
dreamui-MacBook-Pro:git-flow dream$ git merge release/0.2
Updating 1a50a15..114698d
Fast-forward
 release-02.txt | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 release-02.txt

dreamui-MacBook-Pro:git-flow dream$ git checkout release/0.2
Switched to branch 'release/0.2'
dreamui-MacBook-Pro:git-flow dream$ git checkout master
Switched to branch 'master'
dreamui-MacBook-Pro:git-flow dream$ git merge --no-ff release/0.2 
Merge made by the 'recursive' strategy.
 main.txt       | 4 +++-
 release-02.txt | 2 ++
 2 files changed, 5 insertions(+), 1 deletion(-)
 create mode 100644 release-02.txt
dreamui-MacBook-Pro:git-flow dream$


create mode 100644 release-02.txt
dreamui-MacBook-Pro:git-flow dream$ git checkout release/0.2
Switched to branch 'release/0.2'
dreamui-MacBook-Pro:git-flow dream$ git checkout master
Switched to branch 'master'
dreamui-MacBook-Pro:git-flow dream$ git merge --no-ff release/0.2 
Merge made by the 'recursive' strategy.
 main.txt       | 4 +++-
 release-02.txt | 2 ++
 2 files changed, 5 insertions(+), 1 deletion(-)
 create mode 100644 release-02.txt
dreamui-MacBook-Pro:git-flow dream$ git branch -d release/0.2
Deleted branch release/0.2 (was 114698d).
dreamui-MacBook-Pro:git-flow dream$ git tag 0.2
dreamui-MacBook-Pro:git-flow dream$ git checkout develop
Switched to branch 'develop'
dreamui-MacBook-Pro:git-flow dream$ git branch feature/short
dreamui-MacBook-Pro:git-flow dream$ git branch feature/long
dreamui-MacBook-Pro:git-flow dream$ git checkout feature/short
Switched to branch 'feature/short'
dreamui-MacBook-Pro:git-flow dream$ git commit -am "short 1"
On branch feature/short
Untracked files:
        short.txt

nothing added to commit but untracked files present
dreamui-MacBook-Pro:git-flow dream$ git add short.txt 
dreamui-MacBook-Pro:git-flow dream$ git commit -am "short 1"
[feature/short 616292e] short 1
 1 file changed, 1 insertion(+)
 create mode 100644 short.txt
dreamui-MacBook-Pro:git-flow dream$ git commit -am "short 2"
[feature/short 4119bc3] short 2
 1 file changed, 2 insertions(+), 1 deletion(-)
dreamui-MacBook-Pro:git-flow dream$ git checkout feature/long
Switched to branch 'feature/long'
dreamui-MacBook-Pro:git-flow dream$ git add long.txt 
dreamui-MacBook-Pro:git-flow dream$ git commit -am "long 1"
[feature/long 7d607db] long 1
 1 file changed, 1 insertion(+)
 create mode 100644 long.txt
dreamui-MacBook-Pro:git-flow dream$ git commit -am "long 2"
[feature/long 794db7f] long 2
 1 file changed, 2 insertions(+), 1 deletion(-)

[feature/long 794db7f] long 2
 1 file changed, 2 insertions(+), 1 deletion(-)
dreamui-MacBook-Pro:git-flow dream$ git checkout develop
Switched to branch 'develop'
dreamui-MacBook-Pro:git-flow dream$ git merge --no-ff feature/short
Merge made by the 'recursive' strategy.
 short.txt | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 short.txt
dreamui-MacBook-Pro:git-flow dream$ git branch -d feature/short
Deleted branch feature/short (was 4119bc3).
dreamui-MacBook-Pro:git-flow dream$ git checkout -b release/1.0
Switched to a new branch 'release/1.0'
dreamui-MacBook-Pro:git-flow dream$ git commit -am "short 3 - bug fix"
[release/1.0 a6b7411] short 3 - bug fix
 1 file changed, 2 insertions(+), 1 deletion(-)
dreamui-MacBook-Pro:git-flow dream$ git commit -am "work 4 - bug fix"
[release/1.0 6aa4592] work 4 - bug fix
 1 file changed, 1 insertion(+)
dreamui-MacBook-Pro:git-flow dream$

reamui-MacBook-Pro:git-flow dream$ git merge --no-ff release/1.0
Merge made by the 'recursive' strategy.
 main.txt  | 1 +
 short.txt | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)
dreamui-MacBook-Pro:git-flow dream$ git checkout master
Switched to branch 'master'
dreamui-MacBook-Pro:git-flow dream$ git merge --no-ff release/1.0
Merge made by the 'recursive' strategy.
 main.txt  | 1 +
 short.txt | 3 +++
 2 files changed, 4 insertions(+)
 create mode 100644 short.txt
dreamui-MacBook-Pro:git-flow dream$ git tag 1.0
dreamui-MacBook-Pro:git-flow dream$ git branch -d release/1.0
Deleted branch release/1.0 (was 6aa4592).
dreamui-MacBook-Pro:git-flow dream$

dreamui-MacBook-Pro:git-flow dream$ git checkout -b hotfixes/1.1
Switched to a new branch 'hotfixes/1.1'
dreamui-MacBook-Pro:git-flow dream$ git add hotfix-1_1.txt 
dreamui-MacBook-Pro:git-flow dream$ git commit -am "hotfix 1.1"
[hotfixes/1.1 b206551] hotfix 1.1
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 hotfix-1_1.txt
dreamui-MacBook-Pro:git-flow dream$ git checkout develop
Switched to branch 'develop'
dreamui-MacBook-Pro:git-flow dream$ git merge --no-ff hotfixes/1.1
hint: Waiting for your editor to close the file... error: There was a problem with the editor 'vi'.
Not committing merge; use 'git commit' to complete the merge.
dreamui-MacBook-Pro:git-flow dream$ git checkout master
A       hotfix-1_1.txt
Switched to branch 'master'
dreamui-MacBook-Pro:git-flow dream$ git merget --no-ff hotfixes/1.1
git: 'merget' is not a git command. See 'git --help'.

The most similar commands are
        merge
        mergetool
dreamui-MacBook-Pro:git-flow dream$ git merge --no-ff hotfixes/1.1
error: Your local changes to the following files would be overwritten by merge:
  hotfix-1_1.txt
dreamui-MacBook-Pro:git-flow dream$ git checkout hotfixes/1.1
M       hotfix-1_1.txt
Switched to branch 'hotfixes/1.1'
dreamui-MacBook-Pro:git-flow dream$ git commit -am "hotfix 1.1"
[hotfixes/1.1 b0bc196] hotfix 1.1
 1 file changed, 1 insertion(+)
dreamui-MacBook-Pro:git-flow dream$ git checkout
dreamui-MacBook-Pro:git-flow dream$ git checkout develop
Switched to branch 'develop'
dreamui-MacBook-Pro:git-flow dream$ git merge --no-ff hotfixes/1.1
Merge made by the 'recursive' strategy.
 hotfix-1_1.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 hotfix-1_1.txt
dreamui-MacBook-Pro:git-flow dream$ git checkout
dreamui-MacBook-Pro:git-flow dream$ git checkout master
Switched to branch 'master'
dreamui-MacBook-Pro:git-flow dream$ git merge --no-ff hotfixes/1.1
Merge made by the 'recursive' strategy.
 hotfix-1_1.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 hotfix-1_1.txt
dreamui-MacBook-Pro:git-flow dream$

  • Git Flow 방식

SvelteJS

  • Svelte Redux 적용

Redux를 이용한 기본설정


import { createStore } from 'redux';
import { bindTracked } from 'svelte3-redux';

const initialState = {
    email: '',
    token: '',
    personEmail: '',
    personName: '',
    sortMenu: ''
};

const persistConfig = {
    key: "root",
    storage,
  };

const rootReducer = (state = initialState, action) => {
    switch (action.type) {
        case 'setToken': { 
            console.log("setToken");
            return { ...state, token: action.token };
        }
        case 'setEmail': { 
            console.log("setEmail");
            return { ...state, email: action.email };
        }
        case 'setNetworkNo': {
            console.log("setNetworkNo");
            return { ...state, networkNo: action.networkNo };
        } 
        case 'setPersonEmail': {
            console.log("setPersonEmail");
            return { ...state, personEmail: action.personEmail };
        } 
        case 'setPersonName': {
            console.log("r");
            return { ...state, personName: action.personName };
        } 
        case 'setSortMenu': {
            console.log("setSortMenu");
            return { ...state, sortMenu: action.sortMenu };
        }
        default: return state;
    }
};


const store = createStore(rootReducer);
export default () => bindTracked(store);

Redux에거 값을 가져와서 사용하기


import getReduxState from '../comm/redux/store'

let reduxState = getReduxState();

let networkNo = $reduxState.networkNo;
let email = $reduxState.email;
let token = $reduxState.token;
let personEmail = $reduxState.personEmail;

  • Svelte SPA Router 적용

App 라우터 적용


<script>
    import Router from 'svelte-spa-router'
    import routes from './routes'
</script>

<Router {routes}/>

<style>

</style>

Router 링크 설정


import NT001_Login from './pages/NT001_Login.svelte'
import NT002_Settings from './pages/NT002_Settings.svelte'
import NT003_PasswordSetting from './pages/NT003_PasswordSetting.svelte'
import NT004_MainScreen from './pages/NT004_MainScreen.svelte'
import NT005_MainDetailScreen from './pages/NT005_MainDetailScreen.svelte'
import NT006_BusinessCard from './pages/NT006_BusinessCard.svelte'
import NT007_Network from './pages/NT007_Network.svelte'
import NT008_Preferences from './pages/NT008_Preferences.svelte'
import NT009_PromiseManagement from './pages/NT009_PromiseManagement.svelte'
import NT010_History from './pages/NT010_History.svelte'

const routes = {
    '/': NT001_Login,
    '/main': NT004_MainScreen,
    '/setting': NT002_Settings,
    '/password': NT003_PasswordSetting,
    '/main/detail': NT005_MainDetailScreen,
    '/businesscard': NT006_BusinessCard,
    '/network': NT007_Network,
    '/preferences': NT008_Preferences,
    '/promisemanagement': NT009_PromiseManagement,
    '/history': NT010_History,

}

export default routes


svelte-spa-router 의 push를 이용한 화면 이동


<button id="network" type="button"   on:click={(event) => onClickButton(event, "network")}  class="btn btn-secondary">
        <svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="currentColor" class="bi bi-alarm"
             viewBox="0 0 16 16">
        <path d="M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z"></path>
        <path d="M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z"></path>
    </svg>
</button>


import {push} from 'svelte-spa-router'

/**
* @description 버튼 클릭시 상세 화면 이동
* @param event
*/
const onClickButton = (event, screen) => {
    push(`/${screen}?email=${email}&networkNo=${networkNo}&personEmail=${personEmail}`)
}