똑같은 삽질은 2번 하지 말자
React vol.2 (props, jsx, state, 리액트의 불변성(Immutability), ref) 본문
개요
React JS 를 공부하면서 기본을 정리해두고자 작성
(props, props.children, jsx의 변환, state, Stateful 컴포넌트와 Stateless 컴포넌트)
1. props
- 컴포넌트 내부의 Immutable Data
- JSX 내부에 { this.props.propsName } / 함수형 일때는 그냥 {props.propsName}
- 컴포넌트를 사용 할 때, < > 괄호 안에 propsName="value"
- this.props.children 은 기본적으로 갖고있는 props로서 <Cpnt>여기에 있는 값이 들어간다 </Cpnt>
- 기본값 설정은 Component.defaultProps = {...} 타입은 Component.propTypes = {...}
클래스형 예제
class Codelab extends React.Component {
render() {
return (
<div style={style}>
<h1>{this.props.name}</h1>
{this.props.children}
<h2>{this.props.number}</h2>
</div>
);
}
}
Codelab.propTypes = {
name: PropTypes.string,
number: PropTypes.number
};
Codelab.defaultProps = {
name: 'Unknown'
};
class App extends React.Component {
render() {
return (
<Codelab name="gwak" number={10}>heewon</Codelab>
)
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
함수형 예제
(상당히 심플해지는 느낌)
import Expenses from "./components/Expenses";
function App() {
const expenses = [
...
];
return (
<div>
<Expenses expenses={expenses} />
</div>
);
}
export default App;
import ExpenseItem from "./ExpenseItem";
const expenses = (props) => {
return (
<div className="expense">
{props.expenses.map((item) => {
return (
<ExpenseItem
title={item.title}
date={item.date}
amount={item.amount}
/>
);
})}
</div>
);
};
export default expenses;
props.children
v-slot같은 친구다.
wrapper스타일이 공통되는 부분이 있을때 그 스타일을 반복적으로 적지않고 컴포넌트화를 할 수 있는데, 예를 들면 이런식
<Card> {이 사이에 오는 태그나 컴포넌트 들은 props.children 이라는 예약어로 받을 수 있다.} </Card>
import "./Card.css";
const card = (props) => {
const classes = "card " + props.className;
console.log(classes);
return <div className={classes}>{props.children}</div>;
};
export default card;
import ExpenseItem from "./ExpenseItem";
import Card from "./Card";
import "./Expenses.css";
const expenses = (props) => {
return (
<Card className="expenses">
{props.expenses.map((item) => {
return (
<ExpenseItem
title={item.title}
date={item.date}
amount={item.amount}
/>
);
})}
</Card>
);
};
export default expenses;
2. jsx의 변환?
리액트의 경우에 꼭 jsx를 쓸 때 하나의 루트 태그가 필요한데 그 이유는 뭘까?
그건 jsx가 html을 변환하기 위해 쓰는 react 의 기능을 까보면 아는데, 밑의 코드 처럼 되어있다.
// return (
// <div>
// <h2>빵 먹고싶다.</h2>
// <Expenses items={expenses} />
// </div>
//)
// 이걸 내부적으로 어떻게 변환하고 있는지 보면
// React.createElement(생성해야하는요소, 이 요소의 속성, 요소 사이에 있는 요소, 요소 사이에 있는 요소, ...)
return React.createElement(
'div',
{},
React.createElement('h2', {}, "빵 먹고싶다."),
React.createElement(Expenses, {items: expenses})
)
이걸 보면 왜 div로 꼭 한번 감싸야하는지 이해할 수 있다.
return 되는 형태를 보면 React.createElement함수를 리턴하고 파라미터로는 자식 태그들이 들어오고 있다.
만약 루트태그가 없는 경우에는 복수의 함수(태그)들을 리턴해야 하는데 자바스크립트 라는 언어에서 return값이 복수는 될 수 없다.
React.createElement는 항상 한개 이상의 자식요소를 가질수 있는 요소 한개를 생성하기 때문이다.
Fragment
위와 같은 자바스크립트의 특징때문에 우리는 한가지 문제점이 있다.
이와 같이 불필요한 div 계층이 생겨버리는 것이다.
그래서 리액트에서는 Frament를 제공하는데 루트 태그 대신에
<></> 을 넣거나 <Fragment></Fragment>를 넣으면 불필요한 태그 생성을 방지할 수 있다.
3. state
- 유동적인 데이터
- JSX 내부에 { this.state.stateName } / 함수형은 useState라는 훅을 사용
- 초기값 설정이 필수, 생성자(constructor) 에서 this.state = {} 으로 설정 / 함수형은 필요없음
- 값을 수정 할 때에는 this.setState{{ .... }}, 렌더링 된 다음엔 this.state = 절대 사용하지 말것 (reactive해지지 않음)
- setState나 함수형의 set변수이름의 함수를 이용해 업데이트하면 해당 컴포넌트를 다시 한번 호출해서 리렌더링된다.
- this.forceUpdate() 로 억지로 렌더링 시킬 수 도 있지만 이건 전체 렌더링이라 성능상 이슈가 있다.
- state를 object로 해서 여러 상태들을 한번에 관리하는 방법도 있지만, 독립적으로 하는게 좋다. 이유는 밑의 코드에 있는데
// 방법 1
const inputHandler = (e) => {
setUserInput({
...useInput,
title: e.target.value
})
}
// 방법 2
const inputHandler = (e) => {
setUserInput((prevState) => {
return { ...prevState, title: e.target.value }
})
}
대부분의 경우에는 방법 1이든 방법2든 괜찮다고는 하지만, 리액트가 상태 업데이트 스케줄을 갖고 있어서 바로 실행되지 않는것을 인지해 두어야한다. 그래서 방법1의 경우에는 이전 userInput의 스냅샷의 값이 최신상태임을 보장할 수 없다고 한다.
그래서 만약에 최신상태임을 보증하면서 상태를 업데이트를 해야하는 경우에는 방법 2으로 해줘야한다.
방법 2의 경우에는 리액트에서 prevState는 최신 상태이며 항상 계획된 상태 업데이트를 염두에 두고 있다는 것을 보장한다.
Stateful 컴포넌트와 Stateless 컴포넌트
React 컴포넌트 중 state 를 사용하는 경우를 Stateful Component or Smart Component라 하고, state 를 사용하지 않는 컴포넌트를 Stateless Component, dumb Component 라 한다. 만약 state를 사용하지 않고 컴포넌트로 작성할 수 있다면 최대한 state를 사용하지 않도록 컴포넌트를 구현해야 이해하기 쉽다. 왜냐하면 상태를 가지는 순간부터 상태관리 로직이 들어가고 또 자식이 가지는 상태 부모가 가지는 상태등등 여러가지 경우를 고려해야 하는 상황이 올 수도 있다.
4. 리액트에서의 불변성(Immutability)이란?
우선 알아야 할 개념으로 javascript에서의 mutable, immutable 한 데이터들에 대해 알아보자.
mutable
- 변하다 유동적이다.
- 참조타입
- 해당 데이터 주소를 찾아 값을 변경
원본을 변형
여기서 말하는 원본을 변형 한다는 말은 배열 arr=[1,2,3,4,5,6]을 변경할 때 위처럼 splice나 arr[0] = 100 과 같은 동작으로 값을 변경할 때 javascript의 내부적으로는 arr 변수가 가리키는 메모리 주소값은 변경되지 않고 그 메모리 주소값을 통해 접근한 메모리에 있는 값을 직접 변경되어 버리기 때문에 원본이 변형된다. (call by reference)
immutable
- 불변, 변하지 않는다.
- 원시타입
- 해당 데이터 주소와 별개로 새로운 메모리 주소에 할당
- 문자열
원본을 유지
여기서 말하는 원본을 유지 한다는 말은 slice나 어떤 let str = "원본"과 같은 변수가 있고 str="유지"라고 하면 원본이 바뀌어 버리는게 아니냐? 라고 생각할 수 있지만, 또 javascript 내부적으로는 str이 가리키고 있는 메모리의 주소값으로 접근한 메모리의 값을 바꾸는게 아니라 새롭게 메모리를 할당해서 새로운 메모리 주소를 str에 부여함으로써 메모리의 값은 불변 하면서 원본을 유지 하는게 되는 것이다.
slice는 call by value 방식으로 arr2의 메모리의 주소 값이 아니라 실제 값을 넘겨서 배열을 변형시키기 때문에 원본의 값은 유지되는 imuutable한 방식이다.
mutable과 immutable에 대한 좀더 자세한 설명은
https://evan-moon.github.io/2020/01/05/what-is-immutable/
변하지 않는 상태를 유지하는 방법, 불변성(Immutable)
이번 포스팅에서는 순수 함수에 이어 함수형 프로그래밍에서 중요하게 여기는 개념인 에 대한 이야기를 해보려고 한다. 사실 순수 함수를 설명하다보면 불변성에 대한 이야기가 꼭 한번은 나오
evan-moon.github.io
그럼 리액트에서는 왜 상태가 불변해야할까?
- 값이 변경됐는지 알기 위해서 (리렌더링을 위해)
- 값을 수정하면, 이전 상태와 바뀐 상태값이 동일해져서 비교할 수 없어서(*최적화와 관련있다.)
2의 *최적화와 관련된 예는 이하의 블로그 참조하면 아주 좋다.
React ❤️ Immutable.js – 리액트의 불변함, 그리고 컴포넌트에서 Immutable.js 사용하기 | VELOPERT.LOG
이 포스트는 React 에서는 불변함 (Immutability) 를 지키며 상태 관리를 하는 것을 매우 편하게 해주는 라이브러리 Immutable.js 에 대해서 알아보겠습니다. 서론 리액트를 사용하신다면, Immutability 라는
velopert.com
간단하게 요약하자면 이전 상태와 지금 상태를 비교했을때 값에 변화가 없으면 렌더링을 하지 않는 최적화 코드를 넣는데,
이전 상태를 가지고 있으려면 불변성을 유지해야 한다. 왜냐하면 이전 상태와 지금 상태가 똑같은 값의 메모리 주소를 가리키고 있으면 과거의 값이 뭐였는지 알 길이 없기 때문이다.
React Immutability Helper란?
state에서 배열같은 값들을 변경 시킬 때 immutability를 보장시키기 위해 도움을 받는 라이브러리를 일컫는다. immutability하게 개발을 하려면 아주 귀찮고 코드가 더러워지기 때문에...대표적으로 react-addons-update가 있다.
5. ref
dom에 접근할 때 사용되는 친구
주의할 점
render method, constructor에서는 ref에 접근할 수 없다.