[ToyProject] 회원가입 유효성 검사(feat. React) - 3. 안내문구 DOM에 추가하고 삭제하기

알락·2022년 10월 3일
0

  • 페이지 레이아웃

    • 로그인 페이지

    • 회원가입 페이지

      회원가입 같은 경우는 입력이 되는 동시에 사용자가 입력값이 유효한지에 대한 안내를 받을 수 있게 안내 문구를 출력해주었다.

  • 작성 파일

    +— src
      +— Page
          +— Component
              +— Input.js
          +— Signin.js  (로그인 페이지)
          +— Signup.js (회원가입 페이지)
      +— Validation
          +— Validation.js (유효 검사 자체 라이브러리)
      +— App.js
  • 구현

    • App.js

      import './App.css';
      import Signin from './Page/Signin';
      import Signup from './Page/Signup';
      import { BrowserRouter, Route, Routes } from 'react-router-dom';
      
      function App() {
        return (
          <BrowserRouter>
          <div className="App">
            <Routes>
              <Route path="/" element={<Signin/>}/>
              <Route path="/signup" element={<Signup/>}/>
            </Routes>
          </div>
          </BrowserRouter>
        );
      }
      
      export default App;

      react-router-dom을 이용해서 SPA(Single Page Application)를 구현해주었다.

    • Signin.js (로그인 페이지)

      import React,{useState} from 'react';
      import './Signin.css';
      import {Link} from 'react-router-dom';
      import Validation from '../Validation/Validation.js'
      
      function Signin(){
      
          const [userId , setuserId] = useState(""); //ID 저장
          const [userPasswd , setuserPass] = useState(""); //Pass 저장
      
          function handleId(e){
              setuserId(e.target.value);
          }
      
          function handlePasswd(e){
              setuserPass(e.target.value);
          }
      
          function ValidPassword(){
              if(userPasswd.length === 0){
                  console.log(userPasswd);
                  return(<div>Password 입력해주세요.</div>)
              }
              else if(userPasswd.length > 0){
                  console.log(userPasswd);
                  return(<div>입력 완료</div>)
              }
          }
      
          return(
              <div className="signin-form"> 
                  <div className="input-group">
                      <input className ="signin_userid" type="text" onChange={handleId} value={userId} placeholder="ID를 입력해주세요."></input>
                  </div>
                  <div className="input-group">
                      <input className ="signin_password" type="password" onChange={handlePasswd} value={userPasswd} placeholder="비밀번호를 입력해주세요."></input>
                  </div>
                  <div className="button-group">
                      <button>Login</button>
                      <Link to="/signup"><button>회원가입</button></Link>
                  </div>
              </div>
          )    
      }
      
      export default Signin;

      사실 로그인 페이지는 나와 같이 프로젝트를 진행한 동료가 작성해주었다. 여기서 설명해야 할 부분은 Link를 이용하여 SPA를 구현하였다는 것이다.

    • Signup.js (회원가입 페이지)

       import {useState} from 'react';
        import {Link} from 'react-router-dom';
        import "./Signup.css";
        import Input from "./Component/Input";
        import Validation from '../Validation/Validation';
      
        function Signup(){
      			  /*... 생략*/
            const [userPassword, setUserPassword] = useState("");
            const [userEmail, setUserEmail] = useState("");
            const [userPhoneNumber, setUserPhoneNumber] = useState("");
            const [validCheck] = useState(new Array(5).fill(false));
      
            function handleInput(setter, validFunc ,order){
                return (event)=>{
                    let value = event.target.value
                    setter(value);
                    if (order === 2) validCheck[order] = (userPassword === value);
                    else validCheck[order] = validFunc(value);
                }
            }
      
            function handleSubmit(event){
                if (validCheck.reduce((result, cur)=> !cur ? false : result )) alert("모두 유효하게 작성되었습니다.");
                else alert("이상합니다.")
            }
      
            return (
                <div className="signup-form">
                    /*생략*/
                    <Input 
                        className="signup_password" 
                        placeholder="비밀번호를 입력해주세요." 
                        changeHandler={handleInput(setUserPassword, Validation.signupPasswordCheck ,1)} 
                        value = {userPassword}
                        valid={validCheck[1]} 
                        valid_message = "비밀번호는 특수문자 !@#$%^&*_- 와 영어 알파벳, 숫자가 각각 한 개씩 포함되게 8자 이상으로 작성해주세요."
                        type = "password" />
      			  /*생략*/
                    <Input 
                        className="signup_email" 
                        placeholder="이메일을 입력해주세요." 
                        changeHandler={handleInput(setUserEmail, Validation.signupEmailCheck, 3)}
                        value = {userEmail}
                        valid={validCheck[3]} 
                        valid_message = "입력하신 이메일은 유효하지 않습니다." />
                    /*...생략*/
                    <div className="button-group">
                        <button onClick={handleSubmit}>가입하기</button>
                        <Link to="/"><button type="button">돌아가기</button></Link>
                    </div>
                </div>
            )
        }
      
        export default Signup; 

      각각의 input 값들을 Signin이 모두 관리, 통제할 수 있게 state로 저장하였다. 비밀번호를 입력하는 input을 집중해서 살펴보자.

      function Signup(){
      	const [userPassword, setUserPassword] = useState("");
      	const [validCheck] = useState(new Array(5).fill(false));
      	/*생략*/
      	function handleInput(setter, validFunc ,order){
                return (event)=>{
                    let value = event.target.value
                    setter(value);
                    if (order === 2) validCheck[order] = (userPassword === value);
                    else validCheck[order] = validFunc(value);
                }
            }
           return(
           	<div className="signup-form">
                {/*생략*/}
                <Input 
                    className="signup_password" 
                    placeholder="비밀번호를 입력해주세요." 
                    changeHandler={handleInput(setUserPassword, Validation.signupPasswordCheck ,1)} 
                    value = {userPassword}
                    valid={validCheck[1]} 
                    valid_message = "비밀번호는 특수문자 !@#$%^&*_- 와 영어 알파벳, 숫자가 각각 한 개씩 포함되게 8자 이상으로 작성해주세요."
                    type = "password" />
                {/*생략*/}
              </div>
           )
      }

      Signup이라는 상위 컴포넌트에서 각 입력의 상태값을 관리하려고 했다. 그래서 useState를 이용하여 Signup 함수 컴포넌트에서 상태 변수들을 만들어 사용하였다. 비밀번호에 해당하는 상태값은 userPassword이다.

      HTML에서 입력되는 값을 리액트 함수 컴포넌트의 상태값으로 저장하기 위해서는 이벤트 핸들러가 필요하다. onChange라는 input 엘리먼트의 이벤트 리스너가 내가 작성한 이벤트 핸들러 handleInput을 호출하게 만들었다. 이를테면 여기서 '상태값 끌어올리기'를 한 것이라고 생각하면 되겠다.

      handleInput은 사실 이벤트 핸들러 함수를 반환하는 고차함수다. 이벤트리스너에 등록되는 함수는 무조건 이벤트핸들러야 하는데, 나는 각 input 컴포넌트마다 다른 setter를 써줘야만 했다.(예를 들면, 비밀번호 같은 경우는 setUserPassword 를 사용하지만 id 같은 경우는 setUserId가 사용되어야 했다.) 똑같은 명령을 다시 작성하여 사용하는 것을 피하기 위해 나는 고차함수로 해결했다.

      심지어 handleInput에는 validFunc라는 콜백함수도 존재한다. 앞선 포스팅에서 살펴봤던 정규표현 함수를 이 파라미터에 넘겨 이벤트 핸들러가 실행될 때 사용되게 한다. 이렇게 작성하게 된 이유는 각 입력값의 종류에 따라 사용하는 정규표현도 다르기 때문이다.
      맨 뒤 order라는 인자가 존재하는 이유는 각 입력값에 대한 유효성을 validCheck라는 배열 state에서 관리하기 때문이다. 따라서 각 input 컴포넌트를 번호로 구분해 마지막으로 입력된 값이 유효한 지에 대한 판별값을 validCheck에 저장하여 관리하게 하였다.

      State를 배열로 사용할 때 생기는 이슈

      state를 배열로 사용하니 그 값을 useState가 제공하는 Setter가 필요없게 되었다. 그렇다면 state를 굳이 useState를 사용하여 선언할 필요가 없어 보인다. 하지만 배열의 값이 바뀌었을 때 해당 값에 의존하는 컴포넌트에 렌더링이 안되는 것처럼 보였다. 결국 useState를 이용하여 렌더링 문제를 해결하였다.

      이벤트 핸들러를 통해 상태값을 바꿔줄 때 주의할 점

      이벤트핸들러를 이용하여 상태값을 저장하게 구현을 하였다. 하지만 발생한 이벤트 내에서 현재 컴포넌트에 저장되어 있는 State를 확인을 하면 아직 이전의 상태값이 저장되어 있는 것을 확인할 수 있다. Setter가 이미 호출이 되었어도 이 현상이 똑같이 발생한다. 이를 통해 추측할 수 있는 바 이벤트핸들러Setter로 바꾸는 상태값은 이벤트가 종료가 되고 난 이후에 저장된다고 생각해 볼 수 있다. 그래서 이번 프로젝트에서처럼 '비밀번호'와 '비밀번호 확인'이 일치하는지를 확인하려면 Event Target의 value로 비교하여야 바라는 구현을 할 수 있다.

    • Input.js (입력 컴포넌트)

      function Input({placeholder, className, changeHandler, value, valid , valid_message, type}){
         return (
             <div className="input-group">
                 <input className={className} placeholder={placeholder} onChange={changeHandler} value={value} type={type === "password" ? "password" : "text"}/>
                 {value.length > 0 && !valid ? <p className="notice-error">{valid_message}</p>: undefined }
             </div>
         )
      }
      
      export default Input ;

      부모(상위) 컴포넌트에서 넘겨준 props에는 여러 값들이 있었다. 그 중에 먼저 살펴볼 것은 이벤트 핸들러다. 부모 컴포넌트에서 받은 이벤트 핸들러의 역할은 하위 컴포넌트에 의해 바뀐 상태값을 끌어올리기 위해서다. 예를 들어 여기서 구현한 비밀번호 입력값 같은 경우는 값의 타이핑이 input엘리먼트에서 일어난다. 하지만 타이핑이 끝난 시점의 비밀번호 문자열은 Signup컴포넌트의 userPassword에 저장되어 관리하려한다. 때문에 이벤트와 이벤트에 해당하는 정보가 발생하는 곳은 input -> 실제로 이벤트핸들러 안에 작성되어 있는 실행문이 실행되는 곳은 상위 컴포넌트 내부로 하여 '상태 끌어올리기'를 구현해낼 수 있다.
      valid값을 props로 전해받아 안내문구를 출력해냈다. 삼항연산자의 조건문으로 쓰이는 것을 확인할 수 있다.

profile
블록체인 개발 공부 중입니다, 프로그래밍 공부합시다!

0개의 댓글