yarn은 npm(node package manager)
→ import, require할 수 있음, 설치하면 package.json에 들어감
npm i -d xxxx 혹은 yarn add xxxx같이 설치함(-d는 개발자모드)
yarn start하면 서버기동(포트번호 3000)
→ 디폴트페이지 index.html을 찾음
→ 이곳에 div있고 id값은 root
→ 이 div에 끼워넣기
→ single page application(SPA)이면서 화면은 변해야 한다
→ 이것을 가능하게 하는 것이 router API
브라우저가 DOM tree를 그려준다
→ 상속구조(부모와 자손)
→ 부모에서 자손으로만 props전달 가능(전개 연산자, 구조분해할당 필요)
→ 상위에서 자손으로 넘길 때 spread 연산자(직관적이기에), 구조분해할당
index.html에는 <div id=”root”></div> 만 있음
index.js
App 컴포넌트를 BrowserRouter로 감싸준다
→ SPA로 처리하면서 화면이동은 필요하기 때문에 router사용
구글 로그인에 대한 처리를 담당하는 공통 코드 authLogic 클래스를 모든 컴포넌트에서 재사용해야 하니까 index.js에서 객체생성을 하였다(const authLogic = new AuthLogic())
index.js에서 인증객체 생성(authLogic.js에 설정해 둠-바닐라 스크립트)
→ AuthLogic 클래스(객체)에 필드와 함수포함되어 있음
→ 자스에서 함수는 객체이기에 주소번지를 갖는다
→ dependency array
생성된 객체(AuthLogic.class)는 App컴포넌트에도 전달(props)해야 한다
props엔 여러 가지를 담을 수 있다(주소번지도 담을 수 있으니 onLogout같은 함수도 담을 수 있다)
→ 이때 props를 활용하여 부모 컴포넌트의 주소번지를 자손 컴포넌트에 넘길 수 있다
(주의: 자손 컴포넌트에서 부모 컴포넌트로는 불가함)
<BrowserRouter>
{/* App에 props로 authLogic를 담는다(주소번지) */}
<App authLogic={authLogic} onLogout={onLogout} />
</BrowserRouter>
useMemo / useCallback
캐시필요(서버), 캐시에 등록하는 걸 메모리제이션(memorization)이라고함
useMemo는 값(리턴이 와야 함), useCallback는 함수(리턴 필요 없음)를 메모리제이션함
App.jsx
jsx는 tag(html)와 js 섞어 쓰기 가능 → {js문법은 중괄호 안에 씀}
<Routes> 안에 화면의 개수만큼 <Route>
→ <Route>의 element에는 사용자 정의 componente가 옴(URL, 이동해야 할 페이지)
path를 정확히 지키려면 exact={true} → *같이 부정확하면 빼야 함
아래 코드에서 WorkoutPage로 넘겨주는 items는 useState훅 → 리렌더링이 일어남(자동으로 동기화됨)
<>
<Routes>
<Route path="/workout" exact={true} element={<WorkoutPage authLogic={authLogic} workouts={items} />} />
<Route path=”/board/*”>
</Routes>
</>
WorkoutPage → Workouts(n개의 rows-객체배열 → map사용, key 설정) → Workout(1개 row-객체배열)
{
workouts.map((item, index) => (
<Workout workout(1개 row)={item} />
))}
<리액트 props 연습 - index.js>
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import AuthLogic from "./service/authLogic";
import firebaseApp from "./service/firebase";
import "@fortawesome/fontawesome-free/js/all.js";
import SampleApp from "./SampleApp";
// 공통코드: service -> authLogic.js -> import 외부 js 재사용 가능 -> export default 클래스명 -> module
// 브라우저에서 import하려면 반드시 babel이 필요함
const authLogic = new AuthLogic(firebaseApp); // 인스턴스화(파라미터가 올 수 있다)
// 왜 파라미터가 필요한가? - firebaseApp -> import firebaseApp from "./service/firebase"; -> export default firebaseApp
// authLogic.파이어베이스에서 제공하는 함수를 호출하겠다
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<>
<BrowserRouter>
{/* App컴포넌트를 렌더링할 때 속성자리에 주소번지를 넘길 수 있다 - props */}
{/* 태그와 JS 섞어쓰기 가능 - 중괄호{} */}
{/* <App authLogic={authLogic} /> */}
<SampleApp />
</BrowserRouter>
</>
);
<리액트 props 연습 - SampleApp.jsx>
import React, { useState } from "react";
import SamplePage from "./components/sample/SamplePage";
const SampleApp = () => {
const [num, setNum] = useState(0);
const handleAdd = () => {
console.log("SampleApp handleAdd 호출");
setNum(num + 1);
};
const handleMinus = () => {
console.log("SampleApp handleMinus 호출");
setNum(num - 1);
};
return (
<>
<SamplePage num={num} handleAdd={handleAdd} handleMinus={handleMinus} />
</>
);
};
export default SampleApp;
<리액트 props 연습 - SamplePage.jsx>
import React from "react";
import SampleBottom from "./SampleBottom";
import SampleHeader from "./SampleHeader";
import SubPage from "./SubPage";
const SamplePage = (props) => {
const handleAdd = (num) => {
console.log("SamplePage handleAdd : " + num);
props.handleAdd(num);
};
const handleMinus = (num) => {
console.log("SamplePage handleMinus : " + num);
props.handleMinus(num);
};
return (
<>
<SampleHeader num={props.num} />
<div
style={{ border: "5px solid darkred", width: "600px", height: "300px" }}
>
<SubPage handleAdd={handleAdd} handleMinus={handleMinus} />
</div>
<SampleBottom />
</>
);
};
export default SamplePage;
<리액트 props 연습 - SampleHeader.jsx>
import React from "react";
const SampleHeader = (props) => {
return (
<>
<div style={{ border: "5px solid yellow", width: "600px", height:'100px' }}>
SampleHeader페이지 영역
<h1>{props.num}</h1>
<br />
</div>
</>
);
};
export default SampleHeader;
<리액트 props 연습 - SampleBottom.jsx>
import React from "react";
const SampleBottom = () => {
return (
<>
<div style={{ border: "5px solid green", width: "600px", height:'100px' }}>
SampleBottom페이지 영역
<br />
</div>
</>
);
};
export default SampleBottom;
<리액트 props 연습 - SubPage.jsx>
import React from "react";
const SubPage = (props) => {
const handleAdd = () => {
console.log("SubPage handleAdd호출 ");
props.handleAdd(props.num);
};
const handleMinus = () => {
console.log("SubPage handleMinus호출");
props.handleMinus(props.num);
};
return (
<div
style={{ border: "5px solid darkblue", width: "300px", height: "150px" }}
>
SubPage
<button onClick={handleAdd}>증가</button>
<button onClick={handleMinus}>감소</button>
</div>
);
};
export default SubPage;
index.js에서 <App />
→ App.jsx
이벤트처리 증가, 감소: array → 객체
→ WorkoutPage.jsx
→ Workouts.jsx
반복문돌림 map(로우 주소번지, 인덱스, 배열객체자신) → 세 번째 파라미터는 안 씀
workouts.map((item) => (
<Workout key={item.id} workout={item} />
))
Workout.jsx는 한 개 row만 처리
부모에서 자손으로 주소번지를 넘길 수 있다 → props로 부름
const Workout = (props) => {
const{workout} = props
return(
)
}
<리액트 props - index.js>
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import AuthLogic from "./service/authLogic";
import firebaseApp from "./service/firebase";
import "@fortawesome/fontawesome-free/js/all.js";
// import SampleApp from "./SampleApp";
// 공통코드: service -> authLogic.js -> import 외부 js 재사용 가능 -> export default 클래스명 -> module
// 브라우저에서 import하려면 반드시 babel이 필요함
const authLogic = new AuthLogic(firebaseApp); // 인스턴스화(파라미터가 올 수 있다)
// 왜 파라미터가 필요한가? - firebaseApp -> import firebaseApp from "./service/firebase"; -> export default firebaseApp
// authLogic.파이어베이스에서 제공하는 함수를 호출하겠다
// document.getElementById("root") -> index.html문서에서 DOM정보를 수집하는 것임
// const root = ReactDOM.createRoot(document.getElementById("root"));
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(
<>
<BrowserRouter>
{/* App컴포넌트를 렌더링할 때 속성자리에 주소번지를 넘길 수 있다 - props */}
{/* 태그와 JS 섞어쓰기 가능 - 중괄호{} */}
<App authLogic={authLogic} />
{/* <SampleApp /> */}
</BrowserRouter>
</>
);
<리액트 props - App.jsx>
import { Route, Routes } from "react-router-dom";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
//import DeptPage from './components/page/DeptPage';
import EmpPage from "./components/page/EmpPage";
import HomePage from "./components/page/HomePage";
import FireDeptPage from "./components/page/FireDeptPage";
import LoginPage from "./components/login/LoginPage";
import BoardPage from "./components/page/BoardPage";
import WorkoutPage from "./components/page/WorkoutPage";
import HackerNewsPage from "./components/page/HackerNewsPage";
import YoutubePage from "./components/page/YoutubePage";
import { useEffect, useState } from "react";
// index.js에서 브라우저 라우터로 감싸진 App태그 속성값으로 넘어온 주소번지를 받는다
const App = ({ authLogic }) => {
console.log("App 호출");
// 상수값 -> 나중에 axios(NodeJS, React-모듈화), fetch(바닐라-브라우저 지원)로 처리(둘 다 비동기 처리방식)
const [items, setItems] = useState([
{ id: 1, name: "벤치프레스", count: 0 },
{ id: 2, name: "랫풀다운", count: 0 },
{ id: 3, name: "스쿼트", count: 0 },
]);
/*
첫번째 파라미터는 콜백함수 - 객체
두번째 파라미터는 의존성 배열 - Dependency Array
의존성 배열이 비어있으면 최초 App 컴포넌트가 렌더링될 때 딱 한번만 실행됨
리렌더링이 되는 대상은 return안에 있는 코드들이다
만약 items가 변경되면 그때는 리렌더링이 일어남
*/
useEffect(() => {
console.log("effect 호출");
}, []);
const handleIncrement = (item) => {
const index = items.indexOf(item);
items[index].count += 1;
setItems([...items]);
};
const handleDecrement = (item) => {
const index = items.indexOf(item);
const count = items[index].count - 1;
items[index].count = count < 0 ? 0 : count;
setItems([...items]);
};
const handleDelete = (item) => {
console.log(`handleDelete => ${item.name}`);
const workouts = items.filter((workout) => workout.id != item.id);
setItems([...workouts]);
};
const handleAdd = (name) => {
console.log(`handleAdd => ${name}`);
// AddForm화면에서 사용자가 입력한 운동이름을 받아온다
// 세번째 파라미터는 0으로 초기화
// 스프레드 연산자를 활용하여 기존 배열에 한 개의 객체를 추가하는 코드
const workouts = [...items, { id: Date.now(), name, count: 0 }];
// 상태 훅에 반영 - spread 연산자를 활용하여 새로운 주소번지가 채번되도록 처리해야함
// 상태값이 변경되었다는 사실을 리액트에서 인지할 수 있다
setItems([...workouts]);
};
// 사용자 정의 컴포넌트에서 return 다음에 오는 코드가 element의 집합(컴포넌트)
// Router를 이용하면 SPA(Single Page Application)를 누릴 수 있다
return (
<>
<Routes>
{/* 아래같이 계속 authLogic 넘기는게 불편 -> 리덕스 배우면 상태관리가능! */}
<Route
path="/"
exact={true}
element={<LoginPage authLogic={authLogic} />}
/>
<Route
path="/home/:userId"
exact={true}
element={<HomePage authLogic={authLogic} />}
/>
<Route
path="/board"
exact={true}
element={<BoardPage authLogic={authLogic} />}
/>
<Route
path="/workout"
exact={true}
element={
<WorkoutPage
authLogic={authLogic}
workouts={items}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onDelete={handleDelete}
onAdd={handleAdd}
/>
}
/>
<Route
path="/hackernews"
exact={true}
element={<HackerNewsPage authLogic={authLogic} />}
/>
<Route
path="/youtube"
exact={true}
element={<YoutubePage authLogic={authLogic} />}
/>
<Route
path="/dept/:id"
exact={true}
element={<FireDeptPage authLogic={authLogic} />}
/>
{/* <Route path="/dept/:id" exact={true} element={<DeptPage authLogic={authLogic} />} /> */}
<Route
path="/emp"
exact={true}
element={<EmpPage authLogic={authLogic} />}
/>
</Routes>
</>
);
};
export default App;
<리액트 props - HomePage.jsx>
import React, { useEffect } from "react";
import Header from "../include/Header";
import Bottom from "../include/Bottom";
import { useNavigate, useParams } from "react-router-dom";
// 로그아웃 처리를 위해서 props에 authLogic 주소번지를 받아온다
const HomePage = ({ authLogic }) => {
const navigate = useNavigate();
let { userId } = useParams();
console.log(userId);
/*
onLogout이라는 변수가 함수객체를 가리키고있다 - 주소번지
함수가 가리키는 주소번지가 다르면 리렌더링이 일어남
리렌더링은 언제 발생하는가?
1. state 상태(데이터)가 변경되었을 때
원시형useState() / 객체-useState({}) / 배열-useState([]) / 배열객체-useState([{}])
2. props가 변경되었을 때
({title, onLogout, content}) -> spread operator(ES6)
3. 부모컴포넌트가 변경되었을 때(캡쳐링)
map(n건) -> item별로 분할(row)
BoardList - BoardRow - BoardDetail
컴포넌트들이 다 함수형이다(객체이다 -> 주소번지를 가짐)
첫글자 소문자 -> 이벤트처리함수
첫글자 대문자 -> 컴포넌트
컴포넌트는 렌더함수에 들어간다(렌더는 리턴에서 실제함수 그려줌)
리턴은 컴포넌트가 불려졌을때 실행되고 렌더링됨
*/
const onLogout = () => {
console.log("HomePage onLogout 호출");
authLogic.logout();
};
useEffect(() => {
authLogic.onAuthChange((user) => {
if (!user) {
navigate("/");
}
});
});
return (
<React.Fragment>
<Header userId={userId} onLogout={onLogout} />
<div>HomePage 페이지</div>
<Bottom />
</React.Fragment>
);
};
export default HomePage;
<리액트 props - HackerNewsPage.jsx>
import axios from "axios";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import Bottom from "../include/Bottom";
import Header from "../include/Header";
const NewsListUL = styled.ul`
background-color: gray;
padding-top: 10px;
padding-left: 6px;
padding-right: 6px;
padding-bottom: 18px;
`
const HackerNewsPage = ({ authLogic }) => {
const [newsList, setNewsList] = useState([]);
const NEWSURL = "https://api.hnpwa.com/v0/news/1.json";
const navigate = useNavigate();
const onLogout = () => {
console.log("HackerNewsPage onLogout 호출");
authLogic.logout();
};
useEffect(() => {
axios.get(NEWSURL).then((response) => {
console.log(response.data);
setNewsList(response.data);
});
}, []);
useEffect(() => {
authLogic.onAuthChange((user) => {
if (!user) {
navigate("/");
}
});
});
return (
<>
<Header onLogout={onLogout} />
<NewsListUL>
{newsList.map((item, index) => (
<li key={item.id}>{item.title}</li>
))}
</NewsListUL>
<Bottom />
</>
);
};
export default HackerNewsPage;
<리액트 props - WorkoutPage.jsx>
import React, { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import Bottom from "../include/Bottom";
import Header from "../include/Header";
import Workouts from "../workout/Workouts";
const WorkoutPage = ({ authLogic, workouts, onIncrement, onDecrement, onDelete, onAdd }) => {
// props로 받았을 경우 구조분해할당
// const {authLogic, workouts, onIncrement, onDecrement} = props
const navigate = useNavigate();
const onLogout = () => {
console.log("WorkoutPage onLogout 호출");
authLogic.logout();
};
const handleIncrement = (item) => {
console.log("handleIncrement => " + item)
/*
상위 컴포넌트(App.jsx)의 함수를 호출하는 코드임
WorkoutPage의 props에 App에서 선언된 workouts로 주소번지를 가지고 있는데
왜 여기서는 처리를 못하고 다시 상위 컴포넌트로 미루는 것인가?
-> setItems훅을 사용할 수 없기때문
setItems는 파라미터로 넘기지 않는다
-> 해당 컴포넌트의 화면 렌더링과 관련된 함수라서!
*/
onIncrement(item);
};
const handleDecrement = (item) => {
onDecrement(item);
};
const handleDelete = (item) => {
onDelete(item);
};
const handleAdd = (name) => {
// 부모에 정의된 함수 호출하기 - AddForm에서 inputRef로 설정된 값
onAdd(name);
};
useEffect(() => {
authLogic.onAuthChange((user) => {
if (!user) {
navigate("/");
}
});
});
return (
<>
<Header onLogout={onLogout} />
<Workouts
workouts={workouts}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onDelete={handleDelete}
onAdd={handleAdd}
/>
<Bottom />
</>
);
};
export default WorkoutPage;
<리액트 props - Workouts.jsx>
import React, { useState } from "react";
import AddForm from "./AddForm";
import Workout from "./Workout";
// props로 넘길 때 파라미터를 따로 작성하지 않아도 넘어감 - 동일한 메소드 선언 불가
const Workouts = ({ workouts, onIncrement, onDecrement, onDelete, onAdd }) => {
const handleIncrement = (item) => {
console.log("Workouts => " + item);
onIncrement(item);
};
const handleDecrement = (item) => {
onDecrement(item);
};
const handleDelete = (item) => {
onDelete(item);
};
const handleAdd = (name) => {
onAdd(name);
};
return (
<>
<div className="habits">
<AddForm onAdd={handleAdd} />
<ul>
{workouts.map((item, index) => (
<Workout
key={index}
workout={item}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onDelete={handleDelete}
/>
))}
</ul>
</div>
</>
);
};
export default Workouts;
<리액트 props - Workout.jsx>
import React, { useState } from "react";
// const Workout = (props) => {}
// const {workout, onIncrement} = props -> 구조분해할당
// 미리 구조분해할당으로 받아옴
const Workout = ({ workout, onIncrement, onDecrement, onDelete }) => {
console.log("Workout => " + workout);
const handleIncrement = () => {
// 이벤트 처리가 되어있지 않고 상위 컴포넌트의 함수를 호출하고있다
// 상위 컴포넌트의 함수는 props를 통해서 가져온다
// 상위 함수를 호출할 때 파라미터도 넘어간다
onIncrement(workout);
};
const handleDecrement = () => {
onDecrement(workout);
};
const handleDelete = () => {
onDelete(workout);
};
return (
<>
<li className="habit">
<span className="habit-name">{workout.name}</span>
<span className="habit-count">{workout.count}</span>
<button
className="habit-button habit-increase"
onClick={handleIncrement}
>
<i className="fas fa-plus-square"></i>
</button>
<button
className="habit-button habit-decrease"
onClick={handleDecrement}
>
<i className="fas fa-minus-square"></i>
</button>
<button className="habit-button habit-delete" onClick={handleDelete}>
<i className="fas fa-trash"></i>
</button>
</li>
</>
);
};
export default Workout;
<리액트 props - AddForm.jsx>
import React from "react";
const AddForm = (props) => {
// createRef는 브라우저에서 DOM요소에 접근해서 그 요소에 값이나 클릭이벤트를 등록했던 것처럼
// 리액트는 바로 DOM 요소에 접근하지 않고 필요할때 createRef 이용해서
// 멤버변수 정의한 다음, 그것을 원하는 해당하는 컴포넌트에 ref로 연결하면 된다
// 전변 선언시 접미어에 Ref를 붙여서 표시
const formRef = React.createRef();
const inputRef = React.createRef();
const onSubmit = (event) => {
// 이벤트 버블링 이슈 해결하기위해 반드시 추가할 것 - onSubmit이슈
event.preventDefault();
const name = inputRef.current.value;
name && props.onAdd(name);
formRef.current.reset();
};
return (
<form ref={formRef} className="add-for" onSubmit={onSubmit}>
<input
ref={inputRef} //이렇게 하면 위에서 선언한 전역변수와 연결됨
type="text"
className="add-input"
placeholder="Workout"
/>
<button className="add-button">Add</button>
</form>
);
};
export default AddForm;
'국비학원 > 수업기록' 카테고리의 다른 글
국비 지원 개발자 과정_Day73 (1) | 2023.03.14 |
---|---|
국비 지원 개발자 과정_Day72 (1) | 2023.03.13 |
국비 지원 개발자 과정_Day70 (1) | 2023.03.09 |
국비 지원 개발자 과정_Day69 (1) | 2023.03.08 |
국비 지원 개발자 과정_Day68 (0) | 2023.03.07 |
댓글