๐Ÿ–ฅFrontEnd/React

useReducer, useReducer ์˜ ๊ตฌ์กฐ

hellohailie 2022. 7. 25. 00:57

 

useReducer

 

state ๊ด€๋ฆฌ๋ฅผ ๋„์™€์ค€๋‹ค. ๋” ๋ณต์žกํ•œ state์— ํŠนํžˆ ์œ ์šฉํ•˜๋‹ค. 

 

์—ฌ๋Ÿฌ state๊ฐ€ ํ•จ๊ป˜ ์†ํ•ด ์žˆ๋Š” ๊ฒฝ์šฐ

์—ฌ๋Ÿฌ state๊ฐ€ ๊ฐ™์ด ๋ฐ”๋€Œ๊ฑฐ๋‚˜ ์„œ๋กœ ๊ด€๋ จ๋œ ๊ฒฝ์šฐ

 

๐Ÿ‘‡

useState๋‚˜ ๊ฑฐ๊ธฐ์—์„œ ์–ป์€ state๋Š” ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ๊ด€๋ฆฌ๊ฐ€ ์–ด๋ ค์›Œ์ง€๊ฑฐ๋‚˜ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฝ๋‹ค. 

๐Ÿ‘‡

์ด๋Ÿฐ ๊ฒฝ์šฐ useState ๋Œ€์‹  useReducer๋ฅผ ์“ธ ์ˆ˜ ์žˆ๋‹ค. 

 

useReducer๋Š” ๋” ๊ฐ•๋ ฅํ•œ state ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์“ด๋‹ค. (๊ทธ๋ ‡๋‹ค๊ณ  ํ•ญ์ƒ ์‚ฌ์šฉ์€ x)


useReducer๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํ•ญ์ƒ ์ข‹์„๋•Œ

 

๋‹ค๋ฅธ state๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” state๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ฉด ํ•˜๋‚˜์˜ state๋กœ ๋ณ‘ํ•ฉํ•˜๋Š” ๊ฒƒ๋„ ์ข‹๋‹ค. 


 

useReducer ์˜ ๊ตฌ์กฐ

 

const [state, dispatchFn] = useReducer(reducerFn, initialState, initFn);

state = ์ตœ์‹  state ์Šค๋ƒ…์ƒท // ์ด๊ฑด state ๊ด€๋ฆฌ ๋งค์ปค๋‹ˆ์ฆ˜์ด๋‹ค. 

dispatchFn = state ์Šค๋ƒ…์ƒท์„ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜

 

useState์™€ ์ฐจ์ด์ 

์ƒˆ๋กœ์šด state ๊ฐ’์„ ์„ค์ •ํ•˜๋Š” ๋Œ€์‹  ์•ก์…˜์„ ๋””์ŠคํŒจ์น˜ํ•œ๋‹ค. 

 

reducerFn = ๊ทธ ์•ก์…˜์€ useReducer์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๊ฐ€ ์†Œ๋น„ํ•  ๊ฑด๋ฐ, reducerFn์ด๋ผ๊ณ  ํ•œ๋‹ค.

์ตœ์‹  state ์Šค๋ƒ…์ƒท์„ ์ž๋™์œผ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜์ด๋‹ค. ์™œ๋ƒ๋ฉด ๋ฆฌ์•กํŠธ๊ฐ€ ์ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด ํ•จ์ˆ˜๋Š” ๋””์ŠคํŒจ์น˜๋œ ์•ก์…˜์„ ๊ฐ€์ ธ์˜จ๋‹ค. ์™œ๋ƒ๋ฉด ๋ฆฌ์•กํŠธ๋Š” ์ƒˆ ์•ก์…˜์ด ๋””์ŠคํŒจ์น˜๋  ๋•Œ๋งˆ๋‹ค ์ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ด ํ•จ์ˆ˜๋Š” ๋ฆฌ์•กํŠธ๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ์ตœ์‹ ์˜ state ์Šค๋ƒ…์ƒท์„ ๊ฐ€์ ธ์˜จ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜ ์‹คํ–‰์„ ํŠธ๋ฆฌ๊ฑฐ ํ•˜๋Š” ๋””์ŠคํŒจ์น˜๋œ ์•ก์…˜์„ ๊ฐ€์ ธ์˜จ๋‹ค. 

 

& ์ƒˆ๋กœ์šด ์—…๋ฐ์ดํŠธ๋œ state๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. (useState ํ›…์˜ ํ•จ์ˆ˜ ํผ๊ณผ ์•ฝ๊ฐ„ ๋น„์Šทํ•จ)

 

initialState = ์ดˆ๊ธฐ state

initFn = ์ดˆ๊ธฐ state๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ํ•จ์ˆ˜ (์ดˆ๊ธฐ state๊ฐ€ ์ข€ ๋” ๋ณต์žกํ•œ ๊ฒฝ์šฐ)

 


useReducer๋ฅผ ์ž…๋ ฅ๊ฐ’๊ณผ ์œ ํšจ์„ฑ์„ ๊ฒฐํ•ฉํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. (id, ๋น„๋ฐ€๋ฒˆํ˜ธ)

๊ทธ๋ฆฌ๊ณ  ์ „์ฒด ํผ state๋ฅผ ๊ด€๋ฆฌํ•˜๋Š”๋ฐ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

์ฆ‰, ์ „๋ถ€ ๋“ค์–ด์žˆ๋Š” ํฐ ํผ state ํ•œ ๊ฐœ ๋˜๋Š” ์ž‘์€ state ์—ฌ๋Ÿฌ ๊ฐœ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

 

์•„๋ž˜ ์˜ˆ์‹œ์—์„œ๋Š” ๊ฐ„๋‹จํžˆ emailState๋งŒ useReducer๋กœ ๊ด€๋ฆฌํ•ด๋ณด์ž!

 

๊ธฐ์กด ์ฝ”๋“œ

import React, { useState, useEffect } from "react";

import Card from "../UI/Card/Card";
import classes from "./Login.module.css";
import Button from "../UI/Button/Button";

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(() => {
    const identifier = setTimeout(() => {
      console.log("checking form validity!!");
      setFormIsValid(
        enteredEmail.includes("@") && enteredPassword.trim().length > 6
      );
    }, 500);

    return () => {
      console.log("clean up!!!!");
      clearTimeout(identifier);
    }; // ์ด๊ฑด clean up ํ•จ์ˆ˜์ด๋‹ค.
  }, [enteredEmail, enteredPassword]);
  //์—ฌ๊ธฐ์„œ setFormIsValid()๋ฅผ ์žฌํ‰๊ฐ€ํ•˜๊ณ  ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ state ์„ค์ • ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•ด์•ผ ํ•œ๋‹ค. ์ด๋ฉ”์ผ ๋ฐ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ์˜ ๋ชจ๋“  ํ‚ค ์ž…๋ ฅ์— ๋Œ€ํ•ด์„œ!
  //setTimeout ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์„œ 500๋ฐ€๋ฆฌ์ดˆ ํ›„์—๋งŒ ์ด ์ž‘์—… (์œ ํšจ์„ฑ ๊ฒ€์‚ฌ)์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

  const emailChangeHandler = (event) => {
    setEnteredEmail(event.target.value);
  };

  const passwordChangeHandler = (event) => {
    setEnteredPassword(event.target.value);
  };

  const validateEmailHandler = () => {
    setEmailIsValid(enteredEmail.includes("@"));
  };

  const validatePasswordHandler = () => {
    setPasswordIsValid(enteredPassword.trim().length > 6);
  };

  const submitHandler = (event) => {
    event.preventDefault();
    props.onLogin(enteredEmail, enteredPassword);
  };

 

useReducer ์‚ฌ์šฉํ•œ ์ฝ”๋“œ

import React, { useState, useEffect, useReducer } from "react";

import Card from "../UI/Card/Card";
import classes from "./Login.module.css";
import Button from "../UI/Button/Button";

const emailReducer = (state, action) => {
  if (action.type === "USER_INPUT") {
    return { value: action.val, isValid: action.val.includes("@") }; // "USER_INPUT" ์•ก์…˜์„ ๋ฐ›์„ ๋•Œ๋งˆ๋‹ค ์—ฌ๊ธฐ์„œ value์™€ isValid๋ฅผ ๋ชจ๋‘ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  }
  if (action.type === "INPUT_BLUR") {
    return { value: state.value, isValid: state.value.includes("@") };
  }
  return { value: "", isValid: false };
};

const passwordReducer = (state, action) => {
  if (action.type === "USER_INPUT") {
    return { value: action.val, isValid: action.val.trim().length > 6 }; // "USER_INPUT" ์•ก์…˜์„ ๋ฐ›์„ ๋•Œ๋งˆ๋‹ค ์—ฌ๊ธฐ์„œ value์™€ isValid๋ฅผ ๋ชจ๋‘ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  }
  if (action.type === "INPUT_BLUR") {
    return { value: state.value, isValid: state.value.trim().length > 6 };
  }
  return { value: "", isValid: false };
};

// ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜๋Š” ์ปดํฌ๋„ŒํŠธ ํ•จ์ˆ˜ ๋ฐ–์— ๋งŒ๋“ฌ. ์™œ๋ƒ๋ฉด ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ๋Š” ์ปดํฌ๋„ŒํŠธ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ๋งŒ๋“ค์–ด์ง„ ์–ด๋–ค ๋ฐ์ดํ„ฐ๋กœ ํ•„์š”ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
// ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ์š”์ฒญ๋˜๊ณ  ์‚ฌ์šฉ๋˜๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋Š” ๋ฆฌ์•กํŠธ๊ฐ€ ์ด ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•  ๋•Œ ์ž๋™์œผ๋กœ ์ด ํ•จ์ˆ˜๋กœ ์ „๋‹ฌ๋œ๋‹ค.

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 [emailState, dispatchEmail] = useReducer(emailReducer, {
    value: "",
    isValid: null,
  });

  const [passwordState, dispatchPassword] = useReducer(passwordReducer, {
    value: "",
    isValid: null,
  });

  useEffect(() => {
    console.log("EFFECT RUNNING");

    return () => {
      console.log("EFFECT CLEANUP");
    };
  }, []);

  // useEffect(() => {
  //   const identifier = setTimeout(() => {
  //     console.log('Checking form validity!');
  //     setFormIsValid(
  //       enteredEmail.includes('@') && enteredPassword.trim().length > 6
  //     );
  //   }, 500);

  //   return () => {
  //     console.log('CLEANUP');
  //     clearTimeout(identifier);
  //   };
  // }, [enteredEmail, enteredPassword]);

  //์—ฌ๊ธฐ์„œ setFormIsValid()๋ฅผ ์žฌํ‰๊ฐ€ํ•˜๊ณ  ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ state ์„ค์ • ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•ด์•ผ ํ•œ๋‹ค. ์ด๋ฉ”์ผ ๋ฐ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ์˜ ๋ชจ๋“  ํ‚ค ์ž…๋ ฅ์— ๋Œ€ํ•ด์„œ!
  //setTimeout ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์„œ 500๋ฐ€๋ฆฌ์ดˆ ํ›„์—๋งŒ ์ด ์ž‘์—… (์œ ํšจ์„ฑ ๊ฒ€์‚ฌ)์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

  const emailChangeHandler = (event) => {
    dispatchEmail({ type: "USER_INPUT", val: event.target.value });

    setFormIsValid(event.target.value.includes("@") && passwordState.isValid);
  };

  const passwordChangeHandler = (event) => {
    // setEnteredPassword(event.target.value);
    dispatchPassword({ type: "USER_INPUT", val: event.target.value });

    //setFormIsValid(enteredEmail.includes("@") && event.target.value.trim().length > 6)
    //setFormIsValid(emailState.value.includes("@") && event.target.value.trim().length > 6)
    // useReducer๋ฅผ ์ผ๊ธฐ ๋•Œ๋ฌธ์— ์ž…๋ ฅ๋œ ๊ฐ’์„ ์ €์žฅํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— emailState.value๋ผ๊ณ  ์”€

    setFormIsValid(emailState.isValid && event.target.value.trim().length > 6);
    // isValid ํ•„๋“œ๊ฐ€ ์žˆ์–ด์„œ ์žฌ๊ฒ€์ฆ ํ•˜๋Š” ๋Œ€์‹  emailState.isValid๊ฐ€ true์ธ์ง€ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  };

  const validateEmailHandler = () => {
    dispatchEmail({ type: "INPUT_BLUR" }); // ์—ฌ๊ธฐ์„œ๋Š” ์ธํ’‹์ด ํฌ์ปค์Šค๋ฅผ ์žƒ์—ˆ๋‹ค๋Š” ๊ฒƒ๋งŒ ์ค‘์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€ํ•ด์•ผํ•˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋‹ค.
  };

  const validatePasswordHandler = () => {
    dispatchPassword({ type: "INPUT_BLUR" });
  };

  const submitHandler = (event) => {
    event.preventDefault();
    props.onLogin(emailState.value, passwordState.value);
  };

  return (
    <Card className={classes.login}>
      <form onSubmit={submitHandler}>
        <div
          className={`${classes.control} ${
            emailState.isValid === 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.control} ${
            passwordState.isValid === false ? classes.invalid : ""
          }`}
        >
          <label htmlFor='password'>Password</label>
          <input
            type='password'
            id='password'
            value={passwordState.value}
            onChange={passwordChangeHandler}
            onBlur={validatePasswordHandler}
          />
        </div>
        <div className={classes.actions}>
          <Button type='submit' className={classes.btn} disabled={!formIsValid}>
            Login
          </Button>
        </div>
      </form>
    </Card>
  );
};

export default Login;

 

 

๐Ÿ˜ƒ ์ž˜๋ชป๋œ ๊ฐœ๋… ์ „๋‹ฌ์ด ์žˆ๋‹ค๋ฉด ๋Œ“๊ธ€ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์ €์˜ ์„ฑ์žฅ์— ํฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค๐Ÿค“