Hooks 배우기 전 HOC에 대한 이해 보충자료 정리
React Hooks
는 ReactConf 2018에서 발표된 class없이
state를 사용할 수 있는 새로운 기능입니다.
항상 기술은 그 전 것에 불편함이나 문제점을 해결하기 위해서 더욱 발전합니다.
그와 같이 React Hooks도 주로 Class Component로 사용되어온 React에서 느껴왔던 불편함이나 문제점들을 해결하기 위해서 개발되었습니다.
원래 React는 주로 Class Component를 사용하고 React Hooks는 Functional Component를 사용하기 때문에 그 부분부터 비교해보겠습니다.
import React, { Component } from 'react'
export default class Hello extends Component {
render() {
return (
<div>
hello my friends!
</div>
)
}
}
import React from 'react'
export default function Hello() {
return (
<div>
hello my friends!
</div>
)
}
리액트 생명주기
이렇게나 중요한 생명주기를 함수형 컴포넌트에서는 사용 못했기 때문에 함수형 컴포넌트가 더 간결하고 빠르더라도 클래스 컴포넌트를 써왔음
버전 업데이트 이후 함수형 컴포넌트에서도 생명주기를 사용할 수 있기에 데이터를 가져오고 컴포넌트 시작하자마자 API도 호출하고 많은 부분을 할 수 있게 되었음.
그럼 Hooks로 인해 어떻게 가능하게 되었는지 소스로도 확인
// Class
import React, { Component } from 'react'
import Axios from 'axios'
export default class Hello extends Component {
construnctor(props) {
super(props);
this.state = { name: "" };
}
componentDidMount() {
Axios.get('/api/user/name')
.then(response => {
this.setState({ name: response.data.name })
})
}
render() {
return (
<div>
My name is {this.state.name}
</div>
)
}
}
// Functional
import React, { useEffect, useState } from 'react'
import Axios from 'axios'
export default function Hello() {
const [Name, setName] = useState('')
useEffect( () => {
Axios.get('/api/user/name')
.then(response => { setName(response.data.name) })
}, [])
return(
<div>
My Name is {Name}
</div>
)
}
// 일반 Class에서 생명주기를 사용하는 부분
componentDidMount() {
// 컴포넌트가 마운트 되면 updateLists 함수를 호출
this.updateLists(this.props.id)
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
// updateLists 함수를 호출할 때
// 사용되는 id가 달라지면 다시 호출
this.updateLists(this.props.id)
}
}
// updateLists 함수 정의
updateLists = (id) => {
fetchLists(id)
.then( (lists) => this.setState({lists}) )
}
// React Hooks를 이용한 생명주기 이용 (Functional)
useEffect( () => {
fetchLists(id)
.then( (repos) => {setRepos(repos)} )
}, [id]
)
Class에서 생명주기를 사용해서 작성한 코드와
Hooks을 이용해 Functional로 생명주기를 이용해 작성한 코드를 비교하면
Hooks 사용했을 때 코드가 더 간결
해진 것을 확인할 수 있다.
그 이유는 Class Component에서는 생명주기를 이용할 때
componentDidMount와 ComponentDidUpdate, componentWillUnmount
이렇게 다르게 처리해주지만,
React Hooks를 사용할 때는
useEffect 안에서 다 처리를 해줄 수 있기 때문이다.
HOC란 화면에서 재사용 가능한 로직만을 분리
해서 component
로 만들고, 재사용 불가능한 UI와 같은 다른 부분들은 parameter
로 받아서 처리하는 방법입니다.
// A 페이지
export default class Apage extends Component {
state = { user: [] }
componentDidMount() {
fetchUsers()
.then(users => { this.setState({users}) })
}
render() {
const {users} = this.state;
return (
<div>
A 페이지
{user.map( ({name, url}) => (
<div key={name}>
<p>{name}, {url}</p>
</div>
)
)}
</div>
)
}
}
// B 페이지
export default class Bpage extends Component {
state = { user: [] }
componentDidMount() {
fetchUsers()
.then(users => { this.setState({users}) })
}
render() {
const {users} = this.state;
return (
<div>
B 페이지
{user.map( ({name, url}) => (
<div key={name}>
<p>{name}, {url}</p>
</div>
)
)}
</div>
)
}
}
DB나 API를 통해서 유저의 리스트를 가져온 후
A페이지와 B페이지, ... 등 모든 페이지에서도
데이터를 뿌려주고 싶을 때 중복되지 않게 코드 작성한다면..
function usersHOC(Component) {
return class usersHOC extends React.Component {
state = { user: [] }
componentDidMount() {
fetchUsers()
.then( users => { this.setState({users}) } )
}
render() {
return (
<Component
{...this.props}
{...this.state}
/>
)
}
}
}
function Apage ({users}) {
// wrapped
}
export default userHOC(Apage)
function Bpage ({users}) {
// wrapped
}
export default usersHOC(Bpage)
=> 유저 리스트를 가져오는 공통적인 부분은 HOC 컴포넌트에 넣어주고
그 HOC 컴포넌트로 각각의 컴포넌트를 감싸주면 모든 컴포넌트에 따로
인증을 위한 부분은 넣어주지 않아도 됩니다. Hooks가 나오기 전에는 이 방법이 추천되는 방법이었습니다.
하지만 여기서도 문제가 있습니다.
그 문제는 바로 너무나 많은 Wrapper 컴포넌트가 생길 수 있다는 겁니다. 아래에 그림처럼 Wrapper가 너무 많아지면 데이터의 흐름을 파악하기가 어려워 집니다. (렌더링된 DOM 확인 시 중첩된 코드)
<LanguageHOC>
<ThemeHOC>
<AuthHOC>
<Apage />
</AuthHOC>
</ThemeHOC>
</LanguageHOC>
function useAuth() {
const [users, setUsers] = useState( [] );
useEffect( () => {
fetchUsers()
.then( users => { setUsers(users); });
}, [] );
return [users];
}
function Apage() {
const [users] = useAuth(); // hooks
return (
<div>
A 페이지
{
users.map( ({name, url}) => (
<div key={name}>
<p>{name}, {url}</p>
</div>
)
)
}
</div>
);
}