// App.js
function App() {
  const Login = lazy(() => import('./page/Login'));
  const LogOut = lazy(() => import('./page/LogOut'));
  const SignPage = lazy(() => import('./page/SignPage/SignPgae'));
  const Questions = lazy(() => import('./page/Questions'));
  const CreateQuestionPage = lazy(() => import('./page/CreateQuestionPage'));
  const Post = lazy(() => import('./components/Post/Post'));
  const AnswerEdit = lazy(() => import('./page/AnswerEdit'));
  //const User = lazy(() => import('./page/User'));
  const Tags = lazy(() => import('./page/Tags'));
  const User = lazy(() => import('./page/User'));

  const [datas, setDatas] = useState(dummyData);

return (
    <Router>
      <GlobalStyles />
      <Main>
        <Suspense fallback={<div>Loading...</div>}>
          <Routes>
			// ... 생략
            <Route
              path="/questions/:id"
              element={<Post datas={datas} setDatas={setDatas} />}
            />
            // ... 생략

dummyData에서 데이터를 datas 배열로 가져와서 Post 로 보내줬다.

그리고 Post에서 꺼내와서 {} 로 값을 동적으로 주면 되는데 datas 에서 어떻게 꺼내야 할 지 감을 전혀 못잡고 있었다

 

 

//dummyData.js

const dummyData = [
  {
    id: 1,
    title: '게시글 제목',
    likes: 0,
    answers: 0,
    views: 2,
    body: `I am building a large project on a remote machine using Bazel. Clean build times are around 30 minutes. Incremental builds (changing code in 1-2 files) typically take around 10-20 seconds.

    The problem I have is that when I log out of my machine and log back in again after 1-2 days the build command takes around 10 minutes even though I have not modified any source code.
    
    If I call bazel shutdown and then call bazel build again the "no-build" op takes around 5-10 seconds (i.e. much better than the other "no-build" op).
    
    If I log out and log back in again immediately I can see there is still a bazel process running in the background, which disappears when I call bazel shutdown. I am guessing that when I do not shut bazel down properly it gets killed in such a way that corrupts or deletes cached data. The long "no-build" op then spends a long time reconstructing data that was previously stored in the Bazel cache.
    
    Is there a way to automatically shut down the bazel server when I am disconnected? Preferably this should work both when (i) I call exit from the command-line to log out, (ii) I get automatically disconnected through some kind of timeout or interruption in network connectivity.`,
    tag: ['java', 'c++'],
    user: {
      username: 'Heera',
      createdAt: 'asked 46 secs ago',
    },
  },
  {
    id: 2,
    title: '2번째 글',
    likes: 2,
    answers: 10,
    views: 56,
    body: '2번재 오랜만에 글을 작성해 보는데 말이죠... 이거는 정말,.. 제가 할 수 있는 일인가에 대한걸 다시 고민해봐야 할 시점인것 같습니다만.. 얼마나 길게 쓴게 잘려서 나올지 한번 테스트 해보도록 하겠습니다.. 이정도면 엄청 길게 쓴게 아닐까요?? 정말 고민이 많이 됩니다.. 이게 한꺼번에 다 나오지 않아야 할텐데 말이죠... 조금만 더 길게 적어보겠습니다.조금만 더 길게 적어보겠습니다.조금만 더 길게 적어보겠습니다. 이정도면 될거같은데, 사이즈가 늘어나 버려서 더 길게 작성하고, 크기 가 넘어가는 내용은 안나오는지 확인해 보려고 합니다~',
    tag: ['java', 'javascript', 'python'],
    user: {
      username: 'seoyeon',
      createdAt: 'asked 46 secs ago',
    },
  },
  {
    id: 3,
    title: '세번째 글입니다만...',
    likes: 34,
    answers: 668,
    views: 23234,
    body: '안녕하세요 제 질문은요/..',
    tag: ['java', 'logic', 'nameerror', 'firebase-authentication'],
    user: {
      username: 'kim.s.y',
      createdAt: 'asked 5 mins ago',
    },
  },
  {
    id: 4,
    title: '4번째 글',
    likes: 2,
    answers: 8,
    views: 45,
    body: '444444 내용이랄게 없지만서도 적으면 또 적을수도 있는데,.. 데체 긴 내용은 어떻게 길이를 줄일까요',
    tag: ['java', 'c++'],
    user: {
      username: 'harnry',
      createdAt: 'asked 7 mins ago',
    },
  },
 //... 생략
 ]

export default dummyData;

더미 데이터는 배열 안에 객체가 들어가있는 형태.

객체다 보니까 인덱스 값이 없었다.

 

const { id } = useParams();

를 사용해 console.log({id})를 찍어보면 url의 id 값이 잘 찍혔다.

{ id }과 datas의 id 값이 같은 걸 찾아서 post에 뿌려주면 될 거 같다는 윤곽이 잡혔다.

 

그런데 datas.id 가 계속 null 혹은 undefined가 나왔다 (객체니까....)

여기서부터 6시간 이상을 별 시도를 다 해봤다.

이것저것 찾다가 아래의 글을 보고 아이디어를 얻었다.

 

https://velog.io/@lilyoh/js-object-%EC%9A%94%EC%86%8C%EC%97%90-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B3%A0-%EC%88%9C%ED%9A%8C%ED%95%98%EA%B8%B0

 

[js] object 요소에 접근하고 순회하기

info 객체에 키를 생성하고 값을 할당해주려고 한다. 키와 값은 변수를 통해 받아온다.위와 같은 변수가 주어졌을 때, info 객체에 키와 값을 할당하는 방법은 2 가지가 있다.객체의 요소는 인덱스(

velog.io

 

for in 문으로 객체를 순회해서 datas[i].id 를 구하고 그것을   { id } 와 비교하자!

개발자도구에 바로 시도를 해봤다.

 

잘 뜬다!

이걸로 시도를 해봤다

 

시도한 코드

=> 문제점

  • if문이 제대로 작동을 안함
    • {id} ==! datas[i].id 가 false 인게 맞다면 error 가 떠야하는데 <Container> 가 화면에 뜸
  • {id} 를 콘솔에 수십번을 찍어봤는데도 문자열이라는 걸 눈치못챔
  • {id} 는 url의 `.../:id` 에 맞게 변하는데 datas[i].id 는 1에서 변하질 않음
    • 그래서인지 질문리스트에서 다른 제목을 눌러봐도 {id}만 제대로 바뀌고, 안에 Post 내용들은 1번째 글이랑 똑같이 뜸...
const Post = () => {
  const { id } = useParams();
  const [datas, setDatas] = useState(dummyData);

  for(let i in datas){
    if({id} ==! datas[i].id){
      return(
        <div>result error</div>
      )
    }
    console.log(`data[i].id`, datas[i], datas[i].id, {id})
    return(
      <>
      <div>
        <Container>
          <Nav />
          <Content>
            <div className="div-css">
              <div>
                <PostTitle datas={datas[i]} setDatas={setDatas} />
              </div>
              <div className="div-inline-block">
                <div className="div-flex">
                  <Like />
                  <PostMainText datas={datas[i]} />
                  <PostAside>
                    <div>aside</div>
                  </PostAside>
                </div>
              </div>
              <Answers />
            </div>
          </Content>
        </Container>
        <Footer />
      </div>
    </>
    );
  }
}

 

구글링으론 원하는 결과가 안 나와서 유튜브에 검색했다.

검색어 : js useParams

그러다가 아래의 영상을 보게 되었다.

 

https://youtu.be/vI-XtN_Zdfg

 

유레카!!

아무 기대없이 봤는데 내 상황에 써먹을 수 있겠다는 생각이 들어 침대에서 벌떡 일어나 다시 노트북을 켰다

 

 

수정한 코드

=> 문제점

  • .map() 을 사용해 data.id와 {id} 가 같은 데이터를 Post에 뿌려주기 때문에 id가 같으면 post가 똑같은 내용이 두번 뜸
    • 하지만 ID는 고유값이기 때문에 괜찮음 ㅎ
  • Answers 컴포넌트도 .map() 안에 넣어야 함.
    • Answers의 답변 내용들도 질문마다 다르기 때문에 넣어야함 근데 넣으면 레이아웃 잡아둔 거 난리날까봐 내일수정하기로 함
const Post = () => {
  const { id } = useParams();
  const [datas, setDatas] = useState(dummyData);
  const datasSame = datas.filter((data) => data.id === Number(id));

  console.log(`dataSame`, datasSame);
  return (
    <>
      <div>
        <Container>
          <Nav />
          <Content>
            <div className="div-css">
              <div className="예시">
                {datasSame.map((data) => (
                  <>
                    <div key={data.id}>
                      <div>
                        <PostTitle datas={data} setDatas={setDatas} />
                      </div>
                      <div className="div-inline-block">
                        <div className="div-flex">
                          <Like />
                          <PostMainText datas={data} />
                          <PostAside>
                            <div>aside</div>
                          </PostAside>
                        </div>
                      </div>
                    </div>
                  </>
                ))}
              </div>
              <Answers />
            </div>
          </Content>
        </Container>
        <Footer />
      </div>
    </>
  );
};

콘솔에도 잘 뜬다

 

=> 추가 (22.11.08)

map 과 필터를 지워져도 정상적으로 구동되게 코드를 아래와 같이 변경하였다.

기존과는 다르게 app.js 데이터를 props 로 내려주지 않고, Post.js 에서 fetch 로 데이터를 바로 받아오면서 수정하게 된 코드이다.

const Post = ({ qid, setQid,setAid, setAnswerEditContent }) => {
  const { id } = useParams();
  const [posts, setPosts] = useState([]);
  
  useEffect(() => {
    fetch(
      `url주소/${id}`,
      { method: 'GET' },
    )
      .then((response) => response.json())
      .then((post) => {
        setPosts(post.data);
        setQid(id);
      });
  }, [id]);
  return (
    <>
      <div>
        <Container>
          <Nav />
          <Content>
            <div className="div-css">
              <div className="예시">
                    <div key={posts.questionId} className="div-css">
                      <div>
                        <PostTitle datas={posts} key={id} />
                      </div>
                      <div className="div-inline-block">
                        <div className="div-flex">
                          <div className="div-inline-flex">
                            <div className="div-inline-block">
                              <div className="div-flex">
                              <Like datas={posts} />
                              <PostMainText datas={posts} />
                              </div>
                              <Answers answers={posts.answers} qid={qid} setAid={setAid} setAnswerEditContent={setAnswerEditContent}/>
                            </div>
                          </div>
                          //...생략

버튼이 죽어도 오른쪽 정렬이 안됨

=> 각 요소를 양쪽 정렬 시켜서 해결

display: flex;
    justify-content: space-between;

 

ask 버튼을 <link> 안에 넣으니 Margin-left:auto 가 안 먹힘

=> Link 태그에 CSS 로 Margin-left:auto 주니 해결 완료

pre 프로젝트 중에 '/post' 화면에서 suspense 로 띄운 Loding... 만 뜨고 페이지가 Post 로 안 넘어가면서 갑자기 이런 에러가 떴다

다른 페이지에선 안 뜨는 에러라 찾아보니

 

https://sudo-minz.tistory.com/100

 

리액트 uncaught syntaxerror: unexpected token '<' 해결

리액트 uncaught syntaxerror: unexpected token '

sudo-minz.tistory.com

http://lab.naminsik.com/4011

 

lab.naminsik

lab.naminsik - 개발자 남인식 Lab.

lab.naminsik.com

https://egg-stone.tistory.com/28

 

[JS]Uncaught SyntaxError: Unexpected token '<'

웹사이트를 만들고나서 개발자모드로 확인하던 도중 오류를 발견하게 되었어. 말그대로 '

egg-stone.tistory.com

https://shanepark.tistory.com/125

 

<!DOCTYPE html> 에 Uncaught SyntaxError: Unexpected token '<' 뜰때 해결하기

Controller에서 일정 패턴으로 URL을 맵핑 시키고 있었습니다. PathVariable을 적극적으로 이용해서 심플한 url 패턴을 만들어보자! 해서 localhost/project이름/로그인사용자닉네임/사용모듈 ... 으로 depth 구

shanepark.tistory.com

https://iot624.tistory.com/88

 

Uncaught SyntaxError: Unexpected token '<' in <!DOCTYPE html> 자문자답 ^0^ - Node.js

https://antdev.tistory.com/32 [NodeJS] npm으로 express, socket.io 모듈 설치 및 웹 서버 구축, 소켓 연결하기 [express와 socket.io을 이용 2019/02/27 - [NodeJS/NodeJS로 간단한 채팅기능 만들기] - [NodeJS] 채팅방UI 구성, 소

iot624.tistory.com

 

나와 같은 사람들은 많았으나 저 해결 방법들 중 맞는 게 없었다.

콘솔 로그에  codemirror.js  에서 생긴 오류라고 떴는데 이게 토스트 Ui 와 함께 사용하려고 넣은 뷰어였다.

리액트 18 버전에선 안 되는지 에러를 뿜뿜 뿜어내길래 여기서 에러를 띄우는 건가 싶었다. 

 

내 해결 방법은

 

index.html 에서

    <!-- <link rel="stylesheet" href="lib/codemirror.css">
    <script src="lib/codemirror.js"></script> -->

적혀 있던 부분을 주석 처리해버리는 거였다.

 

 

토스트 UI 랑 뷰어 오류 정말... 진절머리가 난다.

 

**** 원래 작업하던 폴더, 파일들 복사해서 잘 모셔두기!

 

  • 원격 레포에서 git clone 으로 새롭게 폴더를 가져온다 (ㅋㅋ...
  • git remote -v 로 원격 레포랑 연결 잘 되어있는 지 확인한다
  • 새로운 브런치를 파고 거기로 이동한다
    • git checkout -b <새로운브런치이름>

 

  • git pull (remote) dev-fe 로 pull 해온다
    • remote는 origin 일 수도 pre015일 수도 있음 2번에 확인 했을 때 뜨는 걸로

 

  • pull 후 병합과 오류를 수정한다...
  • 자신이 작성했던 파일, 폴더들을 git clone 으로 새로판 폴더에 다시 작성한다
  • 오류가 없는 걸 확인했다면      git push <remote> <아까 만들었던 새로운 브런치 이름>      한다! 

 

  • giithub 으로 가서 pr을 날린다

 

 


 

 

이렇게 했는데도 이상한 commit 들이 끼어있긴 하더라...

다음에 pull 해올 때 아래 같은 게 또 뜨면

 

git config pull.rebase false 가 아니라    (항상 얘로 했었음....)

git config pull.ff only 로 해 보던가 해야겠다.

 * branch            dev-fe     -> FETCH_HEAD
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint: 
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
hint: 
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.

 

 

 

 

**** 11/07 수정 ****

 

내가 작업한 거 pr 보내고, 머지하고 -> 아무것도 안하다가 -> 다른 분이 머지하신 거 받아올 때 git clone 부터 다시 해왔다 ㅋㅋㅋㅋ....

 

내거 머지하고 코드 작업 했으면

1) git clone 으로 새로 하나 파자

2) main 브랜치로 되어있으니까 브랜치 새로 파서

3) 거기서 pull 하고

4) 작업했던 코드들 넣는 식으로 하자...

 

 

Consider adding an error boundary to your tree to customize error handling behavior.

=>

 

styled-component 로 작성한 button 컴포넌트에 class가 적용되지 않는 에러

=> .sign 앞에 & (자기 자신)을 붙여줘서 해결 완료

export const ButtonStyled = styled.button`
  border: solid 1px #2d8de0;
  background-color: #e8f2fa;
  border-radius: 3px;
  width: 60px;
  margin: 3px;
  padding: 5px 0 5px 0;
  text-align: center;
  color: #2273ba;
  font-size: 0.8rem;

  &:hover {
    background: #d5e4f0;
  }

  &.sign {
    background-color: #2d8de0;
    color: #ffffff;
  }

  &.sign:hover {
    background-color: #2273ba;
  }
`;

 

Uncaught Error: input is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.

=> 자식을 가질 수 없는 (input)태그에 자식을 넣어서 생기는 오류이다.

const SearchInput = () => {
  const [value, setValue] = useState('');
  const handleChange = (e) => {
    setValue(e.target.value);
    alert('input 값 입력중');
  };
  return (
    <>
      <input
        type="text"
        value={value}
        onChange={handleChange}
        placeholder="Search..."
       >
       <SearchGuide> //이 부분을 삭제하여 해결 가능
      </input>
    </>
  );
};

 

 

Uncaught Error: Expected `onClick` listener to be a function, instead got a value of `object` type.

=> 자식 컴포넌트가 props으로 함수를 받았는데 그걸 이벤트함수에 그냥 받을 경우 발생하는 오류

//X
<input
          type="text"
          value={value}
          onChange={handleChange}
          onClick={
            isSearchHandler ? <SearchGuide /> : '되는 거 맞냐고'
          }
          placeholder="Search..."
/>
// O
<input
          type="text"
          value={value}
          onChange={handleChange}
          onClick={() =>
            isSearchHandler() ? <SearchGuide /> : '되는 거 맞냐고'
          }
          placeholder="Search..."
/>

 

 

<magnifyingGlass /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.

=> 스타일드 컴포넌트인데 컴포넌트명이 소문자로 시작해서 생긴 오류. m을 대문자로 바꿔주면 해결 가능.

 

 

 

Uncaught ReferenceError: Cannot access 'ButtonStyled' before initialization

=> ButtonStyled 스타일드 컴포넌트는 Header.js에서 만들었고,

그걸 SearchBar.js 로 import 해 가져가 사용하는 중이었는데

아래 코드에서 보다시피 ButtonStyled 보다 SearchBar 컴포넌트가 먼저 불러와져서 생긴 오류였다.

 

해결방법 : ButtonStyled를 Header.js 에서 만들어서  SearchBar.js 에서도 사용하는 게 아니라

SearchBar.js에서 만들어서 Header.js 에서도 사용하는 걸로 변경했다.

return (
      <>
        <GuestsStyled>
          <ProductsStyled>About</ProductsStyled>
          <ProductsStyled>Products</ProductsStyled>
          <ProductsStyled>For Teams</ProductsStyled>
          <SearchBar />
          <ButtonStyled>Log in</ButtonStyled>
          <ButtonStyled className="sign">Sign up</ButtonStyled>
        </GuestsStyled>
      </>
    );
  }

 

 

Using exported name 'UserProfileStyled' as identifier for default export.

=> Header 컴포넌트에 들어가는 UserProfileStyled 컴포넌트를 Post 컴포넌트에서도 import 해서 가져와 사용하려고 하니까 이런 에러가 떴다. 해결 방법은 UserProfileStyled 를 중괄호 안에 넣어주면 된다.

import UserProfileStyled from '../components/Header/UserProfile';  //X
import { UserProfileStyled } from '../components/Header/UserProfile'; //O

 

 

onClick 이벤트를 사용해 아이콘 클릭시 div 박스 class hide(기본값)에 show가 추가되게끔 구현하는 것이 안 됨.

=> 콘솔에도 오류가 뜨지 않아 대체 뭐가 문제인지 고민했다.

serchBar 부분에 똑같은 식으로 적용했을 땐 문제가 없었는데...

고민을 하다보니 컴포넌트 안에 하위 컴포넌트에 식을 적용한 게 문제였다.

//안 됐을 때
<UserIconStyled>
        <MdMessage className="userIcon" onClick={isIconHandler} />
</UserIconStyled>
<LogOutBox>
        <InBox className={`hide ${isIconClick ? 'show' : null}`} />
</LogOutBox>

//수정 후 잘 작동함
<UserIconStyled onClick={isIconHandler}>
        <MdMessage className="userIcon" />
</UserIconStyled>
<LogOutBox className={`hide ${isIconClick ? 'show' : null}`}>
        <InBox />
</LogOutBox>

 

prettier 오류

npm install --save-dev prettier

npm install --save-dev prettie --legacy-peer-deps

=> 위에 거 둘 다 해봤는데 아래처럼 오류가 뜨고 안 되길래 npm install --save-dev prettier --force 로 해봤더니 오류 해결

npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR! 
npm ERR! While resolving: @toast-ui/react-editor@3.2.2
npm ERR! Found: react@18.2.0
npm ERR! node_modules/react
npm ERR!   peerOptional react@"^16.9.0 || ^17.0.0 || ^18" from @reduxjs/toolkit@1.8.6
npm ERR!   node_modules/@reduxjs/toolkit
npm ERR!     @reduxjs/toolkit@"^1.8.6" from the root project
npm ERR!   peer react@"^18.0.0" from @testing-library/react@13.4.0
npm ERR!   node_modules/@testing-library/react
npm ERR!     @testing-library/react@"^13.4.0" from the root project
npm ERR!   10 more (react-dom, react-icons, react-moment, react-redux, ...)
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^17.0.1" from @toast-ui/react-editor@3.2.2
npm ERR! node_modules/@toast-ui/react-editor
npm ERR!   @toast-ui/react-editor@"^3.2.2" from the root project
npm ERR! 
npm ERR! Conflicting peer dependency: react@17.0.2
npm ERR! node_modules/react
npm ERR!   peer react@"^17.0.1" from @toast-ui/react-editor@3.2.2
npm ERR!   node_modules/@toast-ui/react-editor
npm ERR!     @toast-ui/react-editor@"^3.2.2" from the root project
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR! 
npm ERR! See /Users/heeraan/.npm/eresolve-report.txt for a full report.

 

 

'/Users/heeraan/Desktop/project/plz/seb40_pre_015/client/node_modules/react/index.js' imported multiple times.
=> react 에서 import 해오는 게 여러개인데 한 꺼번에 Import 한 게 아니라 여러개로 import 해와서 생긴 오류
// X
import { Suspense, lazy } from 'react';
import { useState } from 'react';

//O
import { Suspense, lazy, useState } from 'react';

 

 

 

 


 

 

 

백엔드 서버에서 질문 데이터를 아래처럼 받아와서 사용을 하려고 하니까 질문 리스트가 화면에 뜨질 않는 문제 해결

더보기

백엔드 서버에서 질문 데이터를 아래처럼 받아와서 사용을 하려고 하니까 질문 리스트가 화면에 뜨질 않았다...

  const [datas, setDatas] = useState([]);
  console.log(datas);

  useEffect(() => {
    fetch(
      '환경변수/questions'
    )
      .then((response) => response.json())
      .then((data) => {
        setDatas(data);
      });
  }, []);
  console.log(`questions`, datas);
  
  
  <Route path="/" element={<Questions datas={data} />} />

 

응답을 보면 응답이 data안에 들어있다. 그래서 아래처럼 한커풀 벗겨서 넘겨줘야 한다.

그리고 환경변수를 사용하니 안 된 거였다. (대체 왜...)

<Route path="/" element={<Questions datas={datas.data} />} />

 

 

 

 

서버에서 데이터를 받아와 질문 리스트를 띄우는 것엔 성공했으나, 질문 리스트의 제목을 클릭 했을 때 Post.js 가 제대로 뜨지 않는 문제 발생

더보기

 

App.js 에서 fetch 받아온 질문 리스트 데이터들을 <Post /> 컴포넌트에 datas 로 내려줌

//App.js

useEffect(() => {
    fetch(
      'http://ec2-3-34-90-191.ap-northeast-2.compute.amazonaws.com:8080/questions'
    )
      .then((response) => response.json())
      .then((data) => {
        setDatas(data);
      });
  }, []); //여기에 data 넣으면 지옥 경헙 삽가능... -지은
  console.log(`questions`, datas);
  
  
  	<Route path="/" element={<Questions datas={datas.data} />} />
	<Route
		path="/questions"
		element={<Questions datas={datas.data} />}
	 />
 	<Route
        path="/questions/:id"
        element={<Post datas={datas.data} />}
   	/>

 Post.js 데이터 받아오는 것을 어느 정도 수정하고 팀원이 dev-fe 브랜치에 머지해 놓은 것을 pull 해왔더니 갑자기 브라우저창이 계속 새로고침 되는 문제가 발생했다! 데이터 잘 들어오나 확인하려고 console.log 를 찍어놓은 것도 많아서 콘솔창이 미친듯이 밑으로 내려가고 ... 화면은 계속 렌더링 되느라 깜빡거리고... 새벽 4시에 컴퓨터 귀신 들린 줄 알고 놀랐다 (...)

문제는 useEffect 두번째 인자로 [] 배열 안에 data를 넣어서였다. 내가 넣은 건 아니였는데 배열안에 data를 넣어서 data가 들어올 때마다 새로 렌더링 되면서 생긴 문제인 거 같다.

 

 

아무튼 datas 를 받아온 Post.js 에 아래와 같이 적어줬다.

const Post = ({ datas }) => {
  const { id } = useParams();
  const [posts, setPosts] = useState([]);
  
  useEffect(() => {
    fetch(
      `http://ec2-3-34-90-191.ap-northeast-2.compute.amazonaws.com:8080/questions?id=${id}`,
      { method: 'GET' }
    )
      .then((response) => response.json())
      .then((post) => {
        setPosts(post.data);
      });
  }, [id]);
  console.log(`id`, {id})
  console.log(`post에 데이터 잘 들어옴?`, posts)

  const idSame = posts?.filter((postId) => postId.questionId === Number(id));
  //{id}를 숫자로 바꾸고 datas의 id 비교. -> 같은 것만 idSame

 

 

이렇게 코드를 짰더니

 

 

 

질문 리스트에서 타이틀을 누르면

 

 

이렇게 뜨는 거다...

url 의 params {id} 는 잘 들어와서 찍히는데... {id} 가 질문 리스트 클릭한 타이틀의 id 로 들어오지 않는 것이다...

{id} 는 제대로 찍히고 있는 걸 보고 url 창에서 직접 질문의 id 를 적어봤다

 

 

이렇게 했더니 잘 뜨는 것이다...!

대체 뭐가 문제인가 이것도 해 보고 저것도 해 보고 했는데...

서버쪽 응답 data의 id가 id 가 아니라 questionId인데

 

Question.js (질문 리스트)에서 타이틀을 클릭했을 때 Link의 to의 url이 잘못 적혀있던 것이었다.

//X
<Link to={`/questions/${id}`}>
   <h3>{title}</h3>
</Link>

<Link to={`/questions/:id`}>
   <h3>{title}</h3>
</Link>


//O
<Link to={`/questions/${questionId}`}>
   <h3>{title}</h3>
</Link>

id 와 {id} 와 questionId... 정말 헷갈린다...@.@

위의 질문 리스트에서 타이틀을 클릭했을 때 안 됐던 것은 {title}을 감싸고 있는 Link 의 문제였고, url에 직접 params를 입력했을 때 data가 잘 뜨던 것은 Post.js 에서 id 를 useParams로 받아오기 때문이었던 거 같다.

const Post = () => {
  const { id } = useParams(); //<<<- 이부분
  const [posts, setPosts] = useState([]);
  
  useEffect(() => {
    fetch(
      `http://ec2-3-34-90-191.ap-northeast-2.compute.amazonaws.com:8080/questions?id=${id}`,
      { method: 'GET' },
    )
      .then((response) => response.json())
      .then((post) => {
        setPosts(post.data);
      });
  }, [id]);

 

+ 추가 

Post.js의 패치를 위처럼 /questions?id=${id} 로 보내면 네트워크에 ${id} 만 보내지는 게 아니라 ( ex: 19) id=19 로 보내져서 혼동이 생길 수 있다고 한다. (정확한 단어가 있었는데 자세히 기억이 안남)

그래서 원래 적었던 것처럼 /questions/${id} 로 적는 게 맞고, 대신 이렇게 적었을 때 화면에 뜨지 않던 오류는 아래처럼 해결하면 된다. (filter 삭제, map 삭제)

 

map 으로 data 를 뿌려줘야 한다고 생각하고 있었는데 완전 잘못된 생각이었다.

왜냐면... 게시글 리스트가 아니라 게시글 1개만 보여지는 화면이니까...! 복잡하게 생각할 것이 많아서 잘못 생각하고 있었다.

//O
const Post = () => {
  const { id } = useParams();
  const [posts, setPosts] = useState([]);
  
  useEffect(() => {
    fetch(
      `http://ec2-3-34-90-191.ap-northeast-2.compute.amazonaws.com:8080/questions/${id}`,
      { method: 'GET' },
    )
      .then((response) => response.json())
      .then((post) => {
        setPosts(post.data);
      });
  }, [id]);
//X
const idSame = posts?.filter((postId) => postId.questionId === Number(id));
  return (
    <>
      <div>
        <Container>
          <Nav />
          <Content>
            <div className="div-css">
              <div className="예시">
                {idSame?.map((posts) => (
                  <>
                    <div key={posts.id} className="div-css">
                      <div>
                        <PostTitle datas={posts}/>
                      </div>
                      <div className="div-inline-block">
                        <div className="div-flex">
                          <div className="div-inline-flex">
                            <div className="div-inline-block">
                              <div className="div-flex">
                              <Like datas={posts} />
                              <PostMainText datas={posts} />
                              </div>
                              <Answers answers={posts.answer} />
                            </div>
                          </div>
                          
                          ... 생략
  
 //O
  return (
    <>
      <div>
        <Container>
          <Nav />
          <Content>
            <div className="div-css">
              <div className="예시">
                    <div key={posts.id} className="div-css">
                      <div>
                        <PostTitle datas={posts}/>
                      </div>
                      <div className="div-inline-block">
                        <div className="div-flex">
                          <div className="div-inline-flex">
                            <div className="div-inline-block">
                              <div className="div-flex">
                              <Like datas={posts} />
                              <PostMainText datas={posts} />
                              </div>
                              <Answers answers={posts.answer} />
                            </div>
                          </div>
                          
                          ... 생략

 

 

게시글 삭제 기능 구현하기

더보기

CRUD CRUD 노래를 불렀는데...

delete 기능 구현을 모두 깜빡하고 있었다

마감 하루를 남기고 구현 시작...!

 

PostFooter.js 에 <p>Delete</p> 를 만들어주고 css 로 커서 포인터를 주고, onClick 함수를 만들어줬다.

onClick 함수엔 fetch 가 들어갈 것이다.

<p onClick={PostDelete} className="delete-margin">Delete</p>

.delete-margin {
    margin-left: 10px;
    cursor: pointer;
  }

 

onClick 함수로 사용할 PostDelete 이다. 딜리트 구현은 생각보다 어렵지 않았다.

그런데 응답에서 status 를 받아오는 것과 navigater 사용이 미숙해서 거기서 실수가 있었다.

 

우선

이것저것 찾아보면서 .then((res) => res.text) 로 응답 메세지를 받아와서 OK 면 alret 창을 띄우는 if문을 구현했는데 서버에서 보내주는 응답에 body가 없었다... (두둥) status 코드 204 를 받아와 저 부분을 해결해야했다.

 

204를 받아오는데 alert 이 삭제 완료가 뜨는 게 아니라 계속 삭제 실패가 떴다. (근데 게시글 데이터 삭제는 잘 되고 있었다)

//X
const PostDelete = () => {
    fetch(`http://ec2-3-34-90-191.ap-northeast-2.compute.amazonaws.com:8080/questions/${questionId}`, {
      method: "DELETE",
    })
    .then(response => {
      if (response.status === '204'){
        console.log(response.status);
        alert('삭제 완료')
        navigator('/')
      }
    })
    .catch((error) => {
      alert('삭제 실패')
    })
  }


//O
import { useNavigate } from 'react-router-dom';
const PostFooter = ({ datas }) => {
  const { questionId, title, likes, answers, views, body, tag } = datas;
  const navigator = useNavigate();
    const PostDelete = () => {
        fetch(`http://ec2-3-34-90-191.ap-northeast-2.compute.amazonaws.com:8080/questions/${questionId}`, {
          method: "DELETE",
        })
        .then(response => {
          if (response.status === 204){
            console.log(response.status);
            alert('삭제 완료')
            navigator('/')
            window.location.reload()
          }
        })
        .catch((error) => {
          alert('삭제 실패')
        })
      }

문제1) 상태 코드 204를 숫자가 아닌 문자열로 if문에 적었다...!

해결 =>  if(response.status ===  '204') 가 아니라 if(response.status === 204) 로 적었어야 했다...

 

 

문제2) navigator('/') 이 작동하지 않음

해결 => react-router-dom 에서  { useNevigate } 를 import 해와야 한다. 그리고 fetch 가 들어간 함수 밖에서 navigator 로 선언을 해줘야 제대로 쓸 수 있었다!

 

 

문제3) 리다이렉션 구현...

해결 => 리다이렉션을 어떻게 써야할지 모르겠어서 팀원분께 여쭤보니 window.location.reload() 를 알려주셨다. 넣으니까 게시글 삭제 후 게시글 리스트 페이지로 잘 이동됐다!

 

 

 

 

로그인 여부에 따라 헤더 다르게 보이기 구현

더보기

이것저것 찾아보면서 코드를 짰더니 짬뽕이 되서 얘가 이상하게 작동을 했다.

 

기본적으로 const [ isLogin, setIsLogin ] = useStates(false) 이다.

그렇다면 !isLogin 이면 true 니까 login이 된 상태니까 아래쪽 리턴문을 띄워야하는데 얘가 로그인이 안 된 상태를 보여주는 윗쪽 리턴문을 띄웠다...

 

이건 내가 수정을 한 것은 아니지만 로그인을 구현하신 팀원분께서 리턴문 두개가 아니라 삼항연산자를 사용해야 한다고 알려주셨다.

const Guest = (isLogin) => {
  if (!isLogin) {
    console.log(`Header is isLogin`, isLogin);
    return (
      <>
        <GuestsStyled>
          <ProductsStyled>About</ProductsStyled>
          <ProductsStyled>Products</ProductsStyled>
          <ProductsStyled>For Teams</ProductsStyled>
          <SearchBar />
          <Link to="/login">
            <ButtonStyled>Log in</ButtonStyled>
          </Link>
          <Link to="/signup">
            <ButtonStyled className="sign">Sign up</ButtonStyled>
          </Link>
        </GuestsStyled>
      </>
    );
  }
  return (
    <>
      <UserStyled>
        <ProductsStyled>Products</ProductsStyled>
        <SearchBar />
        <UserHeader />
      </UserStyled>
    </>
  );
};

Section 4 회고록

시간이 정말 빠르게 지나갔다. 내 시간 어디로 갔지? 6월에 시작했던 게 엊그제 같은데 벌써 프로젝트를 앞두고 있다. 프로젝트가 가까워지니까 이상하게 알 수 없는 자신감이(ㅋㅋ?) 생겨서 프로젝트보단 부트캠프 종료 후 취업이 더 걱정되기 시작했다. 포트폴리오를 만들려면 개인 프로젝트도 몇 개 더 해야할 텐데... ... 아이디어가 없다 (ㅠ) 서버쪽도 자신이 없어서 혼자서 잘 만들 수 있을까? 걱정이 태산이다. 

 

이건 딴 이야기지만 디자인할 땐 기획하고, 와이어프레임 만드는 게 너무너무 싫었는데 막상 코딩하니까 피그마로 기획하는 게 제일 할 만하더라 ㅋㅋㅋㅋㅋ 익숙함에 속아 소중함을 잊지 말자 (이거 아님) 디자인도 막상 할 땐 싫었지만 지금 와서 하니까 할만한 것처럼 코딩도 지금은 뭣같이 어려워서 아방방하게 울기만 할지라도 나중엔 뭐... 언젠가 할만해지는 날이 오지 않을까? 하고 생각한다. 

 

 

목표 상기

부트 캠프 무사 졸업

⇒ 코딩에 '코'도 모르는 사람을 이해시킬 수 있을 정도의 지식 갖추기

⇒ 1인분 하는 개발자 되기

2023년도 취업

⇒ 개인 프로젝트를 열심히 만들어 보자. 포트폴리오도 어떻게 만들 건지 생각해 두자.

 

Keep

  1. 나 스스로가 선택한 길임을 잊지 말고 끝까지 최선을 다해보기
  2. 건강 관리하기
  3. 스트레스 관리하기

 

Problem

  • 나 스스로가 선택한 길임을 잊지 말고 끝까지 최선을 다해보기 
    •  남들보다 뒤쳐지는데 노력도 열심히 안 하는 내 모습을 보며 은은하게 스트레스 축적되고 있는 거 같다.
  • 건강 / 스트레스 관리하기
    • 원래 있던 지병이 오락가락한다. 좋을 때도 있고, 나빠질 때도 있고... 수면 패턴이랑 스트레스 때문인 거 같다.

 

Try

1. 계획적으로 살기

⇒  원래 계획하고 실행하는 것을 좋아하는 성격이다. (대신 계획대로 안 되면 2배로 스트레스를 받지만) 요샌 그래도 섹션 3때 보다 스트레스도 줄고, 번아웃도 괜찮아진 거 같아서 널널하게 일일계획을 짜고 실행하면서 계획적으로 사는 것에 시동을 좀 걸려고 노력하고 있다. 처음부터 빡빡하게 일정 짜고 공부하는 것보단 다시 습관 들이듯 하는 것이 계획대로 안 됐을 때 스트레스를 덜 받을 수 있을 것 같았기 때문이다.

 

2. 까먹지 말기

⇒  요새 청년 치매가 심각해진 거 같다. 안 적어두면 찜찜~하게 "뭐 해야됐던 거 같은데?" 하고만 기억이 나서 찜찜~하고 은은~하게 스트레스도 쌓여서 바로바로 메모장이나 플래너에 적어두려고 하고 있다. 

 

3. 까먹지 말기

⇒  요새 청년 치매가 심각해진 거 같다. 안 적어두면 찜찜~하게 "뭐 해야됐던 거 같은데?" 하고만 기억이 나서 찜찜~하고 은은~하게 스트레스도 쌓여서 바로바로 메모장이나 플래너에 적어두려고 하고 있다. 

 

 

 

마치며...

 섹션 4는 생각보다 빠르게 끝난 거 같다. 어려운 내용이 많았고, 이해가 안 되는 것들이 많지만 결국은 부트캠프 내내 한 이야기를 하고 있다는 생각이 든다. 오늘 배운 것이 일주일 전에 배운 거랑 이어진다거나... 이런 식으로 말이다. 코딩에 대해 찾아보다 보면 워낙 방대하고 프로그래밍 언어가 많다보니 뭐부터 배워야할 지 난감한데 부트캠프를 수료하면서 뭐부터 배우면 좋은 지에 대해 알게 되었다.

 

 페어 프로그램도 해 보기 전까진 으... 싶었는데 좋은 분들을 많이 만났고, 이런저런 대화를 해 보면서 세상 돌아가는 이야기도 듣고~ 어떻게 공부하고 계신지도 듣고~ 많은 도움과 공부 자극이 되었던 거 같다. 어려운 문제도 머리 하나보단 둘이 맞대니까 좀 더 쉽게 풀 수도 있었다.

 

 

 

 공부를 정말 코피날 정도로 열심히 해 본 기억은 없어서 내가 정말 코딩쪽 길이 맞는 걸까? 라는 생각이 자주 들었다. 코드를 제대로 짜고 있는 거 같지도 않고... 지식도 어정쩡하고... 차라리 실력을 갈고 닦아 그림 쪽으로 나가는 게 낫지 않을까 하는 생각도 자주 했는데... 그럴 때마다 이 시를 보며 마음의 위안을 얻었던 것 같다. 40기 아자아자 파이팅!

 

이미지 출처 : https://m.blog.naver.com/hongsiiya/220546955472

 

 

 

 

'Codestates > Section 4' 카테고리의 다른 글

[10/19] 기술면접  (0) 2022.10.19
[10/13] proxy 블로깅  (0) 2022.10.13
[10/12] CI/CD 파이프라인, 깃헙 액션 블로깅  (0) 2022.10.12
[10/7] Optimization 블로깅  (1) 2022.10.07
[9/26] 번들링과 웹팩  (0) 2022.09.26

JavaScript

  • Hoisting과 Temporal Dead Zone이 어떻게 연관되어 있는지 설명하세요.

=> 호이스팅은 선언되지 않은 함수, 변수를 import 구문으로 상단으로 끌어올려 사용할 수 있게 하는 방식을 뜻합니다.

 

브라우저 렌더링

  • 브라우저 렌더링 방식에 대해 설명하세요.

=> 우선 HTML과 CSS 파일을 파싱해서 각각 DOM tree를 만듭니다. 그 다음 두 tree를 결합해 렌더링 tree를 만들고, 렌더링 tree에서 각 노드의 위치와 크기를 계산합니다. 계산된 값을 이용해 각 노드를 화면상의 실제 픽셀로 변환하고, 레이어를 만듭니다. 그 다음 만들어진 레이어들을 합성하여 실제 화면으로 나타냅니다.

  • 리플로우와 리페인트에 대해 설명하세요.

=> 리플로우와 리페인트는 DOM 요소가 시각적으로 변경되었을 때, 다시 계산하고 화면에 그려주는 역할을 합니다. 리플로우는 발생하는 변화들에 맞춰 다시 계산 작업을 하는 역할을 하고 레이아웃이라고 부르기도 합니다. 리페인트는 다시 계산되어 변경된 요소들을 실제 화면에 그려주는 작업을 합니다. 그러므로 리플로우가 발생하면 필연적으로 리페인트도 따라옵니다.

  • 반응형 웹은 무엇이고 장단점에 대해 설명하세요.

=> 반응형 웹이란 다양한 디바이스 환경에서 원활하게 렌더링되는 웹 페이지를 뜻합니다.

 반응형 웹의 장점은 모든 기기에서 최적화된 웹사이트를 제공할 수 있다는 점, 모바일과 웹사이트 두 가지 버전을 만들지 않아도 되기 때문에 비용, 시간, 인력을 줄일 수 있고 유지보수가 쉽다는 점, 검색엔진 노출이 용이하다는 점이 있습니다. 

 반응형 웹의 단점으론 사용자가 다른 기기로 이용할 생각이 없는데도 모든 기기를 위한 css를 전부 다운로드 해야하기 때문에 데이터를 낭비하고, 로딩 시간이 길어진다는 점이 있습니다.

  • 자바스크립트 엔진의 콜 스택이 무엇인지 설명할 수 있나요?

=> 콜 스택이란 메모리 영역에서 스택 영역에 해당하며, 함수의 호출을 기록하는 스택 자료구조입니다.

 

번들링과 웹팩

  • 번들링은 왜 필요한가요?

=> 번들링은 모듈간의 의존성을 파악해서 그룹화를 시켜주는 작업을 뜻합니다. 여러 개의 파일을 브라우저에서 로딩한다면 속도가 느려지고, 모듈 간의 변수 충돌 등 위험성이 존재하기 때문에 분리되어 있는 파일들을 하나로 묶어주는 번들링 작업이 필요합니다. (난독화 내용 추가) 

 

React

  • Virtual DOM이 무엇이고, Virtual DOM이 어떤 면에서 좋은가요?

=> 버추얼 돔은 가상의 돔 객체로 실제 돔의 가벼운 사본 같은 개념입니다. 돔은 계층적 구조로 되어 있는 트리이기 때문에 저장된 데이터를 효과적으로 탐색하는 것에 초점이 맞춰있습니다. 실제 돔을 조작하는 일은 브라우저에 실제로 그리기 때문에 느리지만, 가상의 돔은 변화 전과 후를 비교하고 바뀐 부분만 적용하기 때문에 훨씬 속도가 빠르다는 장점이 있습니다. 또한 버추얼 돔을 사용하면 실제 DOM은 최소한의 작업만 수행하기 때문에 DOM의 업데이트 비용을 줄일 수 있다는 장점도 있습니다.

  • Class Component와 Function Component의 차이점이 무엇인가요?

=> 

  • React Hook의 사용 규칙에 대해 설명하세요.

=>

 

 

 

운영체제

  • Node.js는 싱글 스레드인가요?

=> 

  • JavaScript는 싱글 스레드입니다. 어떻게 싱글 스레드 방식으로 비동기 호출을 할 수 있는 지에 대해 설명할 수 있나요?

=> 

  • Event Loop에 대해 설명할 수 있나요?

=> 

  • 가비지 컬렉션이란 무엇이며, 가비지 컬렉션을 가진 언어에는 무엇이 있나요?

=>

 

 

자료구조

  • Stack과 Queue의 차이점은 무엇인가요?

=> 

  • Tree와 Graph의 차이점은 무엇인가요?

=> 

  • 이진 탐색 방법에 대해 설명할 수 있나요?

=> 

'Codestates > Section 4' 카테고리의 다른 글

[10/19] Section 4 회고 블로깅  (0) 2022.10.19
[10/13] proxy 블로깅  (0) 2022.10.13
[10/12] CI/CD 파이프라인, 깃헙 액션 블로깅  (0) 2022.10.12
[10/7] Optimization 블로깅  (1) 2022.10.07
[9/26] 번들링과 웹팩  (0) 2022.09.26

CORS 정책이 필요한 이유

- 기본적으로 브라우저의 현재 주소와 요청한 API의 주소의 도메인이 일치해야 데이터에 접근할 수 있다.

- 다른 도메인에서 API를 요청해 사용할 수 있게 하려면 CORS 설정이 필요하다.

 

CORS

  • 교차 출처 리소스 공유.
  • 추가 HTTP 헤더를 사용해, 실행 중인 웹 애플리케이션이 다른 출처의 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제.

 

출처

  • 웹 콘텐츠의 출처(origin)은 접근할 때 사용하는 URL의 스킴(프로토콜), 호스트(도메인), 포트로 정의된다. 두 객체의 앞의 3가지가 모두 일치하는 경우 같은 출처를 가졌다고 한다.
  • 일부 작업은 동일 출처 콘텐츠로 제한되나, CORS를 통해 제한을 해제할 수 있다.

 

=> 로컬 환경(3000포트)에서 개발한 프로젝트를 클라이언트 서버의 API로 요청하게 되면, 차단되는 것을 경험할 수 있다. 하지만 귀찮다고 모든 출처의 접근을 허락한다면 보안성이 현저히 낮아지고, 해킹의 위험에 노출되게 된다. (DB에 쌓인 라이브 데이터가 갈취당하게 될 수도...)

 

=> 따라서, 모든 도메인이 아닌 특정 도메인만 허용하도록 구현해야 한다.

 

 

CORS 에러 해결 방법

프론트엔드가 백엔드에게 개발 서버 도메인을 허용해달라고 요청하고, 백엔드가 응답 헤더에 필요한 값을 담아 전달해야 한다.

(서버에서 적절한 응답 헤더를 받지 못하면 브라우저에서 에러가 발생하기 때문)

 

혹은 Proxy를 이용해 해결 가능하다.

 

 

 

 

Proxy 란?

: 프론트엔드가 백엔드에게 요청을 하고, 응답을 받고 하는 과정없이 CORS 정책을 우회할 수 있도록 만들어 준다.

 

  • proxy를 사용해 CORS 정책을 우회하면,
    • 백엔드 서버는 응답을 React 앱으로 보내고, React 앱은 받은 응답을 백엔드 서버 대신 브라우저에게 전달하기 때문에 출처가 같아지게 된다.
  • 리액트 앱이 서버로부터 받은 응답 데이터를 다시 브라우저로 전달하는 방법을 쓰기 때문에 브라우저는 CORS 정책을 위반한지 모른다.
  • 별도의 응답 헤더를 만들 필요없음.

 

 

 

(1) Webpack Dev Server

브라우저 API를 요청할 때 백엔드 서버에 요청하지 않고, 현재 개발서버의 주소로 우회 요청을 하게 됨.

-> 웹팩 개발 서버에서 해당 요청을 받아 백엔드 서버로 전달함

-> 백엔드 서버에서 응답한 내용을 다시 브라우저쪽으로 반환함

 

웹팩 개발서버의 proxy 설정값은 웹팩 설정을 통해 적용하지만, 

CRA를 통해 만든 리액트 프로젝트에선 package.json에 "proxy" 값을 설정해주면 됨.

...
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
	"proxy" : "우회할 API 주소"
}

그리고 기존의 fetch 혹은 axios를 통해 요청하던 부분의 도메인을 수정함.

//수정전
export async function getAllfetch() {

    const response = await fetch('우회할 api주소/params');
    .then(() => {
			...
		})
}

//수정후
export async function getAllfetch() {

    const response = await fetch('/params');
    .then(() => {
			...
		})
}

 

 

 

(2) htttp-proxy-middleware 

위의 (1) 방법의 프록시는 전역설정이기 때문에, 종종 적용되지 않을 수도 있다.

그래서 수동으로 프록시를 설정해줘야 하는 상황이 발생할 수 있는데, 이때는 http-proxy-middleware 라이브러리를 사용해야 한다.

 

 

우선 npm으로 라이브러리 설치

npm install http-proxy-middleware --save

'src/setupProxy.js' 파일 생성 후 아래와 같이 작성

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api', //proxy가 필요한 path prameter를 입력합니다.
    createProxyMiddleware({
      target: 'http://localhost:5000', //타겟이 되는 api url를 입력합니다.
      changeOrigin: true, //대상 서버 구성에 따라 호스트 헤더가 변경되도록 설정하는 부분입니다.
    })
  );
};

기존의 fetch 혹은 axios를 통해 요청하던 도메인 부분 수정.

//수정전
export async function getAllfetch() {

    const response = await fetch('우회할 api주소/params');
    .then(() => {
			...
		})
}

//수정후
export async function getAllfetch() {

    const response = await fetch('/params');
    .then(() => {
			...
		})
}

'Codestates > Section 4' 카테고리의 다른 글

[10/19] Section 4 회고 블로깅  (0) 2022.10.19
[10/19] 기술면접  (0) 2022.10.19
[10/12] CI/CD 파이프라인, 깃헙 액션 블로깅  (0) 2022.10.12
[10/7] Optimization 블로깅  (1) 2022.10.07
[9/26] 번들링과 웹팩  (0) 2022.09.26

 

CI/CD란?

- CI : 개발자를 위한 자동화 프로세스인 지속적인 통합을 의미함.

  • CI를 성공적으로 구현할 경우, 애플리케이션에 대한 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트되어 공유 리포지토리에 통합되므로 여러 명의 개발자가 동시에 애플리케이션 개발과 관련된 코드 작업을 할 경우 서로 충돌할 수 있는 문제를 해결 가능.

- CD : 지속적인 서비스 제공 및/또는 지속적인 배포를 의미함. (제공과 배포는 상호 교화적으로 사용됨)

  • 두 가지 의미 모두 파이프라인의 추가 단계에 대한 자동화를 뜻함.
  • 어쩔 때는 얼마나 많은 자동화가 이루어지고 있는 지를 설명하기 위해 별도로 사용되기도 함

 

 

 

 

CI/CD 단계

일반적인 앱의 개발 및 유지보수 단계

PLAN -> CODE -> BUILD -> TEST -> RELRASE -> DEPLOY -> OPERATE -> PLAN ...

 

+

 

지속적인 통합 (CI)

: 개발자를 위한 자동화 프로세스. 코드 - 빌드 - 테스트 단계에서 꾀할 수 있음

  • Code : 개발자가 코드를 원격 코드 저장소(깃헙 리포지토리라던가) 에 push 하는 단계
  • Build : 원격 코드 저장소로부터 코드를 가져와 유닛 테스트 후 빌드하는 단계
  • Test : 코드 빌드의 결과물이 다른 컴포넌트와 잘 통합되는 지 확인하는 과정

 

- 장점

  • 보안 이슈, 에러 등을 쉽게 파악해 빠르게 개선 가능.
  • 테스트가 완료된 코드를 빠르게 전달 가능.
  • 지속적인 배포 가능.

 

지속적인 배포 (CD)

: 지속적인 서비스 제공 및 지속적인 배포를 의미. 릴리즈 - 디플로이 - 오퍼레이트 단계에서 꾀할 수 있음.

  • Release : 배포 가능한 소프트웨어 패키지를 작성
  • Deploy : 프로버저닝 ()을 실행하고 서비스를 사용자에게 노출함. 실질적 배포 부분.
  • Operate : 서비스 현황을 파악하고 생길 수 있는 문제를 감지함.

 

- 장점

  • 운영팀이 빠르고 쉽게 애플리케이션을 프로덕션으로 배포할 수 있음.

 

지속적인 배포 사례

깃헙 페이지.

지정한 디렉터리에 정해진 방식에 따라 잘 커밋만 하면 깃헙 페이지가 알아서 해당 index.html 파일을 해당 디렉터리에 있는 파일들을 잘 번들링해서 깃헙 페이지 서버에 업로드함. (자동으로 인터넷에 배포)

 

 

 

 


 

배포 자동화

- 필요한 이유

  • 수동적이고 반복적인 배포 과정을 자동화해서 시간 절약 가능
  • 휴먼 에러를 방지할 수 있음
    • 휴먼 에러란? 사람이 수동적으로 배포 과정을 진행하는 중에 생기는 실수를 뜻함.

 

 

 

CI/CD 파이프라인

: 배포 과정을 자동화시키는 방법을 뜻함.

 

배포 과정

1. 개발자가 코드를 원격 저장소에 push

2. (code) 코드 저장소

3. (build, test, release) 테스트/ 빌드 서버

4. 모든 과정을 통과한 빌드를 배포 서버로 전달 -> (deploy) 프로비저닝 배포 서버

6. 애플리케이션 서버 (결과물을 유저가 확인)

 

=> 코드가 빌드 되고, 최종적으로 배포되는 단계까지를 일련의 자동화 단계로 만드는 것을 "파이프라인을 구축한다"고 표현함.

 

 

 

CI/CD 파이프라인을 구성하는 기본 단계와 수행 작업

파이프라인 : 소스 코드의 관리부터, 실제 서비스로의 배포 과정을 연결하는 구조를 뜻함.

  전체 배포 과정을 여러 단계로 분리함. 각 단계는 파이프라인 안에서 순차적으로 실행되며, 각 단계마다 주어진 작업(Actions)을 수행함.

  • Source 단계 : 원격 저장소에 관리되고 있는 소스 코드에 변경사항이 생길 경우, 이를 감지하고 다음 단계로 전달하는 작업을 수행.
  • Build 단계 : 앞에서 전달받은 코드를 컴파일, 빌드, 테스트하여 가공함. 단계를 거쳐 생성된 결과물을 다음 단계로 전달함.
  • Deploy 단계 : 전단계에서 전달받은 결과물을 실제 서비스에 반영하는 작업을 수행.

 

 

파이프라인 구성 요소 및 장점

  • 빌드 (소프트웨어 컴파일)
  • 테스트 (호환성 및 오류 검사)
  • 릴리스 (버전 제어 저장소의 애플리케이션 업데이트)
  • 배포 (개발에서 프로덕션 환경으로의 변환)
  • 규정 준수 및 유효성 검사

 

 

 


 

 

Github Actions이란?

: 깃헙이 공식적으로 제공하는 빌드, 테스트, 배포 단계 파이프라인을 자동화할 수 있는 CI/CD 플랫폼

  • 레포지토리에서 Pull Request, Push 같은 트리거로 깃헙 작업 워크플로를 구성함.
    • 워크플로란? 하나 이상의 작업이 실행되는 자동화 프로세스. 각 작업은 자체 가상 머신 또는 컨테이너 내부에서 실행됨
  • 워크플로는 .yml(혹은 .yaml) 파일에 의해 구성됨.
  • 테스트, 배포 등 기능에 따라 여러개의 워크플로를 만들 수 있음.
  • 생성된 워크플로는 .github/workflows 디렉토리 이하에 위치함.
  • 비공개 레포지토리는 깃헙액션의 작동 용량과 시간이 제한되어 있음. 공개 레포지토리는 무료로 사용 가능.

 

YAML, AML

: (Yet Another Markup Language)의 약자, 사람이 읽을 수 있는 데이터 직렬화 언어를 의미함.

- 배우기 쉬운 프로그래밍 언어.

- 다른 프로그래밍 언어와 함께 사용할 수 있음

=> 유연성과 접근성이 좋아 자동화 프로세스를 생성하는 데에도 사용됨.

name: Bare Minimum Requirements
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Bare Minimum Requirements
        uses: actions/setup-node@v1
        with:
          node-version: '16'
      - run: npm install
      - run: npm test
  • key-value 형태로 작성된 파일. 계층 구조를 가짐.(JSON과 같음) 
  • 큰따옴표없이 문자열 작성 가능. => 한 눈에 들어옴
  • {} 형태로 감싸줄 필요 없음. => 스코프를 잘못 쓸 일이 사라짐
  • 주석 사용 가능 (JSON은 못씀)
  • JSON의 상위호환격. => 기존 json 문서를 yaml 파일로 사용하거나 원하는 부분만 손보기 가능. (반대도 가능)

 

문법

# : 주석

--- : 문서 시작 (선택 사항)

... : 문서 끝 (선택 사항)

#기본 표현
# : 다음엔 무조건 공백 문자가 와야함
key: value

int, string, boolean, 리스트, 매핑을 지원함.

int, string 타입은 스칼라라고 부르고, 배열 혹은 리스트는 시퀀스라 부름.

#객체 표현
key: 
	key: value
    key: value
   
#가독성을 위해 이렇게 작성하기도 함
key: {
	key: value
    key: value
}
#줄바꿈 표현 |
#줄바꿈 무시표현 >

key: |
	hello world
    i'm kimcoding.
    
key: >
	hello world
    i'm kimcoding.
#value에 :가 들어간 경우 반드시 따옴표 필요
#이렇게 쓰면 error남
key: value:

#이렇게 써야함
key: "value:"

 

 


 

마이 아고라 스테이츠 레퍼런스 코드를 깃헙 액션을 통해 s3로 배포하는 법에 대해 공부했다.

yml 파일 적는 법은 생각보다 어렵지 않았다. 

그런데 aws 아이디와 키 오류를 만나서 이것저것 찾아봤는데 정말 뭐가 뭔지 모르겠더라...

기다려서 새로온 메일에 적힌 걸로 바꿔서 넣어봤더니 잘 됐다... (?)

결국 aws 엑세스 키랑 시크릿 키값이 잘 못되어서 안 되는 문제였다.

 

 

s3 배포 링크

https://fe-70-heera1.s3.ap-northeast-2.amazonaws.com/index.html

 

'Codestates > Section 4' 카테고리의 다른 글

[10/19] Section 4 회고 블로깅  (0) 2022.10.19
[10/19] 기술면접  (0) 2022.10.19
[10/13] proxy 블로깅  (0) 2022.10.13
[10/7] Optimization 블로깅  (1) 2022.10.07
[9/26] 번들링과 웹팩  (0) 2022.09.26

Lighthouse

사이트를 검사해(성능, 접근성, PWA, SEO 등) 성능 측정하여 웹 페이지의 품질을 개선할 수 있는 자동화 툴

 

더보기

Chrom 개발자 도구에서 실행하기

(1) 크롬에서 검사하고 싶은 페이지의 url을 입력

(2) 개발자 도구 열기

(3) lighthouse 탭 클릭

(4) Generate report 클릭

 

 

Node CLI에서 실행하기

(1) Lighthouse 설치

-g 옵션을 사용해 전역 모듈로 설치하는 것이 좋음

npm install -g lighthouse

(2) lighthouse <url> 명령어로 검사 실행

(3) lighthouse --help 명령어로 모든 옵션을 볼 수 있음

 

 

 

 

분석 결과 항목

(1) Performance

- 웹 성능 측정

- 화면에 콘텐츠가 표시되는데 걸리는 시간, 표시된 후 사용자와 상호작용하기까지 얼마나 걸리는 지, 화면에 불안정한 요소는 없는 지 등을 확인

 

(2) Accessibility

- 웹 페이지가 웹 접근성을 잘 갖추고 있는지 확인

- 대체 텍스트, 배경색과 콘텐츠 색상의 대비, 적절한 WAI-ARIA 속성을 사용했는 지 등을 확인

 

(3) Best Practices

- 웹 페이지가 웹 표전 모범 사례를 잘 따르는 지 확인

- HTTPS 프로토콜 사용 여부, 콘솔 창에 오류가 표시되지는 않는 지 등을 확인

 

(4) SEO

-웹 페이지가 검색 엔진 최적화가 잘 되어있는 지 확인

- 애플리케이션의 robots.twt가 유효한지, <meta> 요소는 잘 작성되어 있는지, 텍스트 크기가 읽기에 무리 없는 지 등을 확인

 

(5) PWA

- 웹 사이트가 모바일 애플리케이션으로서도 잘 작동하는 지 확인

- 앱 아이콘 제공, 스플래시 화면 여부, 화면 크기에 맞게 콘텐츠를 적절하게 배치했는 지 등을 체크리스트로 확인

 

 

 

Performance 측정 메트릭

(1) Frist Contentful Paint

- 성능 지표를 추적하는 메트릭

- 사용자가 페이지에 접속했을 때 브라우저가 DOM 컨텐츠의 첫 번째 부분을 렌더링하는데 걸리는 시간을 측정

- 우수한 사용자 경험을 제공하려면 1.8초 이하여야 함

- 이미지, <canvas>, SVG 요소 등 모두 DOM 콘텐츠로 구분됨. <iframe>은 포함되지 않음.

 

 

(2) Largest Contentful Paint

- 뷰포트를 차지하는 가장 큰 콘텐츠(이미지 또는 텍스트 블록)의 렌더 시간을 측정.

 

 

(3) Speed Index

- 성능 지표를 추적하는 메트릭

- 페이지를 로드하는 동안 얼마나 빨리 콘텐츠가 시각적으로 표시되는 지를 측정.

 

 

(4) Time to interactive

- 페이지가 로드되는 시점부터 사용자와의 상호작용이 가능한 시점까지의 시간을 측정.

- 기준

  • 페이지에 FCP로 측정된 콘텐츠가 표시되어야 함
  • 이벤트 핸들러가 가장 잘 보이는 페이지의 엘리먼트에 등록됨
  • 페이지가 0.05초안에 사용자의 상호작용에 응답함

 

(5) Total Blocking Time

- 페이지가 유저와 상호작용하기까지의 막혀있는 시간을 측정

- FCP와 TTI 사이에 긴 시간이 걸리는 작업들을 모두 기록하여 TBT를 측정함

- 0.05초를 초과하는 작업은 긴 작업으로 간주됨

 

 

(6) Cumulative Layout Shift

- 사용자에게 콘텐츠가 화면에서 얼마나 많이 움직이는지(불안정한 지)를 수치화한 지표

 (ex : 온라인 기사를 읽다가 갑자기 페이지 일부분이 바뀐다거나)

 

 

 

 

 

Zigzag 홈페이지 Lighthouse 사용해 보기

더보기

 

<개선해야할 점>

(1) 차세대 형식을 사용해 이미지 제공하기

=> 용량이 큰 PNG, JPEG 확장자가 아닌 WebP나 AVIF 사용하여 데이터 소비량 줄이기

 

 

(2) 효율적으로 이미지 인코딩하기

=> 이미지 CDN 사용, 이미지 압축, 애니메이션 GIF를 비디오로 대체 등을 통해 이미지를 최적화하여 데이터 소비량 줄이기

 

 

(3) 이미지 크기 적절하게 설정하기

사용자 화면에 렌더링된 버전보다 큰 이미지가 페이지에 제공되고 있음.

반응형 이미지 제공하기 : 이미지 크기 조정 도구 Sharp npm 패키지와 ImageMagick CLI 도구를 사용하기

 

=> 반응형 이미지 제공을 통해 적절한 크기의 이미지를 제공하여 데이터를 절약하고 로드 시간을 줄이기

 

 

(4) 네트워크 페이로드가 커지지 않도록 관리하기

네트워크 페이로드는 긴 로드 시간과 높은 상관관계가 있음. ( => 사용자는 더 많은 셀룰러 데이터에 대한 비용을 지불하게 됨)

페이로드 크기를 줄이는 법 :

  • 네트워크 페이로드 최소화 및 압축하기
  • 이미지 확장자를 WebP나 AVIF 로 변경하기
  • 페이지에 반복 방문 시 리소스를 다시 다운로드하지 않도록 요청을 캐시하기

 

(5) 캐시 정책을 사용하여 정적인 에셋 제공하기

=> 캐시를 사용하여 반복 방문시 리소스를 다시 다운로드하지 않도록 만들기

 

 

(6) 웹 폰트가 로드되는 동안 텍스트가 계속 표시되는지 확인하기

폰트를 로드하는데 시간이 많이 걸리는 대용량 파일인 경우, 일부 브라우저는 글꼴이 로드될 때까지 텍스트를 숨기면서 보이지 않는 텍스트의 플래시(텍스트를 숨겨버림)를 일으킴.

 

=> 보이지 않는 텍스트가 표시되지 않도록 시스템 글꼴을 일시적으로 표시하여 개선함.

 CSS에 @font-face 스타일에 font-display: swap 을 포함하면 대부분의 최선 브라우저에서 텍스트의 플래시를 피할 수 있음

 

 

'Codestates > Section 4' 카테고리의 다른 글

[10/19] Section 4 회고 블로깅  (0) 2022.10.19
[10/19] 기술면접  (0) 2022.10.19
[10/13] proxy 블로깅  (0) 2022.10.13
[10/12] CI/CD 파이프라인, 깃헙 액션 블로깅  (0) 2022.10.12
[9/26] 번들링과 웹팩  (0) 2022.09.26

+ Recent posts