Hooks는 리액트 v16.8에 새로 도입된 기능으로 함수로 만든 리액트 컴포넌트에서 클래스로 만든 리액트 컴포넌트의 기능을 이용하도록 도와주는 함수들이다. Hook은 기존의 함수형 컴포넌트에서는 할 수 없었던 작업을 할 수 있다.
앞서 공부한 내용에서 리액트 컴포넌트에는 클래스형 컴포넌트와 함수형 컴포넌트 두 종류가 있고 state라는 중요한 개념이 있었다. 클래스형 컴포넌트는 생성자에서 state를 정의하고 setState() 함수를 통해 state를 업데이트하고 라이프 사이클에 따라 사용할 수 있었다. 반면, 함수형 컴포넌트에서는 state를 사용하거나 라이프 사이클에 따라 구현할 수 없다. Hook의 사용은 이러한 부분을 보완하고 함수형 컴포넌트에서도 클래스로 만든 컴포넌트의 기능을 이용할 수 있도록 도와준다.
useState는 함수형 컴포넌트에서도 가변적인 상태를 지닐 수 있게 해주는 함수이다. useState는 다음과 같이 사용한다.
const [value, setValue] = useState(0);
useState 사용법
const = [변수명, set변수명] = useState(초기값); 형식이다. 위는 초기값을 0으로 설정하겠다는 뜻이다. 이 함수가 호출되면 배열을 반환한다. 그 배열의 첫 번째 원소는 상태 값, 두 번째 원소는 상태를 설정하는 함수이다. 이 함수에 파라미터를 넣어서 호출하면 전달받은 파라미터로 값이 바뀌고 컴포넌트가 정상적으로 리렌더링된다.
※ 하나의 useState() 함수는 하나의 상태 값만 관리할 수 있다. 따라서 컴포넌트에서 관리해야 할 상태가 여러 개 useState를 여러 번 사용하면 된다.
useEffect는 리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다.
useEffect(이펙트 함수, 의존성 배열);
기본적으로 이펙트 함수는 처음 컴포넌트가 렌더링 된 이후와 업데이트로 인한 리렌더링 이후에 실행된다. 이펙트 함수를 언제 호출하고 싶은지에 따라 useEffect를 다르게 작성해 볼 수 있다.
1) Mount 또는 Unmount시에 단 한번씩만 실행하고 싶을 때
useEffect(이펙트 함수, []);
위와 같이 두 번째 파라미터 값으로 빈 배열을 작성하면 해당 효과가 props나 state에 있는 어떤 값에도 의존하지 않아서 Mount 또는 Unmount시에 한 번만 실행이 된다.
2) 컴포넌트가 업데이트 될 때마다 실행하고 싶을 때
useEFfect(이펙트 함수);
의존성 배열로 아무것도 작성하지 않고 그냥 생략해버린다면, 컴포넌트가 업데이트 될 떄마다 호출된다.
3) 특정 값이 업데이트 될 때만 실행하고 싶을 때
useEffect( () => {
console.log(name);
}, [name]);
두 번째 파라미터로 전달되는 배열 안에 검사하고 싶은 값을 작성하면 해당 값이 업데이트 될 떄 실행된다.
※ 뒷정리하기 (cleanup)
기본적으로 useEffect는 렌더링되고 난 직후마다 실행되며, 두 번째 파라미터 배열에 무엇을 넣는지에 따라 실행되는 조건이 달라진다. 컴포넌트가 언마운트되기 전이나 업데이트되기 직전에 어떠한 작업을 수행하고 싶다면 useEffect에서 뒷정리(cleanup) 함수를 반환해 주어야 한다.
실습을 위해 component 폴더 아래에 새로운 컴포넌트 Info.js를 아래와 같이 작성한다.
import React, { useState, useEffect } from 'react';
function Info () {
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
useEffect(() => {
console.log('effect');
console.log(name);
return () => {
console.log('cleanup');
console.log(name);
};
});
const onChangeName = e => {
setName(e.target.value);
};
const onChangeNickname = e => {
setNickname(e.target.value);
};
return (
<div>
<div>
<input value={name} onChange={onChangeName} />
<input value={nickname} onChange={onChangeNickname} />
</div>
<div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임:</b> {nickname}
</div>
</div>
</div>
);
};
export default Info;
다음으로, App 컴포넌트에서 Info 컴포넌트의 가시성을 바꿀 수 있게 아래와 같이 App.js 코드를 수정한다.
import "./App.css";
import { useState } from "react";
import Info from "./component/Info";
function App() {
const [visible, setVisible] = useState(false);
return (
<div>
<button
onClick={() => {
setVisible(!visible);
}}
>
{visible ? '숨기기' : '보이기'}
</button>
<hr />
{visible && <Info />}
</div>
);
};
export default App;
이제 렌더링을 해보면 보이기/숨기기 버튼으로 effect와 cleanup을 확인할 수 있다.
보이기 버튼을 누르면 Info 컴포넌트가 나타나고 콘솔에 effect가 나타나고 숨기기 버튼을 누르면 Info 컴포넌트가 사라지고 cleanup이 나타난다. input에 이름이나 닉네임을 적어보고 콘솔을 확인해보면 렌더링될 때마다 뒷정리 함수가 계속 나타나는 것을 확인할 수 있다. 그리고 뒷정리 함수가 호출될 때는 업데이트되기 직전의 값을 보여준다.
cf. 만약, Unmount될 때만 뒷정리 함수를 호출하고 싶으면 useEffect 함수의 두 번째 파라미터에 비어있는 배열을 넣으면 된다. 아래와 같이 Info.js의 useEffect 함수의 두 번째 파라미터 값을 수정해 볼 수 있다.
useEffect(() => {
console.log('effect');
console.log(name);
return () => {
console.log('cleanup');
console.log(name);
};
}, []);
useReducer는 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해 주고 싶을 때 사용하는 Hook이다. reducer는 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 action 값을 전달받아 새로운 상태를 반환하는 함수이다. 리듀서 함수에서 새로운 상태를 만들 때는 반드시 불변성을 지켜줘야한다.
function reducer(state, action) {
return { ... }; // 불변성을 지키면서 업데이트한 새로운 상태를 반환합니다.
}
액션 값의 주된 형태는 아래와 같다.
{
type: 'INCREMENT',
// 다른 값들이 필요하다면 추가로 들어감
}
useReducer에서 사용하는 액션 개체는 반드시 type을 지니고 있을 필요는 없다. 액션값은 객체가 아니라 문자열이나 숫자여도 상관없다.
component 폴더 아래 Counter.js 컴포넌트를 만들고 useReducer를 이용해 구현한다.
import React, { useReducer } from 'react';
function reducer(state, action) {
// action.type에 따라 다른 작업 수행
switch (action.type) {
case 'INCREMENT':
return { value: state.value + 1 };
case 'DECREMENT':
return { value: state.value - 1 };
default:
// 아무것도 해당되지 않을 때 기존 상태 반환
return state;
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { value: 0 });
return (
<div>
<p>
현재 카운터 값은 <b>{state.value}</b>입니다.
</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
</div>
);
};
export default Counter;
useReducer의 첫 번째 파라미터에는 리듀서 함수를 넣고, 두 번째 파라미터에는 해당 리듀서의 기본값을 넣어준다.
이 Hook을 사용하면 state 값과 dispatch 함수를 받아 오는데, 여기서 state는 현재 가리키고 있는 상태고, dispatch는 액션을 발생시키는 함수이다. dispatch(action)과 같은 형태로, 함수 안에 파라미터로 액션 값을 넣어 주면 리듀서 함수가 호출되는 구조입니다.
useReducer를 사용했을 때의 가장 큰 장점은 "컴포넌트 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다"는 것입니다.
이제 App에서 Counter를 렌더링 해주고 브라우저에서 +1/-1 버튼을 눌러보면 잘 작동하는 것을 확인할 수 있다.
import React from 'react';
import Counter from './Counter';
const App = () => {
return <Counter />;
};
export default App;
위의 useState에서 실습한 Info 컴포넌트에서는 input이 여러 개여서 useState를 여러 번 사용했는데, useRedecer를 사용하면 기존에 클래스형 컴포넌트에서 input 태그에 name 값을 할당하고 e.target.name을 참조하여 setState를 해 준 것과 유사한 방식으로 작업을 처리할 수 있다.
import React, { useReducer } from 'react';
function reducer(state, action) {
return {
...state,
[action.name]: action.value
};
}
const Info = () => {
const [state, dispatch] = useReducer(reducer, {
name: '',
nickname: ''
});
const { name, nickname } = state;
const onChange = e => {
dispatch(e.target);
};
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
<div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임: </b>
{nickname}
</div>
</div>
</div>
);
};
export default Info;
src/component/Info.js
useReducer에서의 액션은 어떤 값도 사용 가능하기 때문에 수정한 Info 컴포넌트에서는 이벤트 객체가 지니고 있는 e.target 값 자체를 액션 값으로 사용했다. 이런 식으로 인풋을 관리하면 개수가 아무리 많아도 짧고 깔끔한 코드를 유지할 수 있다.
import React from 'react';
import Info from './Info';
const App = () => {
return <Info />;
};
export default App;
src/App.js
마찬가지로 App에서 Info 컴포넌트를 렌더링하면 잘 작동되는지 확인할 수 있다.
useMemo는 Memoized value를 리턴하는 Hook이다. Memoization은 최적화를 위해서 사용하는 개념이다. Memoization이 된 결괏값을 영어로 Memoized value이라고 한다. (우리가 흔히 매모한다(메모해뒀다가 사용)는 표현과 비슷한 맥락이다.)
▷ useMemo 사용법
const memoizedValue = useMemo(
() => {
//연산량이 높은 작업을 수행하여 결과를 반환
return computeExpensiveValue(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]}
);
useMemo로 전달된 함수는 렌더링이 일어나는 동안 실행된다는 점을 기억해야한다. 따라서 일반적으로 렌더링이 일어나는 동안 실행되서는 안될 작업을 useMemo의 함수에 넣으면 안 된다.
▷ 의존성 배열을 넣지 않은 경우
const memoisedValue = useMemo(
() = > computeExpensiveValue(a,b)
);
렌더링이 일어날 때마다 매번 함수가 실행된다. useMemo 훅에 의존성 배열을 넣지 않고 사용하는 건 아무런 의미가 없다.
▷ 의존성 배열이 빈 배열([])인 경우
const memoisedValue = useMemo(
() = > computeExpensiveValue(a,b)
);
컴포넌트 마운트 시에만 호출되기 떄문에 마운트 이후에는 값이 변경되지 않는다. 따라서 마운트 시점에만 한 번 값을 계산할 필요가 있는 경우에 이렇게 사용하면 된다.
※ 하지만 대부분의 경우에는 useMemo Hook에 의존성 배열의 변수들을 넣고 해당 변수들의 값이 바뀜에 따라 새로 값을 계산할 때 사용한다.
useMemo는 함수형 컴포넌트 내부에서 발생하는 연산을 최적화 할 수 있다. 실습해보기 위해 리스트에 숫자를 추가하면 추가된 숫자들의 평균을 보여주는 함수형 컴포넌트 Average.js 파일을 아래와 같이 작성한고 App에서 렌더링한다.
import React, { useState } from 'react';
const getAverage = numbers => {
console.log('평균값 계산 중..');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = e => {
setNumber(e.target.value);
};
const onInsert = e => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
};
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값:</b> {getAverage(list)}
</div>
</div>
);
};
export default Average;
src/component/Average.js
import React from 'react';
import Average from './Average';
const App = () => {
return <Average />;
};
export default App;
src/App.js
이제 브라우저에서 숫자를 입력하고 등록 버튼을 통해 등록해서 평균값이 잘 나타나는 지 확인해보면, 숫자를 등록할 때 뿐만 아니라 input 내용이 수정될 떄도 getAverage 함수가 호출 되는 것을 확인할 수 있다. input 내용이 바뀔 때는 평균값을 다시 계산할 필요가 없기 때문에 렌더링 할 때마다 계산하는 것은 낭비이다.
useMemo를 사용하면 이러한 작업을 최적화할 수 있다. 렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고, 원하는 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용하는 방식이다. useMemo를 사용하도록 Average 컴포넌트 코드를 아래와 같이 수정한다.
import React, { useState, useMemo } from 'react';
const getAverage = numbers => {
console.log('평균값 계산 중..');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = e => {
setNumber(e.target.value);
};
const onInsert = () => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
};
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값:</b> {avg}
</div>
</div>
);
};
export default Average;
이제 렌더링 해보면 list 배열의 내용이 바뀔 때만 getAverage 함수가 호출된다.
useCallback은 useMemo랑 상당히 비슷하지만 값이 아닌 함수를 반환한다. 주로 렌더링 성능을 최적화해야하는 상황에서 사용한다. 이 Hook을 사용하면 이벤트 헨들러 함수를 필요할 때만 생성할 수 있다.
위에서 구현한 Average 컴포넌트를 보면 onChange와 onInsert 라는 함수를 선언해줬는데, 이렇게 선언하면 컴포넌트가 리렌더링 될 때마다 이 함수들이 새로 생성된다. 물론 대부분의 경우 문제되진 않지만 컴포넌트의 렌더링이 자주 발생하거나 렌더링해야 할 컴포넌트의 개수가 많아지면 이 부분을 최적화 해주는 것이 좋다.
useCallback의 사용법으로 첫 번째 파라미터에는 생성하고 싶은 함수를 넣고, 두 번째 파라미터에는 어떤 값이 바뀌었을 떄 함수를 새로 생성해야 하는지 명시하는 배열을 작성한다.
import React, { useState, useMemo, useCallback } from 'react';
const getAverage = numbers => {
console.log('평균값 계산 중..');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []); // 컴포넌트가 처음 렌더링될 때만 함수 생성
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
}, [number, list]); // number 혹은 list가 바뀌었을 때만 함수 생성
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값:</b> {avg}
</div>
</div>
);
};
export default Average;
src/component/Average.js
onChange에서는 비어있는 배열을 넣었는데, 이 경우는 컴포넌트가 렌더링 될 때 한 번만 함수가 생성된다. 반면에 onInsert처럼 배열 안에 number와 list 를 넣게 되면 인풋 내용이 바뀌거나 새로운 항목이 추가될 때마다 함수가 생성된다.
cf. 아래의 두 코드는 완전히 같은 코드이다.
useCallback(() => {
console.log('hello world!');
}, [])
useMemo(() => {
const fn = () => {
console.log('hello world!');
};
return fn;
}, [])
useCallback은 결국 useMemo로 함수를 반환하는 상황에서 더 편하게 사용할 수 있는 Hook이다.
숫자, 문자열, 객체 처럼 일반 값을 사용 → useMemo를 사용하고,
함수를 재사용 → useCallback을 사용하면 된다.
useRef는 reference를 사용하기 위한 Hook으로 리액트에서 Reference란 특정 컴포넌트에 접근할 수 있는 객체를 의미한다. 따라서 useRef는 이 reference를 반환하는 함수이다. Reference 객체에는 current라는 속성이 있는데 이것은 현재 레퍼런스 하고 있는 요소를 의미한다.
▷ useRef 사용법
const = refContainer = useRef(초깃값);
파라미터로 초깃값을 넣으면 해당 초깃값으로 초기화 된 레퍼런스 객체를 반환한다. 이렇게 반환된 레퍼런스 객체는 컴포넌트 라이프 타임 전체에 걸쳐서 유지된다. (즉, 컴포넌트가 마운트 해제 전까지는 계속 유지된다는 뜻.)
리액트의 Ref를 이용하면 돔(DOM) 요소들을 직접 조작할 수 있다. Average 컴포넌트에서 등록 버튼을 눌렀을 때 포커스가 인풋 쪽으로 넘어가도록 코드를 아래와 같이 작성한다.
import React, { useState, useMemo, useCallback, useRef } from 'react';
const getAverage = numbers => {
console.log('평균값 계산 중..');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const inputEl = useRef(null);
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []); // 컴포넌트가 처음 렌더링될 때만 함수 생성
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
inputEl.current.focus();
}, [number, list]); // number 혹은 list가 바뀌었을 때만 함수 생성
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} ref={inputEl} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값:</b> {avg}
</div>
</div>
);
};
export default Average;
src/component/Average.js
useRef를 사용하여 ref를 설정하면 useRef를 통해 만든 객체 안의 current 값이 실제 요소를 가리킨다.
추가로 컴포넌트 로컬 변수를 사용해야 할 때도 useRef를 활용할 수 있다. 여기서 로컬 변수란 렌더링과 상관없이 바뀔 수 있는 값을 의미한다. 아래는 로컬 변수를 사용하는 예시 코드이다.
import React, { useRef } from 'react';
const RefSample = () => {
const id = useRef(1);
const setId = (n) => {
id.current = n;
}
const printId = () => {
console.log(id.current);
}
return (
<div>
refsample
</div>
);
};
export default RefSample;
이렇게 ref 안의 값이 바뀌어도 컴포넌트가 렌더링되지 않는다는 점에는 주의해야 한다. 렌더링과 관련되지 않은 값을 관리할 때만 이러한 방식으로 코드를 작성해야 한다.
useRef() Hook은 내부의 데이터가 변경되었을 떄 별도로 알리지 않는다. 그래서 current 속성을 변경한다고 해서 리렌더링되진 않는다. 따라서 ref에 DOM 노드가 연결되거나 분리되었을 경우, 어떤 코드를 실행하고 싶다면 "Callback ref" 를 사용해야 한다.
Callback ref 는 DOM 노드의 변화를 알기 위한 가장 기초적인 방법으로 리액트는 ref가 다른 노드에 연결될 때마다 callback을 호출하게 된다. Callback ref 방식을 사용하게 되면 자식 컴포넌트가 변경되었을 때 알림을 받을 수 있고 이를 통해 다른 정보들을 업데이트할 수 있다.
▷ Callback-ref 예제 코드
function MeasureExample(props) {
const [height, setHeight ] = useState(0);
const measureRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<>
<h1> ref={measuredRef}>안녕 리액트</h1>
<h2>위 헤더의 높이는 {Math.round(height)}px 입니다.</h2>
</>
);
}
이 예제 코드의 경우, <h1> 태그의 높이값을 매번 업데이트하고 있고, useCallback Hook의 의존성 배열에 빈 배열을 넣어 <h1> 태그가 마운트 또는 언마운트 될 때만 콜백함수가 호출된다. (리렌더 될 때는 호출되지 않는다.)
여러 컴포넌트에서 비슷한 기능을 할 경우, 나만의 Hook으로 작성하여 로직을 재사용할 수 있다. 위에서 Info 컴포넌트에서 여러 개의 input을 관리하기 위해 useReducer로 작성했던 로직을 useInputs 라는 Hook으로 따로 분리해보는 실습을 하기 위해 component 폴더 아래에 useInputs.js 파일을 만들고 아래의 코드를 작성한다.
import { useReducer } from 'react';
function reducer(state, action) {
return {
...state,
[action.name]: action.value
};
}
export default function useInputs(initialForm) {
const [state, dispatch] = useReducer(reducer, initialForm);
const onChange = e => {
dispatch(e.target);
};
return [state, onChange];
}
src/useInputs.js
작성한 Hook을 Info 컴포넌트에서 사용해보기 위해 Info 컴포넌트를 수정하면 커스텀한 Hook을 사용해볼 수 있다.
import React from 'react';
import useInputs from '../useInputs';
const Info = () => {
const [state, onChange] = useInputs({
name: '',
nickname: ''
});
const { name, nickname } = state;
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
<div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임: </b>
{nickname}
</div>
</div>
</div>
);
};
export default Info;
src/component/Info.js
커스텀 Hooks를 만들어서 사용해 본 것 처럼 다른 개발자가 만든 Hooks도 라이브러리로 설치하여 사용할 수 있다.
다른 개발자가 만든 다양한 Hooks 리스트는 아래의 링크를 통해 확인해 볼 수있다.
https://nikgraf.github.io/react-hooks/
https://github.com/rehooks/awesome-react-hooks
useMemo(() => {
const fn = () => {
console.log('hello world!');
};
return fn;
}, [])
2. useCallback을 사용해 버튼 클릭 시 리스트에 값을 추가하는 컴포넌트를 구현하는 코드의 빈칸을 채워라.
import React, { useState, useMemo, useCallback } from 'react';
const getAverage = numbers => {
console.log('평균값 계산 중..');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = useCallback(e => {
setNumber(e.target.value);
}, 【______】);
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
}, 【______】);
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값:</b> {avg}
</div>
</div>
);
};
export default Average;
빈칸과 O/X 답
: 빈 배열([]) / 최적화 / 액션(action) / 컴포넌트 / 로컬변수 / X(한 번에 여러 상태를 관리하고 싶으면 useState를 여러개 사용해야 한다.) / X (useMemo는 숫자나 문자열과 같은 값을 반환하고 함수를 재사용하기 위한 훅은 useCallback이다.)
코드 작성 1번 :
useCallback(() => {
console.log('hello world!');
}, [])
코드 작성 2번 :
[], [number, list]
출처 : 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023), p184-220, https://reactjs.winterlood.com
출처: 김민준, 『리액트를 다루는 기술』, 길(2023), p190-215, https://thebook.io/080203/0242/
이인제(소플), [처음 만난 리액트(React)], (2022, 5월 19일), 섹션 8. Hooks, inflearn, https://inf.run/yPc4
Corner React.js 2
Editor: ahyohyo
[React.js 2팀] project 2 [할 일 관리] 앱 만들기 2 (Read: 할 일 리스트 렌더링하기 ~ Delete: 할 일 삭제하기) (1) | 2025.01.03 |
---|---|
[React.js 2팀] project 2 [할 일 관리] 앱 만들기 1 (프로젝트 준비하기 ~ Create: 할 일 추가하기) (1) | 2024.12.27 |
[React.js 2팀] project 1 [카운터] 앱 만들기 ~ 6장. 라이프 사이클과 리액트 개발자 도구 (0) | 2024.11.22 |
[React.js 2팀] 5장. 리액트의 기본 기능 다루기 (2) (1) | 2024.11.15 |
[React.js 2팀] 5장. 리액트의 기본 기능 다루기 (1) (0) | 2024.11.08 |