일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Java
- Interface
- 자바스크립트 이벤트처리
- 자바 재귀 조합
- str to char array
- 서로소
- jquery 이벤트 처리
- 알고리즘
- 후위표기
- 재귀
- jquery 필터선택자
- 자바 조합 재귀
- parseInt()
- 자바
- jquery dom 계층 선택자
- 상속
- java Collections.sort()
- 자바 순열 코드
- 조합 재귀
- 알고리즘 그래프
- 자바스크립트 이벤트중지
- inner class
- char to str
- java lambda
- 자바입출력
- java 내부 클래스
- jquery 속성선택자
- 순열코드
- 순열 재귀
- 재귀함수
- Today
- Total
유블로그
[React] react-redux 에서 Hooks 사용하기 본문
www.youtube.com/playlist?list=PL9FpF_z-xR_F-nxvw-VDld5wcCzYmSnrW
위 강의와
리액트를 다루는 기술 - 김민준
책 참고하였음
github.com/velopert/learning-react
위의 링크도 참고하였음
** ducks 패턴 적용 **
- components
- containers
- modules
components 에는 뷰
containers 에는 데이터 연결
modules 에는 리듀서, store 등 로직 저장
리듀서 작성법
/modules/index.js
import { combineReducers } from 'redux';
import counter from './counter';
const rootReducer = combineReducers({
counter
});
export default rootReducer;
/modules/counter.js
const INCREMENT = "counter/INCREMENT";
const DECREMENT = "counter/DECREMENT";
export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });
const initialState = 0;
const counter = (state = initialState, action) => {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
export default counter;
Hooks
- useState : 함수형 컨포넌트에서 상태를 관리하기 위한 Hook.
const Counter = () => {
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;
value를 직접 변경하면 안 됨! setValue등 setter를 통하여 변경해야한다.
- useSelector : store에 있는 값을 들고올 수 있다.
/containers/Counter.js
import React from "react";
import { useSelector } from "react-redux";
import Counter from "../components/Counter";
const CounterContainer = () => {
// dependencies 배열에 있는 값이 바뀔때 useSelector 다시 실행
// 아무것도 없으면 한 번만 실행
const counter = useSelector((state) => state.counter, []);
// 여기에 useActions 로 리듀서 연결 하는데... 현재 useActions 없으므로 생략
return <Counter />;
};
export default CounterContainer;
/components/Counter.js
import React from "react";
const Counter = ({ onIncrease, onDecrease, number }) => {
return (
<div>
<h1>{number}</h1>
<div>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
</div>
);
};
export default Counter;
- useEffect : 리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook
useEffect(() => {
console.log('마운트 될 때만 실행');
}, []);
useEffect(() => {
console.log('name 값이 바뀔 때만 실행');
}, [name]);
useEffect(() => {
return () => {
console.log('언마운트되기 전 그리고 업데이트되기 직전에 실행')
};
});
useEffect(() => {
return () => {
console.log('언마운트될 때만 실행')
};
}, [name]);
본격적으로 todo 구현을 통해 hooks 를 알아보자~~!
/modules/index.js
루트리듀서에 todo 리듀서 추가
import { combineReducers } from 'redux';
import counter from './counter';
import todos from './todos';
const rootReducer = combineReducers({
counter,
todos,
});
export default rootReducer;
/modules/todos.js
todo 관련 action들 등록하여 리듀서 만들기
const CHANGE_INPUT = "todos/CHANGE_INPUT";
const INSERT = "todos/INSERT";
const TOGGLE_CHECK = "todos/TOGGLE_CHECK";
const REMOVE = "todos/REMOVE";
let id = 0;
export const changeInput = (input) => ({ type: CHANGE_INPUT, payload: input });
export const insert = (text) => ({
type: INSERT,
payload: {
id: ++id,
text,
},
});
export const toggleCheck = (id) => ({ type: TOGGLE_CHECK, payload: id });
export const remove = (id) => ({ type: REMOVE, payload: id });
const initialState = {
input: "",
todos: [],
};
const todos = (state = initialState, action) => {
switch (action.type) {
case CHANGE_INPUT:
return {
...state,
input: action.payload,
};
case INSERT:
return {
...state,
todos: state.todos.concat({
...action.payload,
done: false,
}),
};
case TOGGLE_CHECK:
return {
...state,
todos: state.todos.map((todo) =>
todo.id === action.payload ? { ...todo, done: !todo.done } : todo
),
};
case REMOVE:
return {
...state,
todos: state.todos.filter((todo) => todo.id !== action.payload),
};
default:
return state;
}
};
export default todos;
/components/TodoList.js
React.memo 로 재렌더링 방지하여 최적화하기
import React from "react";
const TodoItem = React.memo(({ todo, onRemove, onToggle }) => {
const { id, text, done } = todo;
return (
<li
style={{
textDecoration: done ? "line-through" : "none",
}}
>
<span onClick={() => onToggle(id)}>{text}</span>{" "}
<button onClick={() => onRemove(id)}>삭제</button>
</li>
);
});
// React.memo 로 감싸주면 컴포넌트가 받은 props(여기선 todos, onRemove, onToggle)가
// 바뀌었을 때만 새로 렌더링한다.
// 즉, 아래의 input에서 value가 바뀌어 onChange 함수가 작동해도
// TodoItems가 불필요하게 재렌더링 되는 현상을 막아 최적화할 수 있다!!
const TodoItems = React.memo(({ todos, onRemove, onToggle }) => {
return todos.map((todo) => (
<TodoItem
todo={todo}
key={todo.id}
onRemove={onRemove}
onToggle={onToggle}
/>
));
});
const TodoList = ({ todos, input, onRemove, onToggle, onChange, onSubmit }) => {
return (
<div>
<form onSubmit={onSubmit}>
<input onChange={onChange} value={input} />
<button type="submit">추가</button>
</form>
<ul>
<TodoItems todos={todos} onRemove={onRemove} onToggle={onToggle} />
</ul>
</div>
);
};
export default TodoList;
/containers/TodoList.js
useCallback, useDispatch 사용
- useReducer : useState보다 다양한 상태로 변경할 수 있는 Hook
리듀서는 현재상태와 액션을 전달받아 새로운 상태를 반환하는 함수다.
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;
아래처럼 리듀서 사용가능하다. state가 너무 많아질경우 유용한 방법이라고 한다.
사실 아직 와닿진 않는다.
좀 더 봐야할 듯!!
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;
- useMemo : 함수형 컴포넌트 내부 연산을 최적화할 수 있는 Hook
위의 React.memo 대신 Hook 으로 사용할 수 있는 것 같다.
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;
useMemo 끝에 있는 의존성 배열 [] 내에 적은 값이 업데이트 될 때만 useMemo내 첫번째 파라미터 실행
- useCallback : 렌더링 성능 최적화할 수 있는 Hook
useMemo와 비슷함. 이벤트 핸들러 함수를 필요할 때만 생성할 수 있다.
useCallback(() => {
console.log('callback');
}, []);
useMemo(() => {
const fn = () => {
console.log('callback');
};
return fn;
}, []);
위의 두코드는 완전히 똑같은 코드이다.
useCallback은 useMemo로 함수를 반환하는 상황에서 더 편하게 쓸 수 있는 것이므로,
숫자, 문자열, 객체처럼 일반 값을 재사용하려면 useMemo를 사용하고
함수를 재사용하려면 useCallback을 사용하면 된다!
- useRef : 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있는 Hook
import React, { useState, useMemo, useRef, 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 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;
input에 ref 를 state값으로 주고, onInsert함수에서 state로 엘리먼트에 접근할 수 있다.
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 쓰면 된다.