Basic Hooks
function 컴포넌트 사용하는 이유
- class는 컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어렵다.
- 복잡한 컴포넌트들은 이해하기 어렵다.
- 사람과 기계를 혼동시킨다.
- 컴파일 단계에서 코드를 최적화하기 어렵게 만든다.
- this.state는 로직에서 레퍼런스를 공유하기 때문에 문제가 발생할 수 있다.
📌 useState
상태값을 관리해주는 훅으로 state를 대체할 수 있다. lazy initialize
import React from "react";
export default function Example() {
const [count, setCount] = React.useState(0); // 배열
return (
<div>
<p>You Clicked {count} times</p>
<button onClick={click}>Click me</button>
</div>
);
function click() {
setCount(count + 1);
}
}
import React from "react";
export default function Example() {
const [state, setState] = React.useState({ count: 0 }); // 객체
return (
<div>
<p>You Clicked {state.count} times</p>
<button onClick={click}>Click me</button>
</div>
);
// 1번째 방식
function click() {
setState({ count: state.count + 1 });
}
// 2번째 방식
// setState가 사용하는 state는 외부에 의존적이지 않고, 함수의 인자로 들어오는 것이다.
function click() {
setState((state) => {
return {
count: state.count + 1,
};
});
}
}
📌 useEffect
의존성 배열(dependency array)에 있는 값이 바뀔 때마다 실행하는 훅이다.
라이프 사이클 훅을 대체할 수 있지만, 동등한 역할을 한다는 것은 아니다.
- componentDidMount
- componentDidUpdate
- componentWillUnMount
import React from "react";
// useEffect 순차적으로 실행
export default function Example() {
const [count, setCount] = React.useState(0); // 배열
React.useEffect(() => {
console.log("componentDidMount & componentDidUpdate", count);
}); // 항상 render가 된 직후에는 이 함수를 실행한다.
React.useEffect(() => {
console.log("componentDidMount");
}, []); // 최초에만 실행된다. []란 의존성 배열로 무엇에 의존할지
React.useEffect(() => {
console.log("componentDidMount & componentDidUpdate by count", count);
return () => {
// cleanup
console.log("cleanup by count", count);
};
}, [count]); // 최초와 count가 업데이트 될 때만 실행한다. lifecycle과 100% 일치하지 않음
React.useEffect(() => {
console.log("componentDidMount");
return () => {
// cleanup
// componentWillUnmount
};
}, []); // 최초에만 실행된다.
return (
<div>
<p>You Clicked {count} times</p>
<button onClick={click}>Click me</button>
</div>
);
function click() {
setCount(count + 1);
}
}
Custom Hooks
단순히 반복만 되면 함수로 만들어주는 것이 좋지만,
hook들이 반복되는 경우는 custom hook으로 만들어주는 것이 좋다.
1. window local storage에 저장하는 훅 만들어보기
keyword, result, typing값이 변경되었을 때, 따로 local storage에 변경하고자 한다. useEffect 만으로 변경하자면 비슷한 코드를 3번 반복해서 적어야할 것이다. 그렇기 때문에 커스텀 훅을 만들어 useState와 useEffect 공통으로 관리하고자 한다.
const root = document.querySelector("#root");
function useLocalStorage(itemName, value="") {
// 새로 고침해도 값이 유지되게
const [state, setState] = React.useState(() => {
return window.localStorage.getItem(itemName) || "";
});
// state가 변경되면 localStorage 저장
React.useEffect(()=> {
window.localStorage.setItem(itemName, state);
}, [state]);
return [state, setState];
}
const App = () => {
const [keyword, setKeyword] = useLocalStorage("keyword");
const [result, setResult] = useLocalStorage("result");
const [typing, setTyping] = useLocalStorage("typing", false);
function handleChange(event) {
setKeyword(event.target.value);
setTyping(true);
}
function handleClick() {
setTyping(false);
setResult(`We find results of ${keyword}`)
}
return(
<>
<input type="text" onChange={handleChange} value={keyword}/>
<button type="submit" onClick={handleClick}>Search</button>
<div>{typing ? `Looking for ...${keyword}` : result}</div>
</>
)
}
ReactDOM.render(<App />, root)
2. window 가로 사이즈 구하는 훅 직접 만들어보기
hooks/useWindowWidth.js
import { useState, useEffect } from "react";
export default function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
// componentDidMount
useEffect(() => {
const resize = () => {
setWidth(window.innerWidth);
};
window.addEventListener("resize", resize);
// cleanup
return () => {
window.removeEventListener("resize", resize);
};
}, []);
return width;
}
App.js
import logo from "./logo.svg";
import "./App.css";
import useWindowWidth from "./hooks/useWindowWidth";
function App() {
const width = useWindowWidth();
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{width}
</header>
</div>
);
}
export default App;
3. useHasMounted
hooks/useHasMounted.js
import { useState, useEffect } from "react";
export default function useHasMounted() {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
return hasMounted;
}
App.js
import logo from "./logo.svg";
import "./App.css";
import useHasMounted from "./hooks/useHasMounted";
function App() {
const hasMountedFromHooks = useHasMounted();
console.log(hasMountedFromHooks);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
</div>
);
}
export default App;
+) withHasMounted(HOC 방식)
요즘에는 HOC보다 hooks를 더 많이 쓴다.
그러나 HOC 방식도 어떻게 쓰는지 정확하게 알아놔야 한다.
hocs/withHasMounted
import React from "react";
export default function widthHasMounted(Component) {
class NewComponent extends React.Component {
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;
}
App.js
import logo from "./logo.svg";
import "./App.css";
import withHasMounted from "./hocs/withHasMounted";
function App({ hasMounted }) {
console.log(hasMounted); // false -> true
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
</div>
);
}
export default withHasMounted(App);
Additional Hooks
📌 useReducer
- 다수의 하위값을 포함하는 복잡한 정적 로직을 만드는 경우
- 다음 state가 이전 state에 의존적인 경우
- Redux를 안다면 쉽게 사용 가능
import { useReducer } from "react";
// reducer => state 를 변경하는 로직이 담겨 있는 함수
const reducer = (state, action) => {
if (action.type === "PLUS") {
return {
count: state.count + 1,
};
}
return state;
};
// dispatch => action 객체를 넣어서 실행
// action => 객체이고 필수 프로퍼티로 type을 가진다.
export default function Example6() {
const [state, dispatch] = useReducer(reducer, { count: 0 }); // 객체
return (
<div>
<p>You Clicked {state.count} times</p>
<button onClick={click}>Click me</button>
</div>
);
function click() {
dispatch({ type: "PLUS" });
}
}
📌 useMemo
import { useMemo, useState } from "react";
function sum(persons) {
console.log("sum...");
return persons.map((person) => person.age).reduce((l, r) => l + r, 0);
}
export default function Example7() {
const [value, setValue] = useState("");
const [persons] = useState([
{ name: "Mark", age: 39 },
{ name: "Hanna", age: 28 },
]);
const count = useMemo(() => {
return sum(persons);
}, [persons]); // persons가 변경 됐을 때만 실행
return (
<div>
<input value={value} onChange={change} />
<p>{count}</p>
</div>
);
function change(e) {
setValue(e.target.value);
}
}
📌 useCallback
어떤 함수를 dependency list에 있는 조건에 맞춰서 새로 만들어 할당해 사용하게 해주는 것이다.
import { useCallback, useMemo, useState } from "react";
function sum(persons) {
console.log("sum...");
return persons.map((person) => person.age).reduce((l, r) => l + r, 0);
}
export default function Example7() {
const [value, setValue] = useState("");
const [persons] = useState([
{ name: "Mark", age: 39 },
{ name: "Hanna", age: 28 },
]);
const count = useMemo(() => {
return sum(persons);
}, [persons]); // persons가 변경 됐을 때만 실행
const click = useCallback(() => {
console.log(value);
}); // [] 함수가 새로 세팅되지 않게 해주고 싶다면
return (
<div>
<input value={value} onChange={change} />
<p>{count}</p>
<button onClick={click}>click</button>
</div>
);
function change(e) {
setValue(e.target.value);
}
}
📌 useRef
useRef는 인자로 넘어온 초깃값을 useRef 객체의 .current 프로퍼티에 저장한다. DOM 객체를 직접 가리켜서 내부 값을 변경하거나 focus() 메서드를 사용하거나 하는 때에 주로 사용하고, 변경되어도 컴포넌트가 리렌더링되지 않도록 하기 위한 값들을 저장하기 위해서도 사용한다. (이는 useRef가 내용이 변경되어도 이를 알려주지 않기 때문이다) .current 프로퍼티를 변경시키는 것은 리렌더링을 발생시키지 않고, 따라서 로컬 변수 용도로 사용할 수 있다.
본질적으로 useRef는 .current 프로퍼티에 변경 가능한 값을 담고 있는 "상자"와 같다.
createRef는 reference를 생성해서 render 될 때 넣어주는 것이고,
useRef는 render 사이에도 유지해주는 큰 차이가 있다.
import { createRef, useRef, useState } from "react";
export default function Example8() {
const [value, setValue] = useState("");
const input1Ref = createRef();
const input2Ref = useRef();
console.log(input1Ref.current, input2Ref.current);
return (
<div>
<input value={value} onChange={change} />
<input ref={input1Ref} />
<input ref={input2Ref} />
</div>
);
function change(e) {
setValue(e.target.value);
}
}
'🍞 Front-End > React' 카테고리의 다른 글
[React] Virtual DOM과 리랜더링, 재조정 (0) | 2022.10.04 |
---|---|
[React] 컴포넌트 간 통신 Context API (0) | 2022.09.13 |
[React] HOC와 Controlled, Uncontrolled (0) | 2022.09.01 |
[React] Styled Component (0) | 2022.09.01 |
[React] SPA와 react-router-dom v6 되면서 바뀐점 (0) | 2022.09.01 |