class T {
let t; //인스턴스가 생성될 때 인스턴스에 할당되는 변수 인스턴스가 유지되는 동안 계속 유지
// 속성(attribute, property, field)
function f(){
let t; //함수가 호출될 때 일시적으로 생성되고 함수의 수행이 종료되면 소멸
Component 클래스로부터 상속을 받아서 이 메서드를 내장하고 있다
필요에 따라 오버라이딩 해서 사용
❗️매우 중요 ❗️함수는 호출되면 메모리 할당을 받고 자신의 작업이 끝나면 바로 메모리 해제가 발생사기 때문에 상태 변화가 없음
함수형 컴포넌트에 상태 유지를 위한 방법 과 수명 주기 관련된 기능을 제시하고 자 만들어진 것이 Hook
useState() : 상태 유지를 위한 함수
useEffect() : 수명 주기 관련된 기능을 사용하기 위한 함수
useRef() : 함수 내에서 사용할 변수나 컴포넌트의 참조를 저장하기 위한 데이터를 생성
useMemo, useCallback, React.memo : 컴포넌트 성능 최적화를 위한 Hook - 함수 호출이나 함수생성 및 리랜더링을 특정한 state의 변화가 생겼을 때 만 수행
useReducer : 상태 관리를 컴포넌트에서 분리시키기 위한 Hook
import { useEffect } from "react";
//타이머가 수행할 함수와 시간을 받아서 타이머를 생성해주고 정리해주는 hook
//함수형 컴포넌트가 아니지만 useEffect를 사용
export const useInterval = (callback, duraion = 1000) =>{
useEffect(()=>{
//duraion 마다 callback 을 호출하는 타이머 작성
const id = setInterval(callback, duraion);
//컴포넌트가 화면에서 제거될 때 수행하는 코드
return () =>{
clearInterval(id);
}
})
}
import { useState } from "react";
import { useInterval } from "./CustomHook";
//1초마다 현재 시간을 리턴하는 사용자 정의 훅
export const useClock = () => {
//현재 시간 및 날짜를 기본값으로 갖는 state를 생성
const [today, setToday] = useState(new Date());
//1 초마다 today를 현재 시간으로 수정
useInterval(()=> setToday(new Date()))
return today;
}
const Clock = ({today}) => {
return(
<div>
<div>{today.toLocaleTimeString()}</div>
<div>{today.toLocaleDateString()}</div>
</div>
)
}
export default Clock;
import './App.css';
import Clock from "./components/Clock";
import { useClock } from "./hook/UseClock";
function App() {
const today = useClock();
return (
<>
<Clock today = {today}/>
</>
);
}
export default App;
const object = {a:1, b:2};
//object 의 b 속서ㅓㅇ의 값을 3으로 수정
object.b=3; //object 가 리액트의 state라면 허용되지 않음
//불변성을 유지하면서 수정
const nextObject = {
...object,
b:3
};
object = nextObject // database 에서의 commit
const todos = [
{}.{} ];
//데이터 추가 - 복제본을 만들어서 객체를 추가하고 리턴
todos.concat(추가할 객체)
//데이터 삭제 - 조건에 맞는 데이터를 삭제
todos.filter(todo => 삭제되지 않을 조건)
//데이터 수정 - 조건에 맞는 데이터만 수정하고 나머지는 그대로
todos.map(todo => 수정할 조건? 수정할 내용 : todo )
[1,2,3]
{a:1, b:2}
의 경우는 ...을 이용하면 복제가 가능하다
[[],[],..., {}] 의 경우는 ...을 이용하면 복제가 불가하다
이런 경우는 직접 복제를 수행하거나 immer 같은 라이브러리를 이용하면 됨
import './App.css';
import React, {useCallback, useState, useRef} from 'react';
function App() {
const [form, setForm] = useState({username:'', name:''});
const [data,setData] = useState({
array:[],
uselessValue: null
})
const onChange = useCallback((e) => {
//form 은 2개의 속성을 갖는 일반 객체이므로 ...으로 복제가 가능
const {name, value} = e.target;
setForm({
...form,
[name]:[value]
})
}, [form]);
const nextId = useRef(1);
//submit 은 form 에 추가하는 이벤트
const onSubmit = useCallback(
(e)=>{
e.preventDefault()//기본 이벤트 처리 코드 무시
const info ={
id:nextId.current,
name: form.name,
username:form.username
}
//배열에 데이터 추가
setData({
...data,
array:data.array.concat(info)
})
//폼 초기화
setForm({
name:'',
username:''
})
//id 깂 1 증가
nextId.current = nextId.current + 1;
}, [data, form.name, form.username]
);
const onRemove = useCallback((id)=>{
setData({
...data,
array: data.array.filter((info)=> info.id !== id)
})
}, [data])
return (
<>
<form onSubmit={onSubmit}>
<input name='username' value={form.username} onChange={onChange}
placeholder='아이디 입력'/>
<input name='name' value={form.name} onChange={onChange}
placeholder='이름을 입력'/>
<button type='submit'>등록</button>
</form>
<div>
<ul>
{data.array.map(info => (<li key={info.id} onClick={()=> onRemove(info.id)}>{info.username}의 이름은({info.name})</li>))}
</ul>
</div>
</>
);
}
export default App;
produce(상태이름, draft=>{
draft.속성이름 = 수정하고자 하는 값이 나 표현식
});
$ yarn add immer
OR
$ npm install immer
import './App.css';
import React, {useCallback, useState, useRef} from 'react';
import produce from 'immer';
function App() {
const [form, setForm] = useState({username:'', name:''});
const [data,setData] = useState({
array:[],
uselessValue: null
})
const onChange = useCallback((e) => {
//form 은 2개의 속성을 갖는 일반 객체이므로 ...으로 복제가 가능
const {name, value} = e.target;
setForm(
produce(draft => {draft[name]=value})
)
}, []);
const nextId = useRef(1);
//submit 은 form 에 추가하는 이벤트
const onSubmit = useCallback(
(e)=>{
e.preventDefault()//기본 이벤트 처리 코드 무시
const info ={
id:nextId.current,
name: form.name,
username:form.username
}
//배열에 데이터 추가
setData(
produce(draft => {
draft.array.push(info);
})
)
//폼 초기화
setForm({
name:'',
username:''
})
//id 깂 1 증가
nextId.current = nextId.current + 1;
}, [form.name, form.username]
);
const onRemove = useCallback((id)=>{
setData(
produce(draft => {draft.array.splice(draft.array.findIndex(info => info.id === id) ,1)}
)
)}
, [])
return (
<>
<form onSubmit={onSubmit}>
<input name='username' value={form.username} onChange={onChange}
placeholder='아이디 입력'/>
<input name='name' value={form.name} onChange={onChange}
placeholder='이름을 입력'/>
<button type='submit'>등록</button>
</form>
<div>
<ul>
{data.array.map(info => (<li key={info.id} onClick={()=> onRemove(info.id)}>{info.username}의 이름은({info.name})</li>))}
</ul>
</div>
</>
);
}
export default App;
const 이름 = createContext(데이터)
export default 이름;
<이름.Consumer>
value.데이터
</이름.Consumer>
<이름.Provider value={이름:값}>
</이름.Provider>
import { createContext } from "react";
const ColorContext = createContext({color:'white'});
export default ColorContext;
import React from "react";
import ColorContext from "../contexts/Color";
const ColorBox = () => {
return(
<ColorContext.Consumer>
{
value => (
<div style={{
width:'64px',
height:'64px',
backgroung:value.color
}}/>
)
}
</ColorContext.Consumer>
)
}
export default ColorBox;
import './App.css';
import React, {useCallback, useState, useRef} from 'react';
import produce from 'immer';
import ColorBox from './components/ColorBox';
function App() {
const [form, setForm] = useState({username:'', name:''});
const [data,setData] = useState({
array:[],
uselessValue: null
})
const onChange = useCallback((e) => {
//form 은 2개의 속성을 갖는 일반 객체이므로 ...으로 복제가 가능
const {name, value} = e.target;
setForm(
produce(draft => {draft[name]=value})
)
}, []);
const nextId = useRef(1);
//submit 은 form 에 추가하는 이벤트
const onSubmit = useCallback(
(e)=>{
e.preventDefault()//기본 이벤트 처리 코드 무시
const info ={
id:nextId.current,
name: form.name,
username:form.username
}
//배열에 데이터 추가
setData(
produce(draft => {
draft.array.push(info);
})
)
//폼 초기화
setForm({
name:'',
username:''
})
//id 깂 1 증가
nextId.current = nextId.current + 1;
}, [form.name, form.username]
);
const onRemove = useCallback((id)=>{
setData(
produce(draft => {draft.array.splice(draft.array.findIndex(info => info.id === id) ,1)}
)
)}
, [])
return (
<>
<ColorBox/>
<form onSubmit={onSubmit}>
<input name='username' value={form.username} onChange={onChange}
placeholder='아이디 입력'/>
<input name='name' value={form.name} onChange={onChange}
placeholder='이름을 입력'/>
<button type='submit'>등록</button>
</form>
<div>
<ul>
{data.array.map(info => (<li key={info.id} onClick={()=> onRemove(info.id)}>{info.username}의 이름은({info.name})</li>))}
</ul>
</div>
</>
);
}
export default App;
import { createContext, useState } from "react";
//const ColorContext = createContext({color:'red'});
const ColorContext = createContext({
state: {color:'black', subcolor:'red'},
actions:{
setColor: () => {},
setSubColor:()=> {}
}
})
const ColorProvider = ({children}) => {
const [color,setColor] = useState('black');
const [subcolor,setSubColor] = useState('red');
const value = {
state:{color,subcolor},
actions:{setColor,setSubColor}
}
return(
<ColorContext.Provider value={value}>{children}</ColorContext.Provider>
)
}
const {Consumer:ColorConsumer} = ColorContext;
export {ColorProvider, ColorConsumer}
export default ColorContext;
import './App.css';
import React, {useCallback, useState, useRef} from 'react';
import produce from 'immer';
import ColorBox from './components/ColorBox';
import ColorContext from './contexts/Color';
function App() {
return (
<ColorContext.Provider value={{color:'red'}}>
<ColorBox/>
</ColorContext.Provider>
);
}
export default App;
import React, {Component} from "react";
import ColorContext from "../contexts/Color";
const colors = ['red', 'orange', 'black','green', 'blue','indigo'];
class SelectColor extends Component{
static contextType = ColorContext;
handleSetColor = color => {
this.context.actions.setColor(color);
}
handleSetSubColor = subcolor => {
this.context.actions.setSubColor(subcolor);
}
render(){
return(
<div>
<h2>색상 선택</h2>
<div style={{display:'flex'}}>
{colors.map(color => (
<div key={color}
style={{
background:color,
width:'24px',
height:'24px',
cursor:'pointer'
}}
onClick={()=> this.handleSetColor(color)}
onContextMenu = {(e) =>{
e.preventDefault();
this.handleSetSubColor(color)
}}
/>
))}
</div>
</div>
)
}
}
export default SelectColor
import './App.css';
import ColorBox from './components/ColorBox';
import SelectColor from './components/SelectColor';
import ColorContext, { ColorProvider } from './contexts/Color';
function App() {
return (
<ColorProvider>
<SelectColor/>
<ColorBox/>
</ColorProvider>
);
}
export default App;
SPA 가 아닌 전통적인 방식의 Web Application
웹 브라우저에서 서버로 요청을 하면 서버는 요청에 따른 HTML 페이지를 만들어서 응답
HTML 을 만들 떄 정적인 HTML 을 이용하기도 하고 Template Engine 이 만들어 낸 HTML을 이용
클라이언트 애플리케이션의 뷰가 어떻게 보여질 것인지 서버가 결정해서 전송하는데 이 때 클라이언트 웹 브라우저는 HTML 을 전송받아서 해석 후 랜더링을 수행
서버에서 제공되는 정보가 많거나 주기적으로 데이터가 변경되는 경우 랜더링 속도에 문제가 발생하는데 캐싱과 압축을 이용해서 서비스를 제공 하면 문제를 어느정도 해소 되자 사용자 와 인터랙션이 많은 모던 웹 애플리케이션에서는 완전한 해결책이 아니다
랜더링을 서버 측에서 담당하게 되면 랜더링을 위한 서버 자원이 소모되고 불필요한 트래픽도 발생
SPA - Single Page Application
SPA 의 단점
$ npm install react-router-dom
OR
$ yarn add react-router-dom
리액트 라우터는 라이브러리의 변화가 최근에 많이 일어나서 1~2년 전의 코드도 제대로 동작하지 않는 경우가 많다
세 프로젝트 생성 - react-router
index.js 수정
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
reportWebVitals();
BrowserRouter 는 HTML5 의 History API 를 사용해서 전체 페이지를 새로 고침하지 않고고 URL 을 변경하고 props 로 조회가 가능하도록 해줌
메인 화면으로 사용할 컴포넌트 생성 - Home.jsx
import React from "react";
const Home = () => {
return(
<div>
<h1>메인 page</h1>
<p>처음 페이지에 오신것을 환영합니다</p>
</div>
)
}
export default Home;
import React from "react";
const About = () =>{
return(
<div>
<h1>소개 page</h1>
<p>리액트 라우터에 대한 페이지 입니다! 환영합니다</p>
</div>
)
}
export default About;
<Routes>
<Route path='url element = {컴포넌트} />
...
</Routes>
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
function App() {
return (
<Routes>
<Route path="/" element={<Home />}/>
<Route path="/about" element={<About />} />
</Routes>
);
}
export default App;
페이지를 전환하면 페이지를 새로 불러오지 않고 애플리케이션은 그대로 유지한 채 HTML5의 History API를 이용해서 URL만 변경
link 도 내부적으로 a 태그를 사용하지만 페이지 전환을 방지하는 기능을 내장
사용
<Link to ='url'>내용</Link>
import React from "react";
import { Link } from "react-router-dom";
const Home = () => {
return(
<div>
<h1>메인 page</h1>
<p>처음 페이지에 오신것을 환영합니다</p>
<Link to ='/about'>소개</Link>
</div>
)
}
export default Home;
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
function App() {
return (
<Routes>
<Route path="/" element={<Home />}/>
<Route path="/about" element={<About />} />
<Route path="/info" element={<About />} />
</Routes>
);
}
export default App;
개념
URL Parameter 는 URL 의 일부분을 파라미터 처럼 데이터로 사용하는 것
bloter.net 의 상세보기 URL 인데 맨 뒤의 blt로 시작하는 부분이 파라미터
Query String
URL 뒤에 ? 을 추가하고 이름=값의 형태로 만들어진 문자열
naver.com 의 검색창에 chat-gpt를 검색한 결과
URL 파라미터의 사용 : useParams() 를 이용해서 읽는 것이 가능
Profile.jsx 파일 생성
import { useParams } from "react-router-dom";
const data ={
lee:{
name: 'LEE',
description:'나는 LEE 사람입니다'
},
eui:{
name: 'EUI',
description:'나는 EUI 사람입니다'
},
joo:{
name: 'JOO',
description:'나는 JOO 사람입니다'
}
}
const Profile = () =>{
//URL 파라미터 읽기
const params = useParams();
// username 이라는 URL 파라미터를 이용해서 data 선택
const profile = data[params.username];
return(
<div>
<h1>사용자의 프로필</h1>
{profile ? (
<div>
<h2>{profile.name}</h2>
<p>{profile.description}</p>
</div>
) : (<p>존재하지 않는 프로필</p>)}
</div>
)
}
export default Profile;
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Profile from './components/About';
function App() {
return (
<Routes>
<Route path="/" element={<Home />}/>
<Route path="/about" element={<About />} />
<Route path="/info" element={<About />} />
<Route path="/profiles/:username" element={<Profile/>} />
</Routes>
);
}
export default App;
import React from "react";
import { Link } from "react-router-dom";
const Home = () => {
return(
<div>
<h1>메인 page</h1>
<p>처음 페이지에 오신것을 환영합니다</p>
<Link to ='/about'>소개</Link>
<ul>
<li><Link to ='/profiles/lee'>lee</Link></li>
<li><Link to ='/profiles/eui'>eui</Link></li>
<li><Link to ='/profiles/joo'>joo</Link></li>
</ul>
</div>
)
}
export default Home;
import React from "react";
//uselocation 이용
import { useLocation } from "react-router-dom";
const About = () =>{
const location = useLocation();
return(
<div>
<h1>소개 page</h1>
<p>리액트 라우터에 대한 페이지 입니다! 환영합니다</p>
<p>query string : {location.search}</p>
</div>
)
}
export default About;
$ npm install qs
OR
$ yarn add qs
import React from "react";
//uselocation 이용 //useSearchParams 임포트
import { useLocation, useSearchParams } from "react-router-dom";
import qs from 'qs';
const About = () =>{
//파라미터를 하나씩 읽기
const location = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const name = searchParams.get('name');
const job = searchParams.get('job');
//전체파라미터를 하나의 객체로 변환
const queryString = qs.parse
(location.search, {ignoreQueryPrefix:true});
console.log(queryString);
return(
<div>
<h1>소개 page</h1>
<p>리액트 라우터에 대한 페이지 입니다! 환영합니다</p>
<p>query string : {location.search}</p>
<p>{name}:{job}</p>
</div>
)
}
export default About;
<Route path='/articles' element={<Articles />}/>
<Route path='/articles/:id' element={<Articles />}/>
//메인으로 들어가야함
import React from 'react';
import { Link } from 'react-router-dom';
const Articles =() =>{
return(
<ul>
<li>
<Link to ='/articles/1'>
손으로 코딩하고 뇌로 컴파일 하고 눈으로 디버깅
코딩을 하기전에 생각을 먼저하는 습관을 가지자.
</Link>
</li>
<li>
<Link to ='/articles/2'>
생각후에는 행동으로 실행하자
</Link>
</li>
<li>
<Link to='/articles/3'>
행동으로 실행 하며 정리하자
</Link>
</li>
</ul>
)
}
export default Articles;
import { useParams } from "react-router-dom";
const Article = () => {
const {id} = useParams();
return(
<div>
<h2>게시글 {id}</h2>
</div>
)
}
export default Article;
import React from "react";
import { Link } from "react-router-dom";
const Home = () => {
return(
<div>
<h1>메인 page</h1>
<p>처음 페이지에 오신것을 환영합니다</p>
<Link to ='/about'>소개</Link>
<ul>
<li><Link to ='/profiles/lee'>lee</Link></li>
<li><Link to ='/profiles/eui'>eui</Link></li>
<li><Link to ='/profiles/joo'>joo</Link></li>
</ul>
<Link to ='/articles'>게시물 전체</Link>
</div>
)
}
export default Home;
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Profile from './components/Profile';
import Articles from './components/Articles';
import Article from './components/Article';
function App() {
return (
<Routes>
<Route path="/" element={<Home />}/>
<Route path="/about" element={<About />} />
<Route path="/info" element={<About />} />
<Route path="/profiles/:username" element={<Profile/>} />
<Route path="/articles" element={<Articles/>} />
<Route path="/articles/:id" element={<Article/>} />
</Routes>
);
}
export default App;
<Route element = {<공통 레이아웃 컴포넌트 />}>
<Route path='url' element={<컴포넌트/>} />
</Route>
url 에 해당하는 컴포넌트를 출력할 때 는 공통 레이아웃 컴포넌트를 출력하고 그 안의 Outlet 부분에 컴포넌트가 출력된다.
공통 레이아웃 적용
import { Outlet } from "react-router-dom";
const Layout = () => {
return(
<div>
<header style={{background:'lightgray', padding:16, fontSize:24}}>
머리말
</header>
<main>
<Outlet />
</main>
</div>
);
}
export default Layout;
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Profile from './components/Profile';
import Articles from './components/Articles';
import Article from './components/Article';
import Layout from './components/Layout';
function App() {
return (
<Routes>
<Route element={<Layout/>}>
<Route path="/" element={<Home />}/>
<Route path="/about" element={<About />} />
<Route path="/info" element={<About />} />
<Route path="/profiles/:username" element={<Profile/>} />
</Route>
<Route path="/articles" element={<Articles/>} />
<Route path="/articles/:id" element={<Article/>} />
</Routes>
);
}
export default App;
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Profile from './components/Profile';
import Articles from './components/Articles';
import Article from './components/Article';
import Layout from './components/Layout';
function App() {
return (
<Routes>
<Route element={<Layout/>}>
<Route index element={<Home />}/>
<Route path="/about" element={<About />} />
<Route path="/info" element={<About />} />
<Route path="/profiles/:username" element={<Profile/>} />
</Route>
<Route path="/articles" element={<Articles/>} />
<Route path="/articles/:id" element={<Article/>} />
</Routes>
);
}
export default App;
import { useNavigate } from "react-router-dom";
import { Outlet } from "react-router-dom";
const Layout = () => {
//Link 컴포넌트를 이용하지 않고 이동이 가능한 훅
const navigate = useNavigate();
const goArticles = () => {
navigate('/articles');
}
const goBack = () => {
navigate(-1);
}
return(
<div>
<header style={{background:'lightgray', padding:16, fontSize:24}}>
머리말
<button onClick={goBack}>뒤로</button>
<button onClick={goArticles}>게시글 전체</button>
</header>
<main>
<Outlet />
</main>
</div>
);
}
export default Layout;
import React from 'react';
import { NavLink, Outlet } from 'react-router-dom';
const Articles =() =>{
//현재 출력 중인 데이터와 동일한 링크에 적용할 스타일
const activeStyle = {
color:'green',
fontSize:24
}
return(
<div>
<Outlet/>
<ul>
<li>
<NavLink to ='/articles/1'
style={({isActive}) => (isActive ? activeStyle : undefined)}>
손으로 코딩하고 뇌로 컴파일 하고 눈으로 디버깅
코딩을 하기전에 생각을 먼저하는 습관을 가지자.
</NavLink>
</li>
<li>
<NavLink to ='/articles/2'
style={({isActive}) => (isActive ? activeStyle : undefined)}>
생각후에는 행동으로 실행하자
</NavLink>
</li>
<li>
<NavLink to='/articles/3'
style={({isActive}) => (isActive ? activeStyle : undefined)}>
행동으로 실행 하며 정리하자
</NavLink>
</li>
</ul>
</div>
)
}
export default Articles;