react 16.8 ver 이전에는 시나리오, 유스케이스 같은 것들이 존재하였고 이때 state를 처리하고 side effect를 다룰 때 클래스 기반 컴포넌트를 사용하였다.
이때는 함수형 컴포넌트는 state 변경이 불가능하고 sied effect도 없었다.
하디만 16.8 ver 이후에 hook이라는 개념이 도입되고 함수형 컴포넌트를 사용하기 시작했다.
또 클래스 기반 컴포넌트는 모든 훅이 사용이 불가능하다;; ㅋㅋㅋ
class 클래스이름{
constructor(){
초기화 작업
}
render(){
jsx 코드
}
}
class에 원하는 만금 메소드를 여러개 추가할 수 있다.
이름도 원하는대로 지을 수 있다.
리액트에 필요한 특정 method로 jsx코드 안에 component가 사용된 것을 확인하면 그때 호출하는 meothd이다.
즉, render함수는 항수형 컴포넌트의 return 부분과 같은 역할을 한다.
자 그럼 함수형 컴포넌트에서 data를 넘겨받는 props의 역할은 어떻게 할것인가?
바로 상속에서 찾아볼 수 있다. react에서 Component를 import하고 이를 상속받으면
자동은 props라는 필드값을 불러오는데 이를 this로 연결하여 사용하면 된다.
class User extends Component{
render(){
return <li className={classes.user}>{this.props.name}</li>;
}
}
const User = (props) => {
return <li className={classes.user}>{props.name}</li>;
};
위 코드와 같이 동일한 것이며 섞어서 쓸 수 있다.
이때 render함수 안에 다른 함수를 추가하지는 않는다. 작동은 하겠지만 버그를 일으킬 가능성이 있다.
클래스 based는 16.8 ver 도입 이전 state를 관리하기위한 도구로 사용되었다. 이때 필요한 것이
1.초기화 2. state 정의 였다.
이때 초기화 구분을 constructor로 사용하였다.
클래스 컴포넌트에서 state는 항상! 객체형태이다!
반면 함수형 컴포넌트에서 state는 원시타입~참조형까지 매우 다양했다.
그래서 컴포넌트를 구성하는 모든 state의 조각들을 하나로 그룹화하여 만들어야했다.
필요하다면 계속하여 props를 추가로 설정할 수 있지만 이 모든 것들을 묶는 state 객체는 무조건 있어야 한다.
class Users extends Component{
constructor(){
super();
this.state={ <= 요부분
showUsers:true,
textField:true
};
}
toggleUsersHandler(){
this.setState((curState) => {
return {showUsers:!curState.showUsers};
});
}
render(){
const usersList = (
<ul>
{DUMMY_USERS.map((user) => (
<User key={user.id} name={user.name} />
))}
</ul>
);
return(
<div className={classes.users}>
<button onClick={this.toggleUsersHandler.bind(this)}> <= 마지막 부분
{this.state.showUsers ? 'Hide' : 'Show'} Users
</button>
{this.state.showUsers && usersList}
</div>
)
}
}
그리고 state를 바꾸고 싶다면 대입 연산자가 아닌 setState()
라는 method를 이용해야한다.
여기 매개변수에는 항상 객체가 들어가지만 prev함수를 사용한 set메서드를 사용해도 괜찮다.
대신 장점(?)은 기존에 state가 여러개 있고 setState 메서드가 1개의 state만 update한다고 해서 전에 있는 state가 사라지는 것은 아니고 해당 필드값만 update(override)된다.
또한 마지막 부분에 bind을 사용하여 다른 블록함수에서 this를 참조할 수 있도록 신경써줘야한다.
useEffect(~~함수~~, [])
포지션이다.useEffect(~~함수~~, [dep1, dep2...])
이런식을 의존성이 있는 형태의 useEffect 포지션이다.useEffect(()=>{return()=>{클린업함수}}, [])
useEffect의 1번째 인수(함수)안에 return 값인 clean-up함수 포지션이다.componentDidUpdate(prevProps, prevState){
if(prevState.searchTerm !== this.state.searchTerm){
this.setState({
filteredUsers:DUMMY_USERS.filter((user) => user.name.includes(this.state.searchTerm))
})
}
}
useEffect(() => {
setFilteredUsers(
DUMMY_USERS.filter((user) => user.name.includes(searchTerm))
);
}, [searchTerm]);
componentDidUpdate
에서 얼마가 웃긴 함수인지 살펴보았다. 본인스스로 만든 함정에 빠져 무한루프가 생기니 이 것은 useEffect가 얼마나 좋은지를 보여주는 것이기도 하다.
굉장히 짧고, 의존성을 명시함으로서 코드 안에 조건문을 추가할 필요가 없기 때문이다.
그리고, 컴포넌트를 재 렌더링시키는 이 이외의 변경 사항이나, 변경 이유는 이 의존성 배열 덕분에 무시된다.
추가로 컴포넌트가 언제 처음으로 렌더링되는지 또는 갱신이 되는지 등에 대해서는 신경쓰지 않아도 된다. 오로지 의존성만 보면 되고 그것이 바뀌면 그 때 로직을 실행하는 것이다.
만약에 컴포넌트의 필드값을 componentDidMount 메서드를 통하여 http request로 가져왔다고 해보자
클래스 컴포넌트같은 경우는 componentDidMount를 한번 써줘야하는 useEffect 같은 경우는 의존성이 없이 쓰거나, 그냥 의존성을 써도 어차피 처음 로드될 때는 실행이 되므로 그때 http를 넣어주면 된다.
함수형, 클래스 컴포넌트 모두 사용 가능함. 하지만 userReduser 훅이 사용되기 때문에, 이 consumer 컴포넌트는 사실 사용할 수 없다
그럼 어케해야함? 사실 정도는 없다. useContext를 이용하면 useConext를 여러 번 호출하고 매 번 다른 컴포넌트를 가리킴으로써 하나의 컴포넌트에서 여러 개의 컨텍스트를 받아올 수 있다. 이는 클래스 컴포넌트는 한 번에 하나의 컨텍스트만 연결할 수 있으므로 불가능하다.
그래서 static prop을 추가하는데 static 예약어를 사용하고, 이를 통해 contextType이라는 프로퍼티를 만들 수 있다.
여기에 UsersContext 값을 할당하면 리액트에게 이 컴포넌트는 UsersContext라는 컨텍스트에 접근할 수 있다고 전달하는 것이다
하지만 이 정적 프로퍼티, 즉 static contextType는 단 한 번만 설정할 수 있으므로, 동시에 연결해야 하는 2개의 컨텍스트가 있다면 다른 방법을 찾아야한다. 예를 들어 다른 컴포넌트로 래핑을 한다거나 하는 것이다.
static contextType = UsersContext;
우리가 web application을 운용할 때 가끔 문제가 발생하곤 한다.
개발자의 관점에서 보는 버그를 의미하는 것이 아니라 예방할 수 없는 오류도 있으며,
어떤 오류는 어플리케이션의 어떤 부분에서 다른 부분으로 무언가가 잘못되었다는 것을 전달할 때 사용되기도 한다.
예를들어 HTTP 요청 전달을 할 때 서버가 일시적으로 응답이 없을 경우에는 이 요청에 대해 응답할 수 없어서 어플리케이션에서는 오류가 발생된 것을 볼 수 있다.
이런 경우 error boundaries를 참고해볼 필요가 있다.
왜냐하면 일반적인 JS는 try-catch문으로 에러 처리를 할 수 있는데 react는 함수를 해당 컴포넌트밖에 못 쓰기 때문에 jsx코드에서는 try-catch가 사용이 불가능하기 때문에 오류처리가 힘들기 때문이다.
예를 들어 검색 결과가 0이 되면 error을 던지는 코드가 있다고 치고 이를 error boundary로 처리해보자.
componentDidUpdate() {
if (this.props.users.length === 0) {
throw new Error('No users provided!');
}
}
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor() {
super();
this.state = { hasError: false };
}
componentDidCatch(error) {
console.log(error);
this.setState({ hasError: true });
}
render() {
if (this.state.hasError) {
return <p>Something went wrong!</p>;
}
return this.props.children;
}
}
export default ErrorBoundary;
this.props.children
은 필수적인 return이라고 할 수 있겠다.또한 이때 여러개의 컴포넌트를 둘러싸도 ok이다.
또한 1번 코드에서 componentDidCatch
의 인자로 들어가는 error는 React.js가 알아서 전달해준다.
각 error마다 error boundary가 필요하고 componentDidCatch
안에 log나 기록을 남길 수 있고 또 오류마다 다른 로직을 실행하게 할 수 있다. 예를 들어 http error 번호에 맞게 로직을 생성하거나 새로고침을 하도록 유도할 수 있겠다.