[React] React 완벽 가이드 섹션 10 : Reducer을 사용하여 부작용처리& Context API 사용하기
https://www.udemy.com/course/best-react/
1. Side Effect란?
- 애플리케이션에서 일어나는 모든 것
- 리액트에서는 state값이 변하는 등의 작업이 일어날 때 리렌더링 됨
- 만약 사이드 이펙트가 계속 리액트에 관여하게 된다면 무한 루프에 빠질 가능성이 크다.
- 이것을 해결하기 위한 방법으로 useEffect 가 있다.
1) useEffect : 컴포넌트가 렌더링 될 때마다 특정 작업을 실행할 수 있도록 하는 hook
useEffect(()=>{ ...}, [ dependecies]);
- 첫번째 인자는 함수로, 모든 컴포넌트 평가 후에 실행되어야 하는 함수
- 두번째 인자 배열이 변경될 때마다 함수가 실행된다.
ⓐ 두번째 인자가 빈 배열일 때
useEffect(()=>{
// 컴포넌트가 처음 렌더링 될 때 한번만 실행하고 싶을 때
}, [ ]);
ⓑ 두번째 인자가 생략 될 때
useEffect(()=>{
// 리렌더링 될 때마다 실행됨
});
ⓒ 두번째 인자의 배열 안 요소가 여러개일 때
useEffect(()=>{
// a 또는 b 또는 c 가 변경될 때 실행된다
}, [ a, b, c ]);
※ 주의 사항
종속성에 추가할 수 있는건 state, props 등 이 있음
state에서 setData 같이 상태 업데이트가 되는 함수는 추가할 필요가 없다. (왜냐하면 절대 변경되지 않도록 보장함)
또한 fetch나 localStroage도 react 구성 요소 렌더링 주기와 관련이 없기 때문에 변경되지 않음
그리고 변수나 함수는 추가할 필요가 없다 ! !
👉 컴포넌트 함수에 정의된 변수나 상태, 컴포넌트 함수에 정의된 props 또는 함수가 종속성에 들어갈 수 있다.
*웹브라우저에 데이터 저장 : 쿠키나 로컬스토리지
✅ Cleanup 함수
: 새로운 사이드이펙트 함수가 실행되기 전에, 혹은 컴포넌트가 제거되기 전에 실행된다.
* 첫번째 사이드 이펙트 함수가 실행되기 전에는 실행 안됨
useEffect(()=>{
const identifier = setTimeout(()=>{
console.log("유효성 검사중~");
setFormIsValid(
enteredEmail.includes("@") && enteredPassword.trim().length > 6
);
}, 500);
// 아래 리턴문이 클린업 함수
return () => {
console.log("입력중~~");
clearTimeout(identifier);
};
}, [enteredEmail, enteredPassword]);
2. useReducer
- 복잡한 state관리를 도와주는 hook
- 하나의 state가 다른 state 들 값에 의존하고 있다면 버그 가능성 있음
- 컴포넌트 함수 밖에서 선언이 가능하다
const [state, dispatchFn] = useReducer(reducerFn, initialState, initFn);
- state 는 우리가 앞으로 컴포넌트에서 사용 할 수 있는 상태
- dispatch 는 액션을 발생시키는 함수
- useReducer 에 넣는 첫번째 파라미터는 reducer 함수이고, 두번째 파라미터는 초기 상태
ex) 로그인 시 이메일 폼의 유효성을 체크하는 코드
import React, { useState, useEffect, useReducer } from "react";
const emailReducer = (state, action) => {
if(action.type === 'USER_INPUT'){
return { value: action.val, isVaild: action.val.includes('@')};
}
if(action.type === 'INPUT_BLUR'){
return { value: state.value, isVaild: state.value.includes('@') };
}
return { value: '', isVaild: false};
}
const Login = (props) => {
const [formIsValid, setFormIsValid] = useState(false);
const [emailState, dispatchEmail] = useReducer(emailReducer, {
value: "",
isVaild: null,
});
const {isVaild :emailIsValid} = emailState;
useEffect(()=>{
const identifier = setTimeout(()=>{
console.log("유효성 검사중~");
setFormIsValid(
emailIsValid
);
}, 500);
return () => {
console.log("입력중~~");
clearTimeout(identifier);
};
}, [emailIsValid]);
const emailChangeHandler = (event) => {
dispatchEmail({type: 'USER_INPUT', val: event.target.value});
setFormIsValid(event.target.value.includes('@') && passwordState.isValid);
};
const validateEmailHandler = () => {
dispatchEmail({type: 'INPUT_BLUR'});
};
const submitHandler = (event) => {
event.preventDefault();
props.onLogin(emailState.value);
};
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<div
className={`${classes.control} ${
emailState.isVaild === false ? classes.invalid : ''
}`}
>
<label htmlFor="email">E-Mail</label>
<input
type="email"
id="email"
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
</div>
<div className={classes.actions}>
<Button type="submit" className={classes.btn} disabled={!formIsValid}>
Login
</Button>
</div>
</form>
</Card>
);
};
export default Login;
✅ useState와 useReducer
- useState는 주요 state 관리도구임 , 개별 state나 독립적인 간단한 state에 적합함, state 업데이트가 쉽고 몇종류 안될 때
- useReducer는 복잡한 로직을 컴포넌트 함수밖으로 이동시킬수있음 , 연관된 state관련 데이터
3. 리액트 컨텍스트api
: state를 전역으로 관리할 수 있게 도와줌(프롭체인을 구현하지 않아도됨)
- 자주 변하는 환경에서는 적합하지 않다 => redux를 사용
- 모든 곳에 context api를 쓰는건 좋지 않다. props를 이용하자 !
> 사용방법
ⓐ js파일을 생성하여 정의할 state를 만든다.
import React from 'react';
const AuthContext = React.createContext({
isLoggedIn: false,
onLogout: () => {}
});
export default AuthContext;
ⓑ 위 state를 전역으로 사용하기 위해서는 App.js에서 provider로 덮어줘야한다.
import React, { useState } from 'react';
import AuthContext from './store/auth-context';
function App() {
// state관련 함수정의
return (
<AuthContext.Provider value={{
isLoggedIn: ...,
onLogout : ...
}}>
<main>
//main 내용
</main>
</AuthContext.Provider>
);
}
export default App;
ⓒ 개별 컴포넌트에서 사용
import React , {useContext} from 'react';
import AuthContext from '../../store/auth-context';
const Navigation = (props) => {
const ctx = useContext(AuthContext); // 이렇게 정의하고 사용해주면된다.
return (
<ul>
{ctx.isLoggedIn && (
<li>
<a href="/">Users</a>
</li>
)}
{ctx.isLoggedIn && (
<li>
<a href="/">Admin</a>
</li>
)}
{ctx.isLoggedIn && (
<li>
<button onClick={ctx.onLogout}>Logout</button>
</li>
)}
</ul>
);
};
export default Navigation;
+ 로그인같은 state를 관리하는 걸 한 컴포넌트에 몰아서 리팩토링 가능하다.
그렇게 하면 다른 컴포넌트에서 login과 관련된 함수를 정의할 필요 없고, props로 전달해야 되는 부분이 간략화 된다.
4. 리액트 훅의 규칙
1) 리액트 훅은 리액트 함수에서만 호출해야 한다
2) 최상위 레벨에서 리액트 훅을 호출해야한다.(중첩함수/block문에서 호출하지마)
+ useEffect를 사용할 때, 참조하는 모든 항목들을 의존성으로 useEffect내부에 추가해야한다. 그러지 않을 이유가 없는거 아니고선
5. useImperativeHandle, forwardRef
- 일반적으로는 사용안하는데 focus나 스크롤링 같은데서 유용하게 쓰일 수 있다.
(일단은 넘어가자..)