리액트는 자바스크립트에서 시작해서 그다음에 html이 됨.
JSX는 javascript를 확장한 문법.
props == propertys
컴포넌트의 첫글자는 반드시 대문자여야 함.
리액트의 좋은점은 UI에서 바뀐 부분만 업데이트 해줌.
컴포넌트 작성법.
1) function a() {
return()
}
2) const b = () => ();
<!DOCTYPE html>
<html lang="en">
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const root = document.getElementById('root');
function Title() {
return (
<h3 id="title" onMouseEnter={() => console.log('mouse enter')}>
Hello I'm a title
</h3>
);
}
// 소괄호는 return 한다는 걸 전제.
const Button = () => (
<button
style={{
backgroundColor: 'tomato',
}}
onClick={() => console.log('im clicked')}
>
Click me
</button>
);
const Container = () => (
<div>
<Title />
<Button />
</div>
);
ReactDOM.render(<Container />, root);
</script>
</html>
기본적으로 데이터가 저장되는 곳.
const root = document.getElementById('root');
function App() {
// setcounter는 실행되면 새값을 가지고 다시 렌더링 함.
const [counter, setCounter] = React.useState(0);
const onClick = () => {
//setCounter(counter + 1);
setCounter((current) => current + 1);
};
return (
<div>
<h3>Total clicks: {counter}</h3>
<button onClick={onClick}>Click me</button>
</div>
);
}
ReactDOM.render(<App />, root);
pratice
converter
<!DOCTYPE html>
<html lang="en">
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
function MinutesToHours() {
const [amount, setAmount] = React.useState(0);
const [inverted, setInverted] = React.useState(false);
const onChange = (event) => {
setAmount(event.target.value);
};
const reset = () => setAmount(0);
const onFlip = () => {
reset();
setInverted((current) => !current);
};
return (
<div>
<div>
<label htmlFor="minutes">Minutes</label>
<input
value={inverted ? amount * 60 : amount}
id="minutes"
placeholder="Minutes"
type="number"
onChange={onChange}
disabled={inverted}
/>
</div>
<div>
<label htmlFor="hours">Hours</label>
<input
value={inverted ? amount : Math.round(amount / 60)}
id="hours"
placeholder="Hours"
type="number"
disabled={!inverted}
onChange={onChange}
/>
</div>
<button onClick={reset}>Reset</button>
<button onClick={onFlip}>{inverted ? 'Turn back' : 'Invert'}</button>
</div>
);
}
function KmToMiles() {
const [amount, setAmount] = React.useState(1);
const [inverted, setInverted] = React.useState(false);
const onChange = (event) => {
setAmount(event.target.value);
};
const reset = () => setAmount(1);
const onFlip = () => {
reset();
setInverted((current) => !current);
};
return (
<div>
<div>
<label htmlFor="km">Km</label>
<input
value={inverted ? amount * 1.609 : amount}
id="km"
placeholder="Km"
type="number"
onChange={onChange}
disabled={inverted}
/>
</div>
<div>
<label htmlFor="miles">Miles</label>
<input
value={inverted ? amount : (amount / 1.609).toFixed(4)}
id="miles"
placeholder="Miles"
type="number"
disabled={!inverted}
onChange={onChange}
/>
</div>
<button onClick={reset}>Reset</button>
<button onClick={onFlip}>{inverted ? 'Turn back' : 'Invert'}</button>
</div>
);
}
function App() {
const [index, setIndex] = React.useState('xx');
const onSelect = (event) => {
setIndex(event.target.value);
};
return (
<div>
<h1>Super Converter</h1>
<select value={index} onChange={onSelect}>
<option value="xx">Selct your units</option>
<option value="0">Minutes & Hours</option>
<option value="1">KM & Miles</option>
</select>
<hr />
{index === 'xx' ? 'Please select your units' : null}
{index === '0' ? <MinutesToHours /> : null}
{index === '1' ? <KmToMiles /> : null}
</div>
);
}
const root = document.getElementById('root');
ReactDOM.render(<App />, root);
</script>
</html>
부모 컴포넌트로부터 자식 컴포넌트에 데이터를 보낼 수 있게 해주는 방법.
컴포넌트는 jsx를 반환하는 함수.
React.js는 자동으로 함수에 넣는 모든 prop들을 오브젝트 안으로 집어넣을 거임.
그 오브젝트는 컴포넌트의 첫번째 인자이자 유일한 인자(두번째 인자 없음.)
ex).
<Btn banana="Save Changes">
// => Btn({banana:"Save Changes"})
function Btn({ banana, big }) {
return (
<button
style={{
backgroundColor: 'tomato',
color: 'white',
padding: '10px 20px',
border: 0,
borderRadius: 10,
fontSize: big ? 18 : 16,
}}
>
{banana}
</button>
);
}
function App() {
return (
<div>
<Btn banana="Save Changes" big={true} />
<Btn banana="Continue" big={false} />
</div>
);
}
const root = document.getElementById('root');
ReactDOM.render(<App />, root);
컴포넌트를 렌더할지 안할지 결정할 수 있음.
function Btn({ text, onClick }) {
console.log(text, 'was rendered');
return (
<button
onClick={onClick}
style={{
backgroundColor: 'tomato',
color: 'white',
padding: '10px 20px',
border: 0,
borderRadius: 10,
}}
>
{text}
</button>
);
}
// state가 변하면 함수는 re render함.
// prop가 변하지 않을때도 컴포넌트가 리 렌더링 될 때가 있음.
// 원하질 않을 경우 memo를 사용.
// 그러면 props가 변할때는 컴포넌트가 변하고 props가 변하지 않을때는 컴포넌트가 리 렌더링 되지 않음.
const MemorizedBtn = React.memo(Btn);
function App() {
const [value, setValue] = React.useState('Save Changes');
const changeValue = () => setValue('Revert Changes');
// 여기서 onClick은 이벤트 리스너가 아니고 prop의 이름
// 왜냐하면 html 요소에 넣는게 아니라 컴포넌트에 넣는거니까.
// 커스텀 컴포넌트에 변수이름에 뭐든지 사용해도 상관없음.
return (
<div>
<MemorizedBtn text={value} onClick={changeValue} />
<MemorizedBtn text="Continue" />
</div>
);
}
const root = document.getElementById('root');
ReactDOM.render(<App />, root);
어떤 타입의 prop을 받고 있는지 체크해줌.
function Btn({ text, fontSize = 16 }) {
console.log(text, 'was rendered');
return (
<button
style={{
backgroundColor: 'tomato',
color: 'white',
padding: '10px 20px',
border: 0,
borderRadius: 10,
fontSize,
}}
>
{text}
</button>
);
}
Btn.propTypes = {
text: PropTypes.string.isRequired,
fontSize: PropTypes.number,
};
function App() {
const [value, setValue] = React.useState('Save Changes');
const changeValue = () => setValue('Revert Changes');
// 여기서 onClick은 이벤트 리스너가 아니고 prop의 이름
// 왜냐하면 html 요소에 넣는게 아니라 컴포넌트에 넣는거니까.
// 커스텀 컴포넌트에 변수이름에 뭐든지 사용해도 상관없음.
return (
<div>
<Btn text={value} fontSize={18} />
<Btn text={value} />
</div>
);
}
리액트 어플리케이션을 만드는 최고의 방식.
npmx create-react-app 프로젝트이름
MODULE.CSS
: CRA의 고유 기능으로, className을 활용하여 전역이 아닌 컴포넌트에 한정해서 스타일을 정해줄 수 있다.
첫 번째 render에만 코드가 실행되고 다른 state변화에는 실행되지 않도록 만들자
예) API를 통해 데이터를 가져올 때 컴포넌트 렌더에서 API를 부르고
이후 상태가 변화할 때 그 API에서 데이터를 다시 가져오지 않게 만들 수 있다
useEffect(callback, [])
: 대표적인 사용법 => API를 딱 한번만 호출하고 그 뒤로는 다시는 호출하기 싫은 경우
import { useState, useEffect } from 'react';
function App() {
const [counter, setValue] = useState(0);
const [keyword, setKeyword] = useState('');
const onClick = () => setValue((prev) => prev + 1);
const onChange = (event) => {
setKeyword(event.target.value);
};
// 한번만 렌더링 되고 실행안됨.
// 아무것도 보고 있지 않기 때문에.
useEffect(() => {
console.log('i run only once.');
}, []);
// keyword가 변할때만 다시 렌더링
useEffect(() => {
console.log("I run when 'keyword' changes.");
}, [keyword]);
// counter가 변할때만 다시 렌더링
useEffect(() => {
console.log("I run when 'counter' changes.");
}, [counter]);
// keyword 또는 counter가 변할때 다시 렌더링
useEffect(() => {
console.log('I run when keyword & counter change.');
}, [keyword, counter]);
return (
<div>
<input
value={keyword}
onChange={onChange}
type="text"
placeholder="Search here...."
/>
<h1>{counter}</h1>
<button onClick={onClick}>Click me</button>
</div>
);
}
export default App;
import { useState, useEffect } from 'react';
function Hello() {
useEffect(() => {
console.log('created :)');
//cleanup function(컴포넌트가 destroy될때 사용)
//ex.컴포넌트가 없어질때 어떤 분석 결과를 보내고 싶을때,
//또는 eventlistener를 지우고 싶을때
return () => console.log('destroyed :(');
}, []);
return <h1>Hello</h1>;
}
function App() {
const [showing, setShowing] = useState(false);
const onClick = () => setShowing((prev) => !prev);
return (
<div>
{showing ? <Hello /> : null}
<button onClick={onClick}>{showing ? 'Hide' : 'Show'}</button>
</div>
);
}
export default App;
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Detail from './routes/Detail';
import Home from './routes/Home';
function App() {
//switch는 route를 찾아서 컴포넌트를 렌더링 .
//한번에 하나의 route만 렌더링 하기 위해서 switch 씀.
return (
<Router>
<Switch>
<Route path="/movie/:id">
<Detail />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
);
}
export default App;
import PropTypes from 'prop-types';
//Link는 브라우저 새로고침 없이도 유저를 다른 페이지로 이동 시켜주는 컴포넌트.
import { Link } from 'react-router-dom';
import styles from './Movie.module.css';
function Movie({ id, coverImg, title, year, summary, genres }) {
return (
<div className={styles.movie}>
<img className={styles.movie__img} src={coverImg} alt={title} />
<div>
<h2 className={styles.movie__title}>
<Link to={`/movie/${id}`}>{title}</Link>
</h2>
<h3 className={styles.movie__year}>{year}</h3>
<p>{summary.length > 235 ? `${summary.slice(0, 235)}...` : summary}</p>
<ul className={styles.movie__genres}>
{genres.map((g, index) => (
<li key={index}>{g}</li>
))}
</ul>
</div>
</div>
);
}
Movie.propTypes = {
id: PropTypes.number.isRequired,
coverImg: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
summary: PropTypes.string.isRequired,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default Movie;
//useparams는 react router의 변수 값을 넘겨줌.
import { useCallback, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import styles from './Detail.module.css';
function Detail() {
const [loading, setLoading] = useState(true);
const [movie, setMovie] = useState([]);
const { id } = useParams();
const getMovie = useCallback(async () => {
const json = await (
await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)
).json();
setMovie(json.data.movie);
setLoading(false);
}, [id]);
useEffect(() => {
getMovie();
}, [getMovie]);
return (
<div className={styles.loader}>
{loading ? (
<h1>Loading...</h1>
) : (
<div className={styles.movie}>
<img
className={styles.img}
src={movie.medium_cover_image}
alt={movie.title}
/>
<div>
<h1>{movie.title}</h1>
<p>{movie.description_intro}</p>
<p>language: {movie.language}</p>
<p>Rating: {movie.rating}</p>
<p>Year: {movie.year}</p>
</div>
</div>
)}
</div>
);
}
export default Detail;
npm i gh-pages
1.(리액트 기준) npm run build를 실행하면 웹사이트의 production ready code를 생성하게 됨.
production ready란 코드가 압축되고 모든게 최적화되어 있는 것.
실행되면 build라는 폴더가 생길거임
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"deploy": "gh-pages -d build",
"predeploy": "npm run build"
},
"homepage": "https://github계정이름.github.io/레포지토리이름"
gh-pages에서 -d는 디렉토리.
predeploy를 만든 이유는 빌드를 하고 deploy를 해야 한다는것을 계속 인지하고 싶지 않아서 만든거.
그래서 deploy를 실행하면 node.js가 predeploy를 먼저 실행
그 다음에 deploy가 시작.
react는 툴이 업데이트되고 나서 망가져서 고쳐야 하는 breaking changes가 일어나지 않음.
편리한 방식으로 데이터를 fetch 시킴.
실행하고 있었던 로직들을 축약해줌.
import { QueryClient, QueryClientProvider } from 'react-query';
const queryClient = new QueryClient();
ReactDOM.render(
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</QueryClientProvider>
document.getElementById('root')
);
fetcher 함수 만들기.
export function fetchCoins() {
return fetch('https://api.coinpaprika.com/v1/coins').then((response) =>
response.json()
);
}
// useQuery hook은 fetcher함수 fetchCoins를 부르고
//fetcher함수가 loading 중이라면 react query는 isLoading에 알려줌.
//fetcher함수가 끝난 값을 data에 넣어줄거임.
//react query는 데이터를 캐시에 저장.
const { isLoading, data } = useQuery<ICoin[]>('allCoins', fetchCoins);
state 관리 library
form 관리를 편안하게 해주는 패키지.