React
20210604 React02 : Event Handling, Component Lifecycle, Lifecycle 변경된 사항 (16.3버전 이후), getDerivedStateFromProps, getSnapshotBeforeUpdate, ComponentDidCatch
React 02
Event Handling
- HTML DOM 에 클릭하면 이벤트가 발생하고, 그에 맞는 변경이 일어남
- JSX에 이벤트를 설정할 수 있음
- camelCase로만 사용가능
- 예) onClick, onMouseEnter
- 이벤트에 연결된 자바스크립트 코드는 함수임
이벤트 = {함수
}와 같이 씀
- 실제 DOM요소들에만 사용 가능함
- 리액트 컴포넌트에 사용하면, 그냥 props로 전달함
Function Component 이벤트 연결
- JSX에 바로 이벤트 적고, 해당 이벤트에 함수를 붙여줌
- 함수 컴포넌트에서는 state, 라이프 사이클을 사용하지 않기 때문에, 상태값을 활용할 수는 없다.
function Component() {
return (
<div>
<button
onClick={() => {
console.log("clicked");
}}
>
클릭
</button>
</div>
);
}
ReactDOM.render(<Component />, document.querySelector("#root"));
Class Component 이벤트 연결
- 상태값 state을 활용하는 경우
- 이벤트에 달 함수를 이원화 시켜 관리하는 2가지 방법
- 해당 함수를 Arrow 함수로 표기하는 방법
- 그냥 class에서의 메소드 정의 형식 표기방법(prototype)
해당 함수를 Arrow 함수로 표기하는 방법
- 객체의 값으로 이전 state를 spread를 사용해서 가져오고, 값을 덮어씌워서 setState로 state값을 변경시킴
// 이벤트에 달 함수를 Arrow 함수로 표기하는 경우
class Component extends React.Component {
state = {
count: 0,
};
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.click}>클릭</button>
</div>
);
}
click = () => {
console.log("clicked");
this.setState((state) => ({
...state,
count: state.count + 1,
}));
};
}
ReactDOM.render(<Component />, document.querySelector("#root"));
class 메소드 정의 표기방법(prototype)
- 일반적인 prototype method처럼 사용하지만, 해당 method의 경우 method가 this 바인딩 하는데 문제가 생김
- 해당 형태로 표기하면
constructor
에서 해당 method에 this를 바인딩 시켜줘야 함
class Component extends React.Component {
// method가 this를 찾을 수 있게 바인딩 해줘야함
constructor(props) {
super(props);
this.click = this.click.bind(this);
}
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.click}>클릭</button>
</div>
);
}
click() {
console.log("clicked");
this.setState((state) => ({
...state,
count: state.count + 1,
}));
}
}
ReactDOM.render(<Component />, document.querySelector("#root"));
Component Lifecycle
- 리책트 컴포넌트는 탄생부터 죽음까지 여러지점에서 개발자가 작업이 가능하도록 메서드를 오버라이딩 할수 있게 해줌
Declarative 성질
컴포넌트가 그려지는 때 (탄생)
- lnitialization (생성) : constructor 가 불리는 상태로 props, state의 초기값이 설정되는 구간
- Mounting :
- componentWillMount : render가 되기 전
- render (최초 render, mount)
- componentDidMount : render가 된 후
컴포넌트가 사라지는 때 (죽음)
- Unmounting :
- componentWillUnmount : 컴포넌트가 사라지기 전
컴포넌트가 업데이트 되는 때
- Updation : props 나 states가 변경 되는 때
- props
- componentWillReceiveProps
- shouldComponentUpdate : 컴포넌트가 업데이트 되어야 하는지 아닌지를 판단 (true, false)
- 불필요하게 render되는 것을 방지하기 위함
- componentWillUpdate : render 되기 직전
- render
- componentDidUpdate
- states
- shouldComponentUpdate : 컴포넌트가 업데이트 되어야 하는지 아닌지를 판단 (true, false)
- 불필요하게 render되는 것을 방지하기 위함
- componentWillUpdate : render 되기 직전
- render
- componentDidUpdate
- shouldComponentUpdate : 컴포넌트가 업데이트 되어야 하는지 아닌지를 판단 (true, false)
- props
- Props만 should 전에 componentWillReceiveProps가 존재함
컴포넌트 생성 및 마운트 (Mount)
- constructor -> componentWillMount -> render! -> componentDidMount! -> setInterval! -> render! -> setInterval! -> render! -> ...
class App extends React.Component {
state = {
age: 39,
};
constructor(props) {
super(props);
console.log("constructor", props);
}
render() {
console.log("render!");
return (
<div>
<h2>
Hello {this.props.name} - {this.state.age}
</h2>
</div>
);
}
// 차후에 안쓰여질 것이라서 주의해야함
componentWillMount() {
console.log("componentWillMount!");
}
componentDidMount() {
console.log("componentDidMount!");
setInterval(() => {
console.log("setInterval");
this.setState((state) => ({ ...state, age: state.age + 1 }));
}, 1000);
}
}
ReactDOM.render(<App name="Mark" />, document.querySelector("#root"));
컴포넌트 props , state 변경 (Update)
- props, state 변경시 -> componentWillReceiveProps (Props만) -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
- componentWillReceiveProps
- props를 새로 지정했을 때 바로 호출
- state 변경에 반응 X
- props의 값에 따라 state를 변경해야 하는경우 -> setState를 이용해서 state 변경
- 다음 이벤트로 각각 가지 않고, shouldComponentUpdate에서 모아 한번에 변경됨
- shouldComponentUpdate
- porps만 변경되어도, state만 변경되어도, 둘다 변경되어도 반응함
- newProps와 new State를 인자로 해서 호출
- return type이 boolean으로 -> true면 render , false면 render 호출 안함
- should를 구현하지 않으면 default값으로 true가 되어 있음
- componentWillUpdate
- 컴포넌트가 재 랜더링 되기 직전 호출
- 여기서 setState 같은거 쓰면 안됨
- componentDidUpdate : 컴포넌트가 재 랜더링을 마치면 불림
class App extends React.Component {
state = {
age: 39,
};
constructor(props) {
super(props);
console.log("constructor", props);
}
render() {
console.log("render!");
return (
<div>
<h2>
Hello {this.props.name} - {this.state.age}
</h2>
</div>
);
}
// 차후에 안쓰여질 것이라서 주의해야함
componentWillMount() {
console.log("componentWillMount!");
}
componentDidMount() {
console.log("componentDidMount!");
setInterval(() => {
this.setState((state) => ({ ...state, age: state.age + 1 }));
}, 10000);
}
// Update
componentWillReceiveProps(nextProps) {
console.log("componentWillReceiveProps", nextProps);
}
shouldComponentUpdate(nextProps, nextState) {
console.log("shouldComponentUpdate", nextProps, nextState);
return true; // 조건부로 update 제어 가능
}
componentWillUpdate(nextProps, nextState) {
console.log("componentWillUpdate", nextProps, nextState);
}
componentDidUpdate(prevProps, prevState) {
// 이전 props를 보여주니까, 전에 이것을 업데이트 했음을 알려줌
console.log("componentDidUpdate", prevProps, prevState);
}
}
ReactDOM.render(<App name="Mark" />, document.querySelector("#root"));
컴포넌트 제거 (Unmount)
- componentWillUnmount : 요소가 없어지기 전에 실행함
class App extends React.Component {
state = {
age: 39,
};
interval = null;
constructor(props) {
super(props);
console.log("constructor", props);
}
render() {
console.log("render!");
return (
<div>
<h2>
Hello {this.props.name} - {this.state.age}
</h2>
</div>
);
}
componentWillMount() {
console.log("componentWillMount!");
}
componentDidMount() {
console.log("componentDidMount!");
// 요소가 사라지더라도 함수는 계속 돌고 있으므로 함수를 클래스 멤버변수에 담아
// willUnmount에서 clear해서 없애줌
this.interval = setInterval(() => {
this.setState((state) => ({ ...state, age: state.age + 1 }));
}, 10000);
}
componentWillReceiveProps(nextProps) {
console.log("componentWillReceiveProps", nextProps);
}
shouldComponentUpdate(nextProps, nextState) {
console.log("shouldComponentUpdate", nextProps, nextState);
return true; // 조건부로 update 제어 가능
}
componentWillUpdate(nextProps, nextState) {
console.log("componentWillUpdate", nextProps, nextState);
}
componentDidUpdate(prevProps, prevState) {
console.log("componentDidUpdate", prevProps, prevState);
}
componentWillUnmount() {
// 해당 함수를 함수에 연결해서 없앰
clearInterval(this.interval);
}
}
ReactDOM.render(<App name="Mark" />, document.querySelector("#root"));
라이프사이클 변경된 사항 (16.3버전 이후)
라이프 사이클 |
---|
constructor |
render |
componentDidMount |
shouldComponentUpdate |
render |
DOM에 적용 |
componentDidUpdate |
componentWillUnmount |
getDerivedStateFromProps
- componentWillMount에 대체해서 나온 라이프사이클 method
- props를 받아서 state를 조절할 수 있었는데 이제는 그 기능을 메인으로 하고 있음
- 일반 라이프사이클과는 다르게 클래스 안에서
static method
로 만들어 주어야 함 return
부분에 새로운 state를 설정해 줄 수 있음- 새로운 props가 들어오면 state가 return으로 바뀜
- update에 의해서 render가 실행되기 전에도 실행됨 (즉, render실행전에 무조건 실행된다고 보면 됨)
- 시간의 흐름에 따라 변하는 props에 state 가 의존하는 경우 주로 사용됨
class App extends React.Component {
state = {
age: 39,
};
interval = null;
constructor(props) {
super(props);
console.log("constructor", props);
}
render() {
console.log("render!");
return (
<div>
<h2>
Hello {this.props.name} - {this.state.age}
</h2>
</div>
);
}
// static method로 설정
static getDerivedStateFromProps(nextProps, prevState) {
console.log("getDerivedStateFromProps", nextProps, prevState);
return {
age: 390,
}; // return 부분에 새로운 state를 설정할 수 있음
}
}
getSnapshotBeforeUpdate
- render메서드가 호출되고, DOM에 적용되기 직전에 실행되는 라이프사이클
- prevProps, prevState 를 인자로 사용하여 변한 props, states와 그전의 값들을 비교한 결과를 DidUpdate때 사용하고자 하는 경우 사용함
let i = 0;
class App extends React.Component {
state = { list: [] };
render() {
return (
<div id="list" style={{ height: 100, overflow: "scroll" }}>
{this.state.list.map((i) => {
return <div>{i}</div>;
})}
</div>
);
}
componentDidMount() {
setInterval(() => {
this.setState((state) => ({
list: [...state.list, i++],
}));
}, 3000);
}
// 과거 state 와 변한 현재 state를 비교하여 snapshot 값으로 null 또는 차이를 넘김
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevState.list.length === this.state.list.length) return null;
const list = document.querySelector("#list");
return list.scrollHeight - list.scrollTop;
}
// snapshot값을 받아서 변경함
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("DidUpdate-snapshot", snapshot);
if (snapshot === null) return;
const list = document.querySelector("#list");
list.scrollTop = list.scrollHeight - snapshot;
}
}
ReactDOM.render(<App name="Mark" />, document.querySelector("#root"));
ComponentDidCatch (컴포넌트 에러 캐치)
- 기존에는 부분적인 에러 발생시 앱이 모두 동작을 못했음
- ComponentDidCatch를 통해 문제발생시 error 화면을 보여줄수 있게 되었음
- 하위 컴포넌트에서 error발생시 해당 부모 컴포넌트에서 error말고 다른 것을 보여 줄 수 있음
- 단, 자기자신에 대한 error는 catch를 못함 -> 최상위 컴포넌트에 달아야 함
- Error Boundaries라는 라이브러리를 보통 사용함
class App extends React.Component {
state = {
hasError: false,
};
render() {
if (this.state.hasError) {
return <div>예상치 못한 에러 발생함!!</div>;
}
return <div>정상 페이지</div>;
}
componentDidCatch(error, info) {
this.setState({ hasError: true });
}
}
ReactDOM.render(<App name="Mark" />, document.querySelector("#root"));