본문 바로가기
📚 시리즈/- Jenkins와 Webhook을 이용한 CICD

[Webhook을 이용하여 CI CD 구성하기] - 프론트엔드 개발하기

by Wonit 2021. 8. 30.

해당 글은 Jenkins와 Github Webhook을 이용한 CICD 파이프라인 구성하기 시리즈 입니다. 자세한 사항은 아래 링크를 참고해주세요!

 

만약 해당 실습 내용의 코드가 궁금하다면 프로젝트 깃허브 에서 확인할 수 있습니다.

 

 

 

순서

  • UI 만들기
  • 통신 로직 구현하기

 

프론트엔드 개발하기

 

이번 편에서는 CICD 파이프라인을 위해서 필요한 프론트엔드 애플리케이션을 개발해보려 한다.

 

우선 사전에 시리즈를 시작하며에서 이야기 했듯, 프론트엔드는 리액트를 이용하려 구성할 예정이다.

 

만약 본인이 리액트에 대해서 알지 못한다면 todo-with-cicd github 에서 코드를 복사하여 사용해도 무방합니다.

 

컨셉은 TodoList이다.

 

우리가 만들 UI를 확인해보자

 

 

다음과 같이 구성이 될 것이며, Todo List 의 Item 들은 백엔드로부터 받아오고 추가할 수 있도록 할 예정이다.

 

UI 만들기

 

위의 UI 에서 컴포넌트를 분리시키면 다음과 같을 것이다.

 

 

총 5개의 컴포넌트가 있고, 각각 다음과 같다.

 

  • TodoPresenter.js : Container-Presenter Pattern 의 Presenter 컴포넌트
  • TodoInput.js : 새로운 Todo 를 입력할 수 있는 input 컴포넌트
  • TodoList.js : Todo Item 들이 모여있는 컴포넌트
  • TodoItem.js : Todo 이름과 삭제 버튼이 있는 컴포넌트
  • App.js : : TodoPresenter의 Container 컴포넌트

 

이제 하나 하나 UI를 구성해보도록 하자.

 

1. 프로젝트 세팅

 

리액트 앱을 개발하기 위해서 우리는 facebook 이 만든 boiler plate project 인 create-react-app 을 이용할 것이다.

 

프로젝트 디렉토리 하나를 만든 뒤, 다음과 같은 커맨드를 입력하자.

 

참고로 해당 커맨드는 node 가 설치되어 있어야 가능하고 npm 을 패키지 관리자로 이용할 것이다.

 

// brew
$ brew install node

$ npx create-react-app frontend

// 혹은 create-react-app 이 global 하게 설치되어있다면

$ create-react-app frontend

 

그리고 우리가 사용할 npm 모듈을 설치할 것인데, /frontend 디렉토리 아래로 가서, 아래의 명령어를 입력하자.

 

UI는 styled-components 를 이용할 것이고 통신 모듈로는 axios 를 이용할 것이다.

 

npm install 은 package.json 이 위치한 경로에서 수행해야 한다

 

$ npm install -y styled-components // 컴포넌트 스타일링
$ npm install -y axios // js 통신 모듈

 

그럼 프로젝트 디렉토리에 /frontend 아래에 public, src 와 같은 디렉토리가 생긴다.

 

/src 디렉토리 아레에 있는 js 파일 중에서 index.jsApp.js 를 제외한 나머지를 지우고 index.js, App.js 에서 삭제된 파일들의 의존성을 모두 없애준다.

 

2. 디렉토리 구조 및 파일 구조

 

/src 디렉토리 아래에서 /components 라는 디렉토리를 생성하고 다음과 같은 파일들을 만들자

 

  • Presenter 컴포넌트
    • /components/TodoInput.js
    • /components/TodoItem.js
    • /components/TodoList.js
    • /components/TodoPresenter.js
  • Container 컴포넌트
    • /App.js
  • React Dom Render 파일
    • /index.js
  • 통신 모듈과 관련된 파일
    • /util/SERVER.js
    • /util/service.js

 

그럼 다음과 같은 구조가 될 것이다.

 

├── package-lock.json
├── package.json
├── public
├── src
│   ├── App.js
│   ├── components
│   │   ├── TodoInput.js
│   │   ├── TodoItem.js
│   │   ├── TodoList.js
│   │   └── TodoPresenter.js
│   ├── index.js
│   └── util
│       ├── SERVER.js
│       └── service.js
└── yarn.lock

 

이제 각각의 컴포넌트와 UI 그리고 통신 모듈을 개발해보자.

 

3. TodoInput.js 개발하기

 

TodoInput 은 해야할 일을 입력하고 해당 text를 서버로 저장 요청을 보내는 컴포넌트이다.

 

 

우리가 앞서 만든 TodoInput.js 파일에 다음과 같이 적어보자

 

import React, { useState } from "react";
import styled from "styled-components";

const Input = styled.input`
  padding: 12px;
  border-radius: 4px;
  border: 1px solid #dee2e6;
  width: 100%;
  outline: none;
  font-size: 14px;
  box-sizing: border-box;
`;

const InputWrapper = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
`;

const Button = styled.button`
  padding: 12px;
  margin: 25px;
  border-radius: 4px;
  border: 1px solid #dee2e6;
  outline: none;
  font-size: 14px;
  box-sizing: border-box;
  cursor: pointer;

  transition: 0.5s ease;

  :hover {
    background-color: #fff;
    border-color: #59b1eb;
    color: #59b1eb;
  }
`;

const TodoInput = ({ addAndSetTodos }) => {
  const [text, setText] = useState("");

  const handleChangeTextBox = (e) => {
    const { value } = e.target;

    setText(value);
  };

  const handleOnKeyPress = (e) => {
    if (e.key === "Enter") {
      handleOnClickAddButton();
    }
  };

  const handleOnClickAddButton = () => {
    if (text === "") {
      alert("값을 입력하세요");
    } else {
      addAndSetTodos(text);
      setText("");
    }
  };

  return (
    <>
      <InputWrapper>
        <Input
          placeholder="새로운 Todo 를 입력하세요"
          value={text}
          onChange={handleChangeTextBox}
          onKeyPress={handleOnKeyPress}
        />
        <Button onClick={handleOnClickAddButton}>추가 하기</Button>
      </InputWrapper>
    </>
  );
};

export default TodoInput;

 

여기에 보이는 addAndSetTodos 함수는 추후 TodoPresenter 의 Container 인 App.js 에서 만들어주고 내려줄 함수인데, 먼저 버튼의 onClick에 바인딩 시켜주도록 하자

 

4. TodoItem.js 개발하기

 

TodoItem 은 App.js 에서 서버로 요청을 보내 받아온 Todo 객체를 받아 하나의 Todo Item 으로 보여지게 할 컴포넌트이다.

 

 

다음과 같이 코드를 작성해보자.

 

import React from "react";
import styled from "styled-components";

const Container = styled.div`
  width: 100%;

  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 14px;
`;

const List = styled.li`
  list-style: none;
  margin: 0 15px;
`;

const Button = styled.button`
  margin: 0 15px;
  cursor: pointer;
  border-radius: 4px;
  border: 1px solid #dee2e6;
  outline: none;
  font-size: 12px;
  box-sizing: border-box;

  transition: 0.5s ease;

  :hover {
    background-color: #fff;
    border-color: #59b1eb;
    color: #59b1eb;
  }
`;

const TodoItem = ({ todo, deleteAndSetTodos }) => {
  const { id, content } = todo;

  console.log(id);

  return (
    <Container>
      <List>[ {content} ]</List>
      <Button onClick={() => deleteAndSetTodos(id)}>삭제</Button>
    </Container>
  );
};

export default TodoItem;

 

deleteAndSetTodos는 서버에게 해당 TodoItem 컴포넌트가 가지고 있는 id를 기반으로 특정 todo를 삭제해달라는 요청을 보낼 때 사용하는데, 이도 마찬가지로 App.js 에서 만들어서 props 로 내려줄 것이기 때문에 일단은 넣어주자

 

5. TodoList.js

 

App.js 에서 컴포넌트가 브라우저에 mount 될 때 서버로 저장된 todo 들을 모두 조회하는 요청을 보낸다.

 

그러면 서버는 Todo의 객체 배열을 반환하게 된다.

 

해당 배열을 TodoList 에게 전달해주게 되고 TodoList는 배열을 map 으로 순회하며 각각의 배열 원소들을 TodoItem.js 의 Props 로 내려주는 역할을 수행한다.

 

 

다음과 같이 구성을 해보자.

 

import React from "react";
import TodoItem from "./TodoItem";

const TodoList = ({ todos, deleteAndSetTodos }) => {
  return (
    <>
      {todos.map((todo, index) => (
        <TodoItem
          key={index}
          todo={todo}
          deleteAndSetTodos={deleteAndSetTodos}
        />
      ))}
    </>
  );
};

export default TodoList;

 

TodoItem 에서 사용할 deleteAndSetTodo 함수도 역시 받아서 TodoItem 으로 내려줘야 한다.

 

6. TodoPresenter.js 개발하기

 

이제 모든 UI 컴포넌트는 구성이 완료되었고, UI들을 하나로 모아줄 Presenter 를 구성해보도록 하자.

 

import React from "react";
import styled from "styled-components";

import TodoInput from "./TodoInput";
import TodoList from "./TodoList";

export const Background = styled.div`
  height: 100vh;
  width: 100vw;

  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  background: #e9ecef;
`;

export const Container = styled.div`
  width: 512px;
  height: 768px;

  background: white;
  border-radius: 16px;
  box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.04);

  margin: 0 auto;

  margin-top: 96px;
  margin-bottom: 32px;
  display: flex;
  flex-direction: column;

  align-items: center;
`;

export const Title = styled.h1`
  font-size: 2rem;
  color: #343a40;
`;

const Subtitle = styled.h2`
  font-size: 1rem;
  color: gray;
  padding-bottom: 30px;
  width: 100%;
  text-align: center;
  border-bottom: 1px solid;
`;

const TodoPresenter = ({ todos, addAndSetTodos, deleteAndSetTodos }) => {
  return (
    <Background>
      <Container>
        <Title>평범한 Todo List</Title>
        <Subtitle>근데 이제, cicd pipeline 을 곁들인</Subtitle>
        <TodoInput addAndSetTodos={addAndSetTodos} />
        <TodoList todos={todos} deleteAndSetTodos={deleteAndSetTodos} />
      </Container>
    </Background>
  );
};

export default TodoPresenter;

 

만약 리액트에 자신이 없거나 잘 모른다면 그냥 복사붙여넣기를 해도 충분하다!

 

7. App.js 개발하기

 

App.js 에서는 TodoPresenter 에서 사용할 모든 함수와 state 들 그리고 props 들을 관리해줘야 한다.

 

즉, 상태에 관한 로직이 들어가는 부분이다.

 

다음과 같이 구성해보자!

 

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

import TodoPresenter from "./components/TodoPresenter";

import { fetchTodos, addTodo, deleteTodo } from "./util/service";

const App = () => {
  const [todos, setTodos] = useState([]);

  const fetchAndSetTodos = async () => {
    const data = await fetchTodos();
    setTodos(data);
  };

  const addAndSetTodos = async (todo) => {
    const data = await addTodo(todo);
    setTodos(todos.concat(data));
  };

  const deleteAndSetTodos = async (id) => {
    const { data: removedTodo } = await deleteTodo(id);
    setTodos(todos.filter((todo) => todo.id !== removedTodo));
  };

  useEffect(() => {
    fetchAndSetTodos();
  }, []);

  return (
    <TodoPresenter
      todos={todos}
      addAndSetTodos={addAndSetTodos}
      deleteAndSetTodos={deleteAndSetTodos}
    />
  );
};

export default App;

 

이제 UI 개발은 완료되었다!

 

 

이제 통신 로직을 개발해보자

 

8. 통신 모듈 개발하기

 

앞서 우리는 util 디렉토리 아리에 다음 2개의 파일을 만들었다.

 

  1. SERVER.js
  2. service.js

 

SERVER.js 에서는 서버의 url 과 통신에 필요한 header 를 세팅해줄 것이고, service.js 에서 실제 통신 함수를 만들 것이다.

다음과 같이 구성하자

 

// SERVER.js
import axios from "axios";

export const SERVER = axios.create({
  baseURL: "http://127.0.0.1:8080",
  headers: {
    "Content-Type": "application/json",
  },
});

// service.js
import { SERVER } from "./SERVER";

export const fetchTodos = async () => {
  const { data } = await SERVER.get("/api/todos");

  return data;
};

export const addTodo = async (todo) => {
  const { data } = await SERVER.post("/api/todos", JSON.stringify(todo));

  return data;
};

export const deleteTodo = async (id) => {
  const data = await SERVER.delete("/api/todos/" + id);

  return data;
};

 

이렇게 프론트엔드 개발이 끝났다.

 

계속해서 강조하지만 이 시리즈는 프론트나 백의 개발 과정을 알려주는 것이 아니라 Jenkins를 이용하는 것이 목적이기 때문에 코드를 복붙해도 아무런 지장이 없다

 

만약 코드가 제대로 동작하지 않는다면 github 에 방문해서 확인할 수도 있으니 참고하자. github 주소는 상단에 있다

 

댓글0