2022.07.11 - React / useState, custom hooks, HOC 고차함수
리액트 컴포넌트에서 동적인 값을 상태(state)라고 한다.
사용자 인터랙션을 통해 컴포넌트의 상태값이 동적으로 바뀔 경우에는 상태를 관리하는것이 필요하다.
React Hooks가 나오기 전에는 컴포넌트의 상태 관리를 하려면 클래스 기반 React 컴포넌트를 작성해야했다.
하지만 리액트 16.8 버전부터 Hooks 기능이 도입되면서 함수형 컴포넌트에서도 상태를 관리할 수 있게 됨.
useState()함수를 통해 함수형 컴포넌트에서도 상태를 관리할 수 있다.
- useEffect : 라이프 사이클 훅을 대체할 수 있다.
componentDidMount
componentDidUpdate
componentWillUnmount
[ useEffect 연습(1) - 커스텀 훅 ]
** useEffect 사용하여 가로길이를 출력하는 커스텀 훅 만들어보기
useWindowWidth.js
// hook의 특징 : hook, 함수 컴포넌트 안에서만 실행이 가능함
import { useState, useEffect } from "react";
// hook안에 다른 훅을 쓸 수 있음
export default function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const resize = () => {
setWidth(window.innerWidth);
};
window.addEventListener("resize", resize);
return () => {
window.removeEventListener("resize", resize);
};
}, []);
return width;
}
// App.js
import logo from "./logo.svg";
import "./App.css";
import Example1 from "./components/Example1";
import Example2 from "./components/Example2";
import Example3 from "./components/Example3";
import Example4 from "./components/Example4";
import Example5 from "./components/Exapmle5";
import useWindowWidth from "./hooks/useWindowWidth";
function App() {
// 함수 컴포넌트이기 때무네 useWindowWidth훅을 가져와서 쓸 수 있음
// return을 width로 해줬으니 width 호출
const width = useWindowWidth();
// (1) 현재 브라우저의 width값이 초기값으로 들어올 것 => 743
// (2) 브라우저를 움직일때마다 width 값 받기
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{/* <Example1 />
<Example2 />
<Example3 />
<Example4 /> */}
<Example5 />
{width}
</header>
</div>
);
}
export default App;
1. const [width, setWidth] = useState(window.innerWidth);
: 초기값 줘야함 => 최초에 useWindowWidth 라는 훅이 실행될때의 창의 가로사이즈를 가져와서 state로 관리
2. 브라우저를 움직일때마다 width 값 받기
=> 초기값을 setWidth 값으로 바꾸면 되는데,
window.addeventlistener resize 라고 하는 이벤트를 최초에 브라우저가 랜더된 직후에 설정하면 됨.
useEffect(() => {
const resize = () => {
setWidth(window.innerWidth);
};
window.addEventListener("resize", resize);
3. cleanup (removeEventListener)
useEffect로 인해서 해당 훅이 사용됐을때 window.addeventlistener resize 가 작동하는데,
어디선가 useWindowWidth를 안쓰게되는 경우가 있을 것이니 대비해서 이벤트를 해지해줘야 함. 그때 cleanup을 이용하는 것임.
4. resize 함수 생성 후 return에 넣기
cleanup 리턴안에 위에서 만들어준 resize를 넣어야하는데,
() => { setWidth(window.innerWidth) } ;
위 처럼 해당 함수가 변수에 담기지 않은 형태로 선언되어 있으므로 변수에 담아야함.
window.addEventListener("resize", () => {
setWidth(window.innerWidth); }); 를 아래의 형태로 resize()함수로 뺀다.
const resize = () => {
setWidth(window.innerWidth);
};
5. window.addEventListener("resize", )에 resize함수 넣기
window.addEventListener("resize", resize);
6. remove에도 resize함수 넣기
window.removeEventListener("resize", resize);
};
7. dependency 설정
현재 dependency가 안되어있기 때문에 랜더가 될때마다 계속 resize를 하려고 할 것임 (하기 전에 클린업 하고 함/ 잊지말자)
결론적으로 resize같은 이벤트들은 최초에 설정되고, 최후에 없애주면 되기때문에
useEffect( () => {}, [] ) : 빈 배열 [] 넣어준다.
}, []);
[ useEffect 연습(2) - HOC방식 ]
- HOC : 고차 컴포넌트 (HOC, higher-order component)는 컴포넌트 로직을 재사용하기 위한 React의 고급 기술이다.
고차 컴포넌트는 컴포넌트를 취하여 새로운 컴포넌트를 반환하는 함수이다. ( 즉 컴포넌트를 인자로 받거나 반환하는 함수 )
[참고 : https://velog.io/@trollering12312/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%8B%A4%EC%A0%84-%ED%99%9C%EC%9A%A9 ]
- 주의사항
1) render 메서드 안에서 고차 컴포넌트를 사용하면 안된다.
-> 컴포넌트의 정의 바깥에 HOC를 적용하여 컴포넌트가 한 번만 생성
2) 정적 메서드는 반드시 따로 복사해야한다.
( 정적 메서드란?
"prototype"이 아닌 클래스 함수 자체에 메서드를 설정할 수도 있습니다. 이런 메서드를 정적(static) 메서드라고 부른다. )
정적 메서드는 어떤 특정한 객체가 아닌 클래스에 속한 함수를 구현하고자 할 때 주로 사용된다.
3) ref는 전달되지 않는다.
( ref 란?React에서 state로만 해결할 수 없고, DOM을 반드시 직접 건드려야 할 때 사용하게 된다.)
1. withHasMounted props를 넣어주는 HOC 함수 생성
import React from "react";
// withHasMounted props를 넣어주는 HOC 함수 생성
export default function withHasMounted(Component) {
class NewComponent extends React.Component {
// *state 초기값 설정
state = {
hasMounted: false,
};
render() {
const { hasMounted } = this.state;
return <Component {...this.props} hasMounted={hasMounted} />;
}
componentDidMount() {
this.setState({ hasMounted: true });
}
}
NewComponent.displayName = `withHasMounted(${Component.name})`;
return NewComponent;
}
2. 초기값 설정 : false
3. render(){return ~ }; 작성
4. componentDidMount(){}
componentDidMount() {
this.setState({ hasMounted: true });
}
최초의 랜더를 마치고 난 직후에 setState라는 함수를 호출해서 hasMounted:true 로 바꿔준다.
최초에는 hasMounted가 false이지만 componentDidMount 되는 순간 true가 된다.
5. 랜더할때 컴포넌트에 state.hasMounted를 넣어주기
render() {
//(4)랜더할때 컴포넌트에 state.hasMounted를 넣어준다.
// <Component hasMounted={hasMounted} />;
// const { hasMounted } = this.state;
const { hasMounted } = this.state;
return <Component {...this.props} hasMounted={hasMounted} />;
}
=> unrelayted props는 그냥 통과시켜줘야 함.
NewComponent에 있는 this.props 라고 하는 원래 들어온 props는 그냥 Component에 넣어줘야하기 때문에
Component {...this.props} 추가해준다.
6. displayName
displayName 문자열은 디버깅메시지 표시에 사용된다. 대부분의 경우 이 값을 설정하지 않아도 된다.
그 이유는 해당 컴포넌트를 정의하는 함수 또는 class의 이름으로부터 추론되기 때문
디버깅을 위해 다른이름을 표시하거나 고차 컴포넌트 생성을 위하여 명시적으로 이 값을 설정함.
NewComponent.displayName = `withHasMounted(${Component.name})`;
=> NewComponent.displayName에 함수 이름 넣고 괄호에 인자로 들어온 Component에 name을 넣어서 보여주면
displayName에 인자로 들어온 이름이 찍힘
7. App.js
// (3) HOC 사용 , 괄호에 (App)을 넣고 export
export default withHasMounted(App);
function App({ hasMounted }) {
const width = useWindowWidth();
console.log(hasMounted);
=> console.log(hasMounted); 콘솔 확인
false -> true가 되는것을 확인할 수 있는데 이것은 앱이라는 컴포넌트가 초기값이 false인 상태로 랜더됐다가 직후에
true가 props로 인해 다시 바뀌고, 또 다시 랜더되면서 false로 변경된다.
[ useEffect 연습(3) - 커스텀 훅 ]
import { useState } from "react";
import { useEffect } from "react";
export default function useHasMounted() {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
// 두번째 인자로 setHasMounted 넣어주고 useEffect에서 실행
// 실행코드 입력 전에 [] 빈배열로 dependency 넣기
setHasMounted(true);
}, []);
return hasMounted; //false가 리턴될것임
}
// APP.js
const hasMountedFromHooks = useHasMounted();
console.log(hasMounted, hasMountedFromHooks);
* 차이점 *
useHasMounted는 상태를 만든 후 그 상태를 변경하는 로직이 useEffect에 주로 담겨있는 형태가 리턴되는 것이고,
withHasMounted는 만들어진 {hasMounted}라는 것이 ...this.props로 내려보내주는 것이다.
요즘은 HOC보다는 hooks를 많이 사용하는 추세이다.