ㅇㅇㅈ Blog

프론트엔드 수행중

0%

객체 배열 true false 반환

Array.prototype.some()

객체로 이루어진 배열에서 true false 를 반환하는 메소드

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/some

미니 프로젝트를 하다가 객체로 이루어진 배열에서 true false를 반환 해야 했는데
forEach로 하나씩 비교하다 보니 사이드 이펙트가 일어나면서 버그가 생겼다..

some()을 이용하면 배열을 순환 후 조건에 알맞는 boolean을 반환 한다

사용법은 filter()와 동일 하나, filter는 새로운 배열을 반환한다는 점이 차이점

1
2
3
4
5
6
7
const array = [1, 2, 3, 4, 5];

// checks whether an element is even
const even = (element) => element % 2 === 0;

console.log(array.some(even));
// true

실제 project에서는 여러번 사용이 되어 함수로 만들어 주었다

1
2
3
4
// store에 있는 clipped배열안의 headline값과 내가 입력한 headline의 값을 비교하여 true false를 반환한다
const clipCheck = (clipped) => {
return !clipped.some((storeData) => storeData.headLine === headLine);
};

검색 요청 최적화

input에 타이핑을 칠때마다 리렌더링이 되면서 데이터요청이 된다..

조건문과 setTimeout으로 인풋에 입력이 끝나면 데이터요청을 하도록 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const App: FC = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [news, setNews] = useState<News[]>([]);
const [hasMore, setHasMore] = useState(false);
const [search, setSearch] = useState('');
const [page, setPage] = useState(0);

// 데이터 요청
const getData = (query: string, page: number) => {
setLoading(true);
setError(false);

axios({
method: 'GET',
url: `https://api.nytimes.com/svc/search/v2/articlesearch.json?api-key=${API_KEY}`,
params: { q: query, page },
})
.then((res) => {
// console.log(res.data.response.docs);
setNews((prevNews) => {
return [...new Set([...prevNews, ...res.data.response.docs])];
});
setHasMore(res.data.response.docs.length > 0);
setLoading(false);
})
.catch((e) => {
if (axios.isCancel(e)) return;
setError(true);
});
};
useEffect(() => {
setNews([]);
}, [search]);

useEffect(() => {
if (search !== '') {
if (!loading) {
const timer = setTimeout(() => {
getData(search, page);
setPage(1);
}, 1500);
return () => {
clearTimeout(timer);
};
}
}
}, [search, page]);

먼저 데이터요청 함수 getData를 작성해주고
useEffect 안에서 함수를 호출 한다.

1
2
3
useEffect(()=>{
getData(search,page)
},[search,page])

이렇게 작성하고 나면 문제점

  1. 돔이 마운트 될때 useEffect가 한번 실행되면서 검색어를 쓰지 않아도 검색이 시작된다는 점.
  2. setSearch로 인풋에서 value를 받아올때마다 리렌더링이 되면서 타이핑 갯수만큼 검색요청이 일어난다는 점.

조건문과 setTimeout으로 해결

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
useEffect(()=>{
if(search !== '') {
// search가 비어있지 않으면 요청 시작
if(!loading) {
// loading이 false이면 요청 시작
const Timer = setTimeout(()=>{
// setTimeout으로 1.5초 후에 getData() 호출
getData(search,page)
},1500)
}
return ()=>{
// cleanUp 으로 clearTimeout
// cleanUp 을 안해주면 그냥 1.5초후에 타이핑 갯수만큼 요청이 일어난다
clearTimeout(timer)
}
}
},[search,page])

Next.js 공식문서 따라만들기 (2)

Assets

Next.js는 최상위 public 디렉토리에서 static assets를 관리한다

img 파일 넣기

public 디렉토리 안에 images 디렉토리를 만들고 이미지를 넣어 준다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import Link from 'next/link';

const FirstPost = () => {
return (
<>
<div>FirstPost</div>
<h2>
<img src='/images/profile.jpg' alt='' />
</h2>
</>
);
};

export default FirstPost;

img src 경로가 신기하다

FirstPost 컴포넌트는 경로가 pages/posts 인데 public 안의 images 경로에 바로 접근 했다 ..

Image 컴포넌트

시매틱 img태그를 사용하면 다양한 화면 크기에서 반응, 뷰포트에 들어갈때만 이미지 로드 등등..을 수동으로 처리 해야 한다

그래서 Next.js에서 Image 컴포넌트를 제공한다
Image 컴포넌트를 사용하면 Next.js가 알아서 최적화를 해준다
Image컴포넌트를 사용하면 빌드할때 이미지를 최적화 하는 대신 사용자가 요청 할때 이미지를 최적화 한단다..
그래서 빌드할때 이미지를 10개를 하든 1천만개 이미지를 하든 빌드 시간이 증가하지 않는다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React from 'react';
import Link from 'next/link';
import Image from 'next/image';

const FirstPost = () => {
return (
<>
<div>FirstPost</div>
<h2>
<img
src='/images/profile.jpg'
height={144}
width={144}
/>
<Image
src='/images/profile.jpg'
height={144}
width={144}
alt='profile'
/>
</h2>
</>
);
};

export default FirstPost;

사용법은 그냥 Image를 임포트하고 일반 img태그 처럼 사용하면 된다

같은 img를 사용했음에도 불구하고 파일 크기를보면 img태그와 Image컴포넌트로 만든 img의 차이가 많이 난다

이미지는 기본적으로 지연 로드됩니다. 즉, 뷰포트 외부의 이미지에 대해서는 페이지 속도에 영향을 미치지 않습니다. 이미지는 뷰포트로 스크롤될 때 로드됩니다.

Head 태그

일반적 HTML의 head태그에 들어가는 정보들을 Head 를 통해 작성 해 줄 수 있다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Head from 'next/head';
import Link from 'next/link';
export default function Home() {
return (
<div className='container'>
<Head>
<title>My page title</title>
<meta property='og:title' content='My page title' key='title' />
</Head>
<h1>
<ul>
<Link href='/posts/first-post'>
<li>this Page</li>
</Link>
<Link href='/posts/Hello'>
<li>Hello World</li>
</Link>
</ul>
</h1>
</div>
);
}

헤드 태그 안에 정보들이 들어가 있다

두 가지 형태의 사전 렌더링

NEXT는 브라우저에 렌더링 할 때 기본적으로 pre-rendering을 한다.
pre-rendering이란 각 페이지들을 사전에 미리 HTML 문서로 생성하여 가지고 있는 것 그리고 서버로 요청이 들어올 때 알맞은 페이지를 반환해준다.
그 중 두가지 형태의 사전렌더링이 있다
👉 Static-Generation(SSG)

  • HTML을 빌드 타임에 각 페이지별로 생성하고 해당 페이지에 요청이 올 때 미리 생성된 HTML문서를 반환
  • 서버에서 페이지가 생성이 되는게 아니라 빌드할때 생성된 문서를 보여주는것이므로 속도가 빠르다
  • 공식문서에서는 마케팅페이지/ 블로그게시물/ 도움말 및 문서 등을 페이지로 사용할때 SSG 사용을 권하고있다

👉 Server-side Rendering(SSR)

  • 서버로 요청이 올 때마다 해당하는 HTML 문서를 그때 그때 생성하여 반환
  • SSR방식은 서버에서 요청을 받을때마다 그에 상응하는 HTML을 생성하여 반환하기 때문에 항상 최신 상태를 유지해야하는 경우가 적합하다 (좋아요 표시라든지)

getStaticProps

블로그에 데이터를 추가하고 불러오기

최상위 경로에 posts directory를 만들고 안에 블로그 게시물을 md문서로 만든다

Read more »

Material-ui 따라 해보기

1
2
3
4
5
$ yarn create vite
..
$ yarn

react-ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// App.tsx

import Header from './components/Header';
import Nav from './components/Nav';
import Article from './components/Article';
function App() {
return (
<>
<Header />
<Nav />
<Article />
</>
);
}
export default App;

간단한 컴포넌트를 만들고 임포트

mui 설치

https://mui.com/material-ui/getting-started/installation/

mui는 기본으로 emotion을 사용한다 근데 styled-component도 사용 할 수 있다
그래서 styled-components 도 함께 설치

1
$ yarn add @mui/material @mui/styled-engine-sc styled-components

설치 후 article 컴포넌트에서 button을 만들어 보았다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import { Button } from '@mui/material';
const Article = () => {
return (
<div>
<article>
<h2>Welcome</h2>
Hello Web!
<br />
</article>
<Button variant='contained'>Text</Button>
</div>
);
};

export default Article;

Button 을 mui/material 에서 임포트후 컴포넌트 처럼 사용 하는 것

error..

저렇게 하고 렌더링을 하면 무한 로딩이 되면서 터미널에 에러가 잔뜩 뜬다

대충 보니 자세히는 몰라도 emotion이 안깔려 있어서 에러를 내뱉는거 같다

찾아보니 mui는 기본적으로 emotion을 사용하기 때문에 styled-components를 사용할려면 추가 설정을 해줘야 한다

https://mui.com/material-ui/guides/styled-engine/

가이드가 꽤 친절하게 되어있다 그냥 따라서 package.json 에 resolutions를 추가해주고

1
2
3
4
5
6
7
8
9
10
"dependencies": {
...

"@mui/styled-engine-sc": "^5.8.0",

},
// 아래는 추가 되는 부분
"resolutions": {
"@mui/styled-engine-sc": "^5.8.0"
}
Read more »

실행 컨텍스트 (Excute Context)

자바스크립트 코드가 실행되고 연산되는 범위를 나타내는 추상적인 개념
-> 우리가 코드를 작성하고 실행한다면 실행 컨텍스트 내부에서 실행되고 있는 것

자바스크립트는 변수를 읽으려고 할때 scope(범위)에서 변수를 찾는다

변수 선언 앞에 아무것도 붙지 않으면 global에 저장 된다

scope 안에 script 안에는 n0 가 없다
global에서 n0를 찾을 수 있다

변수 선언 앞에 var가 붙으면 아무것도 붙지 않은것과 마찬가지로 global에 저장 된다

let이 붙으면 script 안에 저장 된다

const도 마찬가지로 script 안에 저장 된다

scope의 global 옆에 보면 window가 쓰여 있다
웹브라우저에서 동작하는 자바스크립트는 window객체가 global객체 또는 global scope이라 할 수 있다

1
2
3
4
5
6
alert(1) 
// 경고창으로 1이 뜬다
window.alert(1)
// 마찬가지로 경고창으로 1이 뜬다

alert은 window 객체 즉 global scope에 포함되어 있다
1
2
3
4
5
// 그럼 global에 속해 있던 변수 n0, v0 역시 window. 으로 호출 할 수 있다
window.n0
// 'n0'
window.v0
// 'v0'

어디서 코드를 실행하던간에 scope 상에서 global은 바닥에 깔려있다
-> 어디서 실행하던 global은 접근이 가능하다

React Link to로 props 전달하기

실습과제를 진행하면서
게시글을 누르면 디테일페이지로 이동하고, 게시글 디테일을 볼 수 있게 만드는 과정에
data를 넘겨주는 방법에 대해 고민이 되었다

첫 번째로 해당 post의 id값을 이용해 데이터를 필터하는 법..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function PostList(props) {
return (
<>
{props.postList &&
props.postList.map((post) => {
// console.log(post);
return (
<PostCardDiv key={post._id}>
<Link to={`/Postdetail/${post._id}`}>
<p className='title'>{post.title}</p>
</Link>

<div className='author'>
<p className='name'>{post.author.name}</p>
<p className='date'>
{moment(post.updatedAt).format('YYYY년 MM월 DD일, hh:mm')}
</p>
</div>
<p className='content'>{post.content}</p>
</PostCardDiv>
);
})}
</>
);
}

postList에서 제목을 누르면 해당 post의 id값을 가진 url 주소로 이동이 된다

그럼 해당 컴포넌트에서 다시 aiox로 데이터를 호출해 id값을 비교해서 해당 게시물을 보여준다..

많이 아닌거 같아 다시 작성

1
2
3
<Link to={`/Postdetail/${post._id}`} state={{ post }}>
<p className='title'>{post.title}</p>
</Link>

react-router-dom에서 Link 를 이용해 데이터를 넘겨 줄 수 있다
해당 게시물의 정보를 state에 담아 보내는 것

그럼 useLocation을 통해 state값을 조회 할 수 있다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react';
import { useLocation } from 'react-router-dom';
const PostDetail = () => {
const location = useLocation();
console.log(location);

return (
<>

</>
);
};

export default PostDetail;

콘솔로 location을 찍어보면

클릭한 게시물의 데이터가 담겨 있다.

PostDetail에서 state를 조회 할 수 있는 건

route path로 설정 해둔 url의 element가 PostDetail 컴포넌트 이기 때문에 해당 컴포넌트로 데이터가 들어가는 것

next.js 공식문서 따라만들기

next.js

nextjs는 React로 만드는 서버사이드 렌더링(Server Side Rendering) 프레임워크

SSR

클라이언트로 전달될 HTML 파일 내용(일부를) 미리 그려서 내려주는 것

  • 클라이언트 렌더링의 속도를 빠르게 하여, 사용자 체감 속도 증진
  • 검색 엔진이 JavaScript를 실행하지 않고 크롤링 가능

✅ 기본적으로 React는 CSR (Client Side Rendering)을 한다.

  • 처음 웹사이트를 요청했을때 빈 html을 가져와 javasciprt를 로딩 한다
    리액트는 index.html의 root div 안의 모든것이 javascript로 그려지기 때문에
    검색엔진은 웹사이트를 분석하기 힘들다

실제 React로 만든 사이트를 자바스크립트를 사용하지 않고 load하면 페이지에 아무것도 표시 되지 않는다.

root div에도 아무것도 그려지지 않는다

반면 Next.js는 서버측에서 html파일 내용(일부를)미리 그려서 내려주기 때문에 화면이 표시가 된다


👍 크롬 개발자도구에서 javascript사용 중지를 했음에도 html이 그려져 있다

Read more »

05.29 작업내용

영화 추가 요청..

영화 추가요청을 하기 위한 로직을 작성했다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
export const fetchAsyncMovies = createAsyncThunk(
'search/fetchAsyncMovies',
async ({ title, type, year, number }) => {
const response = await axios
.get(
`http://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=1`
)
.then((res) => res.data)
.catch((error) => error.message);
const { totalResults } = response;
const total = parseInt(totalResults, 10);
const pageLength = Math.ceil(total / 10);

return response;

// if (pageLength > 1) {
// for (let page = 2; page < pageLength; page++) {
// if (page > number / 10) break;
// const response = await axios
// .get(
// `http://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=${page}`
// )
// .then((res) => res.data);
// return response;
// }
// }
}

문제는 처음 요청 했을때 state에 값을 저장해놓고 추가 요청이 들어가야 하는데
분리를 할 수가 없다.. 어떻게 해야되나

버튼 active 활성

영화 아이템을 클릭해서 상세 페이지로 넘어가면 버튼이 활성화가 되지 않는다
path 속성을 따로 만들어 movie와 일치하면 active class가 붙게 했다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const navigations = [
{
name: 'Home',
href: '/',
},
{
name: 'Movie',
href: '/movie/tt2294629',
path: /^\/movie/,
},
];

export default function Header() {
const location = useLocation();

const isMatch = (path) => {
if (!path) return false;
return path.test(location.pathname);
};
return (
<header className='header'>
<div className='nav nav-pills'>
<div className='nav-item'>
{navigations.map((nav) => {
return (
<NavLink
to={nav.href}
key={nav.name}
className={isMatch(nav.path) ? 'active nav-link' : 'nav-link'}
style={{ marginRight: 15 }}
>
{nav.name}
</NavLink>
);
})}
</div>
</div>
</header>
);
}

여기서 react-router-dom 의 useLocation을 사용했는데 location을 콘솔로 찍어보면

pathname이 찍힌다

isMatch 함수를 통해 path가 location.pathname에 들어있는지를 true,false로 반환 받는다

삼항연산자로 active가 붙을지 안붙을지를 작성해준다

Read more »

CSS in JS

➡️ CSS in JS란 말 그대로 .css파일이 아닌 자바스크립트 안에 CSS를 정의하는 것

https://ko.reactjs.org/docs/faq-styling.html#what-is-css-in-js

why?

  • 모든 스타일이 Global에 선언되어 중복되지 않는 이름을 적용해야 하는 문제
  • css에서 에러를 감지하기가 어렵기 때문에
  • 사용하지 않는 코드 관리
  • vue같은 scope설정으로 컴포넌트내에서 css를 관리하는게 용이하지만 리액트는 그러지 못한다..

styled-components 설치

1
2
3
$ yarn add styled-components

$ npm i styled-components
1
2
3
4
5
"dependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0",
"styled-components": "^5.3.5"
},

기본 문법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import styled from 'styled-components';

const Title = styled.h1`
text-align: center;
color: palevioletred;
`;

const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;

function App() {
return (
<Wrapper>
<Title>Hello world</Title>
</Wrapper>
);
}

컴포넌트 생성하는것처럼 이름을 정의하고 styled.element`` 로 요소를 만들고 back quote 안쪽에 일반 css처럼 스타일을 정의해 준다
그리고 진짜 컴포넌트처럼 사용해주면 된다.

클래스이름이 자동 생성 되어있다

props

props 전달을 통해 스타일을 정의 할 수 도 있다
js이기 때문에 리터럴보간법으로 작성 할 수 있다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

const Title = styled.h1`
text-align: center;
color: palevioletred;
`;

const Wrapper = styled.section`
padding: 4em;
background-color: ${(props) => (props.primary ? 'royalblue' : 'papayawhip')};
`;

function App() {
return (
<Wrapper primary>
<Title>Hello world</Title>
</Wrapper>
);
}

스타일 확장

기존의 요소에서 스타일을 상속받고 새로운 것만 추가해 새롭게 만들 수 있다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;

const TomatoButton = styled(Button)` // 기존 버튼을 상속받을때
color: tomato;
border-color: tomato;
background-color: royalblue;
`;


function App() {
return (
<Wrapper>
<Button>Normal Button</Button>
<TomatoButton>Tomato Button</TomatoButton>
</Wrapper>
);
}
1
2
3
4
5
6
7
8
9
10
11
function App() {
return (
<Wrapper>
<Title>Hello world</Title>
<Button as='a' href='#'>
Normal Link Button
</Button>
<TomatoButton>Tomato Button</TomatoButton>
</Wrapper>
);
}

이런식으로 요소의 속성을 바꿔줄 수도 있다

로그인 폼 만들기

2주차 실습과제

  • Form handling width state
  • State Handling with useEffect
  • State handling with Redux
  • component Handling with state

간단하게 매니저님이 서버를 만들어 주시고 id와 pw를 맞게 입력하면 로그인, 틀릴경우에 에러코드에 맞게 메세지를 출력하는 실습

로그인 컴포넌트

input으로 id와 password를 받아 각각 setId와 setPassword로 저장

저장된 id와 password를 body에 넣어주고,

axios post요청 할 때 url 주소와 body를 같이 넘겨 주면 된다.

그리고 loading의 boolean 값으로 로그인버튼의 disable을 조절 해 줄 예정 이므로

로그인 버튼이 눌리면 setLoading으로 값을 true로 변경 시켜 준다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import React, { useState, useEffect, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { loginUser } from '../reducer/userSlice.js';
import axios from 'axios';

function LoginComponent() {
const dispatch = useDispatch();
const [id, setId] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [msg, setMsg] = useState('');
const idInput = useRef(null);
const pwInput = useRef(null);

const body = {
id,
password,
};

useEffect(() => {
const timeout = setTimeout(() => {
setMsg('');
}, 1500);
return () => {
clearTimeout(timeout);
};
}, [msg]);

// 로그인 요청
const getUsers = async () => {

};
// 로그인
const LoginFunc = (e) => {
e.preventDefault();
setLoading(true)
getUsers();
};
// 에러 핸들
const handleError = (response) => {

};

// input handle
const handleChangeID = (e) => {
setId(e.target.value);
};
const handleChangePW = (e) => {
setPassword(e.target.value);
};

return (
<>
<h1>LoginComponent</h1>
<form onSubmit={LoginFunc}>
<label htmlFor='id'>ID : </label>
<input
type='text'
ref={idInput}
value={id}
id='id'
onChange={handleChangeID}
/>

<br />
<label htmlFor='password'>Password : </label>
<input
type='password'
id='password'
ref={pwInput}
value={password}
onChange={handleChangePW}
/>
<br />
<br />
<button disabled={loading} type='submit'>
로그인
</button>
<br />
</form>

<br />
{msg}
<br />
</>
);
}

export default LoginComponent;

Read more »