싱글페이지 애플리케이션이란 하나의 페이지로 이루어진 애플리케이션이라는 의미이다.

 

이를 이해하기위해선 싱글페이지 애플리케이션이란 개념이 생기기전에 사용되던 

멀티 페이지 애플리케이션이 어떻게 작동하는지 살펴봐야한다.

 

멀티페이지 애플리케이션에서는 사용자가 다른페이지로 이동할때마다 새로운 html을 받아오고, 페이지를 로딩할때마다

서버에서 CSS,JS, 이미지 파일등의 리소스를 전달받아 브라우저의 화면에 보여주었다.

각페이지마다 다른 html 파일을 만들어서 제공을 하거나, 데이터에 따라 유동적인 html을 생성해주는 템플릿 엔진을 사용하기도했다.

 

 

페이지마다 그에맞는 html 로딩

 

 

 

 

 

사용자 인터랙션이 별로 없는 정적인 페이지들은 기존의 방식이 적합하지만,

사용자 인터랙션이 많고 다양한 정보를 제공하는 모던 웹 애플리케이션은 이 방식이 적합하지 않는다. 

새로운 페이지를 보여줘야 할 때마다 서버측에서 모든 준비를 한다면 그만큼 서버의 자원을 사용하는것이고, 트래픽도 더 많이 나오게된다.

 

그래서 리액트 같은 라이브러리를 사용해서 뷰 렌더링을 사용자의 브라우저가 담당하도록 하고, 우선 웹 애플리케이션을 브라우저에 불러와

실행시킨 다음 사용자와의 인터랙션이 발생하면 필요한부분만 자바스크립트를 사용하여 업데이트하는 방식을 사용하게 되었다.

만약 새로운 데이터가 필요하다면 서버 API를 호출하여 필요한 데이터만 새로 불러와 애플리케이션에서 사용 할 수있게 되었다.

 

이렇게 html은 한번만 받아와서 웹 애플리케이션을 실행시킨 후,  이후에는 필요한 데이터만 받아와서 화면에 업데이트하는 것이 싱글 페이지 애플리케이션이다.

 

 

html한번로딩후 필요할때마다 javascript로 upDate

 

 

 

리액트 라우터같은 라우팅 시스템은 사용자의 브라우저 주소창의 경로에 따라 알맞은 페이지를 보여주는데, 이후 링크를 눌러서 다른 페이지로 이동할 때 서버에 다른 페이지의 html을 새로 요청하는 것이 아니라, 브라우저의 History API를 사용하여 브라우저의 주소창의 값만 변경하고 기존에 페이지에 띄었던 웹 애플리케이션을 그대로 유지하면서 라우팅 설정에 따라 또 다른 페이지를 보여주게 된다.

 

 

그래서 Single page application 인 spa라 용칭을 부르게된다.

 

브릿지란?

브릿지란 안드로이드와 웹뷰의 통신을 위해 만들어지는 javascript 용 인터페이스다. 

웹뷰에서는 안드로이드의 메서드를 직접 호출하는 것이 불가능하기 때문에 브릿지라는 통로를 통해 호출해야한다.

브릿지는 웹뷰에 붙는 인터페이스의 구현체이다.

 

 

만약에 데이터를  update 해준다고한다면?

graphql에서 update 되는 값이 ndefined 라면 동작을하지않는다.

그러면 비밀번호가 바뀐다면?? 

바뀐비밀번호를 다시  암호화해서 넣어주는 작업이필요하다

 

 

개인정보수정을 크게 두가지로 나뉜다.

 

1. 수정할 User가 있을때 update

 - 비밀번호를 수정할때 ( 입력한비밀번호 -> 암호화 ) update

 - 나머지 필드 update

 - 성공시 return true

2. 수정할 User가 없을때 not update

 - 실패  return false ,error 

 

 

2. 토큰값 설정

 

로그인후 토큰값이 설정된후

사용자가 프로필수정, 등록등  어떤 동작을취할때

토큰을 확인해 이 사용자구나 확인을한후 그 동작을 처리해준다

 

 

1. 로그인된경우

로그인 -> 개인 토큰값 생성 -> client가 프로필수정버튼클릭 -> 토큰확인후 그프로필의 데이터를 수정

2. 로그인안된경우

에러발생

 

 

 

 

 

 

1. 로그인

 

로그인은 크게 3가지로 나뉘어진다.

1. id 가 존재하는가

2. 비밀번호가 db에 저장되어있는 비밀번호랑 같은것인가

3. 토큰을 생성한다.

 

 

위의 코드로 설정해주고 테스트를 해주었다.

여기서 알아야할점은  토큰이다.

 

토큰이란?

토큰은 쉽게보면 내가 누구인지 알수있게하는 고유번호라 생각하면된다.

다른  요청이있을때 이토큰을이용해서 나의 고유번호를 확인할수 있는과정이라 생각하면된다.

 

 

토큰을 발생하기위해서는

jsonwebtoken 설치를 해주면된다.

npm install jsonwebtoken

 

 

발생된 토큰에 대한 정보를 다시보려면

https://jwt.io/

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

에서 내만의 토큰 설정을해주면된다.

 

 

 

 

2. Divide and conquer

프로파일 수정중  Divide and conquer의 작업을 해주었다.

목적에맞게 서로 파일을 나누는 분할정복법이다.

 

많은  typedef들이 한파일에 모여있으니 관리의 어려움을 겪었다.

그래서 폴더를 나눠서 editProfile 이라는 폴더안에  typedef 와 Mutation을 다시 설정해주었다.

 

 

1. prisma 생성하기

 

npx prisma init 명령어로 prisma 를 생성한다

 

그후 env파일은 전에했던건 처럼 database와 user이름을 잘 맞춰준다.

 

 

 

1. model 을 만든다

 

Schema.prisma 파일

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id Int @id @default(autoincrement())
  firstName String
  lastName String?
  userName String @unique
  email String @unique
  password String
}

 

 

2.   users 폴더,  mutation, queries, typeDefs 파일들을 만든다.

 

 

 

 

3. typeDefs의 타입과 위에서 설정해준 model과 일치시킨다.

 

 

 

4. 한번더 migrate시킨다. 

 

* prisma schema 를 update 할경우에는

항상 migrate 를 시켜줘야한다.

 

 

 

4.  계정생성과, 프로필보기위한  mutation, query 설정

 

import { gql } from "apollo-server";

export default gql`
  type User {
    id: String!
    firstName: String!
    lastName: String
    username: String!
    email: String!
    createAt: String!
    updatedAt: String!
  }

  type Mutation {
    createAccount(
      firstName: String!
      lastName: String
      username: String!
      email: String!
      password: String!
    ): User
  }

  type Query {
    seeProfile(username: String): User
  }
`;

 

 

2.. Mutation 설정하기

 

import client from "../client";

export default {
  Mutation: {
    createAccount: async (
      _,
      { firstName, lastName, userName, email, password }
    ) => {
      // check if username or email are already on DB.
      const existingUser = await client.user.findFirst({
        where: {
          OR: [
            {
              userName,
            },
            {
              email,
            },
          ],
        },
      });
      console.log(existingUser); //체크용
      // hash password
      // save and return the user
    },
  },
};

 

여기서주의해야할점은 

첫번째 생성할 user가 userName과 email이 반드시 중복되면안되고

두번째 await 와 async 를 같이사용해서 비동기적으로 처리해 ( 실패에대한 처리를해야햔다.)

 

또한 prisma의 filter 기능도 알아보면 좋을거같다.

 

 

 

3. 비밀번호 HASH 처리하기

 

비밀번호를 그대로 저장하는건 있을수없는일이다.

그렇기때문에  hasing 처리를 해줘야한다.

 

사용자가로그인한 비밀번호 => return hashing처리한 패스워드 == DB에있는 비밀번호 처리를해줘야한다.

 

npm i bcrypt 

 

 

우선  설치해준다.

 

 

https://www.npmjs.com/package/bcrypt

 

bcrypt

A bcrypt library for NodeJS.. Latest version: 5.0.1, last published: a year ago. Start using bcrypt in your project by running `npm i bcrypt`. There are 3429 other projects in the npm registry using bcrypt.

www.npmjs.com

이문서를 참고해서 구현해주었다

 

 

import bycrpt from "bcrypt";
import client from "../client";

export default {
  Mutation: {
    createAccount: async (
      _,
      { firstName, lastName, userName, email, password }
    ) => {
      // 1. check if username or email are already on DB.
      const existingUser = await client.user.findFirst({
        where: {
          OR: [
            {
              userName,
            },
            {
              email,
            },
          ],
        },
      });
      console.log(existingUser); //체크용
      
      
      // 2. hash password   									-->추가
      const uglyPassword = await bycrpt.hash(password, 10);
      console.log(uglyPassword);

      // save and return the user								-->추가 end
    },
  },
};

 

 

 

 

4. 사용자 계정 생성하기

 

import bycrpt from "bcrypt";
import client from "../client";

export default {
  Mutation: {
    createAccount: async (
      _,
      { firstName, lastName, userName, email, password }
    ) => {
      // 1. check if username or email are already on DB.
      const existingUser = await client.user.findFirst({
        where: {
          OR: [
            {
              userName,
            },
            {
              email,
            },
          ],
        },
      });
      console.log(existingUser); //체크용
      // 2. hash password
      const uglyPassword = await bycrpt.hash(password, 10);
      console.log(uglyPassword);
      return client.user.create({							--> 추가
        data: {
          userName,
          email,
          firstName,
          lastName,
          password: uglyPassword,
        },
      });													--> end 추가

      // save and return the user
    },
  },
};

 

이렇게 return 문으로  data라는 오브젝트에 담아서 return을 하면

 

 

 

mutation {
  createAccount(
    firstName:"yun"
    lastName:"yeji"
    email:"yun@las.com"
    password:"123"
    userName:"yunrap"
  ){
    userName
  }
}

 서버실행후  graphql 명령어를 입력해주면 계정이 들어감을 확인할수있다.

 

 

확인하기쉬운방법은 prisma studio 에서 하면 쉽게할수있다.

 

 

 

 

5. try catch 문 작성하기

 

 

async 사용에서는 try catch 문을 사용하는것이좋다

 

변경하기

import bycrpt from "bcrypt";
import client from "../client";

export default {
  Mutation: {
    createAccount: async (
      _,
      { firstName, lastName, userName, email, password }
    ) => {
      try {
        // 1. check if username or email are already on DB.
        const existingUser = await client.user.findFirst({
          where: {
            OR: [
              {
                userName,
              },
              {
                email,
              },
            ],
          },
        });
        console.log(existingUser); //체크용

        if (existingUser) {
          //유저가 존재할시 생성 x
          throw new Error("This username/password is already taken.");
        }

        // 2. hash password
        const uglyPassword = await bycrpt.hash(password, 10);
        console.log(uglyPassword);
        return client.user.create({
          data: {
            userName,
            email,
            firstName,
            lastName,
            password: uglyPassword,
          },
        });

        // save and return the user
      } catch (e) {
        return e;
      }
    },
  },
};

 

 

 

 

6. 프로필 보기

 

users.quries파일

import client from "../client";
export default {
  Query: {
    seeProfile: (_, { userName }) =>
      client.user.findUnique({
        where: {
          userName,
        },
      }),
  },
};

users  quries 파일에 QUERY 를 추가해준다.

 

 

type Query {
seeProfile(userName: String): User
}
 
 

그후 서버실행하면

 

 

이렇게 프로필보기를 할 수 있다.

1. 리덕스로 폼 상태 관리하기

auth  모듈을 수정해준다.

 

/*
 * <pre>
 * @title auth.js
 * @desc auth 모듈생성 (redux toolkit적용 ), counterSlice api사용
 * </pre>
 *
 * @author yunrap
 * @since 2022.07.17 17:27:11
 * @version 0.1.0
 * @see =================== 변경 내역 ==================
 *   날짜       변경자     내용
 *  2022.07.17.  yunrap  최초생성
 */

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  register: {
    username: "",
    password: "",
    passwordConfirm: "",
  },
  login: {
    username: "",
    password: "",
  },
};

export const authSlice = createSlice({
  name: "auth",
  initialState,
  reducers: {
    sample_action: (state) => {
      state.action = "auth/SAMPLE_ACTION";
    },
    initialize_form: (state, { payload: form }) => ({
      ...state,
      [form]: initialState(form),
    }),
  },
});

export const { sample_action, initialize_form } = authSlice.actions;

export default authSlice.reducer;

처음에 useEeffect로  처음렌더링후 initailiaze_form 액션생성 함수를 호출해준다.

initailiaze_form을 통해초기값을 우선 설정해준다.

 

 

 

 

2.  LoginForm 화면 구현

그후 useDispatch 와 useSelector 함수를 사용하여 컴포넌트와 리덕스를 연동시킨다.

로그인하는 함수를 구현한다.

 

/*
 * <pre>
 * @title LoginForm.js
 * @desc auth 컨테이너 생성
 * </pre>
 *
 * @author yunrap
 * @since 2022.07.25 22:39:34
 * @version 0.1.0
 * @see =================== 변경 내역 ==================
 *   날짜       변경자     내용
 *  2022.07.25.  yunrap  최초작성
 */

import React, { useEffect } from "react";
import AuthForm from "../../components/auth/AuthForm";
import { change_field, initialize_form } from "../../modules/auth";
import { useDispatch } from "react-redux";

const LoginForm = () => {
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(initialize_form);
  }, [dispatch]);

  //인풋 변경 이벤트 핸들러
  const onChange = (e) => {
    const { value, name } = e.target;
    console.log(value);
    dispatch(
      change_field({
        form: "login",
        key: name,
        value,
      })
    );
  };

  return <AuthForm type="login" onChange={onChange}></AuthForm>;
};

export default LoginForm;

onChange 이벤트핸들러를 달아준다면 

input 의값을 넣었을때 dispatch 함수를 통해서 해당값들을 리덕스에 전역으로 값을 보관할수있다.

+ Recent posts