이어서 hooks API를 보도록 하겠습니다.
useMemo는 하나의 상태값을 변경하였을 때 해당 컴포넌트가 다시 랜더링이 될때 불필요한 작업들을 하는경우가 발생합니다.
전에 했던 Hooks를 사용한 예제를 통해 설명드리겠습니다.
Hedaer.jsx
count 함수에 log가 찍히게 작성해보겠습니다.
import React, { useContext, useMemo } from "react"; import { TodoContext } from "./App.js"; const Header = ({ state }) => { const count = (state) => { console.log("Header 랜더링~"); //추가 let cnt = state.filter((v) => v.status === "re").length; return cnt; }; return ( <div> <h1>Hooks를 사용한 예제</h1> <div>갯 수 : {count(state)}개</div> </div> ); }; export default Header;
실행하여 text입력란에 입력을 할경우
이렇게 Header가 랜더링 되는 것을 알 수 있습니다.
현재 change이벤트가 발생하여 setNewTodo를 하여 리랜더링이 발생을 하였는데 해당 Header는 불필요한 랜더링이 발생하는 걸 볼 수 있습니다.
Header에는 newTodo를 사용하지않고 state를 사용하는데 랜더링이 다시 될필요는 없던거죠.
그래서 이러한 불필요한 랜더링으로 앱의 성능을 저하시킬 수 있는걸 방지하기 위해 useMemo를 사용합니다.
useMemo에 해당 state를 저장하여 리랜더링 되었을 때 저장된 값과 비교하여 값이 동일하면 리랜더링이 되지 않게 해줍니다.
이제 코드를 변경해보겠습니다.
import React, { useMemo } from "react"; import { TodoContext } from "./App.js"; const Header = ({ state }) => { const count = (state) => { console.log("Header 랜더링~"); let cnt = state.filter((v) => v.status === "re").length; return cnt; }; const memoCount = useMemo(() => count(state), [state]); return ( <div> <h1>Hooks를 사용한 예제</h1> <div>갯 수 : {memoCount}개</div> </div> ); }; export default Header;
작성 후 아까와 동일하게 작업을 했을 때 로그가 발생하지 않는 걸 확인 하실 수 있습니다.
useCallback 은 useMemo와 상당히 비슷한 함수입니다. 주로 랜더링 성능을 최적화해야 하는 상황에서 사용하는데요, 이 Hook을 사용하면 이벤트 핸들러 함수를 필요할 때만 생성 할 수 있습니다.
컴포넌트가 리렌더링 될 때마다 함수들이 새로 생성됩니다. 대부분의 경우에는 이러한 방식이 문제가 되지 않지만, 컴포넌트의 렌더링이 자주 발생하거나, 렌더링 해야 할 컴포넌트의 개수가 많아진다면 이 부분을 최적화 해주시는 것이 좋습니다.
App.js에 적용시켜보도록 하겠습니다.
import './App.css'; import List from './List.jsx'; import React, { Component, useState, useEffect, useCallback } from 'react'; import Header from './Header.jsx'; import Form from './Form.jsx'; const App = () => { //useState를 사용하여 state 생성, 초기값 지정 const [state, setState] = useState([ {"id" :"js공부", "status" : "re"}, {"id" :"react", "status" : "re"}]); const[newTodo, setNewTodo] = useState();//useState를 사용하여 newTodo 생성 초기값 없음 const changeInputData = useCallback((e) =>{ // onChange 이벤트 발생 시 NewTodo에 해당 값 저장 setNewTodo(e.target.value); }); const addTodo = useCallback((e) => { // onClick 이벤트 발생 시 state에 해당 값 저장 console.log("랜더링~~"); e.preventDefault(); setState([...state, {id : newTodo, status : 're'}]); }); const changeT = useCallback((id) => { // 할 일 목록 내 item을 클릭 시 발생하는 이벤트 클릭 시 해당 값에 변화를 주기 위함 const updateT = state.map(todo =>{ if(todo.id === id){ if(todo.status === 'done') todo.status= 're'; else todo.status = 'done'; } else{ } return todo; }); setState(updateT); }); useEffect( () =>{ //랜더링 될 때 호출 console.log('새로운 내용이 랜더링됐네요', state); }, [state]); // 특정값 state에 지정하였기 때문에 newTodo가 변경될때는 타지 않음 return ( <> <Header state = {state}></Header> <form action=""> <input type="text" name="" onChange={changeInputData} /> <button onClick={addTodo}>할일추가</button> </form> <List todos= {state} changeT = {changeT}></List> </> ) } export default App;
이 useContext을 사용하면 함수형 컴포넌트에서 Context 를 보다 더 쉽게 사용 할 수 있습니다.
useContext는 React.createContext로 context를 생성하여줍니다.
export const TodoContext = React.createContext();
그리고 상위 컴포넌트에서 Provider로 하위 컴포넌트에게 값을 넘겨 줄 수 있습니다.
<TodoContext.Provider value= {{state, changeInputData, addTodo}}>
</TodoContext.Provider
이 방법을 적용 시켜 보도록하겠습니다.
App.js
import './App.css'; import List from './List.jsx'; import React, { Component, useState, useEffect, useCallback } from 'react'; import Header from './Header.jsx'; import Form from './Form.jsx'; export const TodoContext = React.createContext();// useContext 생성 const App = () => { const [state, setState] = useState([ {"id" :"js공부", "status" : "re"}, {"id" :"react", "status" : "re"}]); const[newTodo, setNewTodo] = useState();//useState를 사용하여 newTodo 생성 초기값 없음 const changeInputData = useCallback((e) =>{ // onChange 이벤트 발생 시 NewTodo에 해당 값 저장 setNewTodo(e.target.value); }); const addTodo = useCallback((e) => { // onClick 이벤트 발생 시 state에 해당 값 저장 console.log("랜더링~~"); e.preventDefault(); setState([...state, {id : newTodo, status : 're'}]); }); const changeT = useCallback((id) => { // 할 일 목록 내 item을 클릭 시 발생하는 이벤트 클릭 시 해당 값에 변화를 주기 위함 const updateT = state.map(todo =>{ if(todo.id === id){ if(todo.status === 'done') todo.status= 're'; else todo.status = 'done'; } else{ } return todo; }); setState(updateT); }); useEffect( () =>{ //랜더링 될 때 호출 console.log('새로운 내용이 랜더링됐네요', state); }, [state]); // 특정값 state에 지정하였기 때문에 newTodo가 변경될때는 타지 않음 return ( <TodoContext.Provider value= {{state}}> <Header></Header> <form action=""> <input type="text" name="" onChange={changeInputData} /> <button onClick={addTodo}>할일추가</button> </form> <List todos= {state} changeT = {changeT}></List> </TodoContext.Provider> ) } export default App;
변경된 부분은
1. export const TodoContext = React.createContext();// useContext 생성
2. return에 최상위 태그 <> 를 <TodoContext.Provider value= {{state}}> 변경
3. Header에 속성 state 제거
다음으로
Header.jsx
import React, { useMemo, useContext } from "react"; // useContext 추가 import { TodoContext } from "./App.js"; const Header = () => { const { state } = useContext(TodoContext); //useContext 사용 const count = (state) => { console.log("Header 랜더링~"); let cnt = state.filter((v) => v.status === "re").length; return cnt; }; const memoCount = useMemo(() => count(state), [state]); return ( <div> <h1>Hooks를 사용한 예제</h1> <div>갯 수 : {memoCount}개</div> </div> ); }; export default Header;
Header.jsx에서 useContext를 추가하여 state값을 useCountext(TodoContext)로 받아와서 사용합니다.
실행해보면 동일하게 실행이 될 것입니다.
다음 시간에는 hooks Reducer를 알아보겠습니다.
Reducer는 redux할 때 도움이 되는 부분이여서 보면 좋습니다.