REACT_심화_review_1편

2021-07-03

컴포넌트를 쪼개는 방법

컴포넌트를 쪼개는 방법은 크게 두 분류로 나뉜다.
1) view만 생각하는 법.
2) 데이터를 기준으로 생각하는 법.

1번 view만 생각하는 것 같은 경우는 기획서를 보면서(와이어프레임 등) 비슷하게 생긴 구조끼리 묶는다(동일 컴포넌트로 뺀다). 내용 게시글이나, 헤더 부분은 비슷하거나 같은 부분들로 겹치는게 많다. 이런 경우들끼리 컴포넌트 단위로 묶어서 디자인하는 것 이다. 즉, 생김새를 보면서 쪼개는 것이 view를 보면서 나눠보는 것이다.

2번 데이터를 기준으로 생각하는 법은, 예를 들어 sns 기능처럼 피드가 있다고 가정하면, 각각의 게시글 목록 데이터를 누가 가지고 있을 건지 개발에 들어가기전에 미리 생각해보는 것 이다. 각각의 목록들을 firestore 라던가 특정 store에서 하나하나를 자체적인 형태로 가지고 있을 건지, 아니면 store에서 어떤 목록 배열 전체를 가지고 있다가, '포스트' 컴포넌트 하나한테 props로 목록 데이터 전체를 넘겨줄건지 데이터 기점으로도 한번은 반드시 고민해본다는 방법이다.

프로젝트 규모가 작을 수록 2번 같은 경우는 큰 걸림돌이 되지는 않지만, 규모가 커지면 커질 수록 이 데이터 리스트를 어떻게 가지고 있을건지, 어떤식으로 넘겨서 뿌려줄건지, 데이터 테이블이 내가 쓴 게시글, 남이 쓴 게시글 사람2가 쓴 게시글 등등 계속 불어난다면 어떤 형식으로 sns 피드에 한번에 정렬해서 쭈욱 뿌려줄 건지 등등.. 구조가 복잡해질수록 데이터 관점으로 생각하는 방법은 더더욱 중요해질 수 밖에 없다.

튜터님 같은 경우는 기획서(와이어프레임)를 a4용지에 쭈욱 뽑아서, 1번과 2번 방법으로 다양하게 고민해보고 상세 개발 기획을 쭉 정리해서 적어본다. 이렇게 하면, 내가 컴포넌트를 본격적으로 만들기 시작할 때에 굉장히 편하게 만들 수 있다. 때문에, 기획서를 보고 반드시 실 개발에 돌입하기전에 한번 쭈욱 정리하면서 컴포넌트를 쪼개보는 연습을 하자.

컴포넌트를 쪼개는 방법은 레고 블록을 쌓는 것과 비슷하다. 각각의 컴포넌트 블록이 너무 커도 끼워맞추기 힘들고, 너무 작아도 힘들며, 별모양 즉, 오류가 나지 않기 위해 필수적으로 받아야되는 값들(props)이 너무 많아도 안된다. -> 별모양 컴포넌트들은 그 필수적으로 받아야하는 props들에 종속이 되기 때문에 재활용 하기가 많이 힘들어서 좋지 않은것인데, 이건 별모양 블록들이 10만개가 있어도 하나의 성을 완성하기가 매우 어려운 것과 같은 맥락이다.

메인 페이지(포스트 목록) 만들기

폴더 구조 잡기

ex)

  • 폴더 구조 설명
    • build: 빌드 후 결과물이 여기에 생성됩니다.
    • node_modules: 우리가 설치한 패키지들이 들어있습니다.
    • public: 가상 돔이 들어갈 빈 껍데기 html(index.html)이 들어있는 폴더입니다.
    • src: 실제 소스 코드가 들어갈 폴더입니다.
      • /elements: button, input등 최소단위 컴포넌트가 들어갑니다.(공통 스타일도 여기서 맞출거예요.)
      • /components: elements의 컴포넌트를 조합해서 우리가 쪼갠 컴포넌트를 만들어 넣을거예요.
      • /pages: 페이지 시작점입니다. components에 있는 컴포넌트를 조합해서 페이지를 꾸릴거예요.
      • /redux: 리덕스 모듈과 스토어가 들어갑니다.
      • /shared: 공용으로 사용할 코드가 들어갑니다.
      • App.js, Request.js, Time.js, Cookie.js, firebase.js 등 시작점으로 쓸 파일과 전역으로 쓸 객체는 여기에 만들어줄거예요.

포스트 목록 만들기

제일 커다란 컴포넌트부터 시작. (페이지 단위의 컴포넌트들..)

순서 정리.

PostList 컴포넌트 생성 -> app.js에서 react-route-dom 설치하고, 라우터 돔 잡아주기 (라우팅, 즉 페이지 이동하는거 잡아주는 작업) -> 페이지를 세분화 시킨다 (헤더, 게시글 파트로 나눔) -> 해당 페이지들을 불러다가 사용할 수 있게 했는데, 그냥 불러다 쓴게 아니라 Post에다가는 PostList에서 데이터를 넘겨주어야 하고, 데이터 없을 때 오류가 난다거나 하는 부분을 방지하기 위해 defaultProps를 선언해서 예외처리를 해준다. -> post 컴포넌트에서는 들어가야 하는 것들을 보기 쉽게 미리 정리해둔다. 이후에 이 정리되어있는 것들을 바탕으로 최소 단위 컴포넌트들만 만들어주면 완료! (여기서 최소 단위 컴포넌트들을 만들때 스타일도 함께 잡아준다).

props로 받아올 때 주의할 점:
a라는 컴포넌트에서 props로 데이터를 받아온다고 가정해보자. 만약, 부모에서 a에게 props로 정보를 받아올 때, 데이터를 안 준다면? -> 에러가 나거나 화면이 다 깨질 수도 있다. 이런 것을 방지해주기 위해 a.defaultProps를 선언해준다.

이건 기본적으로 필요한 props들을 미리 넣어두는 작업인데, 이렇게 하면 만약 부모에서 몇몇 필수적인 props들을 (데이터들을) 넘겨주지 않아도, 에러가 나는 것을 사전에 방지할 수 있다. (다만 props 데이터 값을 잘 못 가져왔을 때의 경우까지 방어하기는 어렵다)

2021-07-04

최소단위 컴포넌트 만들기

최소단위 컴포넌트 만들기 순서도

box-sizing의 의미?

스타일 요소중에 box-sizing이란 친구가 있다. 이 친구는 각각 요소들의 padding이나 border와 같은 부분들이 가지는 값들을 전체 넓이 값에 포함시킬 것이냐는 개념이다.

반응형으로 비율이 깨지지 않게 스타일링 하는법

padding(정확히 말하면 padding-top 속성)을 이용해서 구현할 수 있다.

  1. div를 두개를 만든다. 각각 바깥 div와 안쪽 div를 나타낸다. 바깥 div는 넓이의 최소값과 최대를 100%로 지정해주고, 안쪽 div는 position: relative로 주고, padding-top은 종횡비 맞춰서 4:3이면 비율에 맞게 75%를 준다던가 하는식으로 딱 4:3 비율에 맞게 반응형이 되는 네모 모양 컨테이너를 만들 수 있다.

Promise

자바스크립트는 싱글 스레드로 동작하는 언어이다.'

스레드란? -> '일꾼' 개념이다. 싱글 스레드는 말 그래도 일꾼이 하나란 의미인데, 때문에 동시 작업이 안되고, 일꾼이 하나인만큼 작업을 하나 수행 -> 다음 하나 수행 방식으로 수행한다.

그렇다면 비동기작업은?

비동기 작업은 동시에 할 수 있다. 싱글 스레드는, 동시 작업이 안되는데 왜 비동기 작업을 동시에 할 수가 있지..? -> 이걸 알려면 우선 자바스크립트 엔진에 대해 알 필요가 있다.

자바스크립트 엔진은 메모리 힙이라는 친구와 콜스택(작업장)을 기준으로 일처리를 하는데, 콜스택에 처리해야 될 녀석들이 하나씩 쌓이고 하나의 동작을 해결할 때 마다 다시 하나씩 콜스택에서 지워지는 구조로 일을 처리한다.

비동기 작업을 할 때 자바스크립트는 싱글 스레드지만, 엔진을 도와주는 친구들과 함께 작업을 하게된다. (Web API(Ajax, DOM 등등)) 콜백 큐라는 녀석도 존재하는데, 비동기 작업을 할 때 처음에 콜스택에 들어갔다가, 콜백 큐에 콜백 함수를(실행한 다음에 실행해야 될 녀석) 넘겨준다. 콜백 함수는 이벤트 루프라는 녀석을 통해 콜스택에다가 콜백 함수를 다시 넘긴다. -> 콜스택이 해당 일을 처리하고, 스택에서 치워버린다. 이런 방식으로 자바스크립트에서는 비동기 작업을 진행한다.

콜백 이란?

위 설명에서 콜백 개념은 자바스크립트가 비동기 처리를 하기 위한 패턴 중 하나이다.
콜백 패턴에는 문제가 있는데, 일반적으로 비동기 작업을 할 때는 -> 웹 API에 위임을 하고, 이 작업이 끝나면 이벤트 루프를 통해서 콜백큐에 콜백함수를 넘겨주었는데, 여기서 이 받아온 콜백 함수를 가지고 또 비동기 작업을 하고 -> 또 나갔다가 다시 들어오고.. 즉 비동기 처리가 중첩이 되는건데, 이 중첩이 계속 되면 코드가 깊어지고, 관리하기가 매우 어려워진다.

중첩이 깊어지고, 관리하기가 어려워지는 것을 '콜백 헬' 혹은 '멸망의 피라미드'라고도 부른다.
-> 꼬리에 꼬리를 물고 계속해서 들어가기 때문.

콜백 헬을 그럼 해결할 방법은 없을까? -> 프라미스

프라미스란?
ES6버전에서 도입한 또 다른 비동기 처리 패턴이다. 비동기 연산이 종료된 이후에 결과를 알기 위해 사용하는 객체다. 비동기 처리 시점(비동기 처리가 끝나는 시점)을 좀 더 명확하게 표현할 수 있다.

프라미스는 Promise 생성자 함수를 통해 생성한다.(new 키워드)

const promise = new Promise((resolve, reject) => {}

-> 여기서 resolve는 작업이 성공한 경우에 호출할 콜백이고, reject는 작업이 실패한 경우에 호출할 콜백이다.

프라미스의 상태값 4가지

프라미스의 후속 처리 메소드란?

비동기 처리 작업을 한 후에 -> 후속 처리를 할 때, promise로 구현된 비동기 함수 같은 경우에는 promise 객체를 반환한다. promise로 구현된 비동기 함수를 호출하는 측에서는 이 promise 객체의 후속 처리 메소드를 통해서 비동기 처리 결과를 받아서 처리를 해야한다.

그럼 콜백 헬을 프라미스로 해결하려면..?

-> 프라미스 체이닝(promise chaining)

프라미스는 후속 처리 메소드를 체이닝해서 여러 개의 프라미스를 연결할 수 있다. (이걸로 콜백 헬을 해결할 수 있다) -> 체이닝은 그럼 어떻게 쓰는걸까..?
후속처리 메소드 (then)을 쭉쭉 계속 이어주는 것 이다.

async 와 await는 키워드

async 와 await는 자바스크립트에서 쓰는 키워드인데, async를 쓰면 어떤 함수에 어떤작업을 리턴해도 promise, 즉 프라미스를 반환을 해준다. 비동기가 아닌 작업을, 프라미스를 따로 만들지 않아도 프라미스를 반환해준다.

await는 async의 짝꿍 같은 존재로, async 없이는 쓸 수가 없다. async 함수 안에서만 동작을 하는 키워드이다. await를 쓰면, 프라미스가 처리될 때 까지 일단은 기다린다. -> 프라미스가 pending(아직 처리가 되지 않음)이 아니라 처리를 다 할 때 까지 (ex: fullfield) 기다린다는 것 이다. -> 이후에 처리 결과를 반환해준다. 자바스크립트에서 비동기 작업을 하면, 스레드라는 일꾼이 "비동기 작업 해줘"라고 위임을 해놓고, 바로 다음 작업을 한다. 그런데 await를 쓰면 넘겨놓고 다음작업을 바로 시작하는게 아니라, 프라미스가 처리될 때 까지 기다리고 있게 되는 것 이다. 그리고 나서 처리가 되면, 그 다음에 작업을 계속하는 것 이다.

토큰 기반 인증

OAuth 2.0
오어스는 토큰을 발급하고, 인증하는 방법에 대한 프레임워크이다.

오어스의 동작원리
클라이언트와 서버 사이에서 인증을 하면, 서버가 엑세스 토큰이라는 것을 준다. 클라이언트는 이 토큰을 가지고 API 요청을 할 수가 있다. 그리고 서버는 API 요청하고 같이 엑세스 토큰을 받는다. 이 엑세스 토큰을 가지고 해당 유저가 어떤 게시글을 보거나 수정하거나 할 수 있는 권한이 있나 없나 확인하고, 결과를 클라이언트에게 보내준다. 이게 딱 클라이언트와 서버만 놓고보면 굉장히 간단한 이야기인데, 이 오어스라는 친구는 외부 서비스에 인증하고 권한 부여를 관리하는 프레임워크이다. 오어스의 특징은 플랫폼 간의 권한을 공유할 수가 있다는 점이다. (오어스는 인증과 권한을 둘 다 관리한다)

사이트에서 구글 로그인과 같은 타입의 인증을 어떻게 하는가? + 오어스가 어떻게 동작하는가?
예를 들어 유저가 우리가 만든 사이트에서 '게시글을 보려고 구글 로그인을 하는 상황이다'라고 가정해보자. 일단은 유저가 우리 사이트 방문을 한다. -> 사이트에서 구글에다가 로그인 페이지를 달라고 요청한다. -> 구글은 로그인 페이지를 넘겨준다. -> 사이트는 다시 유저에게 로그인 페이지를 준다. -> 유저는 구글에다가 아이디 / 패스워드를 넣고 로그인을 한다. -> 구글은 아이디 / 패스워드를 통해 유저임을 확인하고, 권한 증서 같은것을 준다. 이걸 "authorization 코드를 발급했다"라고 말한다. 주의할 점은 여기서 구글이 권한증서를 넘겨주는 것은 사이트가 아니라 유저에게 주는 것 이다. 때문에, 인증된 유저임을 증명하기 위해 유저는 권한 증서를 다시 우리 사이트한테 넘겨준다. -> 우리 사이트에서는 authorization 코드(권한 증서)를 구글에게 넘기고, 엑세스 토큰을 넘겨달라고 요청한다. -> 구글은 사이트에 엑세스 토큰을 넘겨준다. -> 이 받아온 토큰을 사이트에서 다시 유저에게 넘겨준다. (유저가 사이트에서 권한 관련된 요청을 보내올때마다 해당 토큰을 바탕으로 인증을 해야하기 때문이다) -> 이후에 유저가 사이트에서 게시글 열람 요청, 글쓰기 요청, 회원 정보 수정 요청 등등의 요청들을 보내올 때 엑세스 토큰을 같이 넘겨주면서 요청을 하게 되고, 사이트에서는 해당 토큰을 확인해서 응답해주는 것 이다.

주의할 점: 여기서 말한 '토큰'은 만료가 되는 녀석이다. 즉, 어느정도 기간이 정해져있다. 구글에서 엑세스 토큰을 우리 사이트가 받아오는데, 이후에 이 토큰이 만료가 되었을 수가 있다. 때문에, 사이트에서는 유저에게 요청과 함께 토큰을 넘겨받아서 검사할 때 해당 토큰이 만료된 토큰인지 아닌지도 확인해야한다. -> 만약 만료가 된 토큰인 경우에는, refresh 토큰이라는 녀석을 쓰게 된다. refresh 토큰을 가지고 구글에게 토큰이 만료가 되었으니, 새로운 토큰을 요청한다. -> 구글은 새로운 엑세스 토큰을 발급해주고, -> 사이트는 새로 받아온 엑세스 토큰을 다시 사용자에게 넘겨준다. (refresh 토큰은 일반 토큰보다 만료 기간이 조금 더 길다) 그렇다면, 만약 refresh 토큰도 만료가 된다면 어떻게 되는가..? -> 둘 다 만료가 되었다면, 유저에게 로그인을 다시 요청해서, 재 로그인을 하게 한다. ... => 이런 방식으로 토큰을 관리해나간다.

JWT(Json Web Token)

JWT는 발전한 토큰이다 라고 생각하면 편하다. -> 토큰이 누군가에 의해 탈취되서 변조가 되었는지 여부를 확인할 수 있는 전자서명이 포함된 토큰이다. (좀 더 보안이 좋다)

웹 저장소(feat.토큰)

Http는 요청을 하고 응답을 받으면 연결이 해제된다. -> 즉, access_token을 우리 사이트 클라이언트 어딘가에 저장을 해두어야한단 이야기이다. -> 그럼 토큰을 어디에 저장해야할까?? -> 클라이언트에서 쓸 수 있는 저장소가 있다.

클라이언트 저장소는 토큰 뿐만 아니라 여러 정보들을 저장할 때 많이 사용된다. 대표적인 예시는 장바구니이다. 쇼핑몰 들어갈 때 보면, 로그인을 하지 않은 상태에서도 장바구니 담기가 되는 경우를 본적이 있을 것 이다.

클라이언트 저장소의 종류들

크롬에 어플리케이션 카테고리를 보면 Storage라고 해서 우리가 쓸 수 있는 저장소들이 나온다. 로컬 스토리지, 세션 스토리지, indexedDB, Web SQL, Cookies까지 다양한 종류의 저장소들이 있다. 이중에서 가장 많이 사용하는 로컬 스토리지, 세션 스토리지, 쿠키 이 세가지를 알아보자.

Cookies

쿠키는 클라이언트 로컬에 저장되는 "key: value"형태의 저장소이다. 약 4kb 정도 저장할 수 있다.

쿠키를 삭제하는법: 만료일(expire-date)을 현재보다 전 날짜로 값을 수정해주면, 만료가 되었다고 인식하기 때문에 쿠키가 삭제된다.

세션 스토리지

HTML5에서 추가된 저장소이고, 로컬 스토리지와 함께 추가되었다. 쿠키와 마찬가지로 "key: value" 값 형태의 저장소이다.

쿠키와 다른점은 브라우저를 닫으면 제거가 된다. 때문에, 브라우저를 닫은 후에 다시 사이트에 들어왔을때도 작업해주어야하는 장바구니와 같은 친구들은 여기에 사용하기가 어렵다. 세션 스토리지는 쿠키보다 관리하기가 상대적으로 쉽다. (만들고, 수정하고, 삭제하는 등등)

어떻게 추가하는가? -> .setItem 메소드를 통해서 추가할 수 있고(sessionStorage.setItem(key, value)), .getItem(sessionStorage.getItem(key))을 통해서 가져올 수 있다. 삭제할 때는 .removeItem 메소드를 통해서 삭제(sessionStorage.removeItem(key))할 수 있다. "sessionStorage"를 통해 접근하고 뒤에 .setItem, .getItem, .removeItem을 통해서 관리한다.

++.clear도 존재하는데, 이 친구는 몽땅 다 지우고 싶을때 사용한다. (ex: sessionStorage.clear() -> 세션스토리지에 들어있는 값을 초기화)

그렇다면, 세션 스토리지 같은 경우 브라우저를 닫으면 없어지는데, 안 없어지는 저장소는 없을까..?

답은 "있다". -> 바로 로컬 스토리지를 사용하면 된다.

로컬 스토리지

로컬 스토리지에 저장을 하면, 따로 지워주는 행위를 하지 않는 이상 계속 해당 값이 브라우저에 남아있게 된다. -> 때문에 유저 아이디, 패스워드 같은 중요한 정보를 넣어두면 안된다.(위험하기 때문)

로컬 스토리지 사용법은 세션 스토리지와 비슷한 메커니즘으로 동작하는데, 세션 스토리지가 sessionStorage.~Item을 통해 관리했다면, 로컬 스토리지는 localStorage.~Item으로 관리한다. .clear도 마찬가지이다.

그럼.. 토큰은 위의 3가지 저장소 중 어디에 저장하는 것이 좋을까?

HTML5 이전까지는 토큰을 저장할만한 곳이 쿠키밖에 없었지만, 이후부터는 LocalStorage에도 토큰을 저장하는 일이 많아졌다.

LocalStorage에 저장하는 이유는..?

  1. 쿠키보다 더 많은 정보 저장 가능. (쿠키용량: 4kb, 로컬 스토리지: 5mb)
  2. 쿠키처럼 모든 http 통신에 딸려서 들어가지 않는다.

그럼 무조건 토큰은 로컬 스토리지에 넣는가?

답은 NO이다. 로컬 스토리지 특성상 모든 데이터가 로컬에 남아있으니 보안상 취약해지기 쉽기 때문에, 프로젝트 성향에 맞춰 저장 장소는 그때 그때 달라져야한다.

2021-07-05

Immer가 어떻게 동작하는가?

불변성 유지해주면서 데이터 관리를 하기 위해서는 Immer를 사용해야한다.
-> 그렇다면, Immer는 어떻게 내부적으로 불변성을 유지해주는걸까?
이걸 정확하게 알기 위해서는 자바스크립트의 "프록시" 개념에 대해 알아야한다.

어떤 상황에 'A'라는 녀석을 불변성을 유지하면서 값을 수정하고 싶다면, Immer로 Produce()라는 녀석을 사용해야한다. -> Immer는 'A'라는 녀석을 받아다가 'A`'를 만들어서, 이 만든 녀석을 고치라고한다.

이렇게 했을 때, 우리에게 편한점은 원래 불변성을 유지하면서 값을 수정하려고 할 때, 우리는 A자체의 값을 수정하면 안되기 때문에, 클론을 만들어서 수정을 하고 다시 넣어준다던가 하는 좀 더 우회하는 방식으로 신경써서 데이터 관리를 해주어야 하는데, 이 Immer라는 친구를 사용하게 되면, A`라는 클론을 알아서 만들어서 우리가 이 부분에 대해 전혀 신경쓸 필요 없이 값을 수정해도 불변성 관리가 내부적으로 가능한 것 이다.

produce() 안에 넘겨주는 값으로는 원본 값을 그대로 넘겨주면 된다. -> 알아서 복사해서 처리하기 때문.

사용예시: produce(state, (원본값을 복사해서 넘겨주는 값을 받을 변수) => {
//실행할 코드

})

Redux 사용해서 유저 정보 관리하기

redux의 action 통해서 로그인, 로그아웃 기능 구현 -> 쿠키 넣어보고, redux 안에서 history 처리해보기.

회원가입 기능 구현(feat.firebase)

회원가입 구현 순서

로그인 기능 구현(feat.firebase)

로그인 구현 순서

로그인 유지 기능 + 로그아웃 기능 구현(feat.firebase)

로그인 유지 기능 구현 순서

redux에 있는 데이터를 보고서, 로그인을 했는지 안 했는지 판단 -> firebase가 어디에 인증 정보를 저장하는지 확인 -> indexedDB에 저장을 하고 있으니, 애를 세션에 저장하게 옮겨줌 -> 세션에서 로그인이 되어 있나 안 되어 있나 판단(세션 '키'를 가지고 판단) -> 들어있다고 판단되면, 유저 정보를 가져와야하니까 유저정보 가져옴 (firebase에 제공해주는 것을 사용해서 가지고 옴) -> 가지고 온 해당 유저 정보를 바탕으로 로그인 유지하는 부분 구현. (redux 보고, 세션에 값이 있나 없나 보고, 유저 정보를 달라고 요청을 보냄 (로그인 한게 맞는지) -> 확인이 됐으면 redux에 유저정보 넣어줌 -> redux랑 세션이 둘 다 만족했으니, 둘 다 로그인 상태로 바꿔준다.)

로그아웃 기능 구현

현재 로그인을 유지하는 것이 redux 데이터와 세션을 보고, 판단을 해서 로그인을 유지중이니까, 이거 두개를 지워주면? -> 로그아웃 기능을 구현함 셈. -> 다만 조금 더 확실하게 하기 위해서, firebase 인증에 로그아웃화 시키는 부분(코드)을 태워서 로그아웃 시켜줌. (이걸 태워주면, 알아서 세션을 지워준다. 그리고 본인들이 가지고 있는 "로그인 했다" 라는 데이터도 지워준다. -> 때문에, 우리는 redux에 남아있는 데이터값만 지워주는 작업을 하면 된다.

2021-07-05

EmailCheck 형식 만들기

"정규식" 사용해서 만든다. -> 정규식이란?

정규 표현식은 문자열에 나타는 특정 문자 조합과 대응시키기 위해 사용되는 패턴이다. 즉, 문자를 조합해서 비교할 때 많이 사용하는 것이라고 생각하면 된다.
(형식 체크, 대체, 갈아끼울 때 많이 사용하는 패턴이다)

ex)

export const emailCheck = (email) => {
    let _reg = /^[0-9a-zA-Z]([-_.0-9a-zA-Z])*@[0-9a-zA-Z]([-_.0-9a-zA-z])*.([a-zA-Z])*/;

    return _reg.test(email);
}

좋은 웹페이지 즐겨찾기