리덕스 스토어와 연동된 컨테이너 컴포넌트를 만들 때 connect 함수를 사용하는 대신 react-redux에서 제공하는 Hooks를 사용할 수 있습니다.
useSelector Hook
을 사용하면 connect 함수를 사용하지 않고도 리덕스의 상태를 조회할 수 있습니다.const 결과 = useSelector(상태 선택 함수)
import React from 'react';
import Counter from '../componenets/Counter';
import { decrease, increase } from '../modules/counter';
import { connect, useDispatch, useSelector } from 'react-redux';
//디스패치
const CounterContainer = ()=>{
const number = useSelector(state=>state.counter.number);
return(
<Counter number={number}/>
)
}
useDispatch는 컴포넌트 내부에서 스토어의 내장 함수 dispatch를 사용할 수 있게 해줍니다.
import React from 'react';
import Counter from '../componenets/Counter';
import { decrease, increase } from '../modules/counter';
import {useDispatch, useSelector } from 'react-redux';
//디스패치
const CounterContainer = ()=>{
const number = useSelector(state=>state.counter.number);
const dispatch =useDispatch()
return(
<Counter number={number}
onIncrease={()=>dispatch(increase())}
onDecrease={()=>dispatch(decrease())}/>
)
}
export default CounterContainer;
- 위의 코드는 숫자가 바뀌어서 컴포넌트가 리렌더링될 때마다 onIncrease 함수와 onDecrease 함수가 새롭게 만들어지고 있습니다.
- 컴포넌트 성능을 최적화해야 하는 상황이 온다면 useCallback으로 액션을 디스패치하는 함수를 감싸 주는 것이 좋습니다.
import React, { useCallback } from 'react';
import Counter from '../componenets/Counter';
import { decrease, increase } from '../modules/counter';
import { connect, useDispatch, useSelector } from 'react-redux';
//디스패치
const CounterContainer = ()=>{
const number = useSelector(state=>state.counter.number);
const dispatch =useDispatch()
const onIncrease = useCallback(()=>dispatch(increase()),[dispatch])
const onDecrease = useCallback(()=>dispatch(decrease()),[dispatch])
return(
<Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease}/>
)
}
export default CounterContainer;
- useStore Hooks를 사용하면 컴포넌트 내부에서 리덕스 스토어 객체를 직접 사용할 수 있습니다.
- useStore Hooks를 사용해야 하는 상황은 흔치 않습니다.
- 여러개의 액션을 사용해야 하는 경우
useActions 유틸 Hook
을 사용해 코드를 훨씬 깔끔하게 정리하여 작성 할 수 있습니다.
// lib/useActions.js
import { bindActionCreators } from "redux";
import { useDispatch } from "react-redux";
import { useMemo } from "react";
export default function useActions(actions,deps){
const dispatch = useDispatch();
return useMemo(()=>{
if(Array.isArray(actions)){
return actions.map(a=> bindActionCreators(a,dispatch));
}
return bindActionCreators(actions,dispatch);
},deps?[dispatch,...deps] :deps)
}
- 위의 코드는 useActions Hook은 액션 생성 함수를 액션을 디스패치하는 함수로 변환해줍니다.
- 액션 생성 함수를 사용하여 액션 객체를 만들고, 이를 스토어에 디스패치하는 작업을 해주는 함수를 자동으로 만들어줍니다.
- useActions는 두가지 파라미터가 필요합니다. 첫번째 파라미터는 액션 생성함수로 이루어진 배열입니다.
- 두번째 파라미터는 deps 배열이며, 이 배열 안에 들어 있는 원소가 바뀌면 액션을 디스패치하는 함수를 새로 만들게 됩니다.
//containers/TodoContainer.js
import Todos from '../componenets/Todos';
import { connect, useDispatch, useSelector } from 'react-redux';
import { changeInput,insert,remove,toggle } from '../modules/todos';
import { useCallback } from 'react';
import useActions from '../lib/useActions';
const TodosContainer = ()=>{
const {input,todos} = useSelector(({todos})=>({
input: todos.input,
todos: todos.todos
}))
// const dispatch = useDispatch();
// const onChangeInput = useCallback((input)=>dispatch(changeInput(input)),[dispatch])
// const onInsert = useCallback((text)=>dispatch(insert(text)),[dispatch])
// const onToggle = useCallback((id)=>dispatch(toggle(id)),[dispatch])
// const onRemove = useCallback((id)=>dispatch(remove(id)),[dispatch])
const [onChangeInput, onInsert, onToggle, onRemove] = useActions([changeInput,insert,toggle,remove],[])
return(
<Todos
input ={input}
todos = {todos}
onChangeInput ={onChangeInput}
onInsert = {onInsert}
onToggle ={onToggle}
onRemove ={onRemove}/>
)
}
export default TodosContainer;
- connect 함수를 사용하여 컨테이너 컴포넌트를 만들었을 경우, 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리렌더링될 때 해당 컨테이너 컴포넌트의 props가 바뀌지 않았다면 리렌더링이 자동으로 방지되어 성능이 최적화됩니다.
- 반면, useSelector를 사용하여 리덕스 상태를 조회했을때는 최적화 작업이 자동으로 이루어지지 않으므로, 성능 최적화를 위해 React.memo를 컨테이너 컴포넌트에 사용해주어야 합니다.
import Todos from '../componenets/Todos';
import { useSelector } from 'react-redux';
import { changeInput,insert,remove,toggle } from '../modules/todos';
import React from 'react';
import useActions from '../lib/useActions';
const TodosContainer = ()=>{
(...)
}
export default React.memo(TodosContainer);