UI와 직접적인 관련이 없는 것은 side effect라고 부른다. side effect들은 http request 보내기, 브라우저에 데이터 저장하기 등 화면에 UI를 rendering하는 기능 외의 것들을 담당한다. 즉 백엔드와의 통신에서 중요하게 사용되는 기능이다. 이러한 effect는 component 함수 내부에서 동작하면 안된다. component함수는 리액트에 의해 자동으로 실행되므로, 내부에 effect를 둘 경우 계속해서 re-rendering되거나, state가 변할 때 마다 http request를 보내는 등의 문제가 발생한다.
실제 웹에서 로그인을 하면 백엔드에 request 보내서 로그인 데이터를 가지고온다. 예를 들면 이 유저가 인증되었다는 토큰과 같은 정보를 가지고온다. 하지만 인증부분은 나중 section에서 다룰 것이기 때문에 여기서 useEffect()를 정리할 때는 백엔드 서버를 생각하지 않고 포스팅을 해보려한다.
지금 하려고 하는 것은 로그인 후 새로고침을 하여도 계속 로그인 되어있도록 하는 것이다. 브라우저를 새로고침하면 리액트의 코드들이 모두 재실행된다. 이때 단순하게 state로 관리하던 로그인 인증 정보 또한 새로고침 되어 로그인이 유지되지 않게 된다.
function App() {
// 로그인 되었는지 설정하는 state
const [isLoggedIn, setIsLoggedIn] = useState(false);
// 사용자가 로그인한 정보를 get해옴
const storedUserLoggedInInformation = localStorage.getItem('isLoggedIn')
if (storedUserLoggedInInformation === '1'){
setIsLoggedIn(true);
}
// 로그인 버튼 누르면 실행되는 함수
// state관련 함수
const loginHandler = (email, password) => {
// 로그인버튼 누르면 local storage에 유저 로그인됨을 저장 key=1
localStorage.setItem('isLoggedIn', '1');
setIsLoggedIn(true);
};
const logoutHandler = () => {
setIsLoggedIn(false);
};
return (
<React.Fragment>
<MainHeader isAuthenticated={isLoggedIn} onLogout={logoutHandler} />
<main>
{!isLoggedIn && <Login onLogin={loginHandler} />}
{isLoggedIn && <Home onLogout={logoutHandler} />}
</main>
</React.Fragment>
);
}
위에서 말했듯이 state로 관리하면 무한 루프를 발생시킬 수 있다. 로그인 정보가 저장되어 있다면 로그인되었다는 state를 true로 바꾼다. 이후에 state를 업데이트하는 함수가 호출되면, state가 업데이트됐으므로 컴포넌트를 재실행하고 이 과정에서 localStorage의 value값은 계속 1로 설정되는 무한루프가 발생할 수 있다.
사용자의 로그인 관련 정보와 같은 것을 브라우저에 저장하려 할 때 가장 많이 쓰이는 것이 쿠키와 로컬저장소이다. 로컬저장소가 사용하기 쉽기 때문에 로컬저장소에 대해 알아보자. 이때
localStorage
는 브라우저에서 제공하는 객체이다.// localStorage에 저장하기 // isLoggedIn: 1 과 같이 key:value의 값으로 저장된다 // 1이면 로그인 O / 0이면 로그인 X localStorage.setItem('isLoggedIn', '1'); // 새로고침시 localStorage에 값 있는지 확인하고, key값에 해당하는 value값 가져오기 const storedUserLoggedInInformation = localStorage.getItem('isLoggedIn') // localStorage의 값 지우기 localStorage.removeItem('isLoggedIn');
이러한 문제를 해결하기 위해 리액트에서 제공하는 Hook으로 useEffect()가 있다. Hook이기 때문에 component 내부의 함수에서 실행할 수 있다.
useEffect(function , [dependencies] )
1. 첫 번째 인자 function
이 함수는 두 번째 인자로 받는 dependencies가 변한 경우 component를 재평가한 후에 실행된다. 즉 설정한 dependency 값의 변화에만 동작하는 함수이다. 이 함수에는 어떠한 effect관련 함수를 실행하여도 된다.2. dependencies
특정 dependency를 배열의 형태로 넣는다. 해당 배열에 포함된 값이 변할 경우 첫 번째 인자로 받은 함수를 실행시킨다.
이를 해결하려면 브라우저가 재실행될 때 로그인 인증 관련 데이터가 유지되었는지 바뀌었는지를 확인하는 방법이 있다. 만약 로그인 인증 데이터가 유지되었다면 사용자가 로그인된 상태로 브라우저에 있을 것이다. 이를 위해 useEffect를 사용한다.
useEffect
는 관련된 dependencies가 바뀐다면, 모든 component가 재평가되고 난 후에 실행되는 함수이다.
✅ dependency 없는 경우
만약 dependency에 아무것도 넣지 않는다면
useEffect
는 리액트가 처음 실행될 때 딱 한 번 실행된다. dependency의 값이 변하면 useEffect함수가 재실행되는데, 아무것도 들어있지 않다면 처음 실행 이후 값이 바뀔 수 없으니 딱 한 번만 실행되는 것이다.✅ dependency 있는 경우
모든 component를 재평가하고 난 후, 어떤 값이 변할 시 재실행하고싶은 코드가 있다면, 해당 값을 dependency로 설정하게된다.
로그인 폼에서 유효성 검사를 하는 경우를 생각해보자. 아이디와 비밀번호 부분에 아무것도 입력하지 않는다면, input의 색을 빨간색으로 변경하려고 한다. 이때까지 공부한대로라면 state를 사용해서 input에 입력이 받아질 때마다 state를 변경하고, 해당 state의 input value를 확인해서 유효성 검사를 했을 것이다.
const Login = (props) => {
const [enteredEmail, setEnteredEmail] = useState('');
const [emailIsValid, setEmailIsValid] = useState();
const [enteredPassword, setEnteredPassword] = useState('');
const [passwordIsValid, setPasswordIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
const emailChangeHandler = (event) => {
// input에 입력된 값 받아와서 email state 설정
setEnteredEmail(event.target.value);
// input에 입력된 값 받아와서 유효성 검사
setFormIsValid(
event.target.value.includes('@') && enteredPassword.trim().length > 6
);
};
const passwordChangeHandler = (event) => {
setEnteredPassword(event.target.value);
setFormIsValid(
event.target.value.trim().length > 6 && enteredEmail.includes('@')
);
};
return (생략...)
}
이것을 useEffect()
를 활용한 방식으로 바꿔보자
dependency를 아이디와 비밀번호로 설정해두고, 해당값들이 바뀔 때 마다 useEffect 내부의 익명함수가 trigger된다. useEffect에서 dependency를 설정할 때의 팁은 익명함수의 내부에서 사용하는 state의 값들과 관련된 값들을 dependency에 넣어주면 된다! 이때 state를 업데이트 하는 함수 자체는 컴포넌트가 실행되는 과정에서 변하지 않으므로 생략하고, state값들만 넣어줘도 동작한다!
const Login = (props) => {
const [enteredEmail, setEnteredEmail] = useState('');
const [emailIsValid, setEmailIsValid] = useState();
const [enteredPassword, setEnteredPassword] = useState('');
const [passwordIsValid, setPasswordIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
useEffect(() => {
setFormIsValid(
enteredEmail.includes('@') && enteredPassword.trim().length > 6
);
}, [setFormIsValid, enteredEmail, enteredPassword])
// 이때 setFormIsValid는 생략해도 된다.
이렇게 useEffect를 이용하여 아이디와 비밀번호의 유효성을 검사하면, dependency로 설정된 아이디와 비밀번호의 값이 바뀔 때마다 useEffect함수를 재실행한다.
만약 useEffect 내부의 함수가 백엔드에 request를 보내서 해당 사용자의 아이디가 존재하는지 안하는지 등을 체크해야 할 때, dependency에 input입력에 따라 업데이트되는 state값이 들어가 있는 경우 너무 많은 request를 보내게된다는 문제점이 있다. 따라서 모든 입력에 반응하도록 하지 말고, Input에 입력하는 작동이 일정 초 이상 멈추거나, 일정 길이 이상이 되거나 할 경우에 사용자가 아이디와 비밀번호를 다 입력했다고 가정하고 백엔드에 request를 보내려는 useEffect() 내부의 함수를 작동시키려고 한다.
useEffect(() => {
// 브라우저 내장함수 사용
setTimeout(()=> {
console.log('Check');
setFormIsValid(
enteredEmail.includes('@') && enteredPassword.trim().length > 6
);
}, 500);
}, [enteredEmail, enteredPassword])
이렇게 해 둘 경우, 모든 input에 대해 5초 후에 반응하여 5초 후 콘솔창에 Check가 input에 입력한 문자열 길이만큼 출력됨을 볼 수 있다. 왜냐면 모든 키보드 입력에 반응하도록 설정되어있기 때문이다. 이제 타이머가 딱 한 번만 실행될 수 있도록 변경해야한다.
🚨 cleanUp
useEffect의 첫번째 인자의 함수에서는 또 다른 함수를 return할 수 있다. 이때 여기서 return하는 함수를
cleanUp function
이라고 한다.
useEffect()
의 첫번째 인자로 받은 함수가 처음 실행될 때는 내부의 익명함수(cleanUp function)가 실행되지 않는다. 그 후로는 첫번째 인자로 받은 함수 내부에서 return한 cleanUp 함수가 먼저 실행되고 첫 번째 인자로 받은 sideEffect함수가 실행된다.
useEffect 내부에는 promise가 반환될 수 없기 때문에 cleanUp 함수를 잘 활용하여야 한다
이에따라 return한 cleanUp 함수에 타이머를 지우는 코드를 넣어 하나의 타이머만 동작할 수 있도록 한다.
useEffect(() => {
const identifier = setTimeout(() => {
setFormIsValid(
enteredEmail.includes('@') && enteredPassword.trim().length > 6
);
}, 500);
// 첫 실행때는 실행되지 않음
// 두번째 실행할 때부터 실행됨
return () => {
clearTimeout(identifier);
};
}, [enteredEmail, enteredPassword]);
복잡한 상태관리를 도와주는 hook으로 하나의 state가 다른 여러개의 state에 의존하여 결정된다면 에러가 생기기 쉬우니 이 경우에는 useReducer를 이용하여 관리한다.
예를들어 사용자가 input에 입력한 이메일을 관리하는 state와 해당 이메일이 유효한 이메일인지 확인하는 state는 서로 연관되어 작동하기 때문에 useReducer를 사용하여 관리할 수 있다.
또한, 로그인form이 유효한지 확인하는 state는 입력받은 이메일state와 비밀번호 state가 유효한지에 따라 결정되므로 이 경우에도 useReducer를 사용해서 해결할 수 있다.
리액트 스케쥴링의 특성상 state를 업데이트할 때 기존의 값에 의존하여 값을 바꾸지 않으면 (함수를 사용하여 업데이트하기) 예전의 state에서부터 업데이트를 할 수 있어 에러가 발생할 수 있다. 하지만 이렇게 여러개의 state에 의존해서 업데이트되는 state의 경우에는 함수를 이용하여 state를 업데이트할 수 없다. 왜냐하면 함수를 이용한다면 현재 업데이트 하려는 state의 가장 최신값을 인자로 받아올 수 있지만, 다른 state의 값을 인자로 받아와서 업데이트할 수 없기 때문이다.
const [enteredEmail, setEnteredEmail] = useState('');
const [emailIsValid, setEmailIsValid] = useState();
// enteredEmail state에 따라 변경되는 emailIsValid state
// 따라서 함수를 사용해서 state를 업데이트할 수 없음
setEmailIsValid(enteredEmail.includes('@'));
import { useReducer } from 'react'; const [state, dispatchFn] = useReducer(reducerFn, initialState);
useState와 같이 state값과 함수(dispatchFn)를 반환하기 때문에 배열 destructing을 사용할 수 있다. 하지만 useState와 다른점은 useState는 기존의 state를 업데이트하는 함수였다면 useReducer에서는 useReducer의 첫번째 인자로 넘긴 함수(reducerFn)에 어떠한 동작을 행하는 함수이다.
1️⃣ reducerFn(state, action)
useReducer의 첫번째 인자로 들어가는 함수
(reducerFn)
는 기존의 useState에서 spread문법을 사용해서 가장 최신의 state를 받아오는 것과 같은 동작을 한다. 이 첫번째 인자의 함수는 dispatchFn이 호출될 때 리액트에 의해 자동으로 새로운 state를 반환한다.useReducer의 첫번째 인자인 reducerFn는 익명함수로 작성해도 되지만, 컴포넌트 밖에서 함수를 변수에 지정한 후 호출해도 된다. 이때 컴포넌트 밖에서 함수를 정의할 수 있는 이유는, useReducer안에서는 컴포넌트 함수 내부에서 정의된 어떠한 데이터도 필요로하지 않기 때문이다.
2️⃣ dispatchFn(action)
dispatchFn
에서 정의한 action(보통은 객체를 담고있음)을reducerFn
의 두번째 인자 action에서 받아와서 처리할 수 있다. 객체로 받아오기 때문에 액션에서 정의한 여러개의 state를 한 번에 업데이트할 수 있다.
// reducerFn
// dispatchFn에서 정의된 액션을 가지고와서 사용한다.
const emailReducer = (state, action) => {
if (action.type === 'USER_INPUT') {
return { value: action.val, isValid: action.val.includes('@')}
}
return { value: '', isValid: false };
};
const Login = (props) => {
// 컴포넌트 밖에서 정의된 reducer함수를 useReduce()의 첫번째 인자로
const [emailState, dispatchEmail] = useReducer(emailReducer, { value: '', isValid: false });
const emailChangeHandler = (event) => {
// dispatch함수에서는 액션을 정의한다.
// 아무거나 넣어도 되지만 (문자열, 숫자)
// 보통은 어떤 식별자를 가진 필드를 가진 객체를 넣는다.
// 여기서는 어떤 액션을 하는지 설명하는 type과 사용자가 입력한 값을 받는 value로 이루어진 객체를 넣는다.
dispatchEmail(type: 'USER_INPUT', val: event.target.value );
setFormIsValid(event.target.value.includes('@') && enteredPassword.trim().length > 6);
};
}
이미 비밀번호가 6자리를 넘어서 valid한 상태에서 비밀번호를 더 입력할 경우에는 유효성 검사를 하지 않고싶다. 이와 같은 경우에는 객체분할할당을 통해서 useReducer의 값중 일부를 가져와서 활용할 수 있다. 만약 두개의 서로 다른 useReducer에서 초기에 설정한 객체의 key값이 동일하다면 객체분해할당을할 때 alias를 통해서 불러오면 된다.
const Login = () => {
const [emailState, dispatchEmail] = useReducer(emailReducer, { value: '', isValid: null });
const [passwordState, dispatchPassword] = useReducer(passwordReducer, {value: '', isValid: null});
// 두 useReducer의 값 모두 isValid를 가지고 있기 때문에
// :을 붙여서 alias를 설정한 후 가지고 온다.
const { isValid: emailIsValid} = emailState
const { isValid: passwordIsValid} = passwordState
useEffect(() => {
const identifier = setTimeout(() => {
setFormIsValid(
// 객체분해 할당으로 뽑아온 reducer의 값 활용하기
emailIsValid && passwordIsValid
);
}, 500);
return () => {
clearTimeout(identifier);
};
// 객체분해 할당으로 뽑아온 reducer의 값 활용하기
}, [emailIsValid, passwordIsValid]);
return (...생략)
}
이렇게 아이디와 비밀번호의 유효성 값만 따로 객체분해할당을 통해 가져와서 사용한다면 useEffect
의 dependency로 유효성 값을 넣을 수 있기 때문에 유효성이 변할 때만 useEffect가 불리게 된다. 이전에는 input에 입력한 아이디와 비밀번호의 값이 바뀔 때마다 유효성검사도 하고, useEffect도 실행시켰다면 이제는 input에 값이 눌려도 useEffect가 실행되지 않고 유효성 검사를 통해 유효하게 되거나 유효하지 않게되는 경우 즉 유효성이 업데이트 되는 경우에만 useEffect가 실행된다!
즉
useEffect
의 dependency값에 객체 자체를 넣거나.
을 통해서 객체의 속성을 넣으면 useEffect가 쓸데없이 많이 쓰이기 때문에, 객체분해할당을 통해 가장 세밀하게 연관성이 있는 값을 추출해서 사용하는 것이 좋다!
prop을 여러단계의 컴포넌트를 거쳐서 전달하는 경우
이 경우에 해당 데이터를 필요로하지 않는 component에도 해당 데이터를 전달하게 된다. 이를 대비하기 위해 리액트에서 context-wide하게 사용할 수 있는 즉 component 전체에서 사용할 수 있는 state 저장소가 있다. 이것을 react context
라고 부른다. 이것을 활용하면 필요없는 단계를 거쳐 데이터를 전달하지 않고 해당 데이터를 필요로 하는 component에 직접적으로 데이터를 전달할 수 있게 된다.
src 폴더
와 같은 레벨에store
라는 폴더를 하나 더 만든다. 해당 폴더 안에auth-context.js
파일을 하나 만든다. (폴더명과 파일명은 개발자 마음!) 해당 파일 안에 전체 context에 전달 가능한 state를 저장할 예정이다.
1. context 생성하기 - React.createContext();
말 그대로 context를 create한다. 대부분의 경우 객체 context를 만든다.
React.createContext();
은 omponent를 return하거나 component를 포함하는 객체를 return한다.// store/auth-context.js import React from 'react'; // component를 담고있는 객체값이 AuthContext에 저장된다 const AuthContext = React.createContext({ isLoggedIn: false }); export default AuthContext;
2. context 사용하기
1️⃣ <Context.provider> - 공급
만약AuthContext
라는 context가 모든 component에서 필요하다면 가장 상위인 App component에 있는 모든 component를 해당 context로 감싸야한다. 혹은 그저 일부의 component에서만 해당 context가 필요하다면 일부 component의 최상단을 해당 context로 감싸면된다. 즉 context를 사용하려고 하는 component는 모두 context로 감싸주어야한다!이때 우리가 만든 context는 component자체가 아닌 component를 포함하고 있는 객체이기 때문에 감쌀 때 일반 component처럼 감싸는게 아닌 객체로 접근해야한다. 이때 context가 기본으로 제공하는 속성을 이용해야하는데 그것이 바로 provider 속성이다.
<AuthContext.provider> <AuthContext.provider />
이를 사용하여 감싸진 모든 컴포넌트와 해당 컴포넌트들의 자손들은 context에 정의된 state에 접근할 수 있다! 이 context는 wrapper의 역할을 하기때문에<div></div>
나<React.Fragment><React.Fragment/>
등으로 감싸두었던 jsx문법을 생략할 수 있다!// App.js import AuthContext from './store/auth-context'; ...생략 function App() { return ( // context에 접근하는 모든 component에 대해 provider로 감싸준다 <AuthContext.Provider> <MainHeader isAuthenticated={isLoggedIn} onLogout={logoutHandler} /> <main> {!isLoggedIn && <Login onLogin={loginHandler} />} {isLoggedIn && <Home onLogout={logoutHandler} />} </main> </AuthContext.Provider> ); }
2️⃣ <Context.consumer> - 사용방법 1
- AuthContext Consumer
App
의 하단에 있는Navigation component
에서 context를 사용하고 싶다고 가정해보자.Navigation component
에서 return하는 부분을<AuthContext.Consumer>
를 이용하여 감싸서 context를 사용할 수 있다.이때
Consumer
는 자식으로 함수를 가진다! 이 자식함수는 context에 들어있는 데이터값들을 매개변수로 받는다. 이 경우에는 객체를 매개변수로 받게된다. 이 자식함수는 원래Navigation.js
에서 return하려고 하던 Jsx 코드를 리턴값으로 가진다!! 이렇게 해서 해당 jsx코드들은 context의 데이터에 접근이 가능하게 된다.// Navigation.js import AuthContext from '../../store/auth-context'; const Navigation = (props) => { return ( // Consumer로 context 사용하기 <AuthContext.Consumer> // context가 가지는 자식함수로 ctx에는 context에서 정의한 데이터의 값들을 포함한다 // 이 자식함수는 navigation.js 컴포넌트에서 return하고자 하는 jsx코드를 reuturn한다. {(ctx) => {return( <nav className={classes.nav}> <ul> // context에 정의된 데이터(IsLoggedIn)들에 ctx를 통해 접근할 수 있다! {ctx.isLoggedIn && ( <li> <a href="/">Users</a> </li> )} {ctx.isLoggedIn && ( <li> <a href="/">Admin</a> </li> )} {ctx.isLoggedIn && ( <li> <button onClick={props.onLogout}>Logout</button> </li> )} </ul> </nav> )}} </AuthContext.Consumer> ); };
하지만 이렇게 작성하면 충돌이나게 된다. 그 이유는, 우리가 context를 만들때 설정해놓은 default 값은 isLoggedIn: false
의 값이다. default값이 사용되는 경우는 공급자 없이 사용되었을 경우이다. 즉 default값이 설정되어있다면 <context.provider>
는 필요하지 않다.
하지만 대부분 하드코딩이 아닌 context에 변할 수 있는 값 (보통은 state)을 넣게되고 이 경우에는 default값이 있는게 아니기 때문에 <context.provider>
를 꼭 필요로 한다. 때문에 공급자와 수요자의 패턴을 기억하는게 중요하다!
때문에 default값이 정해져 있을 때 <context.provider>
를 없애는 방법보다는, <context.provider>
에 value prop을 추가하는 방법으로 이 충돌문제를 해결할 수 있다. 이 value라는 prop은 정해져있는 이름이다. 이 value prop에 객체를 넣어서 consumer에 전달할 수 있다. 이때 value에 넣는 객체는 context에서 선언된 default 객체를 내가 원하는 값으로 변경하여 <Context.consumer>
전달 가능하다. 때문에 <context.provider>
를 선언하는 component 내부에서 상황에 따라 변하는 state를 만들어서 해당 state값을 value에 넣어서 prop으로 consumer에 전달하게 되면 유연하게 내가 원하는 state값을 모든 context에서 사용할 수 있게 된다!
// App.js
import AuthContext from './store/auth-context';
...생략
function App() {
// 상황에 따라 변하는 state 정의
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<AuthContext.Provider value={{
// state값으로 value업데이트하고, consumer에 전달!!
isLoggedIn: isLoggedIn,
}}>
// MainHeader의 자식 component에 isLoggedIn이 필요했기 때문에 prop chain으로전달 하던 것을
// 한 번에 value를 통해 전달할 수 있기 때문에 이 prop은 필요X!
<MainHeader //isAuthenticated={isLoggedIn}
onLogout={logoutHandler} />
<main>
{!isLoggedIn && <Login onLogin={loginHandler} />}
{isLoggedIn && <Home onLogout={logoutHandler} />}
</main>
</AuthContext.Provider>
);
}
2️⃣ useContext Hook - 사용방법 2 (대부분!)
import React, { useContext } from 'react'; import AuthContext from '../../store/auth-context'; const Navigation = (props) => { // useContext(사용하려는 context이름) const ctx = useContext(AuthContext); return ( <nav className={classes.nav}> <ul> // ctx로 context의 값에 접근하고 사용 {ctx.isLoggedIn && ( <li> <a href="/">Users</a> </li> )} {ctx.isLoggedIn && ( <li> <a href="/">Admin</a> </li> )} {props.isLoggedIn && ( <li> <button onClick={props.onLogout}>Logout</button> </li> )} </ul> </nav> ); }; export default Navigation;
<context.provider>
의 value에 값뿐만 아닌 함수도 전달해서 <context.provider>
외의 다른 component에서는 prop을 사용해서 전달하지 않아도 되도록 만들기
// app.js
...생략
return (
<AuthContext.Provider value={{
isLoggedIn: isLoggedIn,
// 함수도 전달하기
onLogout: logoutHandler,
}}>
보통의 경우에는 props를 컴포넌트간에 전달해서 사용한다. 하지만 정말 다수의 컴포넌트에서 동일한 state나 데이터를 사용하는 경우에는 context를 통해서 관리하는 것이 훨씬 효율적이다. 복잡하게 props chain을 이용해서 구현하여도 되지만, context를 통해서 더 간결하게 코드를 작성할 수 있다
최상단의 App component
에서 외부에서 사용되는 다양한 로직을 가져오고 싶은 경우에는, context
를 정의하는 파일에 context
를 관리하는 component를 별도로 만들어두고 export해서 사용하면 좋다. 이렇게 context를 관리하는 로직을 따로 context 폴더에 분리하는 이유는 App component에서는 최소한의 코드만으로 동작하도록 하기 위함이다. 하지만 그냥 App 컴포넌트에서 provider를 조작해도 된다. 선호도 차이!
// store/auth-context.js
import React, { useState } from 'react';
// context 선언하기
const AuthContext = React.createContext({
// default값 , 함수의 경우 빈 함수로 넣어도 됨
isLoggedIn: false,
onLogout: () => {},
onLogin: (email, password) => {},
});
// context말고도 contex.Provider도 export하기
// props를 가져온 이유는 context를 쓰고자 하는 컴포넌트들은 context.provider로 감싸져야 하므로
// {props.children}을 사용해줘야 함!
export const AuthContextProvider = (props) => {
// component이니깐 내부에서 state 선언가능
// 이때 provider내부에 선언되는 값, 함수는 context에서 선언한 key값들에 대한 것이어야 함
const [isLoggedIn, setIsLoggedIn] = useState(false);
const logoutHandler = () => {
setIsLoggedIn(false);
};
const loginHandler = () => {
setIsLoggedIn(true);
};
return (
<AuthContext.Provider
value={{
isLoggedIn: isLoggedIn,
onLogout: logoutHandler,
onLogin: loginHandler,
}}>
{props.children}
</AuthContext.Provider>
);
};
export default AuthContext;
기존 App component
에 들어있던 모든 로직을 로그인 관련 context를 다루는 context파일 내부에 context Provider
와 관련된 component를 만들고 그 안에 관련 로직을 모두 옮겨 담는다.이후, App component
에서 사용할 때는 context lister와 같이 const ctx = useContext(AuthContext);
를 통해서 접근하면 된다.
이렇게하면 로그인과 관련된 로직은 모두 context파일로 몰아서 접근할 수 있고, App component에서는 정말 화면에 띄우는 부분만 관여할 수 있어서 보다 로직과 화면을 파악하기에 수월하다. 하지만 이것 또한 선호도와 상황에 따라서 다르게 작성하면 되는 듯 하니 일단은 알아만 두자!
변경이 잦은 경우에는 context의 사용이 안 좋을 수 있다. 예를들어 매초마다 변경되는 state의 경우에는 기존처럼 useState를 사용하는 방법이 훨씬 적합하다. 그렇다면 context를 써서 관리하고 싶은 multiple state와 연관되어 있는데, 너무 자주 변경되는 것들은 어떻게 관리하지? 그럴 때 쓰는게 바로 Redux
이다! Redux
는 추후에 다뤄볼 예정이다
리액트 Hook은 use로 시작하는 모든 함수를 말한다.
- 리액트 hook은 리액트 함수 내부에서만 호출되어야 한다.
즉 리액트 컴포넌트 함수 또는 custom Hook에서만 호출되어야 한다.
- 최상위 수준에서 사용되어야 한다.
예를들어 중첩함수의 내부라던가
useEffect(() => { useState()... })
if문 내부라던가
if(true) { useState()... }
이렇게 함수의 내부에 사용하면 에러가 발생한다. 가장 최상위단에서 사용해야 한다!
useEffect
의 dependency에는useEffect
내부에서 사용하는 모든 것을 넣자!
setter함수의 경우에는 생략 가능하다.
...
즐겁게 읽었습니다. 유용한 정보 감사합니다.