컴포넌트를 선언하는 방식은 두 가지입니다. 함수형 컴포넌트, 클래스형 컴포넌트
파일 생성 -> 코드 작성하기 -> 모듈 내보내기 및 불러오기
/src/components/MyComponent.js 파일 생성
import React, { Component } from "react";
export default class MyComponent extends Component {
render() {
return <div>new Component</div>;
}
}
/src/App.js 에서 컴포넌트를 불러와서 렌더링해줍니다.
import React, { Component } from "react";
import "./App.css";
import MyComponent from "./components/MyComponent";
export default class App extends Component {
render() {
return (
<div className="mystyle">
<MyComponent />
</div>
);
}
}
/src/App.js
import React, { Component } from "react";
import "./App.css";
import MyComponent from "./components/MyComponent";
export default class App extends Component {
render() {
return (
<div className="mystyle">
<MyComponent name="react" />
</div>
);
}
}
/src/components/MyComponent.js
import React from "react";
const MyComponent = props => {
return <div>props = {props.name}</div>;
};
export default MyComponent;
prop가 전달이 안되면 오류가 날거기때문에 기본값을 셋업해주는게 좋습니다.
import React from "react";
const MyComponent = props => {
return <div>props = {props.name}</div>;
};
MyComponent.defaultProps = {
name: "기본이름"
};
export default MyComponent;
/src/App.js
import React, { Component } from "react";
import "./App.css";
import MyComponent from "./components/MyComponent";
export default class App extends Component {
render() {
return (
<div className="mystyle">
<MyComponent>리액트</MyComponent>
</div>
);
}
}
/src/components/MyComponent.js
import React from "react";
const MyComponent = props => {
return (
<div>
<h1>props = {props.name}</h1>
<h2>children = {props.children}</h2>
</div>
);
};
MyComponent.defaultProps = {
name: "기본이름"
};
export default MyComponent;
destructing assignment이라고 핵 편한 구문입니다.
/src/components/MyComponent.js
import React from "react";
const MyComponent = props => {
const { name, children } = props;
return (
<div>
<h1>props = {name}</h1>
<h2>children = {children}</h2>
</div>
);
};
MyComponent.defaultProps = {
name: "기본이름"
};
export default MyComponent;
더 줄이면
import React from "react";
const MyComponent = ({ name, children }) => {
return (
<div>
<h1>props = {name}</h1>
<h2>children = {children}</h2>
</div>
);
};
MyComponent.defaultProps = {
name: "기본이름"
};
export default MyComponent;
컴포넌트의 필수 props를 지정하거나 props의 타입을 지정할 때는 propTypes를 사용합니다.
import React from "react";
import PropTypes from "prop-types";
const MyComponent = ({ name, children }) => {
return (
<div>
<h1>props = {name}</h1>
<h2>children = {children}</h2>
</div>
);
};
MyComponent.defaultProps = {
name: "기본이름"
};
MyComponent.propTypes = {
name: PropTypes.string
};
export default MyComponent;
MyComponent를 사용하는 곳에서 favoriteNumber를 props로 전달 안하면 경고메시지를 띄워줍니다.
import React from "react";
import PropTypes from "prop-types";
const MyComponent = ({ name, favoriteNumber, children }) => {
return (
<div>
<h1>props = {name}</h1>
<h2>children = {children}</h2>
좋아하는 숫자는 {favoriteNumber}
</div>
);
};
MyComponent.defaultProps = {
name: "기본이름"
};
MyComponent.propTypes = {
name: PropTypes.string,
favoriteNumber: PropTypes.number.isRequired
};
export default MyComponent;
더 자세한 propTypes 정보는 여기에 있습니다.
import React from "react";
import PropTypes from "prop-types";
class MyComponent extends React.Component {
render() {
const { name, favoriteNumber, children } = this.props;
return (
<div>
<h1>props = {name}</h1>
<h2>children = {children}</h2>
좋아하는 숫자는 {favoriteNumber}
</div>
);
}
}
MyComponent.defaultProps = {
name: "기본이름"
};
MyComponent.propTypes = {
name: PropTypes.string,
favoriteNumber: PropTypes.number.isRequired
};
export default MyComponent;
state
란 컴포넌트 내부에서 관리하고 바뀔 수 있는 값을 의미합니다.
props
는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값입니다.
읽기 전용으로만 사용가능합니다.
state
사용하는 방법클래스형 컴포넌트가 지닌 state
라는 변수를 통해 사용
함수형 컴포넌트에서 useState
라는 함수를 통해 사용
state
라는 변수를 통해 사용import React, { Component } from "react";
export default class Counter extends Component {
constructor(props) {
super(props);
//state 초기값 셋업
this.state = {
number: 0
};
}
render() {
const { number } = this.state; // state를 조회할때는 this.state!
return (
<div>
<h1>{number}</h1>
<button
onClick={() => {
// this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
this.setState({ number: number + 1 });
}}
>
+1
</button>
</div>
);
}
}
import React, { Component } from "react";
export default class Counter extends Component {
state = {
number: 0
};
render() {
const { number } = this.state; // state를 조회할때는 this.state!
return (
<div>
<h1>{number}</h1>
<button
onClick={() => {
// this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
this.setState({ number: number + 1 });
}}
>
+1
</button>
</div>
);
}
}
this.setState를 사용하여 state 값을 업데이트할 때는 상태가 비동기적
으로 업데이트됩니다. 만약 다음과 같이 onClick에 설정한 함수 내부에서 this.setState를 두 번 호출하면 어떻게 될까요?
이거 해보시면 number 2씩 증가 안되는걸 알 수 있습니다.
리액트에서 상태값을 올려주는 메커니즘이 그런거니까 받아들여야 됩니다.
onClick = { () => {
this.setState({ number: number + 1});
this.setState({ number: this.state.number + 1 });
}}
굳이 하고 싶으시다면 이렇게 setState
부분만 다음과 같이 바꾸면 됩니다.
prevState 기존 상태
onClick = { () => {
this.setState( (prevState) => {
return {
number: prevState.number + 1;
};
});
this.setState( (prevState) => {
return {
number: prevState.number + 1;
};
});
}}
import React, { Component } from "react";
export default class Counter extends Component {
state = {
number: 0
};
render() {
const { number } = this.state; // state를 조회할때는 this.state!
return (
<div>
<h1>{number}</h1>
<button
onClick={() => {
// this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
this.setState({ number: number + 1 }, () => {
console.log("setState has just called");
});
}}
>
+1
</button>
</div>
);
}
}
useState
라는 함수를 통해 state 변경하기리액트 16.8 이전 버전에서는 함수형 컴포넌트에서 state 사용할 수 없었습니다.
이제는 useState 메서드로 가능합니다.
배열 비구조화 할당 구문을 알고나면 useState 사용 방법을 쉽게 이해할 수 있습니다.
const array = [1,2];
const one = array[0];
const two = array[1];
// 위와 같이 그 동안 할당해왔는데, 다음과 같이 할당할 수가 있게 되었습니다.
const [one, two] = array;
/src/components/Say.js
import React, { useState } from "react";
export default function Say() {
const [message, setMessage] = useState("");
const onClickEnter = () => setMessage("hello~");
const onClickLeave = () => setMessage("good bye~");
return (
<div>
<button onClick={onClickEnter}>Enter</button>
<button onClick={onClickLeave}>Leave</button>
<h3>{message}</h3>
</div>
);
}
useState함수에는 상태의 초기값을 넣어줍니다.
이 함수의 리턴값을 배열입니다. 0번째 값은 상태, 1번째 값은 상태를 변경하는 함수입니다.
state
를 사용할 때 주의 사항리액트는 state의 변화를 그 state를 참조하는 변수의 참조값 ( 즉, 가상 메모리 주소 )가 바뀌었는지로 판단합니다.
일반 변수의 경우 위와 같이 지정한 setter함수를 사용하여 상태를 변경합니다.
객체의 경우
spread operator를 사용합니다.
이렇게 새로운 객체, 새로운 배열을 생성해서 할당해주는 것은 리액트의 가장 중요한 것중 하나죠.
const obj = { a: 1, b: 2, c: 3};
const nextObj = { ...obj, b:2};
const array = [
{ id: 1, value: true},
{ id: 2, value: true},
{ id: 3, value: false}
];
let nextArray = array.concat({id:4});
nextArray.filter( el => el.id !== 2);
nextArray.map( el => el.id === 1? { ...item, value: false} : el);
filter, map같은 Array의 인스턴스 메서드를 사용하여 새로운 배열을 할당해주는 방법으로 구현해야 리액트가 상태값을 추적 메커니즘에 걸려서 렌더링이 다시 되든 상태값이 업데이트되든 합니다.
사용자가 웹 브라우저에서 DOM 요소들과 상호 작용하는 것을 event라고 합니다.
예를 들어, 버튼을 클릭했 을때는 onClick 이벤트를 실행합니다. Form요소는 값이 바뀔 때 마다 onChange 이벤트를 실행하죠.
camelCase
를 사용합니다.onClick = { () => {} }
이런 식을 넣는다든지, onClick = { 미리 정의해둔 함수 (this바인딩 신경써야합니다) }
/src/components/EventPractice.js
import React, { Component } from "react";
export default class EventPractice extends Component {
state = {
message: ""
};
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
message: e.target.value
});
}
handleClick() {
alert(this.state.message);
this.setState({
message: ""
});
}
render() {
return (
<div>
<h1>이벤트연습</h1>
<input
type="text"
name="message"
placeholder="입력하세요"
value={this.state.message}
onChange={this.handleChange}
/>
<button onClick={this.handleClick}>확인</button>
</div>
);
}
}
메서드 바인딩은 생성자에서 하는 것이 정석입니다.
더 간단하게 해보기
babel의 transform-class-properties 설정을 통해 화살표 함수로 바꾸면 this를 바인딩해주는 작업이 필요없어집니다.
import React, { Component } from "react";
export default class EventPractice extends Component {
state = {
message: ""
};
handleChange = e => {
this.setState({
message: e.target.value
});
};
handleClick = () => {
alert(this.state.message);
this.setState({
message: ""
});
};
render() {
return (
<div>
<h1>이벤트연습</h1>
<input
type="text"
name="message"
placeholder="입력하세요"
value={this.state.message}
onChange={this.handleChange}
/>
<button onClick={this.handleClick}>확인</button>
</div>
);
}
}
handleChange 메서드의 코드를 보면 ES6 구문중
computed property name을 보면 이해가 됩니다.
각괄호 [] 안에 식을 넣을 수 있고, 식이 계산되고 그 결과가 속성명으로 사용됩니다.
import React, { Component } from "react";
export default class EventPractice extends Component {
state = {
message: "",
username: ""
};
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleClick = () => {
alert(this.state.username + " : " + this.state.message);
this.setState({
message: "",
username: ""
});
};
render() {
return (
<div>
<h1>이벤트연습</h1>
<input
type="text"
name="username"
placeholder="사용자명"
value={this.state.username}
onChange={this.handleChange}
/>
<input
type="text"
name="message"
placeholder="입력하세요"
value={this.state.message}
onChange={this.handleChange}
/>
<button onClick={this.handleClick}>확인</button>
</div>
);
}
}
/src/components/EvenPractice.js
import React, { useState } from "react";
const EventPractice = () => {
const [username, setUsername] = useState("");
const [message, setMessage] = useState("");
const onChangeUsername = e => {
setUsername(e.target.value);
};
const onChangeMessage = e => {
setMessage(e.target.value);
};
const handleClick = e => {
alert(username + " : " + message);
setUsername("");
setMessage("");
};
return (
<div>
<h1>이벤트연습</h1>
<input
type="text"
name="username"
placeholder="사용자명"
value={username}
onChange={onChangeUsername}
/>
<input
type="text"
name="message"
placeholder="입력하세요"
value={message}
onChange={onChangeMessage}
/>
<button onClick={handleClick}>확인</button>
</div>
);
};
export default EventPractice;
아니면 객체로 상태값을 묶어서 사용할 수도 있습니다.
import React, { useState } from "react";
const EventPractice = () => {
const [form, setForm] = useState({
username: "",
message: ""
});
const { username, message } = form;
const onChange = e => {
const nextForm = {
...form,
[e.target.name]: e.target.value
};
setForm(nextForm);
};
const handleClick = e => {
alert(username + " : " + message);
setForm({
username: "",
message: ""
});
};
return (
<div>
<h1>이벤트연습</h1>
<input
type="text"
name="username"
placeholder="사용자명"
value={username}
onChange={onChange}
/>
<input
type="text"
name="message"
placeholder="입력하세요"
value={message}
onChange={onChange}
/>
<button onClick={handleClick}>확인</button>
</div>
);
};
export default EventPractice;
일반 HTML에서 DOM요소에 이름을 달 때는 id를 사용합니다.
ReactDOM.render(<컴포넌트>, document.getElementById("root"));
HTML에서 id를 사용하여 DOM에 이름을 다는 것처럼 리액트 프로젝트 내부에서 DOM에 이름을 다는 방법이 있습니다.
그게 바로 ref 개념입니다.
리액트 컴포넌트 안에서 id를 사용할 수 있습니다.
JSX 안에서 DOM에 id를 달면 해당 DOM를 렌더링할 때 그대로 전달됩니다.
HTML에서 DOM id는 유일해야 하는데, 중복 id를 가진 DOM이 여러 개 생긴다면 잘못된 사용입니다.
ref는 전역적으로 작동하지 않고, 컴포넌트 내부에서만 동작하기 때문에 이런 문제가 생기지 않습니다.
id를 사용하지 않고도 원하는 기능을 구현할 수 있지만, 다른 라이브러리나 프레임워크와 함께 id를 사용해야 하는 상황이 발생할 수 있습니다.
그럴땐 id+추가텍스트를 붙여 방지합니다.
먼저 ref는 어떤 상황에서 사용해야 하는지? DOM을 꼭 직접 건드려야 할 때
ref를 사용하는 두가지 방법
ref를 만드는 가장 기본적인 방법은 콜백 함수를 사용하는 것입니다. ref를 달고자 하는 요소에 ref라는 콜백 함수를 props로 전달해 주면 됩니다. 이 콜백 함수는 ref값을 파라미터로 전달받습니다. 그리고 함수 내부에서 파라미터로 받은 ref를 컴포넌트의 멤버 변수로 설정해 줍니다.
this.input은 input 요소의 DOM을 가리킵니다. ref 이름은 임의로 정할 수 있고, DOM 타입과 관계없이
this.ee = ref 처럼 마음대로 지정합니다.
import React, { Component } from "react";
import "../style/ValidationSample.css";
export default class ValidationSample extends Component {
state = { password: "", clicked: false, validated: false };
handleChange = e => {
this.setState({
password: e.target.value
});
};
handleButtonClick = () => {
this.setState({
clicked: true,
validated: this.state.password === "0000"
});
this.input.focus(); <-
};
render() {
return (
<div>
<input
ref={ (ref) => {this.input = ref} } <-
type="password"
value={this.state.password}
onChange={this.handleChange}
className={
this.state.clicked && this.state.validated ? "success" : "failure"
}
/>
<button onClick={this.handleButtonClick}>검증하기</button>
</div>
);
}
}
리액트에 createRef함수를 이용합니다. 리액트 16.3부터 도입되었으며 이전 버전에서는 동작하지 않습니다.
createRef를 사용하여 ref를 만들려면 우선 컴포넌트 내부에서 멤버 변수로 React.createRef()를 담아 주어야 합니다.
그리고 해당 멤버 변수를 ref를 달고자 하는 요소에 ref props로 넣어 주면 ref 설정이 완료됩니다.
설정한 뒤 ref를 설정해 준 DOM에 접근하려면 this.input.current 를 조회하면 됩니다.
import React, { Component } from "react";
import "../style/ValidationSample.css";
export default class ValidationSample extends Component {
state = { password: "", clicked: false, validated: false };
input = React.createRef(); <-
handleChange = e => {
this.setState({
password: e.target.value
});
};
handleButtonClick = () => {
this.setState({
clicked: true,
validated: this.state.password === "0000"
});
this.input.current.focus(); <-
};
render() {
return (
<div>
<input
ref={this.input} <-
type="password"
value={this.state.password}
onChange={this.handleChange}
className={
this.state.clicked && this.state.validated ? "success" : "failure"
}
/>
<button onClick={this.handleButtonClick}>검증하기</button>
</div>
);
}
}
리액트에서는 컴포넌트에도 ref를 달 수 있습니다. 이 방법은 주로 컴포넌트 내부에 있는 DOM을 컴포넌트 외부에서 사용할 때 씁니다. 컴포넌트에 ref를 다는 방법은 DOM에 ref를 다는 방법과 똑같습니다.
<MyComponent
ref = { (ref) => { this.myComponent = ref }}
/>
MyComponent 내부의 메서드 및 멤버 변수에도 접근할 수 있습니다. 즉, 내부의 ref에도 접근할 수 있습니다. (예: myComponent.handleClick, myComponent.input 등 )
예제를 위한 참고 지식
/src/components/ScrollBox.js
import React, { Component } from "react";
export default class ScrollBox extends Component {
scrollToBottom = () => {
const { scrollHeight, clientHeight } = this.box;
console.log(`scrollHeight:${scrollHeight} , clientHeight:${clientHeight}`);
this.box.scrollTop = scrollHeight - clientHeight;
};
render() {
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 => {
this.box = ref;
}}
>
<div style={innerStyle}></div>
</div>
);
}
}
/src/App.js
import React, { Component } from "react";
import "./App.css";
import ScrollBox from "./components/ScrollBox";
export default class App extends Component {
render() {
return (
<>
<ScrollBox
ref={ref => {
this.scrollBox = ref; // 콜백방식으로 ref를 달기
}}
/>
<button onClick={() => this.scrollBox.scrollToBottom()}>
맨밑으로
</button>
</>
);
}
}
시뮬레이션 순서도
App 컴포넌트에서 ScrollBox 컴포넌트 this.scrollBox에 ref를 달기 ->
App 컴포넌트에서 onClick 이벤트 발생 ->
this.scrollBox를 통해 ScrollBox 메서드, 멤버 변수에 접근할 수 있음 그러므로
ScrollBox 컴포넌트 내에 scrollToBottom 메서드를 호출 ->
ScrollBox 내에 scrollToBottom 메서드 호출 ->
ScrollBox의 div 태그에 this.box로 ref를 담았기 때문에 가장 맨 아래로 내려가게끔 내부 메서드를 호출함으로써 this.box.ScrollTop이 맨 아래로 내려가게 할 수 있게됩니다.
scrollHeight :
읽기 전용 속성인 Element.scrollHeight는 수직 스크롤바가 있는 엘리먼트(element)의 CSS 높이를 초과하여, 보이지 않는 부분까지 포함한 내용(content)의 높이(height)입니다
clientHeight :
https://developer.mozilla.org/ko/docs/Web/API/Element/clientHeight
엘리먼트 높이를 말해요.
높이는 엘리먼트의 높이를 나타내는 픽셀 단위의 integer입니다.
ScrollTop은 스크롤의 맨 위 포인트 지점을 말합니다.