본문 바로가기

SK 쉴더스 루키즈 지능형 AI 애플리케이션 개발자 트랙 2기/강의

[SK shieldus 지능형 애플리케이션 개발 트랙 2기] 4 (react_2)

반응형
/**
 * useState 사용
 */
// 1. 모듈 가져오기
import './App.css';
import {
    useState,       // 상태변수->화면갱신
    useEffect,      // 중요한 생애 주기 함수 대체(부분)
    useMemo,        // 데이터     
    useCallback,    // 함수
    useRef,         // 데이터, 변경되어도 랜더링 되지 않는다, 참조
    useContext,     // 여러 컴포넌트가 접근하여 사용하는 데이터 관리    
} from "react";

// 2. 대표 모듈화 + 커스텀 컴포넌트 정의 ( 객체형 | (*)함수형 )
export default function App ( props ) {
    // 함수형 컴포넌트 기본 로직, 변수, 함수등 구현하는 위치
    console.log( '함수형 컴포넌트 구성' );
    
    // A-1. 상태변수 초기화 => useState 훅 활용
    // const|let [ 변수명, set변수명 ] = useState( 초기값 )
    // set변수명 는 생략가능함 => 업데이트 않함
    const [ count, setCount ] = useState( 1 ); // xxx(초기값);

    // A-3. 상태변수 업데이트 및 동적 JSX 구성
    const countJsx = (
        <>
            {/* 
                - 버튼을 클릭하면 상태변수 +1증가 -> 화면갱신 
                - 실습 : 클릭이벤트 등록, inline 방식으로 이벤트핸들러 등록
                - 1분                
            */}
            <button onClick={ ()=>{ setCount( count+1 ) } }>
                카운트 : { count }
            </button>
        </>
    );

    // B-1. useState를 이용한 조건부 랜더링(null 검토)
    // 통신을 통해 데이터를 받아서 세팅할때 -> 동적 화면 처리
    // JSX의 세팅값 null이면 처리 x
    // 실습 뒤쪽 항목 채우기
    const [ pageContent, setPageContent ] = useState( null );

    // B-2. JSX 생성
    // div 요소 준비, onClick 이벤트 등록, 화살표함수 이벤트핸드러 등록
    // 내용은 pageContent = [ {pageContent} ]
    const condiJsx = (
        <>
            {/* 클릭하면 => pageContent이 업데이트됨 */}
            <div onClick={ ()=>{ 
                // 값을 번갈아 세팅 : null -> '내용세팅됨' ->null ..
                setPageContent( pageContent ? null : '내용세팅됨' )  
            } }>
                {/* null  자체는 랜더링 x */}
                pageContent = [ {pageContent} ]
                <br/>
                {/* null 조건식에 위치하면 무조건 false */}
                { pageContent ? "참일때값" : "거짓일때값" }
            </div>
        </>
    );

    // JSX 반환
    return (
        <div className='App'>
            <div className='App-header'>
                작성
                <br/>
                {/* useState 
                    A-2. 상태변수 사용
                */}
                { countJsx }
                
                {/* B-2, 상태변수 사용 - 조건부랜더링 */}
                { condiJsx }
            </div>
        </div>
    );
}​
# 프로젝트 구조
frontend
┠ react        : 리액트
┃  ┗ basic-cdn
┃      ┗ *.html: 리액트 기본
┃      ┗ readme.MD: 리액트 개요, 설명
┃  ┗ basic-npm
┃      ┗ demo
┃          ┗ public : 리소스
┃          ┗ src
┃              ┗ *.* : npm 기반 react 
┃          ┗ package.json
┃      ┗ (*)hook-test
┃          ┗ public : 리소스
┃          ┗ src
┃              ┗ *.* : npm 기반 react 
┃          ┗ package.json
┃      ┗ esm <- nodejs 기본 구조
┃          ┗ *.js    : 소스
┃          ┗ package.json : 프로젝트 정보
┠ js           : 자바스크립트
┃  ┗ *.js      : 기본 문법 적용 코드  	
┃  ┗ readme.MD : JS 개요 및 문법 설명  	
┣ css
┣ html         : html
┃  ┗ data      : 더미 데이터
┃  ┗ *.html    : html 규칙 적용 코드
┃  ┗ readme.MD : html 설명
┗ readme.MD : 웹 이용 및 서비스 이해

┗react

   ┗app-npm

         ┗src

              ┗App.js 

커스텀 컴포넌트, 홈페이지 담당(의미를 부여)

커스텀 컴포넌트, 하나의 화면(로그인..)|하나의 모듈(버튼|게시판)|구조

 

1. 리소스(이미지, css) 가져오기, 개발자 의도에 따라 달라질 수 있음

import logo from './logo.svg';
import './App.css';

 

LifeCycle.js로부터 대표 모듈 가져오기 
대표모듈명 => LifeCycle 지정

import LifeCycle from './LifeCycle';

 

대표 모듈을 제외한 private한 컴포넌트들은 여기에 같이 존재할수 있다

 

2. 함수형 컴포넌트 -> 커스텀 JSX를 생성, 특정기능 가지고 있다(대문)

function App() {
  // JSX를 반환
  return (
    <div className="App">
      {/* 요소에 css 적용중 class 적용 예시 className="클레스값" */}
      <header className="App-header">
        <MySelect languages={ ['자바','자바스크립트','리액트'] } 
                  initValue='리액트' />
        <MyCheckBox label="연말에 여행을 갈까?"/>
        <MyInput/>
        {/* <LifeCycle/> */}
        <img src={logo} className="App-logo" alt="logo" />
        <p>SPA기반 react로 구성한 홈페이지</p>        
      </header>
    </div>
  );
}

현 파일에 대표!!
3. 대표 모듈화 처리 -> App 컴포넌트는 외부에서 사용 가능하다!!

export default App;

index.js

 

엔트리 포인트(진입로)
가급적 수정 x, 커스텀 컴포넌트 수정 OK

ES Module (ESM) 방식의 모듈 가져오기 문법
1. 모듈 가져오기

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';

커스텀 컴포넌트
대표모듈명으로 App을 지정

import App from './App';
import reportWebVitals from './reportWebVitals';

 

 2. 아이디가  root인 요소와 동일한 형태의 VDOM을 만드는 과정
    아이디가 roor인 요소를 특정하여 스왑처리를 위한 v-dom 생성

const root = ReactDOM.createRoot(document.getElementById('root'));


 React.StrictMode : 차후 체크!! -> 2번 수행된다!!
 편의상 조정 React.StrictMode -> div

root.render(
  <div>
    {/* React.StrictMode : 잠재된 오류를 체크, 개발시 사용 */}
    <App />
  </div>
);

reportWebVitals();

 


LifeCycle.js


 컴포넌트의 생애주기 확인
  - 컴포넌트의 내부 구동 매커니즘 및 워크플로우 이해
  - 객체형 컴포넌트 구성
  - 훅이라는 주제부터 함수형 컴포넌트로 전환
       - 훅을 통해서 생애주기와 비슷하게 처리할수 있는 기능제공
  - react 16부터 지원  => 함수형 컴포넌트로 개발 패더라임 전환

 1. 모듈가져오기

import React, { Component } from "react"; // react 모듈로부터 가져온다


 Component 는 개별모듈
 React는 대표모듈 ( export default XX; )


 2. 클레스형 컴포넌트 구성

class LifeCycle extends Component{
    constructor( props ) {
        super( props );
        // 상태변수
        this.state = {
            r:Math.random() // 난수값을 상태변수로 세팅
        }
    }
    render () {
        const { r }= this.state;
        return (
            <>
                라이프사이클!! { r }
                <button onClick={ ()=>{
                    // 버튼을 누르면 -> 상태변수 업데이트 -> .. -> 화면갱신
                    this.setState( { r:Math.random() } );
                } }>상태 변경</button>
            </>
        );
    }


    생애 주기 함수 추가 -> 참고용!!
     1. 컴포넌트가 마운트 되기전 => 컴포넌트가 화면에 보이기 직전 타이밍

  componentDidMount () {
        // 재정의
        // 이런 타이밍에 뭔가 작업할게 있다면 사용, 없다면 미사용
        console.log('1. 컴포넌트가 화면에 보일려고 한다');
    }


     2. 컴포넌트가 마운트 된후 화면 갱신시 발생되면 호출
        화면 갱신 감지, 이 타이밍에 작업할것 필요하면 사용, 아니면 미사용

    shouldComponentUpdate (nextProps, nextState, nextContext) {
        // 갱신되고자 하는 컴포넌트의 요소들이 전달!!
        console.log( '2. 변경되려고 한다 shouldComponentUpdate()', 
                    nextProps, nextState, nextContext );        
        return true;
    }


     3. 컴포넌트 업데이트 완료

    componentDidUpdate () {
        console.log( '3. 컴포넌트 업데이트 완료' );
    }


    화면갱신 워크플로우
     상태변수 수정 -> shouldComponentUpdate() 
                  -> render() -> componentDidUpdate()

     4. 컴포넌트 마운트 해제-> 컴포넌트가 사라질려고 한다!!

    componentWillUnmount () {
        // 뒷정리 코드를 발동 -> 이벤트 해제, 리소스 해제등등 정리
        // 필요하면 구현
        // 라우트(라우팅처리)를 통해서 화면 전환 되면 그때 확인!!
        console.log("4. 컴포넌트가 사라질려고 한다!!");
    }

}


 3. 대표 모듈화 구성

export default LifeCycle;

 


MyCheckBox.js

 


 소스 붙여 넣기 이후 -> 자동 간격 맞추기
      => Ctrl + A => Ctrl(누른채로) K->F 연속입력

 1. 모듈 가져오기

import React, { Component } from "react";



 2. 커스텀 컴포넌트 구현 ( (*)객체형 | 함수형 )

class MyCheckBox extends Component {
  constructor(props) {
    super(props);
    // 1. 상태변수 초기화
    this.state = {
        checked:false
    }
  }
  render() {
    // props 분해
    const { label } = this.props;
    // 2. 상태변수 사용-분해
    const { checked } = this.state;
    return (
        <div>
            {/* 선택여부를 상태변수로 저장 */}
            {/* 2. 상태변수 사용-실사용 */}
            <input  type     = 'checkbox' 
                    checked  = { checked }            
                    onChange = { ()=>{ 
                        // 이벤트 함수 직접 구현 -> 체크값 부정
                        this.setState( { checked:!checked } ) 
                    } }
            /> { label }
        </div>
    );
  }
}


3. 커스텀 컴포넌트의 대표 모듈화

export default MyCheckBox;

 


MyInput.js

/**
 * 커스터 컴포넌트
 * 숫자만 입력받는 입력 요소 구성
 * <input type='number' ... /> <= 모바일에서 숫자 키패드로 등장
 * PC 버전에서 사용시 숫자만 입력되게 구성
 */

// 1. 모듈 가져오기
import React, { Component } from "react";

// 2. 커스텀 컴포넌트 구현 ( (*)객체형 | 함수형 )
class MyInput extends Component {
    constructor(props) {
        super(props);
        // 4. 상태변수 생성
        this.state = {
            inputValue:'' // 사용자가 입력한 값을 담을 변수
        }
    }
    // 3. 컴포넌트의 커스텀 맴버 함수
    onChangeHandlerMember (evt) {
        let ori_text = evt.target.value;
        console.log( '입력', ori_text );
        // 요구사항 : 숫자만 입력 받는 컴포넌트!! , 해결
        // 입력값 => 정규식(데이터 전처리 활용) => 수치만 추출
        
        // 정규식 적용하여 필터링
        // 문자열.replace( 대상 | 정규식, 대체문자열)
        //  : 특정 문자열의 특정 표현을 지정한형식으로 대체
        // 숫자가 아닌 문자를 모두 특정해서 -> '' 문자열로 대체
        /*
           정규식
           - /정규식/g : 앞뒤 마크는 /, /g <= 정규식을 사용하겠다 
           - [문자등 정규식] : 문자 1개를 표현
           - [0-9] : 0,1,2,...,9까지 표현(숫자중 10진수 표현)
           - [^0-9] : 0,1,..,9를 제외한 모든 문자 :숫자를 제외한 모든문자
           - 한글 => [ㄱ-ㅎㅏ-ㅣ가-힣]
        */
        // replace()는 모조리 다 찾아서 바꿔라
        const value = ori_text.replace( /[^0-9]/g, '' ); // 수치만획득
        console.log( value );

        // 6. 상태변수 업데이트 -> ... -> render() 호출 -> .. -> 화면갱신
        //    수치값만 세팅
        this.setState( { inputValue:value } );
    }
    // 9. 전송 처리 메소드(함수) 구현
    onSubmitHandler (evt) {
        // 전송 이벤트 무효화
        evt.preventDefault();
        // 확인
        console.log( '전송', this.state.inputValue );
        // 서버와 통신 (생략)
        // 입력창 초기화 => 실습 1분 => .... -> 화면갱신
        this.setState( { inputValue:'' } );
    }
    render () {
        // 2. 이벤트 함수
        // evt : 이벤트 객체가 이벤트가 발생하면 전달됨
        // const onChangeHandler = (evt)=>{
        //     // 맴버 접근 : this.맴버
        //     this.onChangeHandlerMember(evt);
        // };
        // render()내에 함수 내에서 맴버함수를 호출
        // 맴버 함수내에서 this 접근 문제를 해결하기 위해(2가지 방안존재)
        const onChangeHandler = evt => this.onChangeHandlerMember(evt);
        const { inputValue } = this.state;

        // 8. 이벤트 등록
        const onSubmitHandler = evt => this.onSubmitHandler(evt);
        return (
            <div>
                {/* 전송 클릭 처리(이벤트), 숫자만 입력(이벤트) */}
                {/* 7. 전송이벤트 등록, onSubmitHandler 구현 실습 1분 */}
                <form onSubmit={ onSubmitHandler }>
                    {/* 1. 입력->값이변했다->onChange */}
                    {/* 5. 상태변수 사용 value 속성 */ }
                    <input  type="text" 
                            onChange={ onChangeHandler }                            
                            value={ inputValue }
                            placeholder="숫자만 입력"/>
                    <input type="submit" value="전송"/>
                </form>
            </div>
        );
    }
}
// 3. 커스텀 컴포넌트의 대표 모듈화
export default MyInput;

MySelect.js

/**
 * - 사용자 정의 select 구현
 * - option 동적 구성
 * - select 상에 기본 선택값 구현 -> initValue
 *      - 상태변수 사용!!
 * - 상태변수 변경!! -> 완성
 */
// 1. 모듈 가져오기
import React, { Component } from "react";

// 2. 커스텀 컴포넌트 구현 ( (*)객체형 | 함수형 )
class MySelect extends Component {
  constructor(props) {
    super(props);
    // 10-3. 상태 변수 초기화
    this.state = {
        selLanguage:this.props.initValue // 컴포넌트 사용시 세팅값
    }
  }
  render() {
    // 10-1. 데이터 획득 (props) 
    const { languages, initValue } = this.props;
    console.log( languages ); // 배열임을 확인
    
    // 매우 빈번하게 등장하는 패턴!!
    // 10-2. 배열 데이터 => map() => 새로운 JSX를 리스트로 구성 =>타 JSX 반영
    const options = languages.map( (language, idx)=>{
        // 동적으로 데이터 + JSX 결합하여 반환 -> 새로운 배열 생성
        return (
            <option value={language} key={idx}>{language}</option>
        );
    } );

    // 10-4. 상태 변수 사용-추출
    const { selLanguage } = this.state;

    // 10-5. 상태 변수 업데이트
    const onChangeHandler = e => {
        // 사용자가 선택한 값으로  select 선택값 변경
        // 선택 => 변경 => 상태변수 변경 => .. => render() => 화면갱신
        this.setState( { selLanguage:e.target.value } );
    }
    return (
        <div>
            {/* 더미 표현, 사용자 선택을 `입력` */}
            <select>
                <option value='서울'>서울</option>
                <option value='부산'>부산</option>
                <option value='제주'>제주</option>
            </select>
            <br/>
            <br/>
            {/* 
                커스텀 컴포넌트활용하여 select를 커스텀 
                - 요구사항, MySelect의 컴포넌트로 전달된 props에 
                  languages를 이용하여, 
                  select 구성 아래가 같이 나오도록 (결과 예시)                  
                - 코드를 작성하시오 실습 5분
                - 구성
                    - 반복적인것은 변수로 동적 생성
                        - 반복 -> for -> 데이터가 배열이므로 => map()
            */}
            {/* <select>
                <option value='자바'>자바</option>
                <option value='자바스크립트'>자바스크립트</option>
                <option value='리액트'>리액트</option>
            </select> */}
            
            {/* 10-4. 상태 변수 사용-실사용 */}
            <select value={ selLanguage } 
                    onChange={ onChangeHandler }
            >
                { options }
            </select>
        </div>
    );
  }
}
// 3. 커스텀 컴포넌트의 대표 모듈화
export default MySelect;

 

 


┗react

   ┗hook-test

         ┗src

              ┗Css

                  ┗CssComponent.jsx

                  ┗ index.js

                  ┗ styles.scss

               ┗ custom

                  ┗ useWindowInfo.js

               ┗ state

                  ┗ Counter.jsx

                  ┗ store.js

                  ┗ test.js

                  ┗ TwoButton.jsx

                  ┗  View.jsx

                  ┗  View2.jsx

              ┗App.js

              ┗App2.js

              ┗App3.js

              ┗App4.js

              ┗App5.js

              ┗App6.js

              ┗App6_mui.js
              ┗App7.js
              ┗App7_mui.js
              ┗App8.js

              ┗CusHook.js

              ┗DashBoard.js

              ┗index.js

              ┗Reducer.js

 

파일의 순서대로 코드 


   ┗hook-test

         ┗src

              ┗Css

                  ┗CssComponent.jsx

                  ┗ index.js

                  ┗ styles.scss

 

CssComponent.jsx

/**
 * 여러 유형의 css 적용 스타일 컴포넌트
 */
// 스타일 2번 사용을 위한 모듈가져오기
//import myStyle from "./index";
import { styled } from "@mui/material";
import myStyle from "."; // 이름이 index이면 .으로 표현 가능함

// 스타일 3번 : css styled-components 방식
// npm i styled-components
import styled_com from 'styled-components';

// 위치조정 => 스타일 4 css 작성하는 방식을 새로운 문법으로 구성하는 스타일
// scss, sass
import './styles.scss';

// Title이라는 컴포넌트를 생성, 스타일을 가지고 있다
const Title = styled_com.h2`
    color:red;
`
// Button이라는 컴포넌트를 생성, 스타일을 가지고 있다, 문법만 다르게
const Button = styled_com.button({
    color:'gray',
});



export default function CssComponent() {
    return (
        <div>
            <h3>CSS 적용 테스트</h3>
            <div style={{ backgroundColor:'lightgray', 
                          margin:'1em', padding:'1em' }}>
                스타일 1, 직접(inline) CSS 적용
            </div>
            <div style={ myStyle.styleDiv }>
                스타일 2, CSS Modules 적용 방식
                CSS를 별도의 JS로 구성하여 스타일 적용
            </div>
            <div>
                <Title>스타일 3, css styled-components</Title>
                <Button>스타일드 버튼</Button>
            </div>
            <div className="title">
                스타일 4, scss, sass 사용
            </div>

        </div>
    );
}

 

index.js

/**
 * CSS Module  전용
 *  - index.js라는 이름은 특정 디렉토리에 대표명이다(특수케이스),편의상 사용
 *  - js 문법으로 구성
 */
const myStyle = {
    styleDiv:{  // cas의 클레스와 동일한 의미
        // 내부 맴버가 CSS 의미를 가진다
        backgroundColor:'lightgreen',   // 배경색
        padding:5, // 내부 여백 (자식과 border 사이)
        margin:5,  // 외부 여백 (부모와 border 사이)
    },
    styleMargin:{
        margin:10,
    }
    // .....
}
export default myStyle;

 

styles.scss

// 스타일자체를 변수처럼 표현, $ 사용
// 스타일에 의미있는 내용을 부여하여 디자인 설계
$title-color: blue;

.title {
    color:$title-color,
}

 


   ┗hook-test

         ┗src

               ┗ custom

                  ┗ useWindowInfo.js

 useWindowInfo.js

/**
 * 커스텀 훅, 사용자 정의 훅, 필요에 의해서 생성
 *  - useXxxx 구성
 *  - 요구사항
 *      - 기존의 훅을 활용하여 새로운 기능을 가진 훅을 생성
 *      - useState, useEffect 활용
 */
// 1. 모듈 가져오기
import {
    useState, 
    useEffect
} from 'react';

// 2. 대표모듈화 + 커스텀훅 생성
const useWindowInfo = ()=>{
    // 목표: 현재 브라우저 화면(윈도우)의 정보를 획득(가로길이, 세로길이)하여 제공
    //       컴포넌트의 크기가 변경되면 같이 윈도우 정보도 변경되어 제공

    // 1. 상태변수 초기화, 현재 브라우저 정보 세팅
    const [windowSize, setWindowSize] = useState( {
        // window는 브라우저상 JS를 통해 접근 가능한 내장객체
        width : window.innerWidth,
        height: window.innerHeight
    } );

    // 2. useEffect 활용
    useEffect( ()=>{
        // 이벤트 핸들러 함수
        const 이벤트핸들러 = () => {
          setWindowSize({
            width : window.innerWidth,
            height: window.innerHeight
          })  
        }
        // 윈도우의 크기가 변경되면 resize이벤트 호출 => 콜백함수 호출 => 정보갱신
        window.addEventListener('resize', 이벤트핸들러 );
        // 뒷정리 코드 -> 훅을 더이상 사용하지 않을때
        return ()=>{
            // 반드시 브라우저상에 존재하는 윈도우 객체에 이벤트 삭제처리
            window.removeEventListener('resize', 이벤트핸들러 );
        };
    }, []);

    // 커스텀훅 상태변수 반환 <-> 컴포넌트는 JSX 반환하는 랜더함수 구성하면서 마무리
    return windowSize;
}


export default useWindowInfo;

 


   ┗hook-test

         ┗src

               ┗ state

                  ┗ Counter.jsx

                  ┗ store.js

                  ┗ test.js

                  ┗ TwoButton.jsx

                  ┗  View.jsx

                  ┗  View2.jsx

Counter.jsx

/**
 * 상태 관리 매니저를 활용한 컴포넌트 구현
 *  - 종합 화면(컴포넌트 구성)
 *  - 해당 화면을 브라우저에 로드 -> 테스트
 */

import TwoButton from "./TwoButton";
import View from "./View";
import View2 from "./View2";

export default function Counter() {
    return (
        <div>
            <p>카운트 : 전역상태관리(zustand 사용)</p>
            <p>저장소, 상태변수사용, 액션작동 하는 JS|JSX가 모두 다르다!!</p>
            <View/>
            <br/>
            <TwoButton/>
            <br/>
            <View2/>
        </div>
    );
}

 

store.js

/**
 * 전역 상태 관리를 위한 저장소, js
 *  - zustand 모듈 사용
 *  - 사용법
 *      - 1. create 함수 사용 -> 저장소 생성
 *          - 저장소 생성시, 상태변수와 액션(함수)를 정의
 */
import { create } from "zustand";

// 1. 저장소 생성 : 객체를 반환하는 콜백함수를 등록
//    set:콜백함수(내부적 정의)
const useStore = create( ( set )=>({
    // 상태변수
    count:0,

    // 액션(함수) => +1, -1 이 행위만 정의 (+버튼 클릭하면 1증가, -버튼 클릭하면 1감소)
    // 업데이트라는 것은 새로운 객체로 대체한다!!
    // +1 => 기존 상태값에 +1을 하여 새로운 객체=>{ .. }를 생성하여 대체처리
    // 화살표 함수에서 반환값이 {} 이고, 수행문이 1개라면 ()=>({ ..}) 표현한다!!
    increment:()=>set( (상태값)=>({ count: 상태값.count +1 }) ),
    decrement:()=>{
        return set( (상태값)=>{
            return { 
                count: 상태값.count -1 
            }
        } )
    }

}) );

// 2. 대표 모듈화
export default useStore;

 

test.js

() => {
  return set((상태값) => ({ count: 상태값.count - 1,}) );
};

 

 

TwoButton.jsx

/**
 * 카운트 증감(액션, 행동)을 처리하는 2개의 버튼 컴포넌트
 *  - 값증가, 값감소하는 이벤트 발생
 */
import useStore from "./store";

export default function TowButton() {
    const { increment, decrement } = useStore(); // 액션 2개 획득
    return (
        <>
            <button onClick={ increment }>+1 증가</button>
            <button onClick={ decrement }>-1 감소</button>
        </>
    );
}

 

 

View.jsx

/**
 * 카운터 값을 표현하는 컴포넌트1
 *  - 전역 상태 변수값 사용
 */
import useStore from "./store";

export default function View() {
    // 전역 상태 관리 저장소에서 상태변수(여기서는 count)만 추출
    const { count } = useStore();
    return (
        <>
            {count}
        </>
    );
}

 

View2.jsx

/**
 * 카운터 값을 표현하는 컴포넌트2
 *  - 전역 상태 변수값 사용
 */
import useStore from "./store";

export default function View2() {
    // 전역 상태 관리 저장소에서 상태변수(여기서는 count)만 추출
    const { count } = useStore();
    return (
        <>
            {count}
        </>
    );
}

 


 

   ┗hook-test

         ┗src

              ┗App.js

              ┗App2.js

              ┗App3.js

              ┗App4.js

              ┗App5.js

              ┗App6.js

              ┗App6_mui.js
              ┗App7.js
              ┗App7_mui.js
              ┗App8.js

              ┗CusHook.js

              ┗DashBoard.js

              ┗index.js

              ┗Reducer.js

 

App.js

import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

 

 

App2.js

/**
 * 함수형 컴포넌트로 구성 세팅
 */
// 1. 모듈 가져오기
// 스타일 가져오기
import './App.css';
// 훅 가져오기
import {
    // useXxxx
    useState,       // 상태변수->화면갱신
    useEffect,      // 중요한 생애 주기 함수 대체(부분)
    // -------------
    // 캐싱!! 개념 => 최적화 => 빠른 연산 및 반응속도(갱신)
    // 메모이제이션 기능 제공 -> 연산비용이 높은 작업 저장하여 재사용
    useMemo,        // 데이터     
    useCallback,    // 함수
    // -------------
    useRef,         // 데이터, 변경되어도 랜더링 되지 않는다, 참조
    // -------------
    useContext,     // 여러 컴포넌트가 접근하여 사용하는 데이터 관리
    // ...
} from "react";

// 2. 커스텀 컴포넌트 정의 ( 객체형 | (*)함수형 )
// JS 파일 내부에 컴포넌트가 여러개 정의 될수 있다!!
// 익명함수 스타일
const App2 = ( props )=>{
    // JSX 반환
    return (
        <div> 
            익명함수 스타일의 컴포넌트
        </div>
    );
}
// 표준함수 스타일
function App ( props ) {
    return (
        <div className='App'> {/* class 형태의 디자인 적용 */}
            <div className='App-header'>
                <App2/>
                표준함수 스타일의 컴포넌트
            </div>
        </div>
    );
}

// 3. 대표 모듈화 -> 오직 1개만 지정
export default App;

 

 

App3.js

/**
 * useState 사용
 */
// 1. 모듈 가져오기
import './App.css';
import {
    useState,       // 상태변수->화면갱신
    useEffect,      // 중요한 생애 주기 함수 대체(부분)
    useMemo,        // 데이터     
    useCallback,    // 함수
    useRef,         // 데이터, 변경되어도 랜더링 되지 않는다, 참조
    useContext,     // 여러 컴포넌트가 접근하여 사용하는 데이터 관리    
} from "react";

// 2. 대표 모듈화 + 커스텀 컴포넌트 정의 ( 객체형 | (*)함수형 )
export default function App ( props ) {
    // 함수형 컴포넌트 기본 로직, 변수, 함수등 구현하는 위치
    console.log( '함수형 컴포넌트 구성' );
    
    // A-1. 상태변수 초기화 => useState 훅 활용
    // const|let [ 변수명, set변수명 ] = useState( 초기값 )
    // set변수명 는 생략가능함 => 업데이트 않함
    const [ count, setCount ] = useState( 1 ); // xxx(초기값);

    // A-3. 상태변수 업데이트 및 동적 JSX 구성
    const countJsx = (
        <>
            {/* 
                - 버튼을 클릭하면 상태변수 +1증가 -> 화면갱신 
                - 실습 : 클릭이벤트 등록, inline 방식으로 이벤트핸들러 등록
                - 1분                
            */}
            <button onClick={ ()=>{ setCount( count+1 ) } }>
                카운트 : { count }
            </button>
        </>
    );

    // B-1. useState를 이용한 조건부 랜더링(null 검토)
    // 통신을 통해 데이터를 받아서 세팅할때 -> 동적 화면 처리
    // JSX의 세팅값 null이면 처리 x
    // 실습 뒤쪽 항목 채우기
    const [ pageContent, setPageContent ] = useState( null );

    // B-2. JSX 생성
    // div 요소 준비, onClick 이벤트 등록, 화살표함수 이벤트핸드러 등록
    // 내용은 pageContent = [ {pageContent} ]
    const condiJsx = (
        <>
            {/* 클릭하면 => pageContent이 업데이트됨 */}
            <div onClick={ ()=>{ 
                // 값을 번갈아 세팅 : null -> '내용세팅됨' ->null ..
                setPageContent( pageContent ? null : '내용세팅됨' )  
            } }>
                {/* null  자체는 랜더링 x */}
                pageContent = [ {pageContent} ]
                <br/>
                {/* null 조건식에 위치하면 무조건 false */}
                { pageContent ? "참일때값" : "거짓일때값" }
            </div>
        </>
    );

    // JSX 반환
    return (
        <div className='App'>
            <div className='App-header'>
                작성
                <br/>
                {/* useState 
                    A-2. 상태변수 사용
                */}
                { countJsx }
                
                {/* B-2, 상태변수 사용 - 조건부랜더링 */}
                { condiJsx }
            </div>
        </div>
    );
}

 

App4.js

/**
 * useRef, useEffect 사용
 *  - 필요시 상태변수 사용
 *  - url을 이용하여 일종의 게시판형태(table 사용) 리스트 구성
 *      - 상품 데이터 => <tr> .. </tr>
 *  - 상품 데이터는? axios를 이용하여 데이터 획득
 *      - 통신 시점? 컴포넌트가 보이기 직전? or 필요시
 *      - 매번 요청하면 느리지 않는가? -> 최적화 개념 - 메모이제이션 기능 사용
 *      - useMemo, useCallback
 *      - 모듈 설치
 *          - npm install axios
 */
// 1. 모듈 가져오기
import './App.css';
import {
    useState,       // 상태변수->화면갱신
    useEffect,      // 중요한 생애 주기 함수 대체(부분)
    useMemo,        // 데이터     
    useCallback,    // 함수
    useRef,         // 데이터, 변경되어도 랜더링 되지 않는다, 참조
    useContext,     // 여러 컴포넌트가 접근하여 사용하는 데이터 관리    
} from "react";
import axios from 'axios';

async function getAllProducts( url ) {
    // 실습 2분 ajax 통신, axios 사용, 비동기처리(async ~ awit 활용)
    console.log('전체 상품 데이터 획득(페이징 기능 x)');
    const res = await axios.get( url );
    // 결과 출력
    console.log('통신 결과 출력', res.data);
    // 획득한 결과를 => JSX로 구성 => 화면에 표시까지 가능!
    // 배열 데이터 순환(map()) -> 데이터 1개씩 추출 -> <li key={idx}>데이터</li>
    return res.data.map( ( product, idx )=>{
        // 실습 1분 <li key={idx}>데이터</li> 이런 배열 나오도록 1차 구성
        // 상품 데이터(객체)에서 특정 데이터만 추출하여 변수에 할당
        // 나중에 실습 : rate값 추출
        const { id, title, description, price, image, category } = product;
        // JSON -> 전처리 -> JSX로 재구성
        return (
            <li key={idx}> {/* id값을 key값으로 사용 가능 */}
                <img src={ image } width='20px'/>
                <b>{title} / {price} $</b>
            </li>
        );
    } );
}

// 2. 대표 모듈화 + 커스텀 컴포넌트 정의 ( 객체형 | (*)함수형 )
export default function App ( {url, pid} ) {
    // 컴포넌트로 전달된 props를 바로 분해하여 변수로 처리
    console.log( url, pid);
    
    // 전달된 변수는 useRef를 통해서 통상 관리
    // 정의 : const|let 변수명 = useRef( 초기값 );
    const marketUrl = useRef( url ); // 값이 변경되어도 화면갱신 x 
    // 실습 : productID라는 useRef를 pid를 넣어서 생성,출력 : 30초
    // 전달되는 데이터는 모두 문자열, 특이케이스 제외하고
    const productID = useRef( parseInt(pid) ); // or Number(pid)

    // 상태 변수
    const [ productAll, setProductAll ] = useState(null);

    // useEffect => 컴포넌트, 상태변수등 모니터링 대상에 따라 호출
    // 기본형 => 컴포넌트가 보일려고 한다와 유사한 방식
    // useEffect( 콜백함수, [관찰대상]);
    useEffect( ()=>{
        // 관찰대상이 없으므로 1회성으로 호출!!
        console.log('컴포넌트가 보일려고 한다-componentDidMount()');
        // 1회성 => 최초에 ajax 통신을 통해서 상품 데이터를 획득
        getAllProducts( marketUrl.current ) // 결과가 비동기로 도착함!!        
        .then( data=>{
            // Promise  패턴에 의해 결과가 언젠가 도착하면(정상이면) then  진입
            // 상태변수값 조정
            // data -> JSX
            setProductAll( data );
        } )
        .catch()
        .finally();
    }, []);
    return (
        <div className='App'>
            <div className='App-header'>
                <p><span style={{ color:'red' }}>상품</span> 목록</p>
                <div>
                    <ul>
                        { productAll }
                    </ul>
                </div>
            </div>
        </div>
    );
}

 

App5.js

/**
 * - 게시판 형태로 UI를 구성
 *      - JSX 수정 
 * - 제품 한개를 클릭 -> 상세 정보 보기
 *      - 창 띠우기? <= 차후 써드파트 GUI 컴포넌트 설치하여 구성
 *      - 상세 페이지 이동 <= 라우팅 처리 
 *      - 현재 화면에서 상단부분에 표시? <= 진행!!
 *      - 워크플로우 (오전 학습 정리시 가능하면 실습 진행)
 *          - 클릭 -> 어떤 상품을 클릭햇는가? -> id값
 *          - id값을 이용하여 상세 정보 획득(ajax 통신)
 *              - URL / 상품의id값
 *              - https://fakestoreapi.com/products/3
 *          - 통신 결과를 파싱 JSX로 구성
 *              - JSX는 일단 자유롭게 구성
 *          - product에 세팅
 *          - 화면갱신되어 확인!!
 *          - 필요시 구조 변경 가능!!
 */
// 1. 모듈 가져오기
import './App.css';
import {
    useState,       // 상태변수->화면갱신
    useEffect,      // 중요한 생애 주기 함수 대체(부분)
    useMemo,        // 데이터     
    useCallback,    // 함수
    useRef,         // 데이터, 변경되어도 랜더링 되지 않는다, 참조
    useContext,     // 여러 컴포넌트가 접근하여 사용하는 데이터 관리    
} from "react";
import axios from 'axios';

// 통신후 JSX 생성하는 별도 함수
async function getAllProducts( url, cb ) {
    const res = await axios.get( url );
    return res.data.map( ( product, idx )=>{
        const { id, title, description, price, image, category } = product;
        // 실습 3분 : <table>의 데이터 항목 <tr><td>... 구성되게 하위 조정
        // 함수로 감싸서 특정정보 id를 콜백함수에 전달하는 전략
        // 어떤 제품에 상세 보기를 요청(이벤트)가 발생했는지 관건!!
        return (            
            <tr key={idx} onClick={ ()=>{ cb( id ) } }>
                <th>{ title }</th>
                <th>{ price }</th>
                <th>{ category }</th>
                <th>{ description }</th> {/*차후 글자 요약 추가 필요*/}
            </tr>
        );
    } );
}

// 2. 대표 모듈화 + 커스텀 컴포넌트 정의 ( 객체형 | (*)함수형 )
export default function App ( {url, pid} ) {
    const marketUrl = useRef( url );
    const productID = useRef( parseInt(pid) );
    const [ productAll, setProductAll ] = useState(null); // 모든 제품 JSX
    const [ product, setProduct] = useState(null); // 1개 상품에 대한 상셍정보JSX
    
    const showProductDetail = async ( id ) => {
        console.log( '상세보기 요청', id);
        // 1. id값 기준 특정 상품 데이터 획득
        const res = await axios.get( `${ marketUrl.current }/${ id }` );
        // 2. 데이터:{}를 파싱 -> 동적으로 상세정보 JSX 구성
        const { title, description, price, image, category } = res.data; // 파싱
        // JSX 생성
        const pdtDetail = (
            <div>
                <img src={image} style={{ width:100, height:100 }}/>
                <p>{title}</p>
                <p>{price}</p>
                <p>{category}</p>
                <p>{description}</p>
            </div>
        );
        // 3. setProduct( 동적구성된 JSX ) 
        setProduct( pdtDetail );
        // 4. 화면갱신 발생!! => 확인
    }

    useEffect( ()=>{
        console.log('컴포넌트가 보일려고 한다 : 최초 1회 실행');
        getAllProducts( marketUrl.current, showProductDetail )       
        .then( data=>{
            setProductAll( data );
        } )
        .catch()
        .finally();
    }, []); // 모니터링 대상이 없다!!

    // useEffect => 특정(product 상태변수) 변수의 변화를 감지하여 호출!!
    // 값이 변화되는 대상을 기준
    useEffect( ()=>{
        // 신규값이 세팅되면 호출!!
        console.log('2 product 감시', product);
        
        // 뒷정리 함수 배치 
        // -> 이전 상태변수값이 바뀌는 과정에서, 이전값을 뭔가 처리해야한다면
        // -> 생략 가능, 필요시 구현, 이전값이 전달됨
        return ()=>{
            // 이전값을 체크할 수 있는 마지막 단계!!
            console.log('1 product 감시', product);
        };
    }, [product]);

    return (
        <div className='App'>
            <div className='App-header'>
                <p><span style={{ color:'red' }}>상품</span> 목록</p>
                <div style={{ margin:'2em' }}>
                    <div>
                        {/* 상품 상세 정보 표시, fieldset:사각 테두리 박스 생성 */}
                        <fieldset>
                            { product }
                        </fieldset>
                    </div>
                    <br/>
                    <table border="1" >
                        <thead>
                            <tr>
                                <th>상품명</th>
                                <th>가격</th>
                                <th>카테고리</th>
                                <th>설명</th>
                            </tr>
                        </thead>
                        <tbody>
                            { productAll }
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    );
}

 

App6.js

/**
 * - 게시판 형태로 UI를 구성
 *  - 메모이제이션 사용 -> 최적화!!, 메모리 절약(상대적)
 *  - 자주 사용되는 데이터 나 함수를 캐싱하여서 화면갱신시 매번 생성 x, 바로 사용
 *      - 데이터는 바로 엑세스 (통신, 전처리등 과정 x)
 *      - 함수 -> 매번 생성 x, 바로 호출
 */

// 1. 모듈 가져오기
import './App.css';
import {
    useState,       // 상태변수->화면갱신
    useEffect,      // 중요한 생애 주기 함수 대체(부분)
    useMemo,        // 데이터     
    useCallback,    // 함수, 함수호출는 매번 작동, 함수의 정의 1회, 이후는 캐싱
    useRef,         // 데이터, 변경되어도 랜더링 되지 않는다, 참조
    useContext,     // 여러 컴포넌트가 접근하여 사용하는 데이터 관리    
} from "react";
import axios from 'axios';

// 통신후 JSX 생성하는 별도 함수
async function getAllProducts( url, cb ) {
    const res = await axios.get( url );
    return res.data.map( ( product, idx )=>{
        const { id, title, description, price, image, category } = product;
        return (            
            <tr key={idx} onClick={ ()=>{ cb( id ) } }>
                <th>{ title }</th>
                <th>{ price }</th>
                <th>{ category }</th>
                <th>{ description }</th> {/*차후 글자 요약 추가 필요*/}
            </tr>
        );
    } );
}

// 2. 대표 모듈화 + 커스텀 컴포넌트 정의 ( 객체형 | (*)함수형 )
export default function App ( {url, pid} ) {
    const marketUrl = useRef( url );
    const productID = useRef( parseInt(pid) );
    const [ productAll, setProductAll ] = useState(null); // 모든 제품 JSX
    const [ product, setProduct] = useState(null); // 1개 상품에 대한 상셍정보JSX
    
    // 해당 함수는 캐싱이 되서, 화면 갱신시 아래코드는 작동되지 x
    // 반복작업 생략 => 화면갱신이 빨라짐 (버벅거림이 상대적으로 줄어듬)
    const showProductDetail = useCallback( async ( id ) => {
        console.log( '상세보기 요청', id);
        // 1. id값 기준 특정 상품 데이터 획득
        const res = await axios.get( `${ marketUrl.current }/${ id }` );
        // 2. 데이터:{}를 파싱 -> 동적으로 상세정보 JSX 구성
        const { title, description, price, image, category } = res.data; // 파싱
        // JSX 생성
        const pdtDetail = (
            <div>
                <img src={image} style={{ width:100, height:100 }}/>
                <p>{title}</p>
                <p>{price}</p>
                <p>{category}</p>
                <p>{description}</p>
            </div>
        );
        // 3. setProduct( 동적구성된 JSX ) 
        setProduct( pdtDetail );
        // 4. 화면갱신 발생!! => 확인
    }, [] );

    useEffect( ()=>{
        console.log('컴포넌트가 보일려고 한다 : 최초 1회 실행');        
    }, []);

    // 메모이제이션-데이터파트 1회성 코드
    // 전체 상품 데이터의 JSX를 저장/관리
    // 변수로 받아서 처리 or (*)상태변수에 저장하고 사용
    // useEffect()보다 먼저 작동
    useMemo( ()=>{        
        console.log('메모이제이션 최초 세팅-데이터');
        getAllProducts( marketUrl.current, showProductDetail )       
        .then( data=>{
            setProductAll( data );
        } )
        .catch()
        .finally();
    }, []);// 1회성 세팅, useEffect() 먼저 작동, 캐싱처리, 화면갱신시 직접 사용
    //}, [marketUrl.current]); // 변하지 않는 모니터링 대상 배치

    return (
        <div className='App'>
            <div className='App-header'>
                <p><span style={{ color:'red' }}>상품</span> 목록</p>
                <div style={{ margin:'2em' }}>
                    <div>
                        {/* 상품 상세 정보 표시, fieldset:사각 테두리 박스 생성 */}
                        <fieldset>
                            { product }
                        </fieldset>
                    </div>
                    <br/>
                    <table border="1" >
                        <thead>
                            <tr>
                                <th>상품명</th>
                                <th>가격</th>
                                <th>카테고리</th>
                                <th>설명</th>
                            </tr>
                        </thead>
                        <tbody>
                            { productAll }
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    );
}

 

App6_mui.js

/**
 * - 게시판 형태로 UI를 구성
 *  - 메모이제이션 사용 -> 최적화!!, 메모리 절약(상대적)
 *  - 자주 사용되는 데이터 나 함수를 캐싱하여서 화면갱신시 매번 생성 x, 바로 사용
 *      - 데이터는 바로 엑세스 (통신, 전처리등 과정 x)
 *      - 함수 -> 매번 생성 x, 바로 호출
 */

// 1. 모듈 가져오기
import "./App.css";
import {
  useState, // 상태변수->화면갱신
  useEffect, // 중요한 생애 주기 함수 대체(부분)
  useMemo, // 데이터
  useCallback, // 함수, 함수호출는 매번 작동, 함수의 정의 1회, 이후는 캐싱
  useRef, // 데이터, 변경되어도 랜더링 되지 않는다, 참조
  useContext, // 여러 컴포넌트가 접근하여 사용하는 데이터 관리
} from "react";
import axios from "axios";

// TODO 게시판-1 mui 관련 디자인이 적용된 컴포넌트 가져오기
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Paper from "@mui/material/Paper";

// TODO 게시판-4 mui 관련 다이얼로그 처리
import {
    Dialog,             // 팝업
    DialogTitle,        // 팝업 제목
    DialogActions,      // 팝업 버튼 액션
    DialogContent,      // 팝업 내용

    // TODO 게시판-9 mui Card는 생략, 학습정리시 한번 적용!!(선택)
    Card,               // 특정 장면을 위한 컨테이너
    CardActions,        // 카드상에 이벤트
    CardContent,        // 카드 내용
    CardMedia,          // 카드내에 멀티미이더 처리(사진, 음악, 동영상) 삽입

    Typography,         // 텍스트 처리
    Button              // 버튼
} from '@mui/material';


// 통신후 JSX 생성하는 별도 함수
async function getAllProducts(url, cb) {
  const res = await axios.get(url);
  return res.data.map((product, idx) => {
    const { id, title, description, price, image, category } = product;
    // TODO 게시판-3 mui 관련 데이터 동적 세팅부분 교체
    return (
        <>            
            <TableRow
              key={idx}
              sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
              onClick={() => {
                cb(id);
                }}
            >
              <TableCell component="th" scope="row">
                {title.substring(0,25) }
              </TableCell>
              <TableCell align="right">{price}</TableCell>
              <TableCell align="left">{category.substring(0,25)}</TableCell>
              <TableCell align="left">{description.substring(0,25)}</TableCell>
            </TableRow>
        </>
        
    );
  });
}

// 2. 대표 모듈화 + 커스텀 컴포넌트 정의 ( 객체형 | (*)함수형 )
export default function App({ url, pid }) {
  const marketUrl = useRef(url);
  const productID = useRef(parseInt(pid));
  const [productAll, setProductAll] = useState(null); // 모든 제품 JSX
  const [product, setProduct] = useState(null); // 1개 상품에 대한 상셍정보JSX

  // 해당 함수는 캐싱이 되서, 화면 갱신시 아래코드는 작동되지 x
  // 반복작업 생략 => 화면갱신이 빨라짐 (버벅거림이 상대적으로 줄어듬)
  const showProductDetail = useCallback(async (id) => {
    console.log("상세보기 요청", id);
    // 1. id값 기준 특정 상품 데이터 획득
    const res = await axios.get(`${marketUrl.current}/${id}`);
    // 2. 데이터:{}를 파싱 -> 동적으로 상세정보 JSX 구성
    const { title, description, price, image, category } = res.data; // 파싱
    // JSX 생성
    const pdtDetail = (
      <div>
        <img src={image} style={{ width: 100, height: 100 }} />
        <p>{title}</p>
        <p>{price}</p>
        <p>{category}</p>
        <p>{description}</p>
      </div>
    );
    // 3. setProduct( 동적구성된 JSX )
    setProduct(pdtDetail);
    // 4. 화면갱신 발생!! => 확인

    // TODO 게시판-7 mui 팝업 띠우기
    dOpenHander()
  }, []);

  useEffect(() => {
    console.log("컴포넌트가 보일려고 한다 : 최초 1회 실행");
  }, []);

  // 메모이제이션-데이터파트 1회성 코드
  // 전체 상품 데이터의 JSX를 저장/관리
  // 변수로 받아서 처리 or (*)상태변수에 저장하고 사용
  // useEffect()보다 먼저 작동
  useMemo(() => {
    console.log("메모이제이션 최초 세팅-데이터");
    getAllProducts(marketUrl.current, showProductDetail)
      .then((data) => {
        setProductAll(data);
      })
      .catch()
      .finally();
  }, []); // 1회성 세팅, useEffect() 먼저 작동, 캐싱처리, 화면갱신시 직접 사용
  //}, [marketUrl.current]); // 변하지 않는 모니터링 대상 배치

  // TODO 게시판-6 mui 상태변수, 이벤트함수, 기타필요기능
  const [open, setOpen] = useState(false); // 팝업을 오픈, 닫는 역활
  const dCloseHander = () => {
    // 팝업닫기
    setOpen( false );
    // 1개 상품정보 초기화
    setProduct( null );
  }
  const dOpenHander = () => {
    // 팝업오픈
    setOpen( true );
  }
  return (
    <div className="App">
      <div className="App-header">
        <p>
          <span style={{ color: "red" }}>상품</span> 목록
        </p>
        <div style={{ margin: "2em" }}>
          <div>
            {/* 상품 상세 정보 표시, fieldset:사각 테두리 박스 생성 */}
            {/* TODO 게시판-8 mui 기존의 제품 상세정보 표기 제거 */}
            {/* <fieldset>{product}</fieldset> */}
          </div>
          <br />          
          {/* TODO 게시판-2 mui 관련 JSX내에 테이블 교체, 기존 table 제거 */}
          <TableContainer component={Paper}>
            <Table sx={{ minWidth: 650 }} aria-label="simple table">
              <TableHead>
                {/* 컬럼 표현 기존의 tr-th */}
                <TableRow>
                  <TableCell>상품명</TableCell>
                  <TableCell align="right">가격</TableCell> {/* 수치는 오른쪽 정렬 */}
                  <TableCell align="left">카테고리</TableCell>
                  <TableCell align="left">설명</TableCell>
                </TableRow>
              </TableHead>
              <TableBody>
                {productAll}
              </TableBody>
            </Table>
          </TableContainer>

          {/* TODO 게시판-5 mui 팝업표시 */}
          {/*       open : 팝업의 오픈 여부 */}
          {/*       onClose : 비워있는 공간을 클릭 */}
            <Dialog
                open={open}              
                onClose={ dCloseHander } 
                aria-labelledby="alert-dialog-title"
                aria-describedby="alert-dialog-description"
            >
                <DialogTitle id="alert-dialog-title">
                    제품 상세 정보
                </DialogTitle>
                <DialogContent>
                    {/* product 내용은 card 컴포넌트 재구성 교체예정 */}
                    { product }
                </DialogContent>
                <DialogActions>
                    <Button onClick={ dCloseHander } autoFocus>
                        닫기
                    </Button>
                </DialogActions>
            </Dialog>

        </div>
      </div>
    </div>
  );
}

 

 

App7.js

/**
 * 환율 계산기
 *  - 환율 정보를 가져와서, 실시간 환전 계산, 수수료(환율우대)부분 제외
 *  - 요구사항
 *      - 화면 
 *          -  왼쪽 (USD, JPY, EUR) 선택 <-> 오른쪽 (KRW 고정) : select
 *          -  왼쪽 [입력창] = 오른쪽 [입력창]
 *      - 오퍼레이션
 *          - 통화 선택 -> 왼쪽입력창에 수치를 입력( 5 $ )
 *          -  -> 오른쪽 계산된값 (5,000 원)
 *          - 정반대로 작동 가능함
 *      - 환율정보
 *          - 1분간격( 편의상 10분간격 )으로 통화 데이터 갱신
 *          - http://api.manana.kr/exchange/rate/KRW/JPY,USD,EUR.json 획득
 */
// 1. 모듈 가져오기
import './App.css';
import {
    useState,
    useEffect,
    useMemo,
    useCallback,
    useRef,
    useContext,
} from "react";
import axios from 'axios'; // 통신모듈

// 전역변수
// 더미데이터 적용 이유 => 환율 제공 openapi에서 크로스도메인 지원 x, ajax 통신불가
const EXCHANGE_URL = 'http://127.0.0.1:5500/app-npm/hook-test/dumy/exchange.json';//'http://api.manana.kr/exchange/rate/KRW/JPY,USD,EUR.json';
// 수치만 입력되게 필터링 처리 함수
// 숫자가 아닌문자를 특정하여 모두 빈값으로 대체
// 숫자 이외에 추가할 기호가 있다면 계속 추가하면됨 => . 추가함
const nanFiltering = s => s.replace(/[^0-9.]/g,""); 

// 2. 커스텀 컴포넌트 정의 ( 객체형 | (*)함수형 )
function App ( props ) {
    // 상태변수, 통화코드, 기본값 null
    // 상태변수 구현 -> exchangeCode
    const [ exchangeCode, setExchangeCode ] = useState( null );
    // 현재 선택한 통화 코드
    const [ curExchangeCode, setCurExchangeCode ] = useState( "USD" ); // 설정
    // 환율 정보 -> 특정 주기로 갱신 -> 화면갱신에 영향 x => useRef, 30초
    // exchangeRate
    const exchangeRate = useRef( null );
    
    //- 상태변수 : leftInput, rightInput
    //- 값 변경 이벤트 처리 함수등록 : onExchangeLeft, onExchangeRight
    const [ leftInput, setLeftInput ]   = useState("");
    const [ rightInput, setRightInput ] = useState("");
    // 왼쪽에 예)달러 입력 => 자동으로 오른쪽에 환전값 계산 및 세팅
    function onExchangeLeft( e ) { 
        // 1. 입력값 -> 필터링(전처리) -> 수치만 획득
        const v = nanFiltering( e.target.value ); // 숫자 , . 만 획득
        // 2. 왼쪽 입력창에 입력한 내용 업데이트 
        setLeftInput( v );
        // 3. 환율 계산을 진행하여 오른쪽 내용 업데이트
        //    (입력한달러값 * 달러대환율).toFixed(2) : 환전값 계산하여 소수점2자리까지 표기
        setRightInput( (parseFloat(v) * exchangeRate.current[ curExchangeCode ]).toFixed(2) )
    }
    // 위와 반대
    function onExchangeRight( e ) {        
        // 1. 입력값 -> 필터링(전처리) -> 수치만 획득
        const v = nanFiltering( e.target.value );
        // 2. 왼쪽 입력창에 입력한 내용 업데이트 
        setRightInput( v );
        // 3. 환율 계산을 진행하여 오른쪽 내용 업데이트
        //    (입력한원화값 / 달러대환율).toFixed(2) : 환전값 계산하여 소수점2자리까지 표기
        //    엔화 계산은 100을 보정 (현재 생략)
        setLeftInput( (parseFloat(v) / exchangeRate.current[ curExchangeCode ]).toFixed(2) )
    }


    // 요구사항 
    // 1. 환율정보 가져오기!! -> 일단 1회성 진행, 차후 지속적 반복 업그레이드
    //    useCallback() 으로 구현, 필요한 위치에서 호출
    const getExchangeInfo = useCallback( async ()=>{
        // 통화정보 획득
        const res = await axios.get( EXCHANGE_URL );
        // 통화 정보에서 select의 요소를 동적으로 구성
        const jsxOptions = res.data.map( ( exchange, idx )=>{
            // "name": "USDKRW=X", => "USD" 이값으로 세팅
            const { name } = exchange; // 데이터 1개의 형태는 객체
            // 실습 1분, 문자열.substring(시작인덱스, 끝인덱스)
            const code = name.substring(0, 3); // 시작인덱스<= 데이터 <끝인덱스
            return (
                <option key={idx} value={ code }>{ code }</option>
            );
        } );
        // 임시로 한국통화코드 삽입, 배열에 맴버 추가 삽입 (push())
        jsxOptions.push( <option key="-1" value="KRW">KRW</option> );
        // 상태 변수에 세팅
        setExchangeCode( jsxOptions );

        // 통화 정보 세팅
        /*
            // 자료구조
            {
                "USD":"1432.xx",
                "JPY":"940.xx"
                "EUR":"1500.xx"
            } 
            // 값 추출
            객체명[ 'USD' ]  => 1432.xx
            실습 3분 : res.data를 전처리등 조치 => 위의 구조 처럼 구성
        */
        // find()은 배열내에서 특정정보가 일치하는 요소만 찾는 함수
        exchangeRate.current = {
            USD:parseFloat((res.data.find( code => code.name === "USDKRW=X" ).rate).toFixed(4)),
            JPY:parseFloat((res.data.find( code => code.name === "JPYKRW=X" ).rate).toFixed(4)),
            EUR:parseFloat((res.data.find( code => code.name === "EURKRW=X" ).rate).toFixed(4))
        }
        console.log( exchangeRate.current );
    }, [] );

    // 1회성 자동 호출
    useEffect( ()=>{
        console.log('환율 정보 획득');
        getExchangeInfo(); // 차후 주기성을 가지고 지속적 호출!! -setInterval()
    }, []);

    // 환율정보 요청하여 "name": "JPYKRW=X"을 가각 추출해서 => JPY, USD,.. 추출
    // exchangeCode에 세팅할수 있는 준비

    // 환전을 위한 통화코드 변경 선택 이벤트 
    function exchangeHander ( evt ) {
        // 1. 입력된 내용 초기화 -> 0 or (*)빈값 <= 네이버는 값을 유지하여 변환
        setLeftInput('');
        setRightInput('');
        // 2. 현재 통화 코드 조정 -> 상태변수 설정
        setCurExchangeCode( evt.target.value );
    }

    return (
        <div className='App'> {/* class 형태의 디자인 적용 */}
            <div className='App-header'>
                <h2>환율 계산기</h2>
                <fieldset>
                    <div>
                        {/* 통화 선택, 최초 선택값은 USD로 지정 */}
                        <select style={{ width:"45%" }}
                                onChange={ exchangeHander }
                                value={ curExchangeCode }
                        >
                            { exchangeCode }
                        </select>
                        =
                        {/* KRW 고정 */}
                        <select style={{ width:"45%" }} 
                                value={"KRW"} 
                                disabled>
                            { exchangeCode }
                        </select>
                    </div>
                    <br/>
                    <div>
                        {/* 환전 계산 */}
                        {/* 
                            - 상태변수 : leftInput, rightInput
                            - 값 변경 이벤트 처리 함수등록 : onExchangeLeft, onExchangeRight
                            실습 2분 : 상태변수, 이벤트처리함수 등록
                        */}
                        <input  placeholder='숫자만 입력'
                                value={ leftInput }
                                onChange={ onExchangeLeft }
                        />
                        =
                        <input placeholder='숫자만 입력'
                                value={ rightInput }
                                onChange={ onExchangeRight }
                        />
                    </div>
                </fieldset>
            </div>
        </div>
    );
}

// 3. 대표 모듈화 -> 오직 1개만 지정
export default App;

 

 

App7_mui.js

/**
 * 환율 계산기
 *  - 환율 정보를 가져와서, 실시간 환전 계산, 수수료(환율우대)부분 제외
 *  - 요구사항
 *      - 화면 
 *          -  왼쪽 (USD, JPY, EUR) 선택 <-> 오른쪽 (KRW 고정) : select
 *          -  왼쪽 [입력창] = 오른쪽 [입력창]
 *      - 오퍼레이션
 *          - 통화 선택 -> 왼쪽입력창에 수치를 입력( 5 $ )
 *          -  -> 오른쪽 계산된값 (5,000 원)
 *          - 정반대로 작동 가능함
 *      - 환율정보
 *          - 1분간격( 편의상 10분간격 )으로 통화 데이터 갱신
 *          - http://api.manana.kr/exchange/rate/KRW/JPY,USD,EUR.json 획득
 */
// 1. 모듈 가져오기
import './App.css';
import {
    useState,
    useEffect,
    useMemo,
    useCallback,
    useRef,
    useContext,
} from "react";
import axios from 'axios'; // 통신모듈

// TODO 환율-mui-1 모듈 가져오기
import {
    InputLabel,     // select의 프럼프트, 어떤값을 선택하는지 표기
    MenuItem,       // <option> 대체제
    Select,         // <select> 대체제
    FormControl,    // <form> 대체제(?)
    TextField       // 텍스트 입력
} from '@mui/material';

// 전역변수
// 더미데이터 적용 이유 => 환율 제공 openapi에서 크로스도메인 지원 x, ajax 통신불가
const EXCHANGE_URL = 'http://127.0.0.1:5500/app-npm/hook-test/dumy/exchange.json';//'http://api.manana.kr/exchange/rate/KRW/JPY,USD,EUR.json';
// 수치만 입력되게 필터링 처리 함수
// 숫자가 아닌문자를 특정하여 모두 빈값으로 대체
// 숫자 이외에 추가할 기호가 있다면 계속 추가하면됨 => . 추가함
const nanFiltering = s => s.replace(/[^0-9.]/g,""); 

// 2. 커스텀 컴포넌트 정의 ( 객체형 | (*)함수형 )
function App ( props ) {
    // 상태변수, 통화코드, 기본값 null
    // 상태변수 구현 -> exchangeCode
    const [ exchangeCode, setExchangeCode ] = useState( null );
    // 현재 선택한 통화 코드
    const [ curExchangeCode, setCurExchangeCode ] = useState( "USD" ); // 설정
    // 환율 정보 -> 특정 주기로 갱신 -> 화면갱신에 영향 x => useRef, 30초
    // exchangeRate
    const exchangeRate = useRef( null );
    
    //- 상태변수 : leftInput, rightInput
    //- 값 변경 이벤트 처리 함수등록 : onExchangeLeft, onExchangeRight
    const [ leftInput, setLeftInput ]   = useState("");
    const [ rightInput, setRightInput ] = useState("");
    // 왼쪽에 예)달러 입력 => 자동으로 오른쪽에 환전값 계산 및 세팅
    function onExchangeLeft( e ) { 
        // 1. 입력값 -> 필터링(전처리) -> 수치만 획득
        const v = nanFiltering( e.target.value ); // 숫자 , . 만 획득
        // 2. 왼쪽 입력창에 입력한 내용 업데이트 
        setLeftInput( v );
        // 3. 환율 계산을 진행하여 오른쪽 내용 업데이트
        //    (입력한달러값 * 달러대환율).toFixed(2) : 환전값 계산하여 소수점2자리까지 표기
        setRightInput( (parseFloat(v) * exchangeRate.current[ curExchangeCode ]).toFixed(2) )
    }
    // 위와 반대
    function onExchangeRight( e ) {        
        // 1. 입력값 -> 필터링(전처리) -> 수치만 획득
        const v = nanFiltering( e.target.value );
        // 2. 왼쪽 입력창에 입력한 내용 업데이트 
        setRightInput( v );
        // 3. 환율 계산을 진행하여 오른쪽 내용 업데이트
        //    (입력한원화값 / 달러대환율).toFixed(2) : 환전값 계산하여 소수점2자리까지 표기
        //    엔화 계산은 100을 보정 (현재 생략)
        setLeftInput( (parseFloat(v) / exchangeRate.current[ curExchangeCode ]).toFixed(2) )
    }


    // 요구사항 
    // 1. 환율정보 가져오기!! -> 일단 1회성 진행, 차후 지속적 반복 업그레이드
    //    useCallback() 으로 구현, 필요한 위치에서 호출
    const getExchangeInfo = useCallback( async ()=>{
        // 통화정보 획득
        const res = await axios.get( EXCHANGE_URL );
        // 통화 정보에서 select의 요소를 동적으로 구성
        const jsxOptions = res.data.map( ( exchange, idx )=>{
            // "name": "USDKRW=X", => "USD" 이값으로 세팅
            const { name } = exchange; // 데이터 1개의 형태는 객체
            // 실습 1분, 문자열.substring(시작인덱스, 끝인덱스)
            const code = name.substring(0, 3); // 시작인덱스<= 데이터 <끝인덱스
            // TODO  환율-mui-3 option -> MenuItem
            return (
                <MenuItem key={idx} value={ code }>{ code }</MenuItem>
            );
        } );
        // 임시로 한국통화코드 삽입, 배열에 맴버 추가 삽입 (push())
        jsxOptions.push( <MenuItem key="-1" value="KRW">KRW</MenuItem> );
        // 상태 변수에 세팅
        setExchangeCode( jsxOptions );

        // 통화 정보 세팅
        /*
            // 자료구조
            {
                "USD":"1432.xx",
                "JPY":"940.xx"
                "EUR":"1500.xx"
            } 
            // 값 추출
            객체명[ 'USD' ]  => 1432.xx
            실습 3분 : res.data를 전처리등 조치 => 위의 구조 처럼 구성
        */
        // find()은 배열내에서 특정정보가 일치하는 요소만 찾는 함수
        exchangeRate.current = {
            USD:parseFloat((res.data.find( code => code.name === "USDKRW=X" ).rate).toFixed(4)),
            JPY:parseFloat((res.data.find( code => code.name === "JPYKRW=X" ).rate).toFixed(4)),
            EUR:parseFloat((res.data.find( code => code.name === "EURKRW=X" ).rate).toFixed(4))
        }
        console.log( exchangeRate.current );
    }, [] );

    // 1회성 자동 호출
    useEffect( ()=>{
        console.log('환율 정보 획득');
        getExchangeInfo(); // 차후 주기성을 가지고 지속적 호출!! -setInterval()
    }, []);

    // 환율정보 요청하여 "name": "JPYKRW=X"을 가각 추출해서 => JPY, USD,.. 추출
    // exchangeCode에 세팅할수 있는 준비

    // 환전을 위한 통화코드 변경 선택 이벤트 
    function exchangeHander ( evt ) {
        // 1. 입력된 내용 초기화 -> 0 or (*)빈값 <= 네이버는 값을 유지하여 변환
        setLeftInput('');
        setRightInput('');
        // 2. 현재 통화 코드 조정 -> 상태변수 설정
        setCurExchangeCode( evt.target.value );
    }

    return (
        <div className='App'> {/* class 형태의 디자인 적용 */}
            <div className='App-header'>
                <h2>환율 계산기</h2>
                {/* TODO  환율-mui-2 JSX 교체 */}
                <FormControl fullWidth>
                    <div>
                        <InputLabel id="demo-simple-select-label">통화선택</InputLabel>
                        {/* 통화 선택, 최초 선택값은 USD로 지정 */}
                        <Select style={{ width:"45%" }}
                                onChange={ exchangeHander }
                                value={ curExchangeCode }
                                labelId="demo-simple-select-label"
                        >
                            { exchangeCode }
                        </Select>
                        =
                        {/* KRW 고정 */}
                        <Select style={{ width:"45%" }} 
                                value={"KRW"} 
                                disabled>
                            { exchangeCode }
                        </Select>
                    </div>
                    <br/>
                    <div>
                        {/* 환전 계산 */}
                        {/* 
                            - 상태변수 : leftInput, rightInput
                            - 값 변경 이벤트 처리 함수등록 : onExchangeLeft, onExchangeRight
                            실습 2분 : 상태변수, 이벤트처리함수 등록
                        */}
                        <TextField label="달러|엔화|유로" variant="outlined"
                                placeholder='숫자만 입력'
                                value={ leftInput }
                                onChange={ onExchangeLeft }
                        />
                        =
                        <TextField label="원화" variant="outlined" 
                                placeholder='숫자만 입력'
                                value={ rightInput }
                                onChange={ onExchangeRight }
                        />
                    </div>
                </FormControl>
            </div>
        </div>
    );
}

// 3. 대표 모듈화 -> 오직 1개만 지정
export default App;

 

App8.js

/**
 * useContext(), createContext()
 *  - 리액트는 컴포넌트의 집합
 *  - 컴포넌트는 트리 구조로, 부모-자식-후손형태로 배치
 *  - 상위 컴포넌트의 정보를 하위 컴포넌트에게 전달
 *      - 컴포넌트간 데이터 전달 방법!!
 *          - 1세대 : redux, 2세대 : Mobx,.. (*)3세대 : ? <= 써드파트 방식
 *      - useContext()를 이용한 방식 추가
 *          - 상위 컴포넌트에서 전달한 데이터가 변경되면 
 *            -> 하위컴포넌트도 영향을 받아 변경된다
 *      - props를 활용 구성 (복잡)
 *      
 */
// 모듈 가져오기
import './App.css';
import {
    useState,       // 상태변수->화면갱신
    useEffect,      // 중요한 생애 주기 함수 대체(부분)
    useMemo,        // 데이터     
    useCallback,    // 함수
    useRef,         // 데이터, 변경되어도 랜더링 되지 않는다, 참조
    useContext,
    createContext,     // 여러 컴포넌트가 접근하여 사용하는 데이터 관리
} from "react";

// 1. 전역공간에 컨텍스트 생성
//    어떤 타입의 데이터던 저장 가능함
const TextContext = createContext("목요일 마지막 타임");

// 커스텀 컴포넌트 정의 ( 객체형 | (*)함수형 )
function End () {
    // 하위 컴포넌트가 사용하는 훅!!
    const data = useContext( TextContext );
    return (
        <>
            <p>가장 하위 컴포넌트</p>
            { data }
        </>
    );
}
function Mid () {
    return (
        <>
            <End/>
        </>
    );
}
function App ( props ) {
    // const [ sendingData, setSendingData ] = useState({
    //     // 필요한 데이터를 나열 -> n개의 데이터나 함수들 전달
    // });
    const [ sendingData, setSendingData ] = useState("전달데이터");
    return (
        <div className='App'> {/* class 형태의 디자인 적용 */}
            <div className='App-header'>
                <h2>useContext 테스트</h2>
                {/*
                    2. 전달할 데이터를 받을 컴포넌트를 감싸서 표현
                       - 전역으로 만든 컨텍스트 활용하여 공급 표현
                       - 상위 컴포넌트의 데이터도 전달
                            - 기본값은 공급할때 변경가능하다!!
                       - 상위 컴포넌트에서 상태변수(전달된 데이터)를 
                         변경하면->하위도영향받는가?
                */}
                <TextContext.Provider value={sendingData}>
                    <Mid/>
                </TextContext.Provider>
                <input  onChange={ e=>setSendingData(e.target.value) } 
                        value={sendingData}
                />
            </div>
        </div>
    );
}

// 대표 모듈화 -> 오직 1개만 지정
export default App;

 

 

CusHook.js

 

/**
 * useWindowInfo라는 커스텀훅을 사용하는 컴포넌트
 */

import useWindowInfo from './custom/useWindowInfo';

const CusHook = () => {
    const { width, height } = useWindowInfo(); // 커스텀훅
    return (
        <>
            <div style={{ margin:"2em"}}>
                <p>윈도우 크기</p>
                <p>{ width } px / { height } px</p>
            </div>
        </>
    );
}

export default CusHook;

 

 

DashBorad.js

/**
 * Mui에서 라이브러리에서 제공하는 툴패드 활용 대시보드 구성
 *  - 기본적인 레이아웃 제공
 *  - 라우팅 제공
 *  - 머터리얼 디자인 제공
 *  - 버전이 올라갈때마다 기능이 수정될수 있음!!(버전 주의)
 * 
 * 레이아웃 적용시
 *  - 기존 샘플 적용
 *      - 필요한 기능 중심 세팅
 *      - 차후 구조가 익숙해지면 커스텀진행
 */
import * as React from 'react';
import PropTypes from 'prop-types';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import MenuList from '@mui/material/MenuList';
import MenuItem from '@mui/material/MenuItem';
import ListItemText from '@mui/material/ListItemText';
import ListItemIcon from '@mui/material/ListItemIcon';
import Avatar from '@mui/material/Avatar';
import Divider from '@mui/material/Divider';
import { createTheme } from '@mui/material/styles';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import { AppProvider } from '@toolpad/core/AppProvider';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
import {
  Account,
  AccountPreview,
  AccountPopoverFooter,
  SignOutButton,
} from '@toolpad/core/Account';

// 아이콘 가져오기
import BarChartIcon from '@mui/icons-material/BarChart';

// 서브페이지 가져오기
import App6_mui from './App6_mui';
import App7_mui from './App7_mui';

// (*)사이드바 메뉴, 진입로(엔트리포인트) ---------------
// 게시판, 환율계산기 연계하도록 진입로 세팅
const NAVIGATION = [
  {
    kind: 'header',            // 메뉴별 카테고리
    title: '메인 메뉴',         // 메뉴명
  },
  {
    segment: 'dashboard',       // URL의미, 라우팅을위한 주소값
    title: 'Dashboard',         // 메뉴명
    icon: <DashboardIcon />,    // 아이콘
  },
  {
    segment: 'orders',
    title: 'Orders',
    icon: <ShoppingCartIcon />,
  },
  {
    kind: 'divider'             // 구분선
  },
  {
    segment: 'reports',
    title: '분석 보고서',
    icon: <BarChartIcon/>,
    children: [
        {
            segment: 'sales',       // URL => /reports/sales
            title: '판매통계',
        },
        {
            segment: 'traffic',
            title: '트레픽-로그분석',
        }
    ]
  },
  {
    kind: 'divider'             // 구분선
  },
  {
    kind: 'header',            // 메뉴별 카테고리
    title: '유틸리티',         // 메뉴명
  },
  {
    segment: 'board',
    title: '간단 게시판',
    icon: <ShoppingCartIcon />,
  },
  {
    segment: 'calculator',
    title: '환율 계산기',
    icon: <ShoppingCartIcon />,
  },
];
// -------------------------------------------------

// 상단바(navi) -------------------------------------
const demoTheme = createTheme({
  cssVariables: {
    colorSchemeSelector: 'data-toolpad-color-scheme',
  },
  colorSchemes: { light: true, dark: true },
  breakpoints: {
    values: {
      xs: 0,
      sm: 600,
      md: 600,
      lg: 1200,
      xl: 1536,
    },
  },
});
// --------------------------------------------------

// (*)라우팅, 페이지등록 ---------------------------------
function DemoPageContent({ pathname }) {
  return (
    <Box
      sx={{
        py: 4,
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        textAlign: 'center',
      }}
    >
      <Typography>현재페이지 주소 {pathname}</Typography>
      {/* URL별로 해당 페이지 표현, 조건부 랜더링 */}
      { pathname === '/board' && <App6_mui 
            url="https://fakestoreapi.com/products" 
            pid="1"/> }
      { pathname === '/calculator' && <App7_mui/> }
      {/* 예비 주소, 해당 페이지가 완성되면 <></> => <커스텀컴포넌트 /> 대체 */}
      { pathname === '/reports/sales' && <></> }

    </Box>
  );
}

DemoPageContent.propTypes = {
  pathname: PropTypes.string.isRequired,
};
// --------------------------------------------------

// 세션, 로그인 인증처리등등(미검토) -------------------
function AccountSidebarPreview(props) {
  const { handleClick, open, mini } = props;
  return (
    <Stack direction="column" p={0} overflow="hidden">
      <Divider />
      <AccountPreview
        variant={mini ? 'condensed' : 'expanded'}
        handleClick={handleClick}
        open={open}
      />
    </Stack>
  );
}

AccountSidebarPreview.propTypes = {
  /**
   * The handler used when the preview is expanded
   */
  handleClick: PropTypes.func,
  mini: PropTypes.bool.isRequired,
  /**
   * The state of the Account popover
   * @default false
   */
  open: PropTypes.bool,
};

const accounts = [
  {
    id: 1,
    name: 'Bharat Kashyap',
    email: 'bharatkashyap@outlook.com',
    image: 'https://avatars.githubusercontent.com/u/19550456',
    projects: [
      {
        id: 3,
        title: 'Project X',
      },
    ],
  },
  {
    id: 2,
    name: 'Bharat MUI',
    email: 'bharat@mui.com',
    color: '#8B4513', // Brown color
    projects: [{ id: 4, title: 'Project A' }],
  },
];

function SidebarFooterAccountPopover() {
  return (
    <Stack direction="column">
      <Typography variant="body2" mx={2} mt={1}>
        Accounts
      </Typography>
      <MenuList>
        {accounts.map((account) => (
          <MenuItem
            key={account.id}
            component="button"
            sx={{
              justifyContent: 'flex-start',
              width: '100%',
              columnGap: 2,
            }}
          >
            <ListItemIcon>
              <Avatar
                sx={{
                  width: 32,
                  height: 32,
                  fontSize: '0.95rem',
                  bgcolor: account.color,
                }}
                src={account.image ?? ''}
                alt={account.name ?? ''}
              >
                {account.name[0]}
              </Avatar>
            </ListItemIcon>
            <ListItemText
              sx={{
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'flex-start',
                width: '100%',
              }}
              primary={account.name}
              secondary={account.email}
              primaryTypographyProps={{ variant: 'body2' }}
              secondaryTypographyProps={{ variant: 'caption' }}
            />
          </MenuItem>
        ))}
      </MenuList>
      <Divider />
      <AccountPopoverFooter>
        <SignOutButton />
      </AccountPopoverFooter>
    </Stack>
  );
}

const createPreviewComponent = (mini) => {
  function PreviewComponent(props) {
    return <AccountSidebarPreview {...props} mini={mini} />;
  }
  return PreviewComponent;
};

function SidebarFooterAccount({ mini }) {
  const PreviewComponent = React.useMemo(() => createPreviewComponent(mini), [mini]);
  return (
    <Account
      slots={{
        preview: PreviewComponent,
        popoverContent: SidebarFooterAccountPopover,
      }}
      slotProps={{
        popover: {
          transformOrigin: { horizontal: 'left', vertical: 'bottom' },
          anchorOrigin: { horizontal: 'right', vertical: 'bottom' },
          disableAutoFocus: true,
          slotProps: {
            paper: {
              elevation: 0,
              sx: {
                overflow: 'visible',
                filter: (theme) =>
                  `drop-shadow(0px 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.32)'})`,
                mt: 1,
                '&::before': {
                  content: '""',
                  display: 'block',
                  position: 'absolute',
                  bottom: 10,
                  left: 0,
                  width: 10,
                  height: 10,
                  bgcolor: 'background.paper',
                  transform: 'translate(-50%, -50%) rotate(45deg)',
                  zIndex: 0,
                },
              },
            },
          },
        },
      }}
    />
  );
}

SidebarFooterAccount.propTypes = {
  mini: PropTypes.bool.isRequired,
};

const demoSession = {
  user: {
    name: 'Bharat Kashyap',
    email: 'bharatkashyap@outlook.com',
    image: 'https://avatars.githubusercontent.com/u/19550456',
  },
};
// ---------------------------------------------------

// 대표 컴포넌트, 전체 구성 ----------------------------
function DashboardLayoutAccountSidebar(props) {
  const { window } = props;

  // 최초 페이지 주소를 설정 => 상태변수
  const [pathname, setPathname] = React.useState('/dashboard');

  // 라우터 구성 : pathname이 변경되면 -> 새로운 페이지로 구성하겠금 조정
  const router = React.useMemo(() => {
    return {
      pathname,
      searchParams: new URLSearchParams(),
      navigate: (path) => setPathname(String(path)),
    };
  }, [pathname]);

  // Remove this const when copying and pasting into your project.
  const demoWindow = window !== undefined ? window() : undefined;

  // 세션 -> 로그인 하면 생성되는 정보 (데모수준)
  const [session, setSession] = React.useState(demoSession); // 상태변수
  const authentication = React.useMemo(() => {
    return {
      signIn: () => {
        setSession(demoSession);
      },
      signOut: () => {
        setSession(null);
      },
    };
  }, []);

  /*
    mui 기반의 대시보드는 아래 구조를 따른다!!
        <AppProvider>
            <DashboardLayout>
                <커스텀 컴포넌트(컨텐츠)/>
            </DashboardLayout>
        </AppProvider> 
  */
  return (
    <AppProvider
      navigation={NAVIGATION}
      router={router}
      theme={demoTheme}
      window={demoWindow}
      authentication={authentication}
      session={session}
    >
      <DashboardLayout
        slots={{ toolbarAccount: () => null, sidebarFooter: SidebarFooterAccount }}
      >
        {/* 메뉴별 상세 페이지 컴포넌트!!, pathname(요청주소)이 변경되면 새로고침발생 */}
        <DemoPageContent pathname={pathname} />
      </DashboardLayout>
    </AppProvider>
  );
}

DashboardLayoutAccountSidebar.propTypes = {
  /**
   * Injected by the documentation to work in an iframe.
   * Remove this when copying and pasting into your project.
   */
  window: PropTypes.func,
};

export default DashboardLayoutAccountSidebar;
// ----------------------------------------------------

 

 

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
//import App from './App';
// import App2 from './App2';
// import App3 from './App3';
//import App4 from './App4'; // 훅-기본
//import App5 from './App5'; // 훅-게시판 구현 및 상세페이지연출
//import App6 from './App6'; // 훅-메모이제이션
//import App7 from './App7';   // 환율 계산기
import App8 from './App8';   // 훅-useContext

// TODO 게시판-0 mui 모듈 가져오기 및 사용
import App6_mui from './App6_mui';// App6를 기반으로 UI를 교체(mui 사용)
import App7_mui from './App7_mui';// App7를 기반으로 UI를 교체(mui 사용) : 입력계열
import DashBoard from './DashBoard'; // 대시보드, 로그인 이후 진입하는 페이지(관리자, CMS, 관제, ERP, ..)
import reportWebVitals from './reportWebVitals';
import ReducerComponent from './Reducer';
import CusHook from './CusHook';
import Counter from './state/Counter';
import CssComponent from './css/CssComponent';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <div>
    {/* <App /> */}
    {/* 함수형 컴포넌트 + 훅 기본형태 */}
    {/* <App2/> 
    <App3/> */}
    {/* <App4 url="https://fakestoreapi.com/products" pid="1"/> */}
    {/* <App5 url="https://fakestoreapi.com/products" pid="1"/> */}
    {/* <App6 url="https://fakestoreapi.com/products" pid="1"/> */}
    {/* <App7/> */}
    {/* <App8/> */}
    {/* <App6_mui url="https://fakestoreapi.com/products" pid="1"/> */}
    {/* <App7_mui/> */}
    {/* <DashBoard/> */}    
    {/* <CusHook/>
    <ReducerComponent />     */}
    {/* <Counter/> */}
    <CssComponent/>
  </div>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

Reducer.js

/**
 * - 훅
 *  - useReducer
 *  - 기존 상태변수 업데이트
 *      - 1가지 형태로 값을 업데이트, 값을 증가 등등 단순한 형태로 진행
 *      - ex) null <->  JSX, 1->2->3,.. 단순한 형태로 반영
 *      - 개선
 *          - 다양한 방식으로 업데이트 
 *              - ex) +1, -1, +2, +10, 100 세팅, 등등 다양한 요구사항 반영
 *          - useReducer를 이용하여 구현
 *              - 복잡, 다양한 업데이트 방식을 지원!
 *  - 요구사항
 *      - 업데이트 액션 (4가지 action을 정의)
 *          - 'up' : 수치값 +1
 *          - 'down' : 수치값 -1
 *          - 'reset' : 100으로 세팅
 *          - 'addLang' : 'ts'를 추가
 */
// 1. 모듈가져오기
import {
    useReducer
} from 'react';

// 2. 필요한 기능
function reducer( state, actions ) {
    // actions : 어떤 행위를 수행하라는 명령 : 'up', 'down', 'reset', 'addLang'
    // state : useReducer에서 정의한 데이터
    // 1. 전달된 데이터 추출
    const { action } = actions;
    const { age, langs } = state;

    // 2. 액션별 상태변수 업데이트(수정x , 새로운 객체로 대체) 처리
    if (action === 'up') {
        return { age:age+1, langs };
    } else if (action === 'down') {
        return { age:age-1, langs };
    } else if (action === 'reset') {
        return { age:100, langs };
    } else if (action === 'addLang') {
        return { age, langs:[ ...langs, 'ts' ] };
    }  
    throw Error('알수 없는 액션이 전달됨');
}

// 3. 커스텀컴포넌트 생성
export default function ReducerComponent(params) {
    // [ 상태변수, 액션을전달하는함수 ]
    const [ state, dispatch ] = useReducer( reducer, {
        // 관리하는 초기 데이터
        age:100,
        langs:[
            'JS', "react", "java", "springboot"
        ]
    } );
    return (
        <div style={{ margin:"1em"}}>
            <p>useReducer 테스트</p>
            {/* 객체 비구조화 할당(분해) 미사용 */} 
            <p>{ state.age }</p>
            <div>{ state.langs.map( (lang, i)=>{
                return (
                    <p key={i}>{lang}</p>
                );
            } ) }</div>
            <p>다양한 상태변수 업데이트</p>
            {/* 버튼클릭 -> 이벤트 발생 -> 액션전달( dispatch(액션) ) 
                -> 액션값에 따라 상태변수 업데이트( reducer가 처리 ) -> 화면갱신 */}
            <button onClick={ ()=>{ dispatch( { action:"up"      } ) } }>+ 1</button>
            <button onClick={ ()=>{ dispatch( { action:"down"    } ) } }>- 1</button>
            <button onClick={ ()=>{ dispatch( { action:"reset"   } ) } }>reset</button>
            <button onClick={ ()=>{ dispatch( { action:"addLang" } ) } }>랭귀지추가</button>
        </div>
    );
}

 

반응형