render() {...}
컴포넌트를 정의하는 render
함수.
html 형식의 문자열을 반환하지 않고, 뷰가 어떻게 생겼고 어떻게 작동하는지에 대한 정보를 지닌 객체를 반환함. 컴포넌트 내부에는 또 다른 컴포넌트들이 들어갈 수 있으며 내부의 컴포넌트들을 재귀적으로 렌더링한다.
뷰가 변형되는 것이 아닌 새로운 요소로 갈아 치우는 것. 데이터를 업데이트 했을 때 render 함수를 호출해, 두 가지 뷰를 최소한의 연산으로 비교한 후 DOM 트리를 업데이트한다.
리액트는 Virtual DOM
을 사용한다.
브라우저에 노출되는 element들에 저급하여 조작하는 대신 추상화한 자바스크립트 객체를 구성하여 사용
import React 'from';
import
구문.package.json
을 수정하면 require/exports
대신 사용할 수 있다.{
...
type: 'module',
...
}
function App() {
return (
<div>
Hello <b>React</b>
</div>
)
}
다음과 같은 방식으로 변환
function App() {
return React.createElement("div", null, "Hello", React.createElement("b", null, "react));
}
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
ReactDom.render
는 컴포넌트를 페이지에 렌더링하는 역할을 한다React.SttrictMode
는 리액트의 레거시 기능을 사용하면 경고를 출력한다import React from 'react';
React17에서 생략 가능
https://ko.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
반드시 부모 요소 하나로 감싸야 한다.
:collision:
import React from 'react';
function App() {
return (
<h1>리액트 안녕!</h1>
<h2>잘 작동하니?</h2>
)
}
export default App;
:+1:
import React from 'react';
function App() {
return (
<div>
<h1>리액트 안녕!</h1>
<h2>잘 작동하니?</h2>
</div>
)
}
export default App;
혹은...
import React, {Fragment} from 'react';
function App() {
return (
<Fragment>
<h1>리액트 안녕!</h1>
<h2>잘 작동하니?</h2>
</Fragment>
)
}
export default App;
또는...
import React from 'react';
function App() {
return (
<>
<h1>리액트 안녕!</h1>
<h2>잘 작동하니?</h2>
</>
)
}
export default App;
JSX
내부에서 {}
로 감싸면 자바스크립트 표현식을 쓸 수 있다
import React from 'react';
const name = '리액트';
function App() {
return (
<>
<h1>{name} 안녕!</h1>
<h2>잘 작동하니?</h2>
</>
);
}
export default App;
import React from 'react';
const name = '리액트';
function App() {
return (
<>
{name === '리액트' ? (
<h1>리액트입니다.</h1>
) : (
<h2>리액트가 아닙니다.</h2>
)}
</>
);
}
export default App;
import React from 'react';
const name = 'ㄹㅇㅌ';
function App() {
return (
<>
{name === '리액트' ? (
<h1>리액트입니다.</h1>
) : (
<h2>리액트가 아닙니다.</h2>
)}
</>
);
}
export default App;
import React from 'react';
const name = 'ㄹㅇㅌ';
function App() {
return (
<>
{name === '리액트' ? (
<h1>리액트입니다.</h1>
) : null}
</>
);
}
export default App;
아래와 같다
import React from 'react';
const name = 'ㄹㅇㅌ';
function App() {
return (
<>
{name === '리액트' && (
<h1>리액트입니다.</h1>
)}
</>
);
}
export default App;
JSX가 여러 줄일 때 주로 괄호로 감싸지만 필수는 아님
import React from 'react';
function App() {
const name = '리액트';
const style = {
backgroundColor: 'black',
color: 'aqua',
fontSize: '48px',
fontWeight: 'bold',
padding: 16, // 단위 생략, px
};
return <div style={style}>{name}</div>;
}
export default App;
또는...
import React from 'react';
function App() {
const name = '리액트';
return <div style={{
backgroundColor: 'black',
color: 'aqua',
fontSize: '48px',
fontWeight: 'bold',
padding: 16, // 단위 생략, px
}}>{name}</div>;
}
export default App;
<div className="react"></div>
<br />
<input></input>
import React from 'react';
function App() {
const name = '리액트';
return (
<>
{/* 주석은 이렇게 작성합니다. */}
<div
className="react" // 시작 태그를 여러 줄로 작성하게 된다면 여기에 주석을 작성 할 수 있습니다.
>
{name}
</div>
// 하지만 이런 주석이나 /* 이런 주석은 페이지에 그대로 나타나게 됩니다. */
<input />
</>
);
}
export default App;
22-05-06
import React from 'react';
const MyComonent = () => {
return <div>나의 새롭고 멋진 컴포넌트</div>;
};
export default MyComponent;
export default MyComponent;
import React from 'react';
import MyComponent from './MyComponent';
const MyComonent = () => {
return <MyComponent>;
};
export default App;
import React from 'react';
const MyComponent = (props) => {
return <div>제 이름은 {props.name}입니다.</div>;
};
export default MyComponent;
import React from 'react';
import MyComponent from './MyComponent';
const App = () => {
return <MyComponent name="React" />;
};
export default App;
...
MyComponent.defaultProps = {
name: '기본 이름'
}
...
import React from 'react';
import MyComponent from './MyComponent';
const App = () => {
return <MyComponent>리액트</MyComponent>;
};
export default App;
import React from 'react';
const MyComponent = (props) => {
return (
<div>
제 이름은 {props.name}입니다. <br />
children 값은 {props.children} 입니다.
</div>
);
};
MyComponent.defaultProps = {
name: '기본 이름',
};
export default MyComponent;
...
const { name, children } = props;
...
...
const MyComponent = ({name, children}) => {
...
import PropTypes from 'prop-types'
import 구문으로 propTypes을 불러옴
MyComponent.propTypes = {
name: PropTypes.string,
favoriteNumber: PropTypes.number.isRequired
};
- array: 배열
- bool: true or false
- func: 함수
- obejct: 객체
- string: 문자열
- instanceOf(클래스): 특정 클래스의 인스턴스
- oneOf(['dog', 'cat']): 주어진 배열 요소 중 하나
- oneOfType([React.PropTypes.string, Proptypes.number]): 배열 안의 종류 중 하나
- any: 아무거나
import React, { useState } from 'react';
const Say = () => {
const [message, setMessage] = useState('');
const onClickEnter = () => setMessage('안녕하세요!');
const onClickLeave = () => setMessage('안녕히 가세요!');
const [color, setColor] = useState('black');
return (
<div>
<button onClick={onClickEnter}>입장</button>
<button onClick={onClickLeave}>퇴장</button>
<h1 style={{ color }}>{message}</h1>
<button style={{ color: 'red' }} onClick={() => setColor('red')}>
빨간색
</button>
<button style={{ color: 'green' }} onClick={() => setColor('green')}>
초록색
</button>
<button style={{ color: 'blue' }} onClick={() => setColor('blue')}>
파란색
</button>
</div>
);
};
export default Say;
//객체 다루기
const object = { a:1, b:2, c:3 };
const nextObject = { ...object, b: 2}; //사본을 만들어서 b값만 덮어쓰기
// 배열 다루기
const array = [
{ id: 1, value: true},
{ id: 2, value: true},
{ id: 3, value: false}
];
let nextArray = array.concat({id:4}) //새 항목 추가
nextArray.filter(item => item.id !== 2); // id가 2인 항목 제거
nextArray.map(item=> (item.id === 1 ? {...item, value: false} : item)); //id가 1인 항목의 value를 false로 설정
리액트의 이벤트 시스템은 HTML 이벤트와 비슷하다
import React, { useState } from 'react';
const EventPractice = () => {
const [form, setForm] = useState({
username: '',
message: ''
});
const { username, message } = form;
const onChange = e => {
setTimeout(() => console.log(e), 500);
const nextForm = {
...form,
[e.target.name]: e.target.value,
};
setForm(nextForm);
};
const onClick = () => {
alert(username + ': ' + message);
setForm({
username: '',
message: ''
});
};
const onKeyPress = e => {
if (e.key === 'Enter') {
onClick();
}
};
return (
<div>
<h1>이벤트 연습</h1>
<input
type="text"
name="username"
placeholder="유저명"
value={username}
onChange={onChange}
/>
<input
type="text"
name="message"
placeholder="아무거나 입력해보세요"
value={message}
onChange={onChange}
onKeyPress={onKeyPress}
/>
<button onClick={onClick}>확인</button>
</div>
);
};
export default EventPractice;
리액트는 자바스크립트에 익숙하면 쉽게 사용할 수 있다!
ref
: DOM에 이름 달기.success {
background-color: lightgreen;
}
.failure {
background-color: lightcoral;
}
import React, { useState, useRef } from 'react';
import './ValidationSample.css';
const ValidationSample = () => {
const input = useRef();
const [password, setPassword] = useState('');
const [clicked, setClicked] = useState('');
const [validated, setValidated] = useState('');
const handleChange = (e) => {
setPassword(e.target.value);
};
const handleButtonClick = () => {
setClicked(true);
setValidated(password === '0000');
};
return (
<div>
<input ref={input} type="password" value={password} onChange={handleChange} className={clicked ? (validated ? 'success' : 'failure') : ''} />
<button onClick={handleButtonClick}>검증하기</button>
</div>
);
};
export default ValidationSample;
import ScrollBox from './ScrollBox';
import { useRef } from 'react';
const App = () => {
const scrollBoxRef = useRef();
return (
<div>
<ScrollBox ref={scrollBoxRef} />
<button
onClick={() => {
const { scrollHeight, clientHeight } = scrollBoxRef.current;
scrollBoxRef.current.scrollTop = scrollHeight - clientHeight;
}}
>
맨 밑으로
</button>
</div>
);
};
export default App;
import React, { forwardRef } from 'react';
const ScrollBox = forwardRef((props, ref) => {
const style = {
border: '1px solid black',
height: '300px',
width: '300px',
overflow: 'auto',
position: 'relative',
};
const innerStyle = {
width: '100%',
height: '650px',
background: 'linear-gradient(white, black)',
};
return (
<div style={style} ref={ref}>
<div style={innerStyle} />
</div>
);
});
export default ScrollBox;
arr.map(callback, [thisArg]);
import React from 'react';
const IterationSample = () => {
const names = ['눈사람', '얼음', '눈', '바람'];
const nameList = names.map(name => <li>{name}</li>);
return <ul>{nameList}</ul>;
};
export default IterationSample;
import { useState } from 'react';
const App = () => {
const [names, setNames] = useState([
{ id: 1, text: '눈사람' },
{ id: 2, text: '얼음' },
{ id: 3, text: '눈' },
{ id: 4, text: '바람' },
]);
const [inputText, setInputText] = useState('');
const [nextId, setNextId] = useState(5); //새로운 항목을 추가할 때 사용할 id
const onChange = (e) => setInputText(e.target.value);
const onClick = () => {
const nextNames = names.concat({
id: nextId,
text: inputText,
});
setNextId(nextId + 1);
setNames(nextNames);
setInputText('');
};
const onRemove = (id) => {
const nextNames = names.filter((name) => name.id !== id);
setNames(nextNames);
};
const namesList = names.map((name) => (
<li key={name.id} onDoubleClick={() => onRemove(name.id)}>
{name.text}
</li>
));
return (
<>
<input value={inputText} onChange={onChange} />
<button onClick={onClick}>추가</button>
<ul>{namesList}</ul>
</>
);
};
export default App;
concat
, filter
등의 배열 내장 함수 사용불변성 유지
import React, { useState } from 'react';
const Counter = () => {
const [value, setValue] = useState(0);
return (
<div>
<p>
현재 카운터 값은 <b>{value}</b> 입니다.
</p>
<button onClick={() => setValue(value + 1)}>+1</button>
<button onClick={() => setValue(value - 1)}>-1</button>
</div>
);
};
export default Counter;
const [value, setValue] = useState(0);
value
: 상태변수setValue
: 상태변수의 값을 갱신하기 위한 함수useState(0)
: 상태변수의 초기값return (
<p>
현재 카운터 값은 <b>{value}</b> 입니다.
</p>
);
setValue()
함수를 통해 갱신되면 자동으로 화면에 실시간 반영import React from 'react';
import useInputs from './useInputs';
const Info = () => {
const [state, onChange] = useInputs({
name: '',
nickname: ''
});
const { name, nickname } = state;
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
<div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임: </b> {nickname}
</div>
</div>
</div>
);
};
export default Info;
useEffect
는 리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook...
useEffect(() => {
console.log('렌더링이 완료되었습니다');
console.log({
name,
nickname,
});
});
...
:question:StrictMode에서는 렌더링이 두 번 됨
함수 두 번째 파라미터로 비어 있는 배열을 넣음
두 번째 파라미터 배열 안에 검사하고 싶은 값을 넣음
언마운트 되기 전이나 업데이트 하기 직전 작업을 수행하고 싶다면 뒷정리 함수를 반환한다
import Info from './Info';
import { useState } from 'react';
const App = () => {
const [visible, setVisible] = useState(false);
return (
<>
<button
onClick={() => {
setVisible(!visible);
}}
>
{visible ? '숨기기' : '보이기'}
</button>
<hr />
{visible && <Info />}
</>
);
};
export default App;
...
useEffect(() => {
console.log('effect');
console.log(name);
return () => {
console.log('cleanup');
console.log(name);
};
}, [name]);
...
언마운트 될 때만 뒷정리 함수를 호출하고 싶다면 두 번째 파라미터에 비어있는 배열을 넣음
state
와 action
값을 전달받아 새로운 상태를 반환import React, { useReducer } from 'react';
function reducer(state, action) {
// action.type 에 따라 다른 작업 수행
switch (action.type) {
case 'INCREMENT':
return { value: state.value + 1 };
case 'DECREMENT':
return { value: state.value - 1 };
default:
// 아무것도 해당되지 않을 때 기존 상태 반환
return state;
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { value: 0 });
return (
<div>
<p>
현재 카운터 값은 <b>{state.value}</b> 입니다.
</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
</div>
);
};
export default Counter;
state
는 현재 가리키고 있는 상태dispatch
는 액션을 발생시키는 함수: 파라미터로 액션 값을 넣음import React, { useReducer } from 'react';
function reducer(state, action) {
return {
...state,
[action.name]: action.value,
};
}
const Info = () => {
const [state, dispatch] = useReducer(reducer, {
name: '',
nickname: '',
});
const { name, nickname } = state;
const onChange = (e) => {
dispatch(e.target);
};
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
<div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임: </b> {nickname}
</div>
</div>
</div>
);
};
export default Info;
e.target
값 자체를 액션 값으로 사용
useMemo
로 함수형 컴포넌트 내부의 연산을 최적화 할 수 있다
import React, { useState, useMemo, useRef, useCallback } from 'react';
const getAverage = numbers => {
console.log('평균값 계산중..');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const inputEl = useRef(null);
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
inputEl.current.focus();
}, [number, list]); // number 혹은 list 가 바뀌었을 때만 함수 생성
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} ref={inputEl} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값:</b> {avg}
</div>
</div>
);
};
export default Average;
list 배열 내용이 바뀔 때만 getAverage
함수 호출
ref
를 쉽게 사용할 수 있게 하는 useRef
import {useRef} from 'react';
const RefSample () => {
const id = useRef(1);
const setId = (n) => {
id.current = n;
}
const printId = () => {
console.log(id.current);
}
return (
<div>
refsample
</div>
)
}
export default RefSample;
import { useReducer } from 'react';
function reducer(state, action) {
return {
...state,
[action.name]: action.value
};
}
export default function useInputs(initialForm) {
const [state, dispatch] = useReducer(reducer, initialForm);
const onChange = e => {
dispatch(e.target);
};
return [state, onChange];
}
import React from 'react';
import useInputs from './useInputs';
const Info = () => {
const [state, onChange] = useInputs({
name: '',
nickname: ''
});
const { name, nickname } = state;
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
<div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임: </b> {nickname}
</div>
</div>
</div>
);
};
export default Info;
.scss
와 .sass
를 지원.sass
: 중괄호와 세미콜론을 사용하지 않음// 변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;
// 믹스인 만들기
@mixin square($size) {
$calculated: 32px * $size;
width: $calculated;
height: $calculated;
}
@import './styles/utils';
:global .somthing {
font-weight: 800;
}
:global
: 난독화되지 않음
import styles from './CSSMOdule.module.css';
...
return (
<div className={style.wrapper}>
</div>
)
...
...
return (
<div className={`${styles.wrapper} ${styles.inverted}`}>
</div>
)
...
또는
className={[styles.wrapper, styles.inverted].join(' ')}
$ yarn add classnames
import classNames from 'classnames';
classNames('one', 'two'); //'one two'
classNames('one', {two: true}); //'one two'
classNames('one', {two: false}); //'one'
classNames('one', ['two', 'three']); //'one two three'
const myClass = 'hello';
classNames('one', myClass, {myCondition: true}); // 'one hello myCondition'
예제
const MyComponent = ({hightlighted, theme}) => (
<div className={classNames('MyComponent', { hightlighted}, theme)}>Hello</div>
)
$ yarn add styled-components
import styled from 'styled-components';
const Box = styled.div`
/* props로 넣어 준 값을 직접 전달해 줄 수 있음 */
background: ${props => props.color || 'blue'};
&:hover{
...
}
...
/* inverted 값이 true일때 특정 스타일을 부여 */
${props =>
props.inverted &&
css `
background: none;
...
`
}
`
const styledComponent = () => {
<Box color="black">
<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>
</Box>
}
...
function tagged(...args) {
console.log(args);
}
tagged`hello ${{ foo: 'bar' }} ${()=> 'world'}!`
import React from 'react';
import styled, { css } from 'styled-components';
const sizes = {
desktop: 1024,
tablet: 768
};
// size 객체에 따라 자동으로 media 쿼리 함수를 만듦
const media = Object.keys(sizes).reduce((acc, label) => {
acc[label] = (...args) => css`
@media (max-width: ${sizes[label] / 16}em) {
${css(...args)};
}
`;
return acc;
}, {});
const Box = styled.div`
${media.desktop`width: 768px;`}
${media.tablet`width: 100%;`};
`;
...
라우팅
: 다른 주소에 다른 화면을 보여줌Next.js
...)index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
import { Routes, Route } from 'react-router-dom';
const App = () => {
return (
<>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about/:id" element={<About />} />
<Route path="/*" element={<NotFount />} />
</Routes>
</>
);
};
export default App;
import { Link } from 'react-router-dom';
...
return (
<>
<li><Link to="/">홈</Link></li>
<li><Link to="/about">소개</Link></li>
</>
)
...
import { useParams } from 'react-router-dom';
const About = () => {
const { id } = useParams();
...
react-router-dom
패키지의 useParams()
로 URL 파라미터가 저장되어 있는 객체를 리턴받을 수 있다import React from 'react';
import { useParams, useLocation } from 'react-router-dom';
function About() {
const location = useLocation();
const { search } = location;
const query = new URLSearchParams(search);
const showDetail = query.get('detail') === 'true';
return (
<>
<h1>About</h1>
<div>{showDetail && <p>detail값을 true로 설정하셨군요~~!</p>}</div>
</>
);
}
export default About;
쿼리의 파싱값은 문자열
APP.js
<Route path="/users/:username/*" element={<UsersPage />}>
<Route path="" element={<UserMain />} />
<Route path="about" element={<About />} />
</Route>
UserPage.js
<Outlet />
<NavLink to="/about" style={({ isActive }) => ({ color: isActive ? 'green' : 'blue' })}>
about
</NavLink>
또는
<NavLink to="/about">about</NavLink>