React + Redux + Router 로그인 기능 구현하기

로그인 api가 존재할 때, 리액트와 리덕스를 사용하여 로그인 기능 구현하기

    Django를 사용해서 API를 구현해 놓은 상태
    /rest-api/login/ 경로로 username / password를 전달하면 JWT 를 응답으로 전달. 

리액트를 통해서 로그인 폼 생성하기 ( react + react_router )
프로젝트 관리를 보다 보기 쉽게 하기 위해서 컴포넌트를 작게 소분해서 관리한다.
리액트 앱에 필요한 모든 소스코드는 src 디렉터리 경로에 존재한다.
컴포넌트들은 별도의 디렉터리에 따로 모아두고, 그 안에서 기능 별로 컴포넌트들을 다시 소분한다.
최종 렌더링 하는 파일은 src 디렉터리 내의 index.js 파일

# src/index.js

모든 컴포넌트들은 container - presenter 패턴을 작성
container : state 변경, 리덕스 액션 호출, 액션 디스패치 등 기능과 관련된 파일
presenter : props(전달 받았다면)를 사용한 출력에 관한 파일
index : redux store와 컴포넌트를 연결

컴포넌트 디렉터리에서 App 컴포넌트를 가져온다.
즉 대표적 경로에 따라서 라우팅을 하는 컴포넌트는 App 컴포넌트.

# src/components/App

# src/components/App/index.js
- 리덕스 스토어에 저장된 state에서 유저로그인 상태에 관한 정보를 가져와서 
    컴포넌트에 전달.

# src/components/App/container.js
- index로 부터 전달받은 props를 presenter에게 넘겨줌

# src/components/App/presenter.js
- 전달받은 props(유저로그인 상태정보:isLoggedIn)을 가지고 
    로그인 하지 않은 유저라면 public 화면을, 로그인 했다면 private 화면을 출력해준다.
    이 화면들은 또 다른 컴포넌트에서 관리되므로 각각 알맞은 컴포넌트를 호출.
    여기서는 로그인 / 회원가입 페이지에 관련된 Auth 컴포넌트를 호출함.

# src/components/Auth

# Auth/index.js
Auth 컴포넌트에서는 리덕스 스토어를 사용하지 않는다.
컴포넌트 자체 state를 가지고 로그인인지 회원가입인지를 결정한다

# Auth/container.js
자체 state 'authType'을 가지고 로그인 / 회원가입을 구분한다.
그리고 함수를 하나 만들어서 presenter에게 props로 넘겨주는데,
이 함수를 통해서 로그인 화면과 회원가입 화면을 번갈아가면서 보여줄 수 있게 한다.

# Auth/presenter.js
전달받은 propType을 정의해주고, authType props가 로그인이라면 LoginForm 컴포넌트를 호출한다.
* 이렇게 컴포넌트를 소분해서 작성하면 에러를 확인하기도 더 쉽고 이해가 더 잘 된다.
그리고 계정이 있으면 회원가입 페이지로 넘어가는 부분을 만들고, 그 부분에서 전달받은 함수 props
changeType을 실행시키면, container에서 state 상태를 반대로 바꾸면서 
로그인 <-> 회원가입 페이지가 전환이 되게 된다.


# src/components/LoginForm
* styles.scss 파일이 없는것은 로그인 / 회원가입 폼 스타일은 같기때문에 따로 만들어서 공유하도록 한다

# LoginForm/index.js
이 부분에서도 리덕스를 사용하게되는데, App 컴포넌트의 index에서 사용했던 mapStateToProps가 아닌
mapDispatchToProps 를 사용한다. 이는 리덕스 모듈에 정의된 액션을 호출하는 것을 의미한다.
리덕스는 액션이 호출되면 그에 맞춰서 리듀서가 실행되고, 리듀서에 의해서 state를 변환시키는 일련의 과정인데
그 첫 동작인 액션을 디스패치 하는 것을 컴포넌트와 연결하는 것이 mapDispatchToProps 이다.

여기서는 users 리덕스 모듈에서 usernameLogin에 username, password 인자를 붙여서 
디스패치해서 로그인 하는 부분을 컴포넌트에 전달하고 있다.

그럼 리덕스 모듈 파일에서 어떤식으로 액션이 정의되고 호출되어 어떤 동작을 하는지 살펴보자
# redux/modules/users.js
리덕스 모듈에 정의된 api action function 이다.
username, password 인자를 받아서 액션이 호출되면 fetch를 통해서 api서버로 
원하는 요청을 보낸다. 요청에는 인자로 받은 username과 password를 붙여서 보낸다.
* api서버는 장고 restframework를 통해 미리 만들어 둠
성공적으로 응답을 받았다면 token인자로 JWT를 돌려받는다.
이 돌려받은 JWT를 saveToken액션의 인자로 넣어 액션을 호출한다.
다음과 같이 saveToken액션이 호출되면 액션 타입과 인자로 받은 JWT를 리턴한다.
액션이 디스패치되면 리듀서가 실행된다
만약 액션 타입이 SAVE_TOKEN이라면 applySetToken 함수를 실행시켜라
액션으로 부터 전달받은 JWT를 localStorage에 JWT라는 이름으로 저장해두고,
리덕스 스토어의 state의 isLoggedIn 을 true로 변경하고 token 인자를 추가 저장한다.

이렇게 리덕스를 통해서 localStorage에 JWT가 저장되어서 클라이언트가 사용할 수 있게 되었고,
리덕스 스토어 state의 isLoggedIn이 true로 변경되면서 여러 컴포넌트에서 출력하는 게 변경될 것이다.

다시 LoginForm 컴포넌트로 돌아가보면
index를 통해서 리덕스의 액션들을 해당 컴포넌트와 연결시켜주었다

# Auth/container.js
여기서는 자체 state와 전달받은 props를 같이 사용한다.
자체 state에는 로그인폼에서 입력받을 username과 password를 저장한다
그리고 자체 함수들 _handleInputChange / _handleSubmit 은 
각각 input 태그에서 입력받는 값들을 그때그때 state에 업데이트해서 저장하는 기능과,
폼을 제출할 때 디폴트로 제공되는 기능을 수행 하는게 아닌, 내가 원하는 기능을 수행하도록 한다.
* event.preventDefault() 를 수행하면 디폴트로 수행되는 기능을 막는다. 이 후에 내가 원하는 기능을 적어주도록 한다
이렇게 생성한 기능들과 state를 presenter 로 전달.

# LoginForm/presenter.js
이 컴포넌트는 말 그대로 LoginForm만 담당하기 때문에 코드가 심플하다.
전달받은 props들의 타입들을 정의해주고, 각 input 태그에 값이 변경될 때마다 state를 변경하고,
값을 state의 username, password로 해준다.
그리고 폼을 제출할 때 앞서 만들어 뒀던 handleSubmit 함수를 실행시켜서
리덕스의 api 액션을 디스패치 하도록 만들어준다.

앱을 실행시켜서 브라우저에 출력되는 모습을 확인해 보면
다음과 같이 로그인 폼이 출력되고, 알맞은 정보를 입력하면
다음과 같이 리덕스 액션이 실행되고, 인증토큰을 얻어오는 것을 확인 할 수 있다.


회원가입 기능도 동일하게 구현할 수 있다 # github

댓글

댓글 쓰기