리액트 레퍼런스를 참고하여 리액트 주요 개념을 정리해보고자 한다
정확한 것은 리액트 레퍼런스 가서 확인하면 되고(이렇게 잘 번역되어있는 레퍼런스도 처음 봤다 번역해주신 분 압도적 감사!)
난 내 쪼대로 이해해보려고 한다
JSX 소개
//셋이 똑같다
1)
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
2)
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
3)
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
엘리먼트 렌더링
<div id = "root"></div>
에 들어가는 모든 엘리먼트를 React DOM에서 관리하며 이를 Root DOM 이라고 부른다 const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
Components와 Props
State and LifeCycle
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
->
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
<Clock/>
)class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
/*
참고로 원래는 이거였음(전달하는 props 존재)
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
*/
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
1) 처음 렌더링 될 때 state를 new Date()로 초기화하여 Clock 만듦
2) 이후 Clock이 DOM에 삽입되면 componentDidMount를 호출하고, 이 함수에서 timer을 설정한다(1초마다 tick()함수 호출)
3) tick함수에서 setState 함수를 사용해 state 값을 바꾸고, setState 함수를 호출해서 React는 state가 바뀐 것을 알고 화면에 표시될 내용을 알기 위해 render()을 다시 호출함 -> DOM 업데이트
4) Clock 컴포넌트가 DOM으로부터 삭제된 적이 있다면, React는 타이머를 멈추기 위해 componentWillUnmount() 함수를 호출한다
setState를 사용할 때 알아야 할 것들이 있다
1) 직접 State를 수정해선 안된다(그럼 컴포넌트를 다시 렌더링하지 않음)
-> setState함수를 사용, this.state를 지정할 수 있는 장소는 오직 constructor 뿐
2) State 업데이트는 비동기적일 수 있다
-> this.props와 this.state가 비동기적으로 업데이트 될 수 있기 때문에 state를 계산할 때 해당 값에 의존하면 안된다
-> 객체보다는 함수를 인자로 사용하는 다른 형태의 setState()를 사용함
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
3) State 업데이트는 병합된다
-> setState()를 호출할 때 React는 제공한 객체를 현재 state로 병합한다
// 이렇게 독립적인 다양한 변수(posts, comments 등)을 포함할 수 있다
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
// 별도의 setState()호출로 이러한 변수를 독립적으로 업데이트 할 수 있다(얕은 병합)
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
<FormattedDate date={this.state.date} />
-> 받음
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
이벤트 처리
<button onClick = {activateLasers}>
Activate Lasers
</button>
function Form(){
function handleSubmit(e){
e.preventDefault();
console.log('You clicked Submit');
}
return (
<form onSubmit = {handleSubmit}>
<button type = "submit>Submit</button>
</form>
)
}
class Toggle extends React.Component{
constructor(props){
super(props);
this.state = {isToggleOn : true};
//콜백에서 this가 동작하려면 아래와 같이 바인딩 해주어야 한다
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return(
<button onClick = {this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
ReactDOM.render(
<Toggle/>,
document.getElementById('root')
);
}
Class LoggingButton extends React.Component{
handleClick = () => {
console.log('this is:' , this);
}
render(){
return(
<button onClick = {this.handleClick}>
Click me
</button>
);
}
}
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
<button onClick = {(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick = {this.deleteRow.bind(this, id)}>Delete Row</button>
조건부 렌더링
function UserGreeting(props){
return <h1>Welcome Back!</h1>
}
function GuestGreeting(props){
return <h1>Please sign up</h1>
}
function Greeting(props){
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn){
return <UserGreeting/>
}
return <GuestGreeting/>
}
ReactDom.render(
<Greeting isLoggedIn = {false}/>,
document.getElementIdById('root')
);
function Mailbox(props){
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
const messages = ['React', 'Re:React', 'Re:Re: React']
ReactDOM.render(
<Mailbox unreadMessages = {message}/>,
document.getElementById('root')
);
}
render(){
const count = 0;
return(
<div>
{count && <h1>Messages : {count}</h1>}
</div>
)
}
위의 경우 <div>0</div>가 반환된다
render(){
const isLoggedIn = this.state.isLoggedIn;
return(
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> Logged in.
</div>
);
}
혹은
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{
isLoggedIn
? <LogoutButton onClick = {this.handleLogoutClick}/>
: <LoginButton onClick = {this.handleLoginClick}/>
}
</div>
);
}
function WarningBanner(props){
if(!props.warn){
return null;
}
return (
<div className = "warning">
Warning!
</div>
);
}
class Page extends React.Component{
constructor(props){
super(props);
this.state = {showWarning : true};
this.handleToggleClick = this.handleToggleClick.bind(this);
}
handleToggleClick(){
this.setState(state => ({
showWarning : !state.showWarning
}));
}
render(){
return(
<div>
<WarningBanner warn = {this.handleToggleClick}/>
<button onClick = {this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
}
ReactDOM.render(
<Page/>,
document.getElementById('root');
);
리스트와 KEY
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
// [ 2, 4, 6, 8, 10]
// 1~5 숫자로 이루어진 리스트를 보여준다
cosnt numbers = [1, 2, 3, 4, 5]
const listItems = numbers.map((numbers) =>
<li>{numbers}</li>
);
ReactDOM.render(
<ul>{listItems}</ul>
document.getElementById('root')
);
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((numbers) =>
<li key = {number.toString()}>
{number}
</li>
);
const todoItems = todos.map((todo) =>
<li key = {todo.id}>
{todo.text}
</li>
);
const todoItems = todos.map((todo, index) =>
<li key = {index}>
{todo.text}
</li>
);
<li>
엘리먼트가 아니라 <ListItem/>
엘리먼트가 key를 가져야한다 function ListItem(props){
return <li>{props.value}</li>
}
function NumberList(props){
const numbers= props.numbers;
const listItems = numbers.map((number) =>
// 배열 안에 key를 지정해야함
<ListItem key = {number.toString()} value = {number} />
);
return(
<ul>
{listItems}
</ul>
);
}
const numbers= [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers}/>,
document.getElementById('root')
);
const content = posts.map((post) =>
<Post
key = {post.id}
id = {post.id}
title = {post.title}
/>
);
폼
class NameForm extends React.Component{
constructor(props){
super(props);
this.state = {value : ' '};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event){
this.setState({value : event.target.value});
}
handleSubmit(event){
alert('A name was submitted :' + this.state.value);
event.preventDefault();
}
render(){
return (
<form onSumit = {this.handleSubmit}>
<label>
Name:
<input type = "text" value = {this.state.value} onChange= {this.handleChange} />
</label>
<input type = "submit" value = "submit" />
</form>
);
}
}
class EssayForm extends React.Component{
constructor(props){
super(props);
this.state = {
value : 'Please write an essay about your favorite DOM element'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event){
this.setState({value : event.target.value});
}
handleSubmit(event){
event.preventDefault();
}
render(){
<form onSubmit = {this.handleSubmit}>
<label>
Essay :
<textarea value = {this.state.value} onChange= {this.handleChange} />
</label>
<input type = "submit" value ="Submit"/>
</form>
}
}
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite flavor:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
<select multiple={true} value={['B', 'C']}>
<input type = "file"/>
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
//es6의 computed property name 구문
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
State 끌어올리기
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value);
}
render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
합성(Composition) vs 상속(Inheritance)
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
<Dialog title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}
React적 사고
중복 배제의 원칙으로 가장 최소한의 state를 찾고 나머지는 필요에 따라 그때그때 계산되도록 만든다
state ? props ?
1. 부모로부터 props를 통해 전달됩니까? 그러면 확실히 state가 아닙니다.
2. 시간이 지나도 변하지 않나요? 그러면 확실히 state가 아닙니다.
3. 컴포넌트 안의 다른 state나 props를 가지고 계산 가능한가요? 그렇다면 state가 아닙니다.