The goals
๐ช๐ปWorking with (Side) Effects
โ๐ปManaging more Complext State with Reducers
๐๐ปManaging App-Wide or Component-Wide State with Context
Let's learn with the small project!
1. We wanna keep our page authenticated even after we reloading the page!
For that, we use 'localstorage' and gonna check on the webpage
and write the code like this so when we get email and password then we set our login status as 'isLoggedIn' and key as '1'
But here the issue is that since React rerenders the webpage every time if there's some update we will be in the loop that we change our status to logged in and rerender and change the status and rerender.....
So here we should use something different from using 'State'
And we put these codes in useEffect().
So the order will be
1. run the component funtions
2. check useEffect and execute the function in it
3. re-run the component
It won't run after every component evaluation, but only if the dependencies in useEffect changes.
So when the app starts for the first time then that will be the case.
If this component function runs for the very first time because our app just started, then the dependencies are considered to have change but we have no dependencies here.
But once it ran for the first time for example, with this set up here, we have no dependencies but therefore of course they also didn't change compared to the first execution cycle.
So therefore this anonymous function here would indeed only run once when the app starts because thereafter the dependencies never changes.
Since we want to run this code only one time, we intentionally put no dependency here.
So once the code starts,
1. the code runs
2. we update our state by using useState
3. update triggers this component to run again
4. and JSX code is evaluated and update DOM accordingly
5. useEffect will run again only when we have some change of our dependencies we set
So based on this logic, even if we re-load the page still we can keep our login status.
Because we don't reload the page until the dependency has been changed
UseEffect & Dependencies
Now we will apply useEffect for the validation
So first of all, for the emailChangeHandler we can put dependency as email so everytime when the email changes, we can reload this page.
So we import 'useEffect' and bring the code fom emailChangeHandler
And we don't have to use event.target.value since we are not getting event so we can just define variable and put the vlaue there.,
And we can put dependencies something we use inside of useEffect()
So that we put function itself as dependency, it means that we add a pointer at this function. So every login component function executes, it will re-run this useEffect function but only if either setFormIsValid or enteredEmail or enteredPassword changed in the last component rerender cycle.
Acutally we can omit this 'setFormIsValid' because those state updating functions by default are ensured by React to never change. So these functions (below pic) will always be the same.
So this is also one of the advantages of useEffect that we don't have to write a long code to check some things are changed then what we should do
It's side effect of the user entering data.
UseEffect is, in general, a super important hook that helps you deal with code that should be executed in response to something.
And something could be the component being loaded.
์ด์ ๊ฐ์์์ useEffect() ์ข ์์ฑ์ ๋ํด ์ดํด๋ณด์์ต๋๋ค.
effect ํจ์์์ ์ฌ์ฉํ๋ "๋ชจ๋ ๊ฒ"์ ์ข ์์ฑ์ผ๋ก ์ถ๊ฐํด์ผ ํจ์ ๋ฐฐ์ ์ต๋๋ค. ์ฆ, ๊ฑฐ๊ธฐ์์ ์ฌ์ฉํ๋ ๋ชจ๋ ์ํ ๋ณ์์ ํจ์๋ฅผ ํฌํจํฉ๋๋ค.
๋ง๋ ๋ง์ด์ง๋ง ๋ช ๊ฐ์ง ์์ธ๊ฐ ์์ต๋๋ค. ๋ค์ ์ฌํญ์ ์๊ณ ์์ด์ผ ํฉ๋๋ค.
- ์ฌ๋ฌ๋ถ์ ์ํ ์ ๋ฐ์ดํธ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ํ์๊ฐ ์์ต๋๋ค. (์ง๋ ๊ฐ์์์ ํ๋ ๊ฒ์ฒ๋ผ setFormIsValid ์ฌ์ฉ): React๋ ํด๋น ํจ์๊ฐ ์ ๋ ๋ณ๊ฒฝ๋์ง ์๋๋ก ๋ณด์ฅํ๋ฏ๋ก ์ข ์์ฑ์ผ๋ก ์ถ๊ฐํ ํ์๊ฐ ์์ต๋๋ค.
- ์ฌ๋ฌ๋ถ์ ๋ํ "๋ด์ฅ" API ๋๋ ํจ์๋ฅผ ์ถ๊ฐํ ํ์๊ฐ ์์ต๋๋ค fetch(), ๋ localStorage ๊ฐ์ ๊ฒ๋ค ๋ง์ด์ฃ (๋ธ๋ผ์ฐ์ ์ ๋ด์ฅ๋ ํจ์ ๋ฐ ๊ธฐ๋ฅ, ๋ฐ๋ผ์ ์ ์ญ์ ์ผ๋ก ์ฌ์ฉ ๊ฐ๋ฅ): ์ด๋ฌํ ๋ธ๋ผ์ฐ์ API/์ ์ญ ๊ธฐ๋ฅ์ React ๊ตฌ์ฑ ์์ ๋ ๋๋ง ์ฃผ๊ธฐ์ ๊ด๋ จ์ด ์์ผ๋ฉฐ ๋ณ๊ฒฝ๋์ง ์์ต๋๋ค.
- ์ฌ๋ฌ๋ถ์ ๋ํ ๋ณ์๋ ํจ์๋ฅผ ์ถ๊ฐํ ํ์๊ฐ ์์ต๋๋ค. ์๋ง๋ ๊ตฌ์ฑ ์์ ์ธ๋ถ์์ ์ ์ํ์ ๊ฒ๋๋ค (์: ๋ณ๋์ ํ์ผ์ ์ ๋์ฐ๋ฏธ ํจ์๋ฅผ ๋ง๋๋ ๊ฒฝ์ฐ): ์ด๋ฌํ ํจ์ ๋๋ ๋ณ์๋ ๊ตฌ์ฑ ์์ ํจ์ ๋ด๋ถ์์ ์์ฑ๋์ง ์์ผ๋ฏ๋ก ๋ณ๊ฒฝํด๋ ๊ตฌ์ฑ ์์์ ์ํฅ์ ์ฃผ์ง ์์ต๋๋ค (ํด๋น ๋ณ์๊ฐ ๋ณ๊ฒฝ๋๋ ๊ฒฝ์ฐ, ๋๋ ๊ทธ ๋ฐ๋์ ๊ฒฝ์ฐ์๋ ๊ตฌ์ฑ ์์๋ ์ฌํ๊ฐ๋์ง ์์ต๋๋ค)
๊ฐ๋จํ ๋งํด์: effect ํจ์์์ ์ฌ์ฉํ๋ ๋ชจ๋ "๊ฒ๋ค"์ ์ถ๊ฐํด์ผ ํฉ๋๋ค. ๊ตฌ์ฑ ์์(๋๋ ์ผ๋ถ ์์ ๊ตฌ์ฑ ์์)๊ฐ ๋ค์ ๋ ๋๋ง ๋์ด ์ด๋ฌํ "๊ฒ๋ค"์ด ๋ณ๊ฒฝ๋ ์ ์๋ ๊ฒฝ์ฐ.๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ปดํฌ๋ํธ ํจ์์ ์ ์๋ ๋ณ์๋ ์ํ, ์ปดํฌ๋ํธ ํจ์์ ์ ์๋ props ๋๋ ํจ์๋ ์ข ์์ฑ์ผ๋ก ์ถ๊ฐ๋์ด์ผ ํฉ๋๋ค!
๋ค์์ ์์์ ์ธ๊ธํ ์๋๋ฆฌ์ค๋ฅผ ๋ ๋ช ํํ ํ๊ธฐ ์ํด ๊ตฌ์ฑ๋ ๋๋ฏธ ์์ ๋๋ค.
import { useEffect, useState } from 'react';
let myTimer;
const MyComponent = (props) => {
const [timerIsActive, setTimerIsActive] = useState(false);
const { timerDuration } = props; // using destructuring to pull out specific props values
useEffect(() => {
if (!timerIsActive) {
setTimerIsActive(true);
myTimer = setTimeout(() => {
setTimerIsActive(false);
}, timerDuration);
}
}, [timerIsActive, timerDuration]);
};
์ด ์์์:
- timerIsActive ๋ ์ข ์์ฑ์ผ๋ก ์ถ๊ฐ๋์์ต๋๋ค. ์๋ํ๋ฉด ๊ตฌ์ฑ ์์๊ฐ ๋ณ๊ฒฝ๋ ๋ ๋ณ๊ฒฝ๋ ์ ์๋ ๊ตฌ์ฑ ์์ ์ํ์ด๊ธฐ ๋๋ฌธ์ด์ฃ (์: ์ํ๊ฐ ์ ๋ฐ์ดํธ๋์๊ธฐ ๋๋ฌธ์)
- timerDuration ์ ์ข ์์ฑ์ผ๋ก ์ถ๊ฐ๋์์ต๋๋ค. ์๋ํ๋ฉด ํด๋น ๊ตฌ์ฑ ์์์ prop ๊ฐ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค - ๋ฐ๋ผ์ ์์ ๊ตฌ์ฑ ์์๊ฐ ํด๋น ๊ฐ์ ๋ณ๊ฒฝํ๋ฉด ๋ณ๊ฒฝ๋ ์ ์์ต๋๋ค(์ด MyComponent ๊ตฌ์ฑ ์์๋ ๋ค์ ๋ ๋๋ง๋๋๋ก ํจ).
- setTimerIsActive ๋ ์ข ์์ฑ์ผ๋ก ์ถ๊ฐ๋์ง ์์ต๋๋ค. ์๋ํ๋ฉด์์ธ ์กฐ๊ฑด์ด๊ธฐ ๋๋ฌธ์ ๋๋ค: ์ํ ์ ๋ฐ์ดํธ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์์ง๋ง React๋ ๊ธฐ๋ฅ ์์ฒด๊ฐ ์ ๋ ๋ณ๊ฒฝ๋์ง ์์์ ๋ณด์ฅํ๋ฏ๋ก ์ถ๊ฐํ ํ์๊ฐ ์์ต๋๋ค.
- myTimer ๋ ์ข ์์ฑ์ผ๋ก ์ถ๊ฐ๋์ง ์์ต๋๋ค. ์๋ํ๋ฉด ๊ทธ๊ฒ์ ๊ตฌ์ฑ ์์ ๋ด๋ถ ๋ณ์๊ฐ ์๋๊ธฐ ๋๋ฌธ์ด์ฃ . (์ฆ, ์ด๋ค ์ํ๋ prop ๊ฐ์ด ์๋) - ๊ตฌ์ฑ ์์ ์ธ๋ถ์์ ์ ์๋๊ณ ์ด๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค(์ด๋์์๋ ). ๊ตฌ์ฑ ์์๊ฐ ๋ค์ ํ๊ฐ๋๋๋ก ํ์ง ์์ต๋๋ค.
- setTimeout ์ ์ข ์์ฑ์ผ๋ก ์ถ๊ฐ๋์ง ์์ต๋๋ค ์๋ํ๋ฉด ๊ทธ๊ฒ์ ๋ด์ฅ API์ด๊ธฐ ๋๋ฌธ์ ๋๋ค. (๋ธ๋ผ์ฐ์ ์ ๋ด์ฅ) - React ๋ฐ ๊ตฌ์ฑ ์์์ ๋ ๋ฆฝ์ ์ด๋ฉฐ ๋ณ๊ฒฝ๋์ง ์์ต๋๋ค.
CleanUp Functions in UseEffect
We can write code like this and if we check on the webpage, we can see that every time if there's keystroke if will console.log
What if we have more complicated code like sending HTTP requests or verify data from server? We don't want to get through those actions by every key stroke.
So we can maybe make a way to check it after some time later or after we put some length of character.
Here we will check when the user stops typing for a while, and understand it as the finishing time of inputting data.
We call this 'Debouncing'
Debouncing
Debouncing is a programming pattern or a technique to restrict the calling of a time-consuming function frequently, by delaying the execution of the function until a specified time to avoid unnecessary CPU cycles, and API calls and improve performance.
So we can put 'setTimeout' and put the code inside of it.
So after some set seconds, it will work.
The trick is that we actually save the timer and for the next key stroke we clear it.
So that we only have one ongoing timer at a time and only the last timer will complete.
How can we make it?
If we write a code like this then after setting time our console.log("checking form validity' works first and then if we type then also console.log('cleanup') would work together.
So based on this, if we put clearTimeout in the place we put 'cleanup' console.log then it will clean after setTimeout effect.
So if we do like this then we can see that checking form validity only occurs one time and many cleanup
If we do like this and how it will work?
every time if we click somewhere or key stroke, it will console.log cause useEffect works after every rendering component cycle.
But what if we put empty array '[]' as dependency in useEffect?
then it will only work for the first time this component was mounted and rendered, but not for any subsequent rerender cycle.
But it doesn't mean that since it's more powerful we should always use this.
Because it needs more set up to use and it's more complicated scenario.
For the same case we have done like emailChangeHandler and passwordChangeHandler
If we don't useState then since we are not managing the state, when this code runs, it might not contain the latest entered password because of how React schedules state updates.
So we got advised to use function form but again, it's not available here because we are depensing on two other states and not the last snapshot of this form validity state.
This is another scenario where we could use 'useReducer', a good replacement of useState.
When we have states that belongs together, like here, with the entered value and the validity of the value and or if we have state updates that depends on other state.
Here we are checking enteredEmail which is another state to set emailIsValid state which is different state.
This is something we shouldn't do
check the below pic
And this is a scenario where useReducer is always a good choice.
If you update a state which depends on another state, then merging this into one state could be a good idea.
And we can do that without useReducer as well.
So how can we use reduecer for email verification
We made emailReducer function outside of the component function.
It's because inside of this reducer function we won't need any data that's generated inside of the component function.
So this reducer function can be created outside of the scope of this component function because it doesn't need to interact with anything defined inside of the component function.
And all the data which will be required and used inside of the reducer function will ba passed into this function when it's executed by React, autmoatically.
This reducer function receives two arguments, two parameters.
Out last state snapshot and the action that was dispatched and we will see what this action would be in just a second.
And we should return a new state. Our new state can be an object where we have the value, which is initiallly an empty string.
So now since we can use the state of email, we can fix our code like this:
enteredEmail -> emailState.value
emailIsValid -> emailState.isValid
So we don't have to take snapshot by using 'useState' since we are managing the state of it with useReducer
We need to dispatch an action eventually. We need to dispatch it, for example, at emailChangeHandler, when we want to update the value OR
here, when we want to update the validity.
Let's start with the value.
Here now we update this by calling 'dispatchEmail' and we pass to it as so-called action.
It's totally up to us what this action is.
It can be a string identifier, something like 'NEW_EMAIL_VALUE' or it could be a number but often will be an object which has some field that holds some identifier.
Usually the field is named type so here it can be something like 'USER_INPUT'.
It doesn't have to be all caps but that's just a convention we see a lot.
And then we can also add an extra payload to this action.
So for example, a val field, that holds the event.target.value.
So! now this is our action!
It's this object with a type field that describes what happened and extra payload in this case, the value the user entered.
This will trigger this function (emailReducer) to execute because that's the reducer function we passed to useReducer.
Now here we can handle this action, for example, with the if statement.
We can check if action.type is equal to user input.
One thing we should keep it mind is that what we dispatch as an action will be an object because that's what we set it to here (in action 'emailChangeHandler')
Now then we don't want to return the empty state snapshot, instead, we want to return the state snapshot for our email where the value is action.val.
That's the payload we appended to our action.
And maybe we also want to update the validity here if we're already added.
Simply by checking if action is valid then the same rule we had before.
**but it should be isValid: action.val.includes('@')
So we put the rule we should check to change the isValid status.
And for any other action that might reach this reducer, this default state will be returned.
Okay now we need to dispatch the action.
At validateEmailHandler
Understand Agian 'UseReducer'
useReducer ์ดํดํ๊ธฐ
์ฐ๋ฆฌ๊ฐ ์ด์ ์ ๋ง๋ ์ฌ์ฉ์ ๋ฆฌ์คํธ ๊ธฐ๋ฅ์์์ ์ฃผ์ ์ํ ์ ๋ฐ์ดํธ ๋ก์ง์ App ์ปดํฌ๋ํธ ๋ด๋ถ์์ ์ด๋ฃจ์ด์ก์์ต๋๋ค. ์ํ๋ฅผ ์ ๋ฐ์ดํธ ํ ๋์๋ useState ๋ฅผ ์ฌ์ฉํด์ ์๋ก์ด ์ํ๋ฅผ ์ค์ ํด์ฃผ์๋๋ฐ์, ์ํ๋ฅผ ๊ด๋ฆฌํ๊ฒ ๋ ๋ useState ๋ฅผ ์ฌ์ฉํ๋๊ฒ ๋ง๊ณ ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ๋ฐ๋ก, useReducer ๋ฅผ ์ฌ์ฉํ๋๊ฑด๋ฐ์, ์ด Hook ํจ์๋ฅผ ์ฌ์ฉํ๋ฉด ์ปดํฌ๋ํธ์ ์ํ ์ ๋ฐ์ดํธ ๋ก์ง์ ์ปดํฌ๋ํธ์์ ๋ถ๋ฆฌ์ํฌ ์ ์์ต๋๋ค. ์ํ ์ ๋ฐ์ดํธ ๋ก์ง์ ์ปดํฌ๋ํธ ๋ฐ๊นฅ์ ์์ฑ ํ ์๋ ์๊ณ , ์ฌ์ง์ด ๋ค๋ฅธ ํ์ผ์ ์์ฑ ํ ๋ถ๋ฌ์์ ์ฌ์ฉ ํ ์๋ ์์ง์.
App ์ปดํฌ๋ํธ์์ useReducer ๋ฅผ ์ฌ์ฉํด๋ณด๊ธฐ์ ์ ์ฐ๋ฆฌ๊ฐ useState ๋ฅผ ์ฒ์ ๋ฐฐ์ธ ๋ ๋ง๋ค์๋ Counter.js ์ปดํฌ๋ํธ์์ useReducer ๋ฅผ ์ฌ์ฉํด๋ณด๊ฒ ์ต๋๋ค.
// Counter.js
import React, { useState } from 'react';
function Counter() {
const [number, setNumber] = useState(0);
const onIncrease = () => {
setNumber(prevNumber => prevNumber + 1);
};
const onDecrease = () => {
setNumber(prevNumber => prevNumber - 1);
};
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
);
}
export default Counter;
useReducer Hook ํจ์๋ฅผ ์ฌ์ฉํด๋ณด๊ธฐ์ ์ ์ฐ์ reducer ๊ฐ ๋ฌด์์ธ์ง ์์๋ณด๊ฒ ์ต๋๋ค. reducer ๋ ํ์ฌ ์ํ์ ์ก์ ๊ฐ์ฒด๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์์์ ์๋ก์ด ์ํ๋ฅผ ๋ฐํํด์ฃผ๋ ํจ์์ ๋๋ค.
function reducer(state, action) {
// ์๋ก์ด ์ํ๋ฅผ ๋ง๋๋ ๋ก์ง
// const nextState = ...
return nextState;
}
reducer ์์ ๋ฐํํ๋ ์ํ๋ ๊ณง ์ปดํฌ๋ํธ๊ฐ ์ง๋ ์๋ก์ด ์ํ๊ฐ ๋ฉ๋๋ค.
์ฌ๊ธฐ์ action ์ ์ ๋ฐ์ดํธ๋ฅผ ์ํ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์ฃผ๋ก type ๊ฐ์ ์ง๋ ๊ฐ์ฒด ํํ๋ก ์ฌ์ฉํ์ง๋ง, ๊ผญ ๋ฐ๋ผ์ผ ํ ๊ท์น์ ๋ฐ๋ก ์์ต๋๋ค.
์ก์ ์ ์์๋ค์ ํ์ธํด๋ณผ๊น์?
// ์นด์ดํฐ์ 1์ ๋ํ๋ ์ก์
{
type: 'INCREMENT'
}
// ์นด์ดํฐ์ 1์ ๋นผ๋ ์ก์
{
type: 'DECREMENT'
}
// input ๊ฐ์ ๋ฐ๊พธ๋ ์ก์
{
type: 'CHANGE_INPUT',
key: 'email',
value: 'tester@react.com'
}
// ์ ํ ์ผ์ ๋ฑ๋กํ๋ ์ก์
{
type: 'ADD_TODO',
todo: {
id: 1,
text: 'useReducer ๋ฐฐ์ฐ๊ธฐ',
done: false,
}
}
๋ณด์ ๊ฒ ์ฒ๋ผ action ๊ฐ์ฒด์ ํํ๋ ์์ ์ ๋๋ค. type ๊ฐ์ ๋๋ฌธ์์ _ ๋ก ๊ตฌ์ฑํ๋ ๊ด์ต์ด ์กด์ฌํ๊ธฐ๋ ํ์ง๋ง, ๊ผญ ๋ฐ๋ผ์ผ ํ ํ์๋ ์์ต๋๋ค.
์, ์ด์ reducer ๋ฅผ ๋ฐฐ์ ์ผ๋ useReducer ์ ์ฌ์ฉ๋ฒ์ ์์๋ด ์๋ค. useReducer ์ ์ฌ์ฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
const [state, dispatch] = useReducer(reducer, initialState);
์ฌ๊ธฐ์ state ๋ ์ฐ๋ฆฌ๊ฐ ์์ผ๋ก ์ปดํฌ๋ํธ์์ ์ฌ์ฉ ํ ์ ์๋ ์ํ๋ฅผ ๊ฐ๋ฅดํค๊ฒ ๋๊ณ , dispatch ๋ ์ก์ ์ ๋ฐ์์ํค๋ ํจ์๋ผ๊ณ ์ดํดํ์๋ฉด ๋ฉ๋๋ค. ์ด ํจ์๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํฉ๋๋ค: dispatch({ type: 'INCREMENT' }).
๊ทธ๋ฆฌ๊ณ useReducer ์ ๋ฃ๋ ์ฒซ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ reducer ํจ์์ด๊ณ , ๋๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ ์ด๊ธฐ ์ํ์ ๋๋ค.
๊ทธ๋ผ, Counter ์ปดํฌ๋ํธ๋ฅผ ๋ง์ฝ์ useReducer ๋ก ๊ตฌํํ๋ค๋ฉด ์ด๋ป๊ฒ ๋ฐ๋๋์ง ์์๋ณผ๊น์?
๋ค์ ์ฝ๋๋ฅผ ํ๋ฒ ๋ฐ๋ผ์ ์์ฑํด๋ณด์ธ์.
// Counter.js
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
function Counter() {
const [number, dispatch] = useReducer(reducer, 0);
const onIncrease = () => {
dispatch({ type: 'INCREMENT' });
};
const onDecrease = () => {
dispatch({ type: 'DECREMENT' });
};
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
);
}
export default Counter;
useReducer vs useState - ๋ญ ์ธ๊น?
์ ์ด์ ๊ถ๊ธํด์ง๋ ์ ์ด ํ๊ฐ์ง ์์ ๊ฒ์ ๋๋ค. ์ด๋จ ๋ useReducer ๋ฅผ ์ฐ๊ณ ์ด๋จ ๋ useState ๋ฅผ ์จ์ผ ํ ๊น์? ์ผ๋จ, ์ฌ๊ธฐ์ ์์ด์๋ ์ ํด์ง ๋ต์ ์์ต๋๋ค. ์ํฉ์ ๋ฐ๋ผ ๋ถํธํ ๋๋ ์๊ณ ํธํ ๋๋ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด์ ์ปดํฌ๋ํธ์์ ๊ด๋ฆฌํ๋ ๊ฐ์ด ๋ฑ ํ๋๊ณ , ๊ทธ ๊ฐ์ด ๋จ์ํ ์ซ์, ๋ฌธ์์ด ๋๋ boolean ๊ฐ์ด๋ผ๋ฉด ํ์คํ useState ๋ก ๊ด๋ฆฌํ๋๊ฒ ํธํ ๊ฒ์ ๋๋ค.
const [value, setValue] = useState(true);
ํ์ง๋ง, ๋ง์ฝ์ ์ปดํฌ๋ํธ์์ ๊ด๋ฆฌํ๋ ๊ฐ์ด ์ฌ๋ฌ๊ฐ๊ฐ ๋์ด์ ์ํ์ ๊ตฌ์กฐ๊ฐ ๋ณต์กํด์ง๋ค๋ฉด useReducer๋ก ๊ด๋ฆฌํ๋ ๊ฒ์ด ํธํด์ง ์๋ ์์ต๋๋ค.
์ด์ ๋ํ ๊ฒฐ์ ์, ์์ผ๋ก ์ฌ๋ฌ๋ถ๋ค์ด useState, useReducer ๋ฅผ ์์ฃผ ์ฌ์ฉํด๋ณด์๊ณ ๋ง์๋๋ ๋ฐฉ์์ ์ ํํ์ธ์.
์ ์ ๊ฒฝ์ฐ์๋ setter ๋ฅผ ํ ํจ์์์ ์ฌ๋ฌ๋ฒ ์ฌ์ฉํด์ผ ํ๋ ์ผ์ด ๋ฐ์ํ๋ค๋ฉด
setUsers(users => users.concat(user));
setInputs({
username: '',
email: ''
});
๊ทธ ๋๋ถํฐ useReducer ๋ฅผ ์ธ๊น? ์ ๋ํ ๊ณ ๋ฏผ์ ์์ํฉ๋๋ค. useReducer ๋ฅผ ์ผ์๋ ํธํด์ง ๊ฒ ๊ฐ์ผ๋ฉด useReducer ๋ฅผ ์ฐ๊ณ , ๋ฑํ ๊ทธ๋ด๊ฒ๊ฐ์ง ์์ผ๋ฉด useState ๋ฅผ ์ ์งํ๋ฉด ๋์ง์.
I finished the small mission to make my own password reducer but I didn't know where to start.
Let's organize the steps
1. define reducer function first
e.g. passwordReducer (which will be the parameter inside of useReducer)
we can just copy from emailrReducer
action names can be the same?
2.
I wrote here like this. But since we check the validation upper code in 'passwordReducer' we don't have to write like this. Instead we can just write 'passwordState.isValid'
And this is important concept if it validity is passed, then we don't console.log again.
So we don't print "Checking form validity" anymore once they pass the validation.
And this will not reset until the destructured object has been updated.
์ด์ ๊ฐ์์์ ์ฐ๋ฆฌ๋ useEffect()์ ๊ฐ์ฒด ์์ฑ์ ์ข ์์ฑ์ผ๋ก ์ถ๊ฐํ๊ธฐ ์ํด dstructuring์ ์ฌ์ฉํ์ต๋๋ค.
const { someProperty } = someObject;
useEffect(() => {
// code that only uses someProperty ...
}, [someProperty]);
์ด๊ฒ์ ๋งค์ฐ ์ผ๋ฐ์ ์ธ ํจํด ๋ฐ ์ ๊ทผ ๋ฐฉ์์ด๋ฉฐ, ์ด๊ฒ์ด ์ ๊ฐ ์ผ๋ฐ์ ์ผ๋ก ์ด ๋ฐฉ์์ ์ฌ์ฉํ๋ ์ด์ ์ด๋ฉฐ ์ฌ๊ธฐ์ ๋ณด์ฌ๋๋ฆฌ๋ ์ด์ ์ ๋๋ค(์ฝ์ค ๋ด๋ด ๊ณ์ ์ฌ์ฉํ ๊ฒ์ ๋๋ค).
ํต์ฌ์ ์ฐ๋ฆฌ๊ฐ destructuring์ ์ฌ์ฉํ๋ค๋ ๊ฒ์ด ์๋๋ผ, ์ ์ฒด ๊ฐ์ฒด ๋์ ํน์ ์์ฑ์ ์ข ์์ฑ์ผ๋ก ์ ๋ฌํ๋ค๋ ๊ฒ์ ๋๋ค.
์ฐ๋ฆฌ๋ ์ด์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ ์๋ ์์ผ๋ฉฐ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์๋ํฉ๋๋ค.
useEffect(() => {
// code that only uses someProperty ...
}, [someObject.someProperty]);
์ด๊ฒ์ ์ ์๋ํฉ๋๋ค!
ํ์ง๋ง ์ฌ๋ฌ๋ถ์ ์ด ์ฝ๋ ์ฌ์ฉ์ ํผํด์ผ ํฉ๋๋ค:
useEffect(() => {
// code that only uses someProperty ...
}, [someObject]);
์ ๊ทธ๋ด๊น์?
์๋ํ๋ฉด effect ํจ์๋ someObject ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ฌ์คํ๋๊ธฐ ๋๋ฌธ์ด์ฃ - ๋จ์ผ ์์ฑ์ด ์๋๋๋ค (someProperty ์์ ์์์)
Context ๋?
Context๋ ๋ฆฌ์กํธ ์ปดํฌ๋ํธ๊ฐ์ ์ด๋ ํ ๊ฐ์ ๊ณต์ ํ ์ ์๊ฒ ํด์ฃผ๋ ๊ธฐ๋ฅ์ ๋๋ค. ์ฃผ๋ก Context๋ ์ ์ญ์ (global)์ผ๋ก ํ์ํ ๊ฐ์ ๋ค๋ฃฐ ๋ ์ฌ์ฉํ๋๋ฐ์, ๊ผญ ์ ์ญ์ ์ผ ํ์๋ ์์ต๋๋ค. Context๋ฅผ ๋จ์ํ "๋ฆฌ์กํธ ์ปดํฌ๋ํธ์์ Props๊ฐ ์๋ ๋ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ์ปดํฌ๋ํธ๊ฐ์ ๊ฐ์ ์ ๋ฌํ๋ ๋ฐฉ๋ฒ์ด๋ค" ๋ผ๊ณ ์ ๊ทผ์ ํ์๋ ๊ฒ์ด ์ข์ต๋๋ค.
Props๋ก๋ง ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ฉด ๋ฐ์ํ ์ ์๋ ๋ฌธ์
๋ฆฌ์กํธ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์ผ๋ฐ์ ์ผ๋ก ์ปดํฌ๋ํธ์๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํด์ฃผ์ด์ผ ํ ๋ Props๋ฅผ ํตํด ์ ๋ฌํฉ๋๋ค. ๊ทธ๋ฐ๋ฐ ๊น์ํ ์์นํ ์ปดํฌ๋ํธ์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํด์ผ ํ๋ ๊ฒฝ์ฐ์๋ ์ฌ๋ฌ ์ปดํฌ๋ํธ๋ฅผ ๊ฑฐ์ณ ์ฐ๋ฌ์์ Props๋ฅผ ์ค์ ํด์ฃผ์ด์ผ ํ๊ธฐ ๋๋ฌธ์ ๋ถํธํ๊ณ ์ค์ํ ๊ฐ๋ฅ์ฑ์ด ๋์์ง์ฃ .
function App() {
return <GrandParent value="Hello World!" />;
}
function GrandParent({ value }) {
return <Parent value={value} />;
}
function Parent({ value }) {
return <Child value={value} />;
}
function Child({ value }) {
return <GrandChild value={value} />;
}
function GrandChild({ value }) {
return <Message value={value} />;
}
function Message({ value }) {
return <div>Received: {value}</div>;
}
์ด๋ฌํ ์ฝ๋๋ฅผ Props Drilling ์ด๋ผ๊ณ ๋ถ๋ฆ ๋๋ค. ์ปดํฌ๋ํธ๋ฅผ ํ ๋๊ฐ์ ๋ ๊ฑฐ์ณ์ Props๋ฅผ ์ ๋ฌํ๋๊ฑฐ๋ผ๋ฉด ๊ด์ฐฎ์ง๋ง ์ด๋ ๊ฒ 4๊ฐ์ ๋๋ฅผ ๊ฑฐ์ณ์ ์ ๋ฌํ๊ฒ ๋๋ค๋ฉด, ๋๋ฌด ๋ถํธํ ๊ฒ์ ๋๋ค. ์๋ฅผ ๋ค์ด์ Message ์ปดํฌ๋ํธ๋ฅผ ์ด์ด์, ์ด value ๊ฐ์ด ์ด๋์ ์ค๋๊ฑด์ง ํ์ ํ๋ ค๊ณ ํ๋ค๋ฉด ๊ทธ ์์ ์ปดํฌ๋ํธ๋ก ํ๊ณ ๋ ํ๊ณ ๊ฑฐ์ฌ๋ฌ ์ฌ๋ผ๊ฐ์ผ ํ๊ธฐ ๋๋ฌธ์ ๋งค์ฐ ๋ถํธํฉ๋๋ค. ๋๋, value ๋ผ๋ ๋ค์ด๋ฐ์ message ๋ก ๋ณ๊ฒฝ์ ํ๊ณ ์ถ์ด์ง๋ค๋ฉด, ํต์ผ์ฑ์ ๋ง์ถ๊ธฐ ์ํด์ ๋ ์ฌ๋ฌ ์ปดํฌ๋ํธ๋ค์ ์์ ํด์ผ ํ๋๊น ๊ทธ๊ฒ๋ ๊ทธ๊ฒ๋๋ก ๋ถํธํฉ๋๋ค.
๋ ๋ค๋ฅธ ์์๋ฅผ ํ์ธํด๋ณผ๊น์? ์ฐ๋ฆฌ๊ฐ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ๋, ์ฌ๋ฌ ์ข ๋ฅ์ ์์ ์ปดํฌ๋ํธ๊ฐ ํน์ ๊ฐ์ ์์กด์ ํ๋ค๊ณ ๊ฐ์ ์ ํด๋ด ์๋ค. ๋ค์๊ณผ ๊ฐ์ด ๋ง์ด์ฃ .
function App() {
return (
<AwesomeComponent value="Hello World" />
)
}
function AwesomeComponent({ value }) {
return (
<div>
<FirstComponent value={value} />
<SecondComponent value={value} />
<ThirdComponent value={value} />
</div>
)
}
function FirstComponent({ value }) {
return (
<div>First Component says: "{value}"</div>
)
}
function SecondComponent({ value }) {
return (
<div>Second Component says: "{value}"</div>
)
}
function ThirdComponent({ value }) {
return (
<div>Third Component says: "{value}"</div>
)
}
์ง๊ธ์ ๊ฒฝ์ฐ์๋ ๊ฐ ์ปดํฌ๋ํธ๊ฐ ํ๋์ Props๋ง ๋ฐ์์ค๊ธฐ ๋๋ฌธ์ ๋ง์ด ๋ณต์กํด๋ณด์ด์ง ์์ ์ ์์ง๋ง, ๋ง์ฝ ๊ฐ ์ปดํฌ๋ํธ์ ๋ ๋ค์ํ Props๊ฐ ๋ค์ด์๊ณ , ์ปดํฌ๋ํธ๋ ๋ ๋ค์ํ๊ณ ๊ตฌ์กฐ๊ฐ ์ข ๋ ๊น๋ค๋ก์ ๋ค๋ฉด, ๊ฐ๋ ์ฑ์ด ๋จ์ด์ง ์ ๋ ์์ต๋๋ค.
์์ ์ธ๊ธํ ๋ฌธ์ ๋ค์ Context ๋ฅผ ์ฌ์ฉํ๋ฉด ๊น๋ํ๊ฒ ํด๊ฒฐํ ์ ์์ต๋๋ค.
Context ์ฌ์ฉ๋ฒ
Context ๋ ๋ฆฌ์กํธ ํจํค์ง์์ createContext ๋ผ๋ ํจ์๋ฅผ ๋ถ๋ฌ์์ ๋ง๋ค ์ ์์ต๋๋ค.
import { createContext } from 'react';
const MyContext = createContext();
Context ๊ฐ์ฒด ์์๋ Provider๋ผ๋ ์ปดํฌ๋ํธ๊ฐ ๋ค์ด์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ , ๊ทธ ์ปดํฌ๋ํธ๊ฐ์ ๊ณต์ ํ๊ณ ์ ํ๋ ๊ฐ์ value ๋ผ๋ Props๋ก ์ค์ ํ๋ฉด ์์ ์ปดํฌ๋ํธ๋ค์์ ํด๋น ๊ฐ์ ๋ฐ๋ก ์ ๊ทผ์ ํ ์ ์์ต๋๋ค.
function App() {
return (
<MyContext.Provider value="Hello World">
<GrandParent />
</MyContext.Provider>
);
}
์ด๋ ๊ฒ ํ๋ฉด, ์ํ๋ ์ปดํฌ๋ํธ์์ useContext ๋ผ๋ Hook์ ์ฌ์ฉํ์ฌ Context์ ๋ฃ์ ๊ฐ์ ๋ฐ๋ก ์ ๊ทผํ ์ ์์ต๋๋ค. ํด๋น Hook์ ์ธ์์๋ createContext๋ก ๋ง๋ MyContext๋ฅผ ๋ฃ์ต๋๋ค.
import { createContext, useContext } from 'react';
function Message() {
const value = useContext(MyContext);
return <div>Received: {value}</div>;
}
์ด๋ ๊ฒ ํ๋ฉด ์ค๊ฐ ์ค๊ฐ ์ฌ๋ฌ ์ปดํฌ๋ํธ๋ฅผ ๊ฑฐ์ณ ์ ๋ฌํ๋ Props๋ฅผ ์ง์์ฃผ์ด๋ ๋์ง์. ์ด์ ์ ์ฒด ์ฝ๋๋ฅผ ํ์ธํด๋ณผ๊น์?
import { createContext, useContext } from 'react';
const MyContext = createContext();
function App() {
return (
<MyContext.Provider value="Hello World">
<GrandParent />
</MyContext.Provider>
);
}
function GrandParent() {
return <Parent />;
}
function Parent() {
return <Child />;
}
function Child() {
return <GrandChild />;
}
function GrandChild() {
return <Message />;
}
function Message() {
const value = useContext(MyContext);
return <div>Received: {value}</div>;
}
export default App;
์ด๋ค๊ฐ์, ํจ์ฌ ๊น๋ํ์ฃ ?
์๊น์ ์ ๋ดค๋ ๋๋ฒ์งธ ์ฝ๋ ์์์๋ Context๋ฅผ ์ ์ฉํ ์์๋ฅผ ํ์ธํด ๋ด ์๋ค.
import { createContext, useContext } from 'react';
const MyContext = createContext();
function App() {
return (
<MyContext.Provider value="Hello World">
<AwesomeComponent />
</MyContext.Provider>
);
}
function AwesomeComponent() {
return (
<div>
<FirstComponent />
<SecondComponent />
<ThirdComponent />
</div>
);
}
function FirstComponent() {
const value = useContext(MyContext);
return <div>First Component says: "{value}"</div>;
}
function SecondComponent() {
const value = useContext(MyContext);
return <div>Second Component says: "{value}"</div>;
}
function ThirdComponent() {
const value = useContext(MyContext);
return <div>Third Component says: "{value}"</div>;
}
export default App;
AwesomeComponent์์ ๋ด๋ถ์ ๊ฐ ์ปดํฌ๋ํธ๋ค์๊ฒ Props๋ฅผ ์ค์ ํด์ฃผ๋ ๊ฒ์ด ์๋๋ผ, ๊ฐ ์ปดํฌ๋ํธ์์ ์ํ๋ ๊ฐ์ useContext๋ก ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ผ๋ก ๋ฐ๋์์ต๋๋ค.
์ ์ฝ๋์ฒ๋ผ ๋ง์ฝ Context๊ฐ ์ฌ๋ฌ ์ปดํฌ๋ํธ์์ ์ฌ์ฉ๋๊ณ ์๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ปค์คํ Hook์ ๋ง๋ค์ด์ ์ฌ์ฉํ๋๊ฒ๋ ์ ๋ง ์ข์ ๋ฐฉ๋ฒ์ ๋๋ค.
Sources
https://blog.bitsrc.io/what-is-debounce-in-javascript-a2b8e6157a5a
What is Debounce in JavaScript?
In this article, I will cover one of the interesting topics of JavaScript Debounce Function and share with you how you can use it to…
blog.bitsrc.io
https://react.vlpt.us/basic/20-useReducer.html
20. useReducer ๋ฅผ ์ฌ์ฉํ์ฌ ์ํ ์ ๋ฐ์ดํธ ๋ก์ง ๋ถ๋ฆฌํ๊ธฐ · GitBook
20. useReducer ๋ฅผ ์ฌ์ฉํ์ฌ ์ํ ์ ๋ฐ์ดํธ ๋ก์ง ๋ถ๋ฆฌํ๊ธฐ ์ด ํ๋ก์ ํธ์์ ์ฌ์ฉ๋ ์ฝ๋๋ ๋ค์ ๋งํฌ์์ ํ์ธ ํ ์ ์์ต๋๋ค. useReducer ์ดํดํ๊ธฐ ์ฐ๋ฆฌ๊ฐ ์ด์ ์ ๋ง๋ ์ฌ์ฉ์ ๋ฆฌ์คํธ ๊ธฐ๋ฅ์์์ ์ฃผ
react.vlpt.us
https://velog.io/@velopert/react-context-tutorial
๋ค๋ฅธ ์ฌ๋๋ค์ด ์ ์๋ ค์ฃผ๋ ๋ฆฌ์กํธ์์ Context API ์ ์ฐ๋ ๋ฐฉ๋ฒ
์ฌ๋ฌ๋ถ, ๋ฆฌ์กํธ๋ก ์น ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ ํ๋ฉด์ Context API๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ๊ณ ๊ณ์ ๊ฐ์? ๊ณผ๊ฑฐ์๋ ๊ด๋ จ ํฌ์คํธ๋ฅผ ์์ฑํ์ ์ด ์๊ธด ํ์ง๋ง, ์ง๋ ๋ช ๋ ๊ฐ Context๋ฅผ ์ฌ์ฉํ๋ฉด์ ์ต๋ํ๊ฒ๋ ํ๋ค์
velog.io