Hooks

2019-06-04

기존 함수형 컴포넌트는 상태관리를 할 수 없었다.
그러나 react 16.8 부터 hooks는 상태 관리가 가능한 useState, 렌더링 직후 작업 설정하는 useEffect 등 여러 기능들이
제공하고 있다.

상태관리가 가능한 useState

const [상태값, 상태를 설정하는 함수] = useState(기본값)
useState에 기본값을 설정하면 [상태값, 상태를 설정하는 함수]를 리턴한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//16.8 버전부터 useState 사용가능
import React, { useState } from "react";

// 함수형 컴포넌트

const Counter = () => {
//useState에 0을 넣는 것 ==> 기본값을 0 으로 설정
//useState() 호출후 반환하는 배열 ==> [상태값, 상태를 설정하는 함수]
const [value, setValue] = useState(0);
return (
<div>
<p>
현재 카운터 값은 <b>{value}</b> 입니다.
</p>
<button onClick={() => setValue(value + 1)}>+1</button>
<button onClick={() => setValue(value - 1)}>-1</button>
</div>
);
};

export default Counter;

렌더링 될때마다 특정 작업을 수행하도록 설정할 수 있는 useEffect

생명주기에 따라 특정 작업을 수행할 수 있다.

1
2
3
4
5
6
//// *** componentDidMount
useEffect(() => {}, [])
///// *** componentDidUpdate
useEffect(() => {}, [배열안에 검사하고 싶은 값 (현재 상태관리되고 있는 값 / props로 전달받은 값)])
///// *** componentUnMount, componentWillUpdate
useEffect(() => {}, return () => {});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import React, { useState, useEffect } from 'react';

const Info = () => {
const [name, setName] = useState('');
const [nickname, setNicname] = useState('');

useEffect(() => {
console.log("렌더링이 완료되었습니다")
console.log({name, nickname})
return () => {
console.log("cleanup");
console.log(name);
}
}, [name])


const onChangeName = e => {
setName(e.target.value)
}

const onChangeNickName = e => {
setNicname(e.target.value)
}

return(
<div>
<div>
<input value={name} onChange={onChangeName} />
<input value={nickname} onChange={onChangeNickName}/>
</div>
<div>
<b>이름: </b>{name}
<b>닉네임: </b>{nickname}
</div>
</div>
)
}

export default Info;

컴포넌트 업데이트 로직을 외부로 분리가능한 useReducer

바로 위에 Info예제를 보면 onchange이벤트를 각각 잡아서 함수를 생성하고 있다.
useReducer를 사용하면 기존에 redux에서 사용하던 action에 따라 상태값을 바꿀 수 있다.
const [state(현재 가르키고 있는 상태), dispatch(액션을 발생시키는 함수)] = useReducer(리듀서 함수, 기본값)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

import React, { useReducer } from "react";

function reducer(state, action) {
switch(action.type) {
case 'INCREMENT':
return { value: state.value + 1 }
case 'DECREMENT':
return { value: state.value - 1 }
default:
return state;
}
}

const Counter_reducer = () => {
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_reducer;

렌더링 성능 최적화 useMemo

렌더링하는 과정에서 특정값이 바뀌었을 때만 연산을 실행한다.
숫자, 문자열, 객체처럼 일반 값을 재사용할 때 useMemo를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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 onChange = e => {
setNumber(e.target.value);
}); // 컴포넌트가 처음 렌더링 될 때만 함수 생성

const onInsert = e => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
}; // number혹은 list가 바뀌었을 때만 함수 생성



//렌더링 성능 최적확: useMeomo
//렌더링하는 과정에서 특정값이 바뀌었을 때만 연산 실행, (list배열의 내용이 바뀔때마다 getAverage함수 호출)
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;

위와 같이 useMemo를 사용하지 않으면 렌더링 될때마다 getAverage라는 평균값을 계산하는 함수가 실행된다.
useMemo에 두번쩨 파라미터 []list가 들어간것은 list값이 바꼈을 때에만 실행하라는 의미이다.
즉 onInsert함수를 통해서 숫자가 추가될 때 getAverage가 실행된다.

렌더링 성능 최적화 useCallback

기존에는 리렌더링 할 때마다 함수를 새로 생성하지만 useCallback는 이벤트 핸들러 함수를 필요할 때 생성한다.
useCallback(생성하고 싶은 함수, 배열: 어떤 값이 바뀌었을 때 함수를 새로 생성해주어야 하는지 명시)

1
2
3
4
/// **** 컴포넌트 처음 렌더링 될 때
useCallback(() => {}, [])
/// **** number나 list값이 바뀌었을 때
useCallback(() => {}, [number, list])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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);

// useCallback: 렌더링 성능 최적화, 이벤트 핸들러 함수 필요할 때만 생성
// 기존은 리렌더링 할때마다 함수 새로 생성...
// useCallback(생성하고 싶은 함수, 배열: 어떤 값이 바뀌었을 때 함수를 새로 생성해주어야하는지 명시)
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성

const onInsert = useCallback(e => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
// useRef로 만든 객체 안의 current값이 실제 엘리먼트
inputEl.current.focus();
}, [number, list]); // number혹은 list가 바뀌었을 때만 함수 생성


//렌더링 성능 최적확: useMeomo
//렌더링하는 과정에서 특정값이 바뀌었을 때만 연산 실행, (list배열의 내용이 바뀔때마다 getAverage함수 호출)
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;

위에 예제에 추가로 useRef를 사용했다.
useRef는 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있게 해준다.

참고: https://github.com/juuuuuuuuuuuuuu/react-test