ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • react정리9
    카테고리 없음 2022. 8. 18. 10:49

    Custom hooks

    커스텀 혹은 커스텀 훅을 포함한 다른 리엑트 훅을 사용할 수 있다
    따라서 useState나 useReducer를 통해 관리하는 리엑트 상태를 활용할 수 있다
    useEffect등에도 접근할 수 있다

    프로그래밍 중 코드가 중복될 때 중복되는 코드를 갖는 함수를 만드는데 이것을 커스텀 훅으로 하려는 것

    커스텀 훅 만들기
    모든 훅을 독립된 파일에 저장하면 된다
    hooks라는 폴더를 먼저 만들고 components 폴더 옆에 둔다
    이 안에 use-counter.js 파일 생성 파일명은 사용자 설정
    상수로 만들고 함수 이름은 use로 시작해야한다

    import { useState, useEffect } from 'react';
    
    const useCountr = () => {
      //재사용하려는 로직 추가(중복되었던)
      //ForwardCounter에서 이것을 사용하는 것이 주 목적
      const [counter, setCounter] = useState(0);
    
      useEffect(() => {
        const interval = setInterval(() => {
          setCounter((prevCounter) => prevCounter + 1);
        }, 1000);
    
        return () => clearInterval(interval);
      }, []);
    };
    
    export default useCountr;

    커스텀 훅 사용

    import Card from "./Card";
    //custom hook import
    import useCounter from "../hooks/use-counter";
    
    const ForwardCounter = () => {
      //출력을 위해 커스텀 훅이 관리하는 counter상태에 접근해야 한다
      //커스텀 훅은 함수이므로 어떤것이든 반환할 수 있다
      //counter가 갖고 있는 것은 숫자이므로 숫자를 반환
      //counter의 상태를 반환 이 상태는 커스텀 훅이 설정하고 관리
      //따라서 ForwardCounter컴포넌트에서는 반환되는 값을 이용할 수 있다
      //counter을 상수로 지정하고 이를 useCounter에 할당한다
      
      const counter = useCounter();
      // const [counter, setCounter] = useState(0);
    
      // useEffect(() => {
      //   const interval = setInterval(() => {
      //     setCounter((prevCounter) => prevCounter + 1);
      //   }, 1000);
    
      //   return () => clearInterval(interval);
      // }, []);
    
      return <Card>{counter}</Card>;
    };
    
    export default ForwardCounter;

    BackwardCounter.js에서도 사용하기 위해 수정해준다

    use-couneter.js

    import { useState, useEffect } from "react";
    
    //true면 덧셈 false면 뺄셈을 하게 한다
    //setInterval안에서는 forwards가 참인지 확인해서 참이면 setCounter를 덧셈으로 갱신
    //forwards를 의존성으로 추가하여 의존성이 발생할 때마다 useEffect함수가 재실행하게 한다
    //BackwardCounter로 가서 useCounter를 import
    
    const useCounter = (forwards = true) => {
    
      const [counter, setCounter] = useState(0);
    
      useEffect(() => {
        const interval = setInterval(() => {
          if (forwards) {
            setCounter((prevCounter) => prevCounter + 1);
          } else {
            setCounter((prevCounter) => prevCounter - 1);
          }
        }, 1000);
    
        return () => clearInterval(interval);
      }, [forwards]);
    };
    
    export default useCountr;

    BackwardCounter.js

    import Card from "./Card";
    import useCountr from "../hooks/use-counter";
    
    //useCounter불러온 후 false를 인자로 전달
    const BackwardCounter = () => {
      const counter = useCountr(false);
      
      return <Card>{counter}</Card>;
    };
    
    export default BackwardCounter;

    현실적인 예시

    NewTask.js

    import { useState } from "react";
    
    import Section from "../UI/Section";
    import TaskForm from "./TaskForm";
    
    const NewTask = (props) => {
      const [isLoading, setIsLoading] = useState(false);
      const [error, setError] = useState(null);
    
      //NewTask 에서는 App.js와 달리 POST로 요청을 보내고 데이터를 추가하며 응답에 대한 로직 다른 형태
      //두 컴포넌트 모두 로딩과 오류 상태를 관리하고 있으며 두 가지의 상태를 동일한 방법으로 설정한다
      //또한 오류를 다루는 로직도 같다 이렇게 같은 코드가 중복되어 재사용 코드가 존재
      
      //firebase에서 가져온 url을 추가하고 tasks.json남겨둔다
      const enterTaskHandler = async (taskText) => {
        setIsLoading(true);
        setError(null);
        try {
          const response = await fetch(
            "https://react-app-ba5b1-default-rtdb.firebaseio.com/tasks.json",
            {
              method: "POST",
              body: JSON.stringify({ text: taskText }),
              headers: {
                "Content-Type": "application/json",
              },
            }
          );
    
          if (!response.ok) {
            throw new Error("Request failed!");
          }
    
          const data = await response.json();
    
          const generatedId = data.name; // firebase-specific => "name" contains generated id
          const createdTask = { id: generatedId, text: taskText };
    
          props.onAddTask(createdTask);
        } catch (err) {
          setError(err.message || "Something went wrong!");
        }
        setIsLoading(false);
      };
    
      return (
        <Section>
          <TaskForm onEnterTask={enterTaskHandler} loading={isLoading} />
          {error && <p>{error}</p>}
        </Section>
      );
    };
    
    export default NewTask;

    use-http.js

    import { useState } from "react";
    
    const useHttp = (requestConfig, applyData) => {
      const [isLoading, setIsLoading] = useState(false);
      const [error, setError] = useState(null);
    
      const sendRequest = async () => {
        setIsLoading(true);
        setError(null);
        try {
          const response = await fetch(requestConfig.url, {
            method: requestConfig.method,
            headers: requestConfig.headers,
            body: JSON.stringify(requestConfig.body),
          });
    
          if (!response.ok) {
            throw new Error("Request failed!");
          }
          //훅에서 applyData함수로 데이터를 전달
          const data = await response.json();
          applyData(data);
        } catch (err) {
          setError(err.message || "Something went wrong!");
        }
        setIsLoading(false);
      };
      return {
        isLoading,
        error,
        sendRequest,
      };
    };
    
    export default useHttp;

    사용자 정의 훅 사용

    import React, { useEffect, useState } from "react";
    
    import Tasks from "./components/Tasks/Tasks";
    import NewTask from "./components/NewTask/NewTask";
    import useHttp from "./hooks/use-http";
    
    function App() {
      const [tasks, setTasks] = useState([]);
    
      const { isLoading, error, sendRequest: fetchTasks } = useHttp();
    
      useEffect(() => {
        const transformTasks = (tasksObj) => {
          const loadedTasks = [];
    
          for (const taskKey in tasksObj) {
            loadedTasks.push({ id: taskKey, text: tasksObj[taskKey].text });
          }
    
          setTasks(loadedTasks);
        };
    
        fetchTasks(
          {
            url: "https://react-app-ba5b1-default-rtdb.firebaseio.com/tasks.json",
          },
          transformTasks
        );
      }, [fetchTasks]);
    
      const taskAddHandler = (task) => {
        setTasks((prevTasks) => prevTasks.concat(task));
      };
    
      return (
        <React.Fragment>
          <NewTask onAddTask={taskAddHandler} />
          <Tasks
            items={tasks}
            loading={isLoading}
            error={error}
            onFetch={fetchTasks}
          />
        </React.Fragment>
      );
    }
    
    export default App;

    NewtTask.js

    import Section from "../UI/Section";
    import TaskForm from "./TaskForm";
    import useHttp from "../../hooks/use-http";
    
    const NewTask = (props) => {
      //useHttp불러오고 적어준 후 객체 반환
      const { isLoading, error, sendRequest: sendTaskRequest } = useHttp();
    
      //함수 생성
      const createTask = (taskText, taskData) => {
        const generatedId = taskData.name; // firebase-specific => "name" contains generated id
        const createdTask = { id: generatedId, text: taskText };
    
        props.onAddTask(createdTask);
      };
    
      const enterTaskHandler = async (taskText) => {
        //폼이 제출될 때마다 sendTaskRequest호출
        sendTaskRequest(
          {
            url: "https://react-app-ba5b1-default-rtdb.firebaseio.com/tasks.json",
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: { text: taskText },
          },
          createTask.bind(null, taskText)
        );
      };
    
      return (
        <Section>
          <TaskForm onEnterTask={enterTaskHandler} loading={isLoading} />
          {error && <p>{error}</p>}
        </Section>
      );
    };
    
    export default NewTask;
Designed by Tistory.