/**
* 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>
);
}
'SK 쉴더스 루키즈 지능형 AI 애플리케이션 개발자 트랙 2기 > 강의' 카테고리의 다른 글
[SK shieldus 지능형 애플리케이션 개발 트랙 2기] 6 (Java) (2) | 2024.12.29 |
---|---|
[SK shieldus 지능형 애플리케이션 개발 트랙 2기] 5 (MySQL) (0) | 2024.12.17 |
[SK shieldus 지능형 애플리케이션 개발 트랙 2기] 4 (react_1) (1) | 2024.12.09 |
[SK shieldus 지능형 애플리케이션 개발 트랙 2기] 3-0 (html, css) (4) | 2024.12.06 |
[SK shieldus 지능형 애플리케이션 개발 트랙 2기] 2-16 (자바스크립트 예외 ) (1) | 2024.12.06 |