상세 컨텐츠

본문 제목

프로그래머 도전기 101일차

프로그래머가 될거야!

by Choyee 2024. 1. 13. 22:38

본문

오늘은

 

오늘은 토요일 입니다

간단히 이력서 한장을 작성하고

공부를 하였습니다

React는 정말.. 가면 갈수록 어렵지만

공부 하면 할수록 최적화와 구조화 하는 방법을 통해

굉장히 신세대 언어이긴 하구나 새삼 느끼게 되었습니다

 

 

 

 

 

Spring 공부

 

sts3스프링MVC(레거시)-5.x버전 -> sts4스프링 부트(Spring Boot)

<<스프링부트>>
=> 3.x 버전
1. 톰캣 서버(내장) = 안정돼있음
2. 필수 라이브러리(내장) = pom.xml에 주입할 내용이 별로 없음
3. 배포 - jar

환경 설정 순서
1. jdk 17 이상 - 아줄시스템즈: open jdk 17(줄루)
2. STS4 설치
3. sts4->인코딩 설정, 글꼴 설정
4. Web Developer Tool 설치
   help -> market place -> web developer 검색 -> 두번째꺼 설치
   window -> enc -> css -> utf-8
                          html -> utf-8
                          jsp -> utf-8
              -> general -> workspace -> utf-8
Web, Jsp

** 프로젝트 생성
file -> new -> spring starter project 
setting 창 -> name -> jwboot
type -> maven
jar, 17버전 확인
group -> com.khit
package -> cohttp://m.khit.study
spring boot version -> 3.1.7
developer tool => Spring Boot DevTools, Lombok 체크
template engines -> thymeleaf 체크
web -> Spring Web 체크
resources


* static - 정적 컨텐츠 (css, images, js, sql)
* teamplates - 동적 컨텐츠(html) : controller(url)

* 설정 파일 : application.properties
                 (-> application.yml)

* 엔티티 - @Entity(db에 table을 생성하는 클래스)

@Entity
class Board {
   title
   content
}


=> table이 생긴다

*json - 대표 데이터 표준 (문자열 - 플랫폼에 독립적)

<api 서버 구성>
* rest api (web에서는 data가 왔다갔다 함)
=> json data 전송(문자열)
= 서버(컨트롤러, 서비스)에서 화면과 관련된 내용을 만드는 것이 아니라
   순수한 데이터만 전송하는 방식
   => 화면 없이 하는 데이터 전송 연습
        @RestController 필요



<JPA>
= Java Persistence Api
* DB와 연동하는 기술
1. JDBC 방식 - RDBMS
2. Spring MyBatis - RDBMS
3. ORM(Object Relational Mapping 객체 관계 매핑) 방식 - ODBMS
    - 하이버네이트 ORM에서 -> JPA로 발전
                                      
* JPA
= ORM을 보다 쉽게 사용할 수 있도록 표준화 시킨 것
=> 클래스(객체)를 생성하고(@Entity어노테이션 적용)
      -> DB에 table이 자동으로 생성됨
      -> SQL도 내부에서 제공됨

@Entity          
class BoardVO{
}


=> board 테이블이 생성됨

* 필수 라이브러리 주입
=> 프로젝트 우클릭 -> Spring -> Add Starter > pom.xml
- MySQL, Oracle, MSSQL 드라이버
- Spring Data JPA
=> classpath 설정 필요

* application.properties

# DataSource 설정 mysql connection
spring.datasource.driver-class-name=cohttp://m.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/bootdb?serverTime=Asia/Seoul
spring.datasource.username=springuser
spring.datasource.password=pwspring



스프링 MVC - SqlSessionTemplate
스프링 부트 - JPARepository(인터페이스) 상속
저장 - save
수정 - (데이터 수정 후)save
조회 - findAll(), findById()
삭제 - deleteById(id)




<스프링부트 게시판 프로젝트>
Boot(프레임워크) + JSP(화면개발) + JPA(DB연동기술)
- 의존성 주입 : jakarta jsp
- CRUD
- 글쓰기
- 글 목록
- 상세보기
- 수정/삭제


의존성 주입
jakarta.servlet.jsp
jakarta.servlet.jsp.jstl
org.glassfish.web
org.apache.tomcat.embed

jsp 설정
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

폴더 생성
webapp > WEB-INF > views > index.jsp

jdbc:mysql://127.0.0.1:3306/bootdb?serverTime=Asia/Seoul
127.0.0.1 <-> localhost


쿼리 메서드(Query Method)
= method의 이름으로 필요한 쿼리를 만들어 주는 기능을 함
=> find + (엔티티이름:생략가능) + By + 변수이름
ex) findByTitle() - 제목으로 검색
    findByTitleContaining() - 제목이 포함된 키워드로 검색
    findByAgeOrderByNameDesc()

 

 

 

 

 

 

React 공부

 

<<복잡한 상태 관리 로직 분리하기>>

const [data, setData] = useState([]);



상태 변화 처리 함수 -> 데이터 생성 (onCreate) 상태 변화 로직
                             setData((data) => [newTem, ...data]);
                         -> 데이터 수정(onEdit) 상태 변화 로직
                             setData((data) => ~ );
                         -> 데이터 삭제(onRemove) 상태 변화 로직
                             setData((data) => filter((it) => ~ );

=> App component에 많은 상태 변화 함수들이 존재
     상태를 업데이트 하기 위해서는 기존의 상태를 참조해야 하기 때문에
     상태변화 함수들은 component 내에서만 존재해야 함

=> 함수들의 setData()안의 data들 모두 const [data, setData] = useState([]); 의 
     data를 가져다 사용해야 하기 때문에
     component 밖에 함수들이 존재하면 안된다



<useReducer>
= React의 상태 관리를 돕는 Hooks 이다
   useState를 대체할 수 있는 기능
=> 상태 변화 logic들을 component에서 분리할 수 있게 되어 
     component를 더 가볍게 작성할 수 있도록 도와준다

* Reducer() 함수를 component 밖으로 분리를 해서 switch-case 문법처럼 쉽게 처리할 수 있도록 바꿀 수 있다

ex> 

const Counter = ( ) => {
  // useState를 사용하듯이 배열을 반환하게 되고, 비구조화 할당을 통해 사용한다

  // useReducer() 함수를 호출할 때 reducer라는 함수를 꼭 전달해주어야 한다
  // 첫번째로 전달하는 인자(reducer) => dispatcher가 raise시킨 상태변화를 처리해주는 역할
  // 두번째로 전달하는 인자(1) => count state의 초기값
  const [count, dispatch] = useReducer(reducer, 1);
  // 0번째 인덱스(count) = state => 그냥 state로 사용한다
  // 1번째 인덱스(dispatch) = state를 변화시키는 action을 발생시키는 함수
  // = dispatch => 상태 변화를 raise 시킴
  return (
    <div>
      {count}
       // dispatch 로직 = add 1 클릭
       // -> dipatch()함수를 호출 -> Action객체(type와 함께 쓴다)를 reducer로 전달
      <button onClick={() => dispatch({ type: 1 })} > add 1</button>
      <button onClick={() => dispatch({ type: 10 })} > add 10</button>
      <button onClick={() => dispatch({ type: 100 })} > add 100</button>
      <button onClick={() => dispatch({ type: 1000 })} > add 1000</button>
      <button onClick={() => dispatch({ type: 10000 })} > add 10000</button>
    </div>
  );
};

// => 위의 버튼을 눌러 dispatch함수를 호출해서 상태 변화가 일어나고
//     그 상태변화에 대한 처리를 Reducer 가 하게 된다  
// 첫번재 파라미터로 현재의 가장 최신의 state를 받고
// 두번째 인자로는 Action객체 ({type: })를 전달 받게 된다
const Reducer = (state, action) => {
  switch (action.type) {
    case 1:
      return state + 1;
    case 10:
      return state + 10;
    case 100:
      return state + 100;
    case 1000:
      return state + 1000;
    case 10000:
      return state + 10000;
    default:
      return state;
  }
  // 전달받은 action 객체 type에 따라 반환하는 값은 새로운 state가 된다
};
// => 새로운 state를 반환하여 다시 Counts에게 전달 하고
// 전달받은 state인 [count, dispatch] count의 값이 변화되어 return의 {count}에 반영이 된다





<일기 리스트 로직 분리>

* App.js

import React, {
  useCallback,
  useEffect,
  useMemo,
  useReducer,
  useRef,
} from "react";
//  useReducer import
import DiaryEditor from "./DiaryEditor";
import DiaryList from "./DiaryList";
import "./App.css";

// 2. 첫번째 파라미터로 상태 변화가 일어나기 직전의 state를 받고,
// 두번째 파라미터로는 어떤 상태변화를 일으켜야 하는지에 대한
// 정보들이 담겨져 있는 action 객체를 받는다
const reducer = (state, action) => {
  // action객체에 담겨져 있는 type property를 통해
  // switch-case를 이용해서 상태 변화를 처리한다
  // reducer가 return하는 값이 새로운 상태의 값이 된다
  switch (action.type) {
    // 4-1. action INIT case를 만들어줌
    case "INIT": {
      // 6-1. data: initData를 data로 받아 반환시켜준다
      // data를 초기화 하는 INIT case 완성
      return action.data;
    }
    // 4-2. CREATE
    case "CREATE": {
      const created_date = new Date().getTime();
      const newItem = {
        ...action.data,
        created_date,
      };
      return [newItem, ...state];
    }
    // 4-3. REMOVE
    case "REMOVE": {
      return state.filter((it) => it.id !== action.targetId);
    }
    // 4-4. EDIT
    // EDIT type의 action이 발생하면 targetId와 newContent를 전달받아

    case "EDIT": {
      // 기존 state에 map()함수를 사용하여
      // targetId와 일치하는 요소를 찾아준 다음
      // 그 요소의 값은 content만 newContent로 수정을 해주고
      // 나머지 요소는 그대로 돌려준다
      // 그 요소들을 합쳐 새로운 배열을 만들어 새로운 state로 보내준다
      return state.map((it) =>
        it.id === action.targetId
          ? {
              ...it,
              content: action.newContent,
            }
          : it
      );
    }
    // 4-5 switch-case문 = default case 필수
    default:
      // 상태를 변화하지 않고 그대로 반환
      return state;
  }
};

const App = () => {
  // useState()는 주석처리
  // App component의 일기 data state를 => useState Hooks가 아닌 useReducer Hooks로 관리한다
  // const [data, setData] = useState([]);
  // 5. setData가 했던 역할을 dispatch와 reducer에게 나눠준다

  const [data, dispatch] = useReducer(reducer, []); // 인자로 reducer와 data state의 초기값을 넣어준다
  // 1. ueseReducer를 사용하는 이유에 따라 App component 밖에 reducer 함수를 만들어준다
  // 3. data state에 어떠한 action들이 필요한지 살펴본다

  const dataId = useRef(0);

  // 3-1. getData() = init data를 api call을 통해
  // 적절히 가공해서 한방에 data를 초기화하는 기능을 담당
  // => action = INIT이라고 정의
  const getData = async () => {
    const res = await fetch(
      "https://jsonplaceholder.typicode.com/comments"
    ).then((res) => res.json());

    const initData = res.slice(0, 20).map((it) => {
      return {
        author: it.email,
        content: it.body,
        emotion: Math.floor(Math.random() * 5) + 1,
        created_date: new Date().getTime(),
        id: dataId.current++,
      };
    });

    // setDate(initData);
    // 5-1. setDate 대신 dipatch를 사용하여 작업
    // 데이터를 초기화 하는 작업을 하겠다는 의미
    // aciton에 필요한 데이터는 initData가 된다
    dispatch({ type: "INIT", data: initData });
  };

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

  // 3-2. onCreate()
  const onCreate = useCallback((author, content, emotion) => {
    // const created_dat = new Date().getTime();
    // const newItem = { author, content, emotion, id: dataId.current };
    // 5-2. dispatch를 통해 CREATE action을 하도록 만듬
    dispatch({
      type: "CREATE",
      // newItem에 있는 property를 그대로 가져다 사용함
      data: { author, content, emotion, id: dataId.current },
    });
    dataId.current += 1;
    // setData((data) => [newItem, ...dat]);
  }, []);

  // 3-3. onRemove()
  const onRemove = useCallback((targetId) => {
    // setData((data) => data.filter((it) => it.id !== targerId));
    dispatch({ type: "REMOVE", targetId });
  }, []);

  // 3-4. onEdit
  const onEdit = useCallback((targetId, newContent) => {
    // setData((data) =>
    //   data.map((it) =>
    //     it.id === targetId ? { ...it, content: newContent } : it
    //   )
    // );
    dispatch({
      type: "EDIT",
      targetId,
      newContent,
    });
  }, []);
  // 3. data를 조작하는 기능을 가진 함수 끝

  const memoizedDiaryAnalysis = useMemo(() => {
    const goodCount = data.filter((it) => it.emotion >= 3).length;
    const badCount = data.length - goodCount;
    const goodRatio = (goodCount / data.length) * 100.0;
    return { goodCount, badCount, goodRatio };
  }, [data.length]);

  const { goodCount, badCount, goodRatio } = memoizedDiaryAnalysis;

  return (
    <div className="App">
      <DiaryEditor onCreate={onCreate} />
      <div>전체 일기 : {data.length}</div>
      <div>기분 좋은 일기 개수 : {goodCount}</div>
      <div>기분 나쁜 일기 개수 : {badCount}</div>
      <div>기분 좋은 일기 비율 : {goodRatio}%</div>
      <DiaryList diaryList={data} onRemove={onRemove} onEdit={onEdit} />
    </div>
  );
};

export default App;








 

 

 

2023. 01. 13 (토)

관련글 더보기