Spring Boot 3 & Spring Framework 6 - Section 15 : 풀스택 Application 실습 #1

이정수·2025년 2월 21일

풀스택 실습1. Todo App 구축 개요

  • React , Spring 활용
    React에서의 Routing, Form, Validation, REST API로 백엔드 API 호출법, JWT Token을 활용한 Authentication 학습
    • React에서 Routing
      。Client가 브라우저에서 URL을 전달 시 해당 URL에 Mapping된 Component를 Rendering하는 기능을 의미.
      SPA상에서는 페이지를 새로고침하지 않고도 페이지 전환처럼 보이게 할 수 있다.
      • React Router DOM :
        。React Application에서 URL에 따라 Component를 Rendering하는 Library
        Client-side Routing으로 페이지 새로고침 없이 빠르게 페이지 전환.
        。URL 경로에 따라 Component를 Rendering.
        History API를 활용해 브라우저의 뒤로가기 / 앞으로가기 지원

        PowerShell 실행 후 React SPA Application이 존재하는 경로로 설정 후 npm install react-router-dom을 입력하여 dependency를 설치하여 활용.

        React SPA Application 경로의 package.json에 다음처럼 "react-router-dom": "^7.2.0" dependency가 구현.
        ▶ 이후 React Component에서
        import { BrowserRouter, Routes , Route , useNavigate} from 'react-router-dom'; 구문으로 사용 가능.

      • SPA ( Single Page Applications ) : 웹프로그래밍 용어 정리
        。하나의 HTML로 구성된 Web Application으로서, 서버에서 필요한 데이터만 비동기로 받아와서 동적으로 현재 화면에 다시 Rendering하는 방식.
        ex) To-do Web Application에서 To-do를 하나 추가할 경우, 전체 페이지가 새로고침되어 변경되는것이 아닌, 페이지에서 변경되는 해당 부분만 새로고침되어 변경됨.

        。Application과 상호작용 할때마다 서버에 요청하며 전체 HTML 화면을 받아오는게 아닌, 화면 렌더링을 Local PC에서 생성하므로 빠르게 화면전환이 가능!

Bootstrap
Bootstrap 공부
Bootstrap 사이트

  • npm 구문을 통해 React SPA ProjectBootstrap Framework 추가
    Powershell에서 ctrl + c를 눌러 서버구동중단 후 다음구문을 입력하여 Bootstrap Library 설치.
    npm install bootstrap



    。 이후 React Project 디렉토리에서 프로젝트\node_modules\bootstrap이 생성되었음을 확인.
    ▶ 해당 디렉토리의 프로젝트\node_modules\bootstrap\dist\css경로의 bootstap.min.css 사용.

    React Application의 전체 React Component에서 Bootstrap을 참조할 수 있도록 index.jsbootstrap.min.css 추가
    node_modules 디렉토리 기준으로 경로 설정.
    import 'bootstrap/dist/css/bootstrap.min.css';

    ▶ 이후 React Component에서 JSX 태그className=""속성에 Bootstrap CSS Class를 입력.

  • React에서 Bootstrap Grid-System 활용
    react-bootstrapbootstrap 라이브러리를 import하여 활용.
    bootstrap 라이브러리 install 후 index.js에서 import 'bootstrap/dist/css/bootstrap.min.css'를 통해 import.

    import { Container, Row, Col } from 'react-bootstrap';를 통해 react-bootstrap에서 <Container>, <Row> , <Col>을 import하여 사용.
return (
    <Container>
      <Row>
        <Col xs={3} md={3} lg={3}><ControlPane/></Col>
        <Col xs={9} md={9} lg={9}>
          <div ref={mapRef}  // React에서 OpenLayers가 맵을 렌더링할 DOM 요소를 참조
          className="map"/>
        </Col>
      </Row>
    </Container>
  );
  • <Container>, <Row> , <Col>의 반응형 중단점 속성
    。1개 Row당 12개 Column을 분할.
    xs={Column숫자} : 모바일 설정용도
    md={Column숫자} : 태블릿 설정용도
    lg={Column숫자} : 데스크탑 설정용도



  • React Icons 종류 react-icons
    npm install react-icons 적용 후
    import { 버튼이름 } from 'react-icons/fa'
    ex)
import { FaHome } from 'react-icons/fa';
// ....
<li key={1}>
                <a href={"/"}>
                  {<FaHome />}
                  {"home"}
                </a>
</li>

Todo App'의 Component 구성
Boostrap을 추가하여 Styling을 수행.

  • Container Component
    TodoApp 구현을 위한 모든 Component( Login , Welcome, Error, ... )의 jsx구문을 Container로서 추가한 후 App() Component로 전달하는 역할을 수행.
  • Login Component
  • Welcome Component
  • Error Component
  • ListTodosComponent
  • Header Component
  • Footer Component
  • Logout Component

Container Component

TodoApp 구현을 위한 모든 Component( Login , Welcome, Error, ... )의 jsx구문을 Container로서 추가한 후 App() Component로 전달하는 역할을 수행.

  • Todo App을 구현하기 위한 모든 Component를 포함하는 Container 역할의 jsx파일 생성
    src/components/Todo/ 디렉토리 생성 후 TodoApp.jsx 생성.
    。Container 역할의 TodoApp() Component jsx구문 내부에 TodoApp 구현을 위한 모든 Component( Login , Welcome, Error, ... )의 jsx구문을 추가.
    ▶ 최종적으로 TodoApp.jsxApp.jsApp() Component에 export하여 jsx구문으로서 반영하기.
 // Todo.jsx
// Todo App의 모든 Component를 포함하는 Container 역할의 Component
export default function TodoApp(){
  return (
    <div className="TodoApp">
      {/* 각각의 Component의 JSX를 포함 */}
      <LoginComponent /> 
      <WelcomeComponent/>
      {/* 이후 Error , ListTodos Component 등을 차후에 포함. */}
    </div>
  );
}
function LoginComponent(){
  return (
    <div>
      {/* LoginComponent 구현내용 */}
    </div>
  )
}
function WelcomeComponent(){
  return (
    <div>
      {/* WelcomeComponent 구현내용 */}
    </div>
  )
}

▶ 이후 TodoApp() Component를 export하여 App.jsApp() Component에 추가.

LoginComponent

  • Login Form을 포함하는 Comtrolled Component인 Login Component 생성 HTML 구문
    Login Component는 React로 ID , PW , Button등의 Form이 구현.
    Controlled Component : React에서 Form element 사용 시 StateDom value를 동기화.
    Controlled Component를 설정하면 사용자가 Form element로 입력한 변수 값을 Component State로서 유지 가능.

    React Component instance는 고유의 State를 가지며 여러개의 State를 가질 수 있다.
    IDPW 용도의 State를 함께 선언.
import './TodoApp.css'
import {useState} from 'react'
export default function LoginComponent(){
  // Contorlled Component로 설정하기위해 State를 선언 및 Form element와 Binding을 수행.
  // [현재State값, State변경함수] = useState(초기값)
  const [ username, setUsername ] = useState("ID"); // ID와 PW에 사용할 State를 각각 생성
  const [ password, setPassword ] = useState("");
  // 현재State값을 <input>의 onChange event로 trigger되어 변경하는 함수 생성.
  function changeUsername(evt){
    // username 입력 field에 입력된 값을 ID State변경함수로 ID State값 설정.
    setUsername(evt.target.value)
  }
  function changePassword(evt){
    // password 입력 field에 입력된 값을 PW State변경함수로 PW State값 설정.
    setPassword(evt.target.value)
    console.log(evt.tartet.value)
  }
  return (
    <div>
      <div>
        <form method="post">
          <div>
            <label htmlFor="text1" className="label">User Name</label>
            {/*  ID에 관련된 State와 Form Element를 Binding 하기위해 
            Form element의 value와 onChange 이벤트를 State변수와 함수에 연결 */}
            <input type="text" id="text1" name="username" className="text" value={username} onChange={changeUsername} />
          </div>
          <div>
            <label htmlFor="password1" className="label">Password</label>
            {/* Password에 관련된 State와 Form Element를 Binding. */}
            <input type="password" id="password1" name="password" className="text" value={password} onChange={changePassword}/>
          </div>
          <button type="submit" name="loginbtn" className="btn">login</button>
        </form>
      </div>
    </div>
  )
}

IDPW Form element와 Binding을 수행할 State를 각각 생성.
changeUsername(evt) : <input>에서 발생하는 onChange 이벤트를 매개변수로 받아서 State변경함수현재State값evt.target.value로 설정하는 함수.
。Form Element <input>에는 value={현재State값} , onChange={함수}를 정의하여 State와 Binding을 수행한다.
Controlled Component 정의.

<label>JSX에서 사용 시 htmlFor="요소ID" 지정.
HTMLfor="요소ID"와 동일.

onChange 이벤트를 통해 입력 field의 값을 변경할때마다 State도 동기화되어 즉각 Update가 수행됨.

  • React Developer Tools를 이용해 LoginComponent 구조 확인 시 State가 2개로 설정되어있으며 해당 State의 값을 확인 가능. Chrome : React Developer Tools

    Controlled Component :
    React ComponentState를 이용하여 StateHTML Form element( <input> 등 )의 DOM value를 동기화하여 제어하는 Component.
    。입력 field에 입력한 value가 React State로서 관리되며 value가 변화할때마다 onChange 이벤트를 통해 State도 같이 동기화되어 Update.
    Controlled Component를 설정하면서 사용자가 Form element로 입력한 변수 값을 Component State로서 유지 가능.
    DOM value : HTML태그로서 <input value="값">Form 자체의 value

    。사용자가 입력 field에 입력할때마다 onChange 이벤트로 State를 상시 Update할 수 있다.

    Controlled Component 선언하는 방법 Input 속성
    。Component의 state<input>onChange , value 속성을 활용해야한다.
    import {useState} from 'react'를 선언하여 State사용.

    const [ 현재State값, State변경함수 ] = useState(초기값)을 선언하여 JSX의 입력 Form element <input value={현재State값}>을 설정하여 Binding.
    <input onChange={함수}>를 설정하여 해당 onChange event를 매개변수로 받는 함수를 구현 및 내부에 State변경함수를 활용해 event객체.target.value를 설정하여 현재State값이 변경되도록 설정.
    event객체.target : onChange 이벤트를 수행한 <input>의 JS객체를 지시.

    // Contorlled Component로 설정하기위해 State를 선언 및 Form element와 Binding을 수행.
    // [현재State값, State변경함수] = useState(초기값)
    const [ username, setUsername ] = useState("wjdtn");
    // 현재State값을 <input>의 onChange event로 trigger되어 변경하는 함수 생성.
    function changeUsername(evt){
      // State변경함수로 state값 설정.
      // evt.target.value : `onChange` 이벤트를 수행한 `<input>`의 value
      setUsername(evt.target.value)
      console.log(evt.target.value)
    }
    return (
        {/*  State와 Form Element를 Binding 하기위해 Form element의 value와 onChange 이벤트를 State와 관련된 기능으로 구현.  */}
        <input type="text" value={username} onChange={changeUsername} />
    )


    onChange 이벤트를 통해 입력 field의 값을 변경할때마다 State도 동기화되어 즉각 Update가 수행됨.



  • 하드코딩 : Login Component에서 Authentication 기능 구현 API를 활용하여 Token을 생성하는 Authentication 기능
    。나중에 Spring Security를 활용할 예정이므로 참고용으로 알고있기.
    Controlled Component를 통한 ID와 PW 입력 field 변경시마다 즉시 Update되는 Binding된 State를 활용.
    Log-in 상태를 지시하는 showSuccess State를 생성하여 Log-in Message를 전달하는 Component( = showLogInMessage )에 property로 전달하여 Message 표시여부 결정.
    <button>onClick={Authentication함수} 구현하여 Log-in Logic 구현하기.
import './TodoApp.css'
import {useState} from 'react'
export default function LoginComponent(){
  const [ username, setUsername ] = useState("ID");
  const [ password, setPassword ] = useState("");
  const [ showSuccess, setShowSuccess] = useState(false);
  function changeUsername(evt){
    setUsername(evt.target.value)
  }
  function changePassword(evt){
    setPassword(evt.target.value)
  }
  function logInStatus(){
    if (username==="wjdtn" && password==="12345"){
      setShowSuccess(true)
    } else {
      setShowSuccess(false)
    }
  }
  return (
    <div>
      <div>
        <ShowLogInMessage showSuccess={showSuccess}/>
        <form method="post">
          <div>
            <label htmlFor="text1" className="label">User Name</label>
            <input type="text" id="text1" name="username" className="text" value={username} onChange={changeUsername} />
          </div>
          <div>
            <label htmlFor="password1" className="label">Password</label>
            <input type="password" id="password1" name="password" className="text" value={password} onChange={changePassword}/>
          </div>
          <button type="submit" name="loginbtn" className="btn" onClick={logInStatus}>login</button>
        </form>
      </div>
    </div>
  )
}
export function ShowLogInMessage({showSuccess}){
  if (showSuccess==true){
    return (
      <div>
        Successfuly Log-in
      </div>
    )
  }else{
    return (
      <div>
              Log-in Failed
      </div>
    )
  }
}


。입력field에 ID와 PW를 입력 후 button을 누를 경우 다음처럼 상단에 ShowLogInMessage() Component에서 구현된 Message를 return.

{showSuccess && <div>Successfuly Log-in</div>}
  • <ShowLogInMessage showSuccess={showSuccess}/>ShowLogInMessage() Component를 삭제 후 다음 구문을 넣어도 잘 동작한다.

    { boolean && JSX코드 }
    Componentreturn ()에서 해당 booleantrue 인 경우, JSX코드를 반환하는 구문.
    ex) return ( { true && <div>true</div>} ) → Page에 true를 반환.



  • React Router DOM를 활용해 Container Component에서 URL 접속 시 특정 Component를 표현하는 Routing 구현 및 Component등 간에 Routing 구현하기

    React Router DOM :
    。React Application에서 URL에 따라 Component를 Rendering하는 Library
    Client-side Routing으로 페이지 새로고침 없이 빠르게 페이지 전환.
    History API를 활용해 브라우저의 뒤로가기 / 앞으로가기 지원
    。주로 <BrowserRouter> , <Routes> , <Route>를 활용해 React에서 Routing을 구현.
    • React Router DOM dependency 설치

      PowerShell 실행 후 React SPA Application이 존재하는 경로로 설정 후 npm install react-router-dom을 입력하여 dependency를 설치한 후 Server를 다시 재구동.
      React SPA Application 경로의 package.json에서 추가된 dependency를 확인 가능.

    • Container Component에서 필요한 기능 import한 후 Component Routing 설정
      Todo.jsxBrowserRouter, Routes , Route를 import.
      import { BrowserRouter, Routes , Route } from 'react-router-dom'; 를 정의.
            // TodoApp.jsx
    import LoginComponent from './LoginComponent'
    import WelcomeComponent from './WelcomeComponent';
    import { BrowserRouter, Routes , Route } from 'react-router-dom';
    export default function TodoApp(){
      return (
        <div className="TodoApp">
          {/* <BrowserRouter> 안에 <Routes> 안에 <Route>를 구현.  */}
          <BrowserRouter>
            <Routes>
              {/* path="접속URL" , element={<URL접속 시 표시할 Component명/>}  */}
              {/* localhost:3000/login */}
              <Route path="/login" element={<LoginComponent/>}/>
              <Route path="/welcome" element={<WelcomeComponent/>}/>
            </Routes>
          </BrowserRouter>
        </div>
      );
    }

    <Route> \in <Routes> \in <BrowserRouter>
    <Route>path 속성으로 routing할 URL 지정 및 element 속성으로 URL 접속 시 표현할 Component 설정.

    LoginComponent.jsx에서 useNavigate를 import.
    import { useNavigate } from 'react-router-dom';

            // LoginComponent().jsx
    import './TodoApp.css'
    import {useState} from 'react'
    import {useNavigate} from 'react-router-dom'
    export default function LoginComponent(){
      const [ username, setUsername ] = useState("ID");
      const [ password, setPassword ] = useState("");
      const [ showSuccess, setShowSuccess] = useState(false);
      // useNavigate Hook 생성
      const navigate = useNavigate() 
      function changeUsername(evt){
        setUsername(evt.target.value)
      }
      function changePassword(evt){
        setPassword(evt.target.value)
      }
      function logInStatus(){
        if (username==="wjdtn" && password==="12345"){
          setShowSuccess(true)
          // useNavigate 객체를 통해 버튼을 누를경우 onClick event를 통해 실행되는 logInStatus()에서 특정 URL로 Routing을 구현.
          // localhost:3000/welcome으로 접속
          navigate("/welcome")   
        } else {
          setShowSuccess(false)
        }
      }
      return (
        <div>
          <div>
            {showSuccess && <div>Successfuly Log-in</div>}
              <div>
                <label htmlFor="text1" className="label">User Name</label>
                <input type="text" id="text1" name="username" className="text" value={username} onChange={changeUsername} />
              </div>
              <div>
                <label htmlFor="password1" className="label">Password</label>
                <input type="password" id="password1" name="password" className="text" value={password} onChange={changePassword}/>
              </div>
              <button type="submit" name="loginbtn" className="btn" onClick={logInStatus}>login</button>
          </div>
        </div>
      )
    }

    const navigate = useNavigate() 로 Hook 객체 생성
    useNavigate Hook객체를 통해 버튼을 누를경우 onClick event를 통해 실행되는 logInStatus()에서 특정 URL로 Routing을 구현.
    localhost:3000/welcome으로 접속

    TodoApp.jsx에서 <Route>를 통한 URL을 사전에 정의 후 localhost:3000/login에 접속 후 IDPW를 입력하여 버튼을 누를경우 useNavigate Hook을 통해 localhost:3000/welcome으로 연결.

    • <Route>로 구현되지 않은 URL 접속 시 도출될 Error Component 구현
      <Route path="*" element={<ErrorComponent/>}> 설정.
        // TodoApp.jsx
    import LoginComponent from './LoginComponent'
    import WelcomeComponent from './WelcomeComponent';
    import ErrorComponent from './ErrorComponent';
    import { BrowserRouter, Routes , Route } from 'react-router-dom';
    export default function TodoApp(){
      return (
        <div className="TodoApp">
          <BrowserRouter>
            <Routes>
              {/* path="접속URL" , element={<URL접속 시 표시할 Component명/>}  */}
              <Route path="/login" element={<LoginComponent/>}/>
              <Route path="/welcome" element={<WelcomeComponent/>}/>
              {/*  구현된 Route의 URL이 아닌 경우 다음으로 연결  */}
              <Route path="*" element={<ErrorComponent/>}/>
            </Routes>
          </BrowserRouter>
        </div>
      );
    }

    ▶ 상단 <Route>path 속성으로 구현된 URL ( /login , /welcome ) 이 아닌 경우의 모든 URL은 path="*"를 통해 접근되어 <ErrorComponent/>을 표현.

    React react-router-dom에서 제공하는 요소
    import { 요소 } from 'react-router-dom';

    • <BrowserRouter>
      React Router의 최상위 Component로서 React Application에 Routing 기능을 제공.
      History API를 활용하여 URL을 관리하여 브라우저의 뒤로가기 / 앞으로가기가 구현.
      。페이지 새로고침 없이도 URL 변경이 가능.

    • <Routes>
      <Route>의 Container 역할 수행
      ▶ 여러 개의 <Route>를 묶어서 URL에 따라 어떤 Component를 Rendering할지 결정.

    • <Route>
      。개별적인 URL 경로를 정의하며 해당 URL 경로에 맞는 Component를 Rendering.
      ▶ 모든 <Route><Routes> 안에 있어야한다.
      <Routes>에 많은 가 구현되어도, Client가 전송한 URL에 해당하는 <Route>의 Component만 Rendering.

      <Route> 속성
      <Route path="/URL" element={<Component />} />

      • path="접속URL" :
        。URL 경로를 지정.
        path="*"를 설정하여 구현되지 않은 URL로 접속 시 Error Component를 표현할 수 있다.

      • element={<URL접속 시 표시할 Component명/>} :
        。해당 URL 경로일때 표시할 Component를 지정.

      • index :
        。기본적으로 표시될 <Route>를 지정
        ex ) <Route index element={<Component />} />


    • useNavigate() :
      React Component에서 Page를 이동하거나 현재 URL에서 특정 URL로 변경 시 사용하는 Hook
      History API를 사용하여 페이지 새로고침 없이 Routing하도록 설정.

      const navigate객체 = useNavigate()를 선언 후, navigate객체("연결할URL")을 통해 연결할 URL을 정의.
      ▶ 연결할URL에 추가적으로 URL Parameter을 작성 및 리디렉션 시 다른 Component에 useParams()를 이용하여 변숫값을 전달하는 방법으로 사용할 수 있다.
      navigate객체(`/URL/${변수}`)템플릿 리터럴로 문자열에 변수를 포함해 표현
      ex)
                  import {useNavigate} from 'react-router-dom'
                  const navigate = useNavigate()
                  const gotoLogin = () => {
                  	navigate("/login")
                  }
                  return (
                  	<button onClick={gotoLogin}/>
                  )
    • useParams()
      <Route>에서 element로 정의된 Component가 path="URL"URL Parameter에 접근하는 Hook
      ▶ 연결되기전의 Component에서 useNavigate를 통해 Path Variable을 포함한 URL로 변경하여 <Route>path="/URL/:사용할변수명"를 통해 element로 정의된 Component에 연결 및 변수가 동적으로 전달되도록 설정

      const useparam객체 = useParams() : { 사용할변수명:URL Parameter }key-value형태의 객체( Object )로 값을 가져올 수 있다.
      ▶ 이때, 구조분해를 통해 const { 사용할변수명 } = useParams()로 직접 참조 가능.
      객체 ( Object ) : JavaScript에서 중괄호 ( { } )로 감싸진 하나 이상의 key : value 쌍을 포함하는 배열 형식.
      Object Literal : 여러가지 변수를 포함한 객체를 중괄호 { } 를 통해 표현하는 방식.
      • useNavigate, useParams 를 활용해 <Route>를 중계하여 URL에 Path Variable에 변수값을 포함하여 Component로 동적으로 전달하기
        。변수값을 전송하는 Component에서 useNavigate()를 선언 후 현재 URL에서 변수값을 포함한 해당 URL로 변경.
                    import { useNavigate } from 'react-router-dom'
                    const navigate = useNavigate()
                    navigate(`/URL/${전달할변수}`)   

      。Component의 Routing을 담당하는 Container Component에서 <Route>path="/URL/:사용할변수명"을 설정하여 element의 Component로 Routing 및 변수 전달.
      Navigate객체("URL") 또는 사용자가 직접 브라우저에 URL Parameter를 포함한 URL을 전송 시 해당 URL Parameter:사용할변수명으로 전달.

      템플릿 리터럴 : JavaScript ES6 버전 이후 console.log(`${변수}`); 으로 백틱(`)을 이용해 감싸면 변수를 문자열 내에 호출해서 사용이 가능.

               <Route path="/URL/:사용할변수명" element={<특정Component/>}/>

      URL Parameter이 포함된 URL를 전송 시 <Route>element 속성으로 Routing된 Component에서 useParams()를 선언하여 URL Parameter를 받아오기.
      ▶ 이때 const params = useParams() 선언 시 params{ 사용할변수명 : 전달할변수 }Object객체

      import {useParams} from 'react-router-dom'
        const { 사용할변수명 } = useParams()
        return(
          <div>
            { 사용할변수명 }
          </div>
        )
      }

      useNavigate 뿐만 아닌, 사용자가 직접 브라우저에 localhost:3000/URL/변수를 전달해도 useParams()를 통해 Component에 표현됨.



    • <Link>
      hyperlink를 정의하여 React Router의 페이지간 Routing을 처리.
      ▶ HTML의 <a>와 유사한 역할을 수행하지만, URL을 통한 React Application 내부의 Component 탐색에 사용하며 전체 페이지가 아닌 특정 Component만 새로고침.
      <Link to="/todos">이동</Link>

    • <Navigate to="/URL">
      。Browser를 특정 URL경로로 Redirect하여 페이지 이동 시 사용.
    import { Navigate } from "react-router-dom";
    const Home = () => {
      return <Navigate to="/URL" />;
    };
    
    > **`<Navigate>` 속성** 
    >- **`replace`** : 
                      。선언 시 브라우저의 History( `stack` )에 남지 않고, 뒤로가기를 눌러도 원래 페이지로 돌아갈 수 없음.

Welcome Component

  • useNavigate()를 통해 Login Component에서 Welcome Component에 전달 시 URL에 전달할 값을 포함해서 전달.
    ▶ 동적으로 로그인한 계정명을 Welcome Component에서도 표시되게 설정.

  • react-router-dom<Link> 태그를 정의하여 ListTodosComponent로 Routing된 URL로 연결하는 기능 구현.
    ▶ HTML의 <a>와 유사한 역할을 수행하지만, URL을 통한 React Application 내부의 Component 탐색에 사용하며 전체 페이지가 아닌 특정 Component만 새로고침.
    • LoginComponent에서 다른 Component에 변수값 전달하도록 useNavigate()Navigate객체("URL")의 URL에 전달할 URL Parameter를 추가 정의
      템플릿 리터럴 : JavaScript에서 ES6 버전 이후 console.log(`${변수}`); 으로 백틱(`)을 이용해 감싸면 변수를 문자열 내에 호출해서 사용이 가능.
                    // LoginComponent.jsx
    import './TodoApp.css'
    import {useState} from 'react'
    import {useNavigate} from 'react-router-dom'
    export default function LoginComponent(){
      const [ username, setUsername ] = useState("ID");
      const [ password, setPassword ] = useState("");
      const [ showSuccess, setShowSuccess] = useState(false);
      // useNavigate 객체 Hook 생성
      const navigate = useNavigate() 
      function changeUsername(evt){
        setUsername(evt.target.value)
      }
      function changePassword(evt){
        setPassword(evt.target.value)
      }
      function logInStatus(){
        if (username==="wjdtn" && password==="12345"){
          setShowSuccess(true)
          // JS의 템플릿 리터럴 기능을 사용하여 URL Parameter를 포함한 URL로 리디렉션을 수행.
          navigate(`/welcome/${username}`)   
        } else {
          setShowSuccess(false)
        }
      }
      return (
        <div>
          <div>
            {showSuccess && <div>Successfuly Log-in</div>}
              <div>
                <label htmlFor="text1" className="label">User Name</label>
                <input type="text" id="text1" name="username" className="text" value={username} onChange={changeUsername} />
              </div>
              <div>
                <label htmlFor="password1" className="label">Password</label>
                <input type="password" id="password1" name="password" className="text" value={password} onChange={changePassword}/>
              </div>
              <button type="submit" name="loginbtn" className="btn btn-secondary" onClick={logInStatus}>login</button>
          </div>
        </div>
      )
    }

    <button>의 경우 Bootstrap을 적용하기위해 className="btn-secondary"로 설정.

    • Container Component에서 <Route>path속성에 URL Parameter를 받을 수 있도록 Welcome Component에서 활용될 변수명을 추가 정의
      。Component의 Routing을 담당하는 Container Component에서 <Route>path="/URL/:사용할변수명"을 설정하여 element의 Component로 Routing 및 변수 전달.
                      // TodoApp.jsx
     import LoginComponent from './LoginComponent'
    import WelcomeComponent from './WelcomeComponent';
    import ErrorComponent from './ErrorComponent';
    import { BrowserRouter, Routes , Route } from 'react-router-dom';
    export default function TodoApp(){
      return (
        <div className="TodoApp">
          <BrowserRouter>
            <Routes>
              {/* path="접속URL" , element={<URL접속 시 표시할 Component명/>}  */}
              <Route path="/login" element={<LoginComponent/>}/>
              {/* path="/URL/사용할변수명" 으로 설정하여 URL로 전송 시 element로 설정된 Component로 연결 및 해당 명칭의 변수로 전달. */}
              <Route path="/welcome/:username" element={<WelcomeComponent/>}/>
              <Route path="*" element={<ErrorComponent/>}/>
            </Routes>
          </BrowserRouter>
        </div>
      );
    }
    • WelcomeComponent에서 useParams()를 통해 <Route>를 통해 연결된 URL의 URL Paramter받아오기
      useParams()로 값을 가져오는 경우 { 사용할변수명:URL Parameter }Object객체로 참조됨.
      ▶ 구조분해를 통해 const { 사용할변수명 } = useParams()로 직접 참조 가능
    import {useParams,Link} from 'react-router-dom'
    export default function WelcomeComponent(){
      const {username} = useParams()
      return(
        <div>
          Welcome {username}
          <div>
            {/* React의 <Link>를 사용하여 ListTodosComponent로 연결하는 URL */}
            Manage Your todos - <Link to="/todos">Go Here</Link>
          </div>
        </div>
      )
    }

    Navigate객체(`/welcome/${username}`) 뿐만 아닌, 사용자가 직접 브라우저에 localhost:3000/welcome/wjdtn를 전달해도 useParams()를 통해 Component에 표현됨.
    React<Link> 태그를 이용한 ListTodosComponent로 연결하는 hyperlink 정의
    react-router-dom에서 Import하여 사용.

                  

ListTodosComponent

  • 배열.forEach(Callback함수)배열.map(Callback함수)은 둘다 배열의 요소에 대하여 매개변수에 구현된 Callback함수를 처리하는 Method지만 forEach는 반환값이 존재하지않고, map은 반환값을 새로운 배열의 요소로 return. JS

  • React의 JSX를 사용하는 경우 JSPJSTL 라이브러리를 활용하지 않고 HTML 태그와 JS함수( ex. forEach )를 활용 가능. JSP-JSTL
    ▶ Component return ()의 JSX를 입력하는 소괄호에서 중괄호 { }를 추가한 후 JSX코드를 소괄호 ( )로 반환하는 배열.map(Callback함수) 작성.

  • React Component에서 List객체를 이용할 경우, List 객체의 데이터를 유일하게 구분하는 primary key 역할을 수행할 특정key를 선언하는게 좋다.
    <tr key={Object객체.특정key}>

  • Javascript-Date
    • Container Component에서 해당 Component의 Routing 구현
    <Route path="/todos" element={<ListTodosComponent/>} />
    • React Component에서 <table>에 배열객체의 데이터를 표현하기
    export default function ListTodosComponent(){
      // 현재 시점의 Date 객체 생성
      const today = new Date()
      // new Date()의 생성자에 특정 시점을 정의하여 특정 시점의 Date 객체 생성.
      const todos = [
        { id : 1 , description : "Learn AWS" , done : false , targetDate : new Date(today.getFullYear()+12, today.getMonth() , today.getDay()) },
        { id : 2 , description : "Learn Cloud", done : false , targetDate : new Date(today.getFullYear()+11, today.getMonth() , today.getDay())},
        { id : 3 , description : "Learn DevOps" , done : false , targetDate : new Date(today.getFullYear()+10, today.getMonth() , today.getDay())}
      ]
      return(
        <div>
          <table>
            <thead>
              <tr>
                <td>id</td>
                <td>description</td>
                <td>Is Done?</td>
                <td>target Date</td>
              </tr>
            </thead>
            <tbody>
              {
                todos.map(
                  (todo)=>
                    // 브라우저로 표현하기위해 Data를 String으로 형변환이 필요.
                    (<tr key={todo.id}>
                      <td>{todo.id}</td>
                      <td>{todo.description}</td>
                      <td>{todo.done.toString()}</td>
                      <td>{todo.targetDate.toDateString()}</td>
                    </tr>) 
                )
              }
            </tbody>
          </table>
        </div>
      )
    }

    。기존에 하드코딩으로 데이터를 Object객체로 저장한 데이터를 <table>로 표현하기 위해 <tbody> 내부에 중괄호 { }를 표현 후 데이터를 배열.map( Callback함수 )로 참조 및 포함하여 JSX코드로 Return하는 JavaScript 코드 작성.
    <th>로 표현 시 <td>보다 강조되어 표현됨
    new Date()에 생성자에 특정 시점을 설정하여 특정 시점의 Date객체 생성.

                  
  • Login , ListTodos , Welcome등의 모든 Component에 표시하도록 설정.

  • <header>가 포함하는 내용
    。로고, 제목, navigation bar , 소개문 등
    react-router-dom<Link> 태그를 정의하여 Navigation bar의 각 URL로 연결되는 Item의 hyperlink를 정의.

  • <footer>가 포함하는 내용
    。연락처정보, 저작권안내, 관련페이지 link , 소셜미디어 link, 작성자 정보 등

    • Header, Footer Component 기본구조 생성
    export function HeaderComponent(){
      return(
        <div>
          Header <hr/>
        </div>
      )
    }
    export function FooterComponent(){
      return(
        <div>
          <hr/> Footer
        </div>
      )
    }

    <hr>을 통해 가로선을 설정.

    • React에 등록된 모든 Component에 표현되도록 Container Component에서 Header, Footer Component를 배치
      <Route>가 구현된 <Routes> 요소 밖에 배치하여 브라우저를 통해 임의의 URL로 전송하여 <Route>를 통해 특정 Component에 연결하더라도 Header, Footer Component가 상시 도출되도록 배치.
      ▶ 각 Componentreact-router-dom<Link>를 사용하는 요소가 존재하므로, 반드시 <BrowserRouter> 안에 선언.
    import { BrowserRouter, Routes , Route } from 'react-router-dom';
    export default function TodoApp(){
      return (
        <div className="TodoApp">
          <BrowserRouter>
          {/* Header Component 위치 */}
          <HeaderComponent/>
            <Routes>
              <Route path="/login" element={<LoginComponent/>}/>
              <Route path="/welcome/:username" element={<WelcomeComponent/>}/>
              <Route path="/todos" element={<ListTodosComponent/>} />
              <Route path="/logout" element={<LogoutComponent/>}/>
              <Route path="*" element={<ErrorComponent/>}/>
            </Routes>
          {/* Footer Component 위치 */}
          <FooterComponent/>
          </BrowserRouter>
        </div>
      );
    }



    • Bootstrap을 활용해 Footer Component 디자인
      <footer> 태그를 최상단 wrapper 요소로 사용 및 className="footer"를 정의하여 Bootstrap CSS 사용.
    export function FooterComponent(){
      return(
        <footer className="footer">
          <div className="container">
            <hr/> Footer
          </div>
        </footer>
      ) 
    }

    CSS를 활용해 해당 FooterComponent가 Page의 최하단에 위치하도록 설정.
    ▶ Styling을 수행하는 Class는 Bootstrap의 Class를 overwrite 하는 방식.

     /* TodoApp.css */
     .footer{
      bottom:0;
      position:absolute;
      width:90%;
      height:40px;
    }
    • Bootstrap을 활용해 Header Component 디자인 Bootstrap Class 관련
      <header>태그를 최상단 wrapper 요소로 사용 및 className="header"를 정의하여 Bootstrap CSS 사용.
      Navigation bar의 item wjdtn747의 경우 외부 URL로 연결되므로 <a href="URL"> 선언.
      Home의 경우 React Application 내부 React Component로 연결되므로 <Link to="URL"> 선언.
      react-router-dom<Link>를 사용하므로, Container Component에서는 Header Component<BrowserRouter> 요소안에 선언해야한다.
    import {Link} from 'react-router-dom'
    export function HeaderComponent(){
      return(
        <header className="border-bottom border-light border-5 mb-5 p-2">
                <div className="container">
                    <div className="row">
                        <nav className="navbar navbar-expand-lg">
                            <a className="navbar-brand ms-2 fs-2 fw-bold text-black" href="https://velog.io/@wjdtn747/">wjdtn747</a>
                            <div className="collapse navbar-collapse">
                                <ul className="navbar-nav">
                                    <li className="nav-item fs-5"><Link className="nav-link" to="/welcome/wjdtn747">Home</Link></li>
                                    <li className="nav-item fs-5"><Link className="nav-link" to="/todos">Todos</Link></li>
                                </ul>
                            </div>
                            <ul className="navbar-nav">
                                <li className="nav-item fs-5"><Link className="nav-link" to="/login">Login</Link></li>
                                <li className="nav-item fs-5"><Link className="nav-link" to="/logout">Logout</Link></li>
                            </ul>
                        </nav>
                    </div>
                </div>
            </header>
      )
    }


    wjdtn747를 누를경우 <a>를 통해 외부 네트워크로 연결되므로 delay 발생.
    ▶ 반면, Home 등을 누를경우 <Link>를 통해 내부 Component로 연결되므로 delay가 거의 존재 X

    • 특정 계정으로 로그인한 정보가 포함된 Context를 다른 Component으로 전달하기
      。기존 Login Component에서 특정 계정으로 로그인 시 다른 URL의 Component로 이동할 경우 해당 ComponentLocal State 정보가 말소됨.
      ▶ 모든 자식 Component에서 참조가능한 Global State의 기능을 수행할 React Component를 생성 후 ReactcreateContext() Hook를 이용해 Context객체를 생성 및 ComponentGlobal State에 로그인 상태정보를 포함하여 다른 자식 ComponentContext객체를 export.
      ▶ 다른 Component 접속시에도 Global State의 로그인상태정보가 유지되어 해당 Context를 통한 State에 접근할 수 있게 설정.

      。 로그인 상태인 경우, { boolean && JSX코드 }를 통해 navigation bar에서 Logout버튼이 Collapse되고 Login 버튼이 지시되도록 설정.
      • Authentication Context를 생성하는 JS파일 생성
        경로 : src/components/Todo/security/AuthContext.js

        JS파일 안에 createContext()를 통해 export를 선언한 Context객체를 생성 한 후 해당 Context 객체를 통해 자식 Component에 State를 공유.
        ▶ 자식 Component는 전체 Component가 되므로, Global State라고 할 수 있다.

        <Context객체.Provider>를 통해 자식 Component에게 Context를 전달하는 Functional Component를 구현 및 Component 내부에 Global State 역할의 State 생성
        <Context객체.Provider>value={{ 전달할변수 }} 속성을 통해 다른 Component에게 Context로서 변수를 전달.
              // AuthContext.js
      import { createContext, useState } from 'react'
      // Global State 공유 목적의 export를 선언한 Context 객체 생성.
      export const AuthContext = createContext();
      // 자식 Component에게 Context를 전달하는 Component.
      // 매개변수 `{ children }`은 `Container Component`에서 wrapping된 
      // 자식 Component를 `props`를 통해 구조분해하여 전달됨.
      export default function AuthProvider({ children }){
        // Context를 통해 Global State 역할을 할 State 생성.
        const [ AuthState , setAuthState ] = useState(0);
        // State가 10초에 1번씩 State값 증가
        setInterval(()=>{
          setAuthState(AuthState+1)
        },10000)
        return( 
          // 자식 Component에게 props로 State를 제공.
          <AuthContext.Provider value={{AuthState}}>
            {children}
          </AuthContext.Provider>
        )
      }

      Functional Component의 매개변수 { children }Container Component에서 wrapping된 자식 Component를 props를 통해 구조분해하여 전달됨.
      props 참고

      createContext()를 통해 생성된 Context객체를 export시 다른 Component에서 const 새로생성할Context객체 = useContext(Context객체)로서 전달받는다.
      ▶ 새로 생성된 Context객체는 <AuthContext.Provider value={AuthState}>를 통해 전달된 State값을 Object객체 형식으로 포함. ( { AuthState : 0 } )
      이때 State의 경우 setInterval()를 통해 동적으로 10초에 한번씩 값이 증가하면서 Update됨

      • 자식 Component에게 Context를 전달하는 ComponentContainer Component에 존재하는 Component에 대하여 전부 wrapping
        。 Wrapping 되어 자식이 된 모든 Component에 일괄적으로 Context를 통한 State 제공.
        ▶ 해당 StateLocal State가 아닌, Global State가 된다.
              // TodoApp.jsx
      import AuthProvider from './security/AuthContext';
      export default function TodoApp(){
        return (
          <div className="TodoApp">
            {/* Context를 포함하는 js 파일의 Component를 State를 전달할 모든 Component에
            wrapping을 수행하여 자식 Component로 설정*/}
            <AuthProvider>
              <BrowserRouter>
              <HeaderComponent/>
                <Routes>
                  <Route path="/login" element={<LoginComponent/>}/>
                  <Route path="/welcome/:username" element={<WelcomeComponent/>}/>
                  <Route path="/todos" element={<ListTodosComponent/>} />
                  <Route path="/logout" element={<LogoutComponent/>}/>
                  <Route path="*" element={<ErrorComponent/>}/>
                </Routes>
              <FooterComponent/>
              </BrowserRouter>
            </AuthProvider>
          </div>
        );
      }

      ▶ 모든 Component가 AuthContex.jsAuthProvider Component의 자식 Component로 설정.

      • Authentication Context가 구현된 JS파일에서 Context전달Component( = <AuthProvider> )로부터 자식 Component로 전달된 Context를 받아서 활용
        createContext()를 통해 생성되어 export된 Context객체useContext(전달된Context객체)를 통해 새로운 Context객체 생성.
        ▶ 새로 생성된 Context객체<AuthContext.Provider value={AuthState}>를 통해 전달된 State값을 Object객체로 포함. ( { AuthState : 0 } )
      // react로부터 useContext를 import.
      import {useContext} from 'react'
      // Context를 포함하는 JS파일에서 Context를 Import.
      import {AuthContext} from './security/AuthContext'
      export function FooterComponent(){
        // AuthContext.js에서 전송된 Context객체를 useContext(전달된Context객체)를 통해 새로운 Context객체 생성.
        // 생성된 Context객체는  <AuthContext.Provider value={{AuthState}}>를 통해 전달된 `State`값을 ``Object객체``로 포함.
        // authContext = { AuthState : 0 }
        const authContext = useContext(AuthContext)
        return(
          <footer className="footer">
            <div className="container">
              <hr/> {authContext.AuthState}
            </div>
          </footer>
        ) 
      }


      Container Component에서 <AuthProvider>가 포함하는 모든 자식Component에서 해당 useContext(전달된Context객체)를 선언함으로써 Context로부터 State를 동적으로 전역참조할 수 있다.

      。이때, AuthContext.js에서 전달하는 React ComponentGlobal StatesetInterval()에 의해 10초에 한번씩 값이 증가하면서 Update하는데, 이를 Context를 통해 참조하는 자식 Component에서도 State가 동적으로 Update되어 반영됨.

      • 축약 : AuthContext.js에서 useContext()를 사용하여 새로운 Context객체를 생성하는 람다식으로 선언하여 전달
        Context객체를 전달받는 모든 자식Component에서 새로운 Context객체를 생성하는 단계를 중복하여 코드를 사용할 필요가 없다.
           // AuthContext.js
      import { createContext, useState , useContext} from 'react'
      // Global State 공유 목적의 export를 선언한 Context 객체 생성.
      export const AuthContext = createContext();
      // 기존 생성된 Context 객체를 전달하여 useContext()를 통해 Global State를 포함하는 Context를 생성하여 export
      export const ExportContext = () => useContext(AuthContext)
      // Context를 포함하는 JS파일에서 정의된 Context를 생성하는 함수를 Import.
      // import된 ExportContext()는 AuthContext.js에서 useContext()를 통해 전처리되어 State 변수를 포함하는 Context객체를 생성하여 return하는 함수
       import {ExportContext} from './security/AuthContext'
      export function FooterComponent(){
        const authContext = ExportContext();
        return(
          <footer className="footer">
            <div className="container">
              <hr/> {authContext.AuthState}
            </div>
          </footer>
        ) 
      }
      • import된 ExportContext()AuthContext.js에서 useContext()를 통해 전처리되어 State 변수를 포함하는 Context객체를 생성하여 return하는 함수
        Context를 전달받는 쪽에서 useContext()를 사용할 필요 없이 전달하는쪽에서 처리하여 State값을 포함한 Context를 직접 전달이 가능하다.

        Context :
        。 React에서 Context API를 통해 Component Tree 전체에 Global State를 전달
        React Component들이 props를 전달하지 않아도 데이터를 공유할 수 있게하는 역할을 수행.
        Props Drilling 방지

        Props Drilling : 중간 Component가 불필요한 props 전달.

        ContextGlobal State를 통해 React ApplicationLogin State 유지 등의 작업을 수행 가능.

        Context 관련 기능
        import { Context관련기능 } from 'react'

        • createContext() :
          export const Context객체 = createContext();
          。React에서 Global State를 관리할때 사용하는 Context 객체를 생성하는 Hook
          ▶ 해당 Context 객체는 Component Tree 전체에 Global State를 전달하기 위한 Context API가 구현됨.
          ▶ 다른 Component에서 Context를 참조하기위해 Context객체 앞에 export를 선언.

        • <Context객체.Provider> :
          。다른 React ComponentGlobal State를 제공하는 역할을 수행.
          • value={{ State변수 }} :
            Context를 통해 데이터를 Component Tree로 전달하는 역할을 수행.
            value 속성에 전달할 데이터를 정의하면 모든 자식 Component가 데이터에 전역적으로 접근할 수 있음.

            value의 값은 Object type으로 설정해야하며 중괄호에 Component에 전달할 변수를 포함하여 Context로서 자식Component로 전달.

            value 속성 설정 시 다른 Component에서 useContext(전달된Context객체)를 통해 새로운 Context객체 생성 시 { State변수 : State변수값 } 으로 생성됨.
            value={{ 변수1,변수2,... }}로 전달 시 새로 생성된 Context객체는 { 변수1 : 변수1값 , 변수2 : 변수2값 , ... }로 전달되어 생성됨.


        • useContext(전달된Context객체) :
          Context의 데이터를 직접 가져오는 역할의 React Hook
          ▶ 다른 JS파일로부터 export된 Context객체를 매개변수로 전달받아 새로운 Context객체를 생성.

          。 새로 생성된 Context객체<기존Context객체.Provider value={{State변수}}>를 통해 전달된 State값을 Object객체 형식으로 포함. ( { State변수 : State변수값 }
          ▶ 해당 방식으로 모든 자식 Component에서 Context의 데이터를 전역 데이터로서 접근 가능.

          Context를 전달 시 props를 활용한 전달방식을 사용하지 않아도된다.


      • Context를 포함하는 JS파일에서 Component에 로그인 상태정보를 저장하는 State( AuthState ) 활용
        。자식 Component LoginComponent에서 해당 인증여부의 State값을 변경해야하므로 State변경함수<Context객체.Provider value={{State변경함수}}>로 참조 되게끔 설정.
                  // AuthUsed.js
      import { createContext, useState , useContext} from 'react'
      export const AuthContext = createContext();
      export const ExportContext = () => useContext(AuthContext)
      export default function AuthProvider({ children }){
        // Context를 통해 Global State 역할을 할 로그인상태정보를 포함하는 State 생성.
        const [ AuthState , setAuthState ] = useState(false);
        // <Context객체.Provider>를 통해 자식 Component에게 전달하기위해 중괄호로 포함.
        // LoginComponent쪽에서 setAuthState()를 통해 로그인상태정보 변경.
        return( 
          // 자식 Component에게 <Context객체.Provider>의 value 속성으로 State값과 State변경함수를 전달.
          <AuthContext.Provider value={ {AuthState, setAuthState}}>
            {children}
          </AuthContext.Provider>
        )
      }

      useContext()를 통해 Global State를 포함하여 생성된 Context객체를 모든 자식Component에 export하여 전달.
      StateState변경함수를 포함한 Context객체를 생성하여 Object로서 자식 Component에 전달.

                   // LoginComponent.jsx
      import { ExportContext } from './security/AuthContext'
      export default function LoginComponent(){
        const [ username, setUsername ] = useState("ID");
        const [ password, setPassword ] = useState("");
        const [ showSuccess, setShowSuccess] = useState(false);
        const navigate = useNavigate() 
        // Context에서 Global State를 포함 및 생성되어 export한 Context객체를 생성.
        const AuthContext = ExportContext();
        function changeUsername(evt){
          setUsername(evt.target.value)
        }
        function changePassword(evt){
          setPassword(evt.target.value)
        }
        function logInStatus(){
          if (username==="wjdtn" && password==="12345"){
            setShowSuccess(true)
            navigate(`/welcome/${username}`)   
            // Context객체에 포함된 AuthState변경함수를 통해 로그인 시 로그인상태정보로서 true로 Context의 State를 Update.
              AuthContext.setAuthState(true)
          } else {
            // 로그아웃 시 false로 Context의 State를 Update.
            AuthContext.setAuthState(false)
            setShowSuccess(false)
          }
          console.log(AuthContext.AuthState)
        }
        return (jsx코드)
        }
      }

      AuthContext.js의 Context생성함수 ( = ExportContext() )를 통해 State가 포함된 Context객체 생성 후 로그인 시 Context객체State변경함수를 통해 Statetrue로 설정.
      ▶ 로그아웃 시에는 false로 설정.

      useNavigate() , useContext()등의 react hook를 사용하여 객체 생성 시 Component 내부 Scope에서 선언.
      Component의 특정함수 내에서 객체를 선언하면 안된다.

      • 로그인 상태에 따라서 HeaderComponentNavigation bar의 특정 item 숨기거나 표시
        AuthContext.js의 로그인상태를 의미하는 boolean의 State값을 Context객체를 통해 가져온후 State값에 따라 item의 표현 여부 결정.
        LoginComponent를 통해 로그인 시 AuthState.jsState값이 true로 설정되고, 새로고침할 경우, AuthState.jsState값이 false로 설정.

        { boolean && JSX코드 }를 활용하여 JSX 태그요소를 동적으로 숨기거나 표현할 수 있다.

        。Logout을 수행하는 함수를 HeaderComponent 내부에 구현 후 <Link>onClick={함수명}을 정의.
       function Logoutfunction(){
          AuthContext.setAuthState(false);
        }

      ▶ Logout 함수는 Context객체로 전달되는 State변경함수를 이용하여 Logout기능 구현.

                    // HeaderComponent.jsx
      import {Link} from 'react-router-dom'
      import {ExportContext} from './security/AuthContext'
      export function HeaderComponent(){
        const AuthContext = ExportContext()
        function Logoutfunction(){
          AuthContext.setAuthState(false);
        }
        return(
          <header className="border-bottom border-light border-5 mb-5 p-2">
                  <div className="container">
                      <div className="row">
                          <nav className="navbar navbar-expand-lg">
                              <a className="navbar-brand ms-2 fs-2 fw-bold text-black" href="https://velog.io/@wjdtn747/">wjdtn747</a>
                              <div className="collapse navbar-collapse">
                                {/* State가 true일때 Navigation bar에 Home, Todos Item이 표현. */}
                                {AuthContext.AuthState && <ul className="navbar-nav">
                                      <li className="nav-item fs-5"><Link className="nav-link" to="/welcome/wjdtn747">Home</Link></li>
                                      <li className="nav-item fs-5"><Link className="nav-link" to="/todos">Todos</Link></li>
                                  </ul>}
                              </div>
                              <ul className="navbar-nav">
                                  {/* State가 false일때 Navigation bar에 Login , true일때 Logout이 표현. */}
                                  {!AuthContext.AuthState && <li className="nav-item fs-5"><Link className="nav-link" to="/login">Login</Link></li>}
                                  {AuthContext.AuthState && <li className="nav-item fs-5">
                                    {/* onClick 이벤트로 클릭 시 Logoutfunction()를 통해 Logout. */}
                                    <Link className="nav-link" to="/logout" onClick={Logoutfunction}>Logout</Link></li>}
                              </ul>
                          </nav>
                      </div>
                  </div>
              </header>
        )
      }


      AuthContext.jsAuthProvider Component의 State = (AuthState) 가 false일때 다음 화면 도출.

      AuthContext.jsAuthProvider Component의 State = (AuthState) 가 true일때 다음 화면 도출.

      Logout 버튼을 누를 경우 AuthContext.jsAuthProvider Component의 State = (AuthState) 가 false로 설정.



    • URL로 입력하여 접속 시 로그인상태에 따라 제한하여 React <Route> 보호
      。로그인 관련 기능을 구현하더라도, 로그인 여부에 관계없이 URL경로로 접속 시 React Application의 아무 Component로 접근이 가능.
      ▶ 사용자가 로그인 했을 경우에만 특정 Component에 접근하도록 설정하기.
      • 하드코딩 로그인, 로그아웃 Logic을 AuthContext.js로 Refactoring
        LoginComponent.jsxlogInStatus()의 로그인 기능을 AuthStatus.jsAuthProvider Component의 함수로서 refactoring
        HeaderComponent에서 사용하는 Logoutfunction()AuthContext.js로 refactoring
        ▶ 해당 함수들을 <Context객체.Provider>를 통해 export.

        。모든 인증관련 기능을 AuthContext.js에 집중하여 다른 Component에 State변경함수를 전달하지 않고 로그인, 로그아웃 관련 기능 구현.
        <Context객체.Provider>로 다른 Component에 Context로서 전달할 변수는 {AuthState , logIn , Logoutfunction}
        // AuthContext.js
      import { createContext, useState , useContext} from 'react'
      export const AuthContext = createContext();
      export const ExportContext = () => useContext(AuthContext)
      export default function AuthProvider({ children }){
        const [ AuthState , setAuthState ] = useState(false);
        // Login Component에서 하드코딩 Login Logic만 구현.
        function logIn(username, password){
          if (username==="wjdtn" && password==="12345"){  
            setAuthState(true)
            return true
          } else {
            setAuthState(false)
            return false
          }
        }
        // Logout Logic 구현
        function Logoutfunction(){
          setAuthState(false);
        }
        return( 
          <AuthContext.Provider value={ {AuthState,logIn,Logoutfunction} }>
            {children}
          </AuthContext.Provider>
        )
      }
             // LoginComponent.jsx
      import './TodoApp.css'
      import {useState} from 'react'
      import {useNavigate} from 'react-router-dom'
      import { ExportContext } from './security/AuthContext'
      export default function LoginComponent(){
        const [ username, setUsername ] = useState("ID");
        const [ password, setPassword ] = useState("");
        const [ showSuccess, setShowSuccess] = useState(true);
        const navigate = useNavigate() 
        // Context에서 Global State 및 기타 함수를 생성하여 export한 Context객체를 생성.
        const AuthContext = ExportContext();
        function changeUsername(evt){
          setUsername(evt.target.value)
        }
        function changePassword(evt){
          setPassword(evt.target.value)
        }
        // AuthContext.js의 로그인 함수에 접근하여 State를 통해 Binding한 ID, PW를 매개변수로 전달 후 Boolean 값을 return.
        function logInStatus(){
          if (AuthContext.logIn(username,password)){
            setShowSuccess(true)
            navigate(`/welcome/${username}`)   
          } else {
            setShowSuccess(false)
          }
        }
        return (
          <div>
            <div>
              {!showSuccess && <div>Failed Login</div>}
                <div>
                  <label htmlFor="text1" className="label">User Name</label>
                  <input type="text" id="text1" name="username" className="text" value={username} onChange={changeUsername} />
                </div>
                <div>
                  <label htmlFor="password1" className="label">Password</label>
                  <input type="password" id="password1" name="password" className="text" value={password} onChange={changePassword}/>
                </div>
                <button type="submit" name="loginbtn" className="btn btn-secondary" onClick={logInStatus}>login</button>
            </div>
          </div>
        )
      }

      。기존 LoginComponentlogInStatus()의 인증 기능은 AuthContext.js로 이전하여 Component 관련 Logic만 수행.

      • Container Component에서 <Route>element 속성의 Component를 자식Component로 wrapping하여 인증보호를 수행하는 Component ( = AuthenticatedRoute ) 생성
        <Route path="/welcome/:username" element={<AuthenticatedRoute>
                    <WelcomeComponent/>
                    </AuthenticatedRoute>}/>

      。해당 Component는 <Route>element속성으로 설정된 Component를 Wrapping하여 자식 Component로 설정.

       function AuthenticatedRoute({children}){
        const AuthContext = ExportContext()
        if (AuthContext.AuthState){
          return ( children )
        } else {
          return ( <Navigate to="/login"/> )
        }
      }

      。 해당 Component의 매개변수를 { 자식Component }로 설정하여 로그인된 State일 경우 return( 자식Component )<Route>element로 지시되도록 설정.
      ▶ 로그아웃 State인 경우, <Navigate to="/login" />으로 LoginComponent로 Routing을 수행.

      import LoginComponent from './LoginComponent'
      import WelcomeComponent from './WelcomeComponent';
      import ErrorComponent from './ErrorComponent';
      import ListTodosComponent from './ListTodosComponent';
      import {HeaderComponent, FooterComponent} from './HeadFootComponent'
      import LogoutComponent from './LogoutComponent';
      import './TodoApp.css'
      import { BrowserRouter, Routes , Route , Navigate } from 'react-router-dom';
      import AuthProvider from './security/AuthContext';
      import { ExportContext } from './security/AuthContext';
      // 자식Component를 매개변수로 가져오는 Component
      // Global State에 의해 로그인 상태인 경우 자식 Component를 그대로 전달하고 로그아웃 상태인 경우 LoginComponent로 routing.
      function AuthenticatedRoute({children}){
        const AuthContext = ExportContext()
        if (AuthContext.AuthState){
          return ( children )
        } else {
          return ( <Navigate to="/login"/> )
        }
      }
      export default function TodoApp(){
        return (
          <div className="TodoApp">
            <AuthProvider>
              <BrowserRouter>
              <HeaderComponent/>
                <Routes>
                  <Route path="/login" element={<LoginComponent/>}/>
                  {/* <Route>의 element 속성의 Component를 AuthenticatedRoute Component로 
                  wrapping하여 자식 Component로 설정하여 매개변수로 전달. */}
                  <Route path="/welcome/:username" element={<AuthenticatedRoute>
                    <WelcomeComponent/>
                    </AuthenticatedRoute>}/>
                  <Route path="/todos" element={<AuthenticatedRoute>
                    <ListTodosComponent/>
                    </AuthenticatedRoute>} />
                  <Route path="/logout" element={<AuthenticatedRoute>
                    <LogoutComponent/>
                    </AuthenticatedRoute>}/>
                  <Route path="*" element={<ErrorComponent/>}/>
                </Routes>
              <FooterComponent/>
              </BrowserRouter>
            </AuthProvider>
          </div>
        );
      }

      ▶ 이후 URL로 localhost:3000/todos를 입력하더라도 <Route>element의 보호목적으로 생성한 <AuthenticatedRoute>에 의해 보호되어 AuthContext.jsGlobal Statetrue가 아니면, Login Component로 연결됨.

profile
공부기록 블로그

0개의 댓글