[코뮤니티 모각코] 웹 리액트 과정 - 3주차
2022-01-03 MON~2022-01-07 FRI의 기록 💻
📌 11일차 미디어쿼리
✅ 오늘의 문제 | 메뉴 없애기 ②
⭐ 핵심 <미디어 쿼리>
@media screen and (max-width: 600px) { // screen의 width가 <= 600px 일 때 .container{ background-color: black; } } @media screen and (min-width: 600px) and (max-width: 1000px) { // screen의 width가 >= 600px && <=1000px 일 때 .container { background-color: red; } } @media screen and (min-width: 1000px) { // screen width가 >=1000px 일 때 .container { background-color: blue; } }
cf) https://developer.mozilla.org/ko/docs/Learn/CSS/CSS_layout/Media_queries
🔽 src/components/shared/Layout.js
import { useState } from 'react';
import styles from './Layout.module.css';
import Header from './Header';
import Menu from './Menu';
function Layout({ children, activeMenu }) {
const [isMenuOn, setIsMenuOn] = useState(true);
function menuOnOff() {
setIsMenuOn(!isMenuOn);
}
return (
<div className={styles.container}>
<Header menuOnOff={menuOnOff}/>
<div className={styles.layout}>
{isMenuOn ? <Menu activeMenu={activeMenu}/> : null}
/* 삼항 조건 연산자로 isMenuOn 변수의 값에 따라 클래스이름 다르게 지정 */
<div className={isMenuOn ? styles['contents-menu-on'] : styles['contents-menu-off']}>{children}</div>
/***************************************************************/
</div>
</div>
);
}
export default Layout;
🔽 src/components/shared/Layout.module.css
.container {
display: flex;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: overlay;
flex-direction: column;
}
.layout {
display: flex;
margin-top: 56px;
flex: 1;
}
/* .contents 클래스 삭제 .contents-menu-on, .contents-menu-off 클래스 추가 및 각각의 css 지정*/
.contents-menu-on {
margin-left: 240px;
flex: 1;
}
.contents-menu-off { // 메뉴가 없을 때
margin-left: 0px; // 컨텐츠의 왼쪽 여백 삭제
flex: 1;
}
@media screen and (max-width: 911px) {
.contents-menu-on { // 적용 클래스이름을 .contents -> .contents-menu-on 로 변경
margin-left: 72px;
}
}
/********************************************************************************************/
📌 12일차 map
✅ 오늘의 문제 | 필터 변경하기
⭐ 핵심 <includes 함수 활용>
// A : 'car', B : 'car, bus' if (B.includes(A)) { ... } // A문자열이 B 내부에 있는 경우 true 리턴
⭐ CSS 관련 추가 공부
👉 가상요소 선택자 ::before & ::after
- 선택한 요소의 앞 또는 뒤에 content 추가 (content 속성 반드시 지정해줄 것!)
- 가상클래스(:focus, :hover, :active, :visited)와 구분
// ::after example .uploader::after, .view::after { content: '·'; margin: 0 4px; }
👉 -webkit-line-clamp
- 필수로 함께 지정해줄 속성들
display: -webkit-box 또는 -webkit-inline-box
-webkit-box-orient: vertical
overflow: hidden // 미설정 시 말줄임표는 노출되나 넘친 콘텐츠가 숨겨지지 않음// -webkit-line-clamp example .title, .desc { /* 2줄이 넘을 경우 ... 표시 */ display: -webkit-box; overflow: hidden; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
cf) https://developer.mozilla.org/ko/docs/Web/CSS/-webkit-line-clamp
🔽 src/pages/Home.js
import styles from './Home.module.css';
import Layout from '../components/shared/Layout';
import youtubeData from '../data/youtubeData.json';
import HomeFilter from '../components/home/HomeFilter';
import HomeCard from '../components/home/HomeCard';
import { useState } from 'react';
const target = ["전체", "BTS", "LISA", "아이폰"]; // 필터링 할 문자열(검색어) 수정
function Home() {
const [filter, setFilter] = useState("전체");
function mapFunc(data, index) {
return (
<HomeFilter
filter={filter}
text={data}
onClickFilter = {function() {
setFilter(data);
}}
key={`home-filter-${index}`}
/>
);
}
function filterFunc(data) {
if (filter === "전체" || data.title.includes(filter) || data.description.includes(filter)) {
// filter가 "전체"이거나 영상의 제목 또는 설명에 filter가 포함된 경우
return true;
}
return false;
}
return (
<Layout activeMenu="home">
<div className={styles.header}>
{target.map(mapFunc)}
</div>
<div className={styles.container}>
<div className={styles.grid}>
{youtubeData['data'].filter(filterFunc).map(HomeCard)}
</div>
</div>
</Layout>
);
}
export default Home;
📌 13일차 Home과 Filter
✅ 오늘의 문제 | input 태그 상태 관리
⭐ CSS 관련 추가 공부
👉 GRID layout
- grid-template-rows(행의 배치), grid-template-columns(열의 배치)는 그리드의 형태를 정의
- fr은 fraction으로 숫자의 비율대로 크기를 나눔
ex)grid-template-columns: 1fr 1fr 1fr // 1:1:1 비율의 3개의 열 생성
- repeat(반복횟수, 반복값)
ex)repeat(5, 1fr) // 1fr 1fr 1fr 1fr 1fr
- minmax 함수는 최솟값과 최댓값을 지정
ex)minmax(100px, auto) // 최소 100px, 최대 자동으로 늘어나게 지정
- auto-fill, auto-fit 개수를 미리 정하지 않고 설정된 너비가 허용되는 한 최대한 셀을 채움
ex)// 한 행에 100%/20% = 5(개)의 셀이 들어감
grid-template-columns: repeat(auto-fill, minmax(20%, auto) // auto-fill의 경우 셀 개수 < 5개일 경우 공간이 남음
grid-template-columns: repeat(auto-fit, minmax(20%, auto) // auto-fit의 경우 셀 개수 < 5개 일 경우 자동으로 남은 공간을 채움
- row-gap, column-gap은 그리드 셀 사이의 간격
// grid example .grid { display: grid; width: 100%; row-gap: 40px; column-gap: 16px; grid-template-columns: repeat(auto-fit, minmax(300px, auto)); }
🔽 src/components/shared/Header.js
import styles from './Header.module.css';
import youtube_logo from '../../data/youtube_logo.png';
import { FiMenu } from 'react-icons/fi';
import { IoSearchOutline } from 'react-icons/io5';
import { BsGrid3X3Gap } from 'react-icons/bs';
import { HiOutlineDotsVertical } from 'react-icons/hi';
import { useState } from 'react';
function Header( {menuOnOff} ) {
const [value, setValue] = useState("");
function onChange(event) {
setValue(event.target.value); // value 값을 event.target(input)의 value로 변경
}
function onClick() {
console.log(value); // value 값을 출력
setValue(""); // value 값 공백으로 초기화하여 input 비우기
}
return (
<div className={styles.header}>
<div className={styles.tab}>
<FiMenu className={styles.icon} onClick={menuOnOff}/>
<img src={youtube_logo} alt="로고" className={styles.logo} />
</div>
<div className={styles['center-tab']}>
<input
className={styles.input}
onChange={onChange} // input의 값이 변할 때마다 event 객체가 onChange 함수에 전달
value={value}
/>
<IoSearchOutline
className={styles['search-icon']}
onClick={onClick} // 검색 버튼에 onClick 함수 연동
/>
</div>
<div className={styles.tab}>
<BsGrid3X3Gap className={styles.icon} />
<HiOutlineDotsVertical className={styles.icon} />
</div>
</div>
);
}
export default Header;
📌 14일차 Moment JS
✅ 오늘의 문제 | 시간 가공 함수 생성
⭐ 정답 예시 보충
- ProcessUploadDate 함수의 return 부분 주석 참고
🔽 src/utils/index.js
// Moment JS 관련 라이브러리 import
import 'moment/locale/ko';
import moment from 'moment';
function ProcessViewCount(viewCount) {
if (viewCount < 1000) { // ex. 조회수 100회
return `조회수 ${viewCount}회`;
}
else if (viewCount < 10000) { // ex. 조회수 1.1천회
return `조회수 ${(viewCount / 1000).toFixed(1)}천회`;
}
else if (viewCount < 100000) { // ex. 조회수 1.3만회
return `조회수 ${(viewCount / 10000).toFixed(1)}만회`;
}
else if (viewCount < 100000000) { // ex. 조회수 103만회
return `조회수 ${(viewCount / 10000).toFixed(0)}만회`;
}
else { // 조회수 3억회
return `조회수 ${(viewCount / 100000000).toFixed(0)}억회`;
}
}
function ProcessUploadDate(date) {
const time = "2021-09-16T13:15:02"; // 기준이 되는 특정 시간 지정
return `${moment(date).from(time)}`; // return moment(date).from(time) 처럼만 작성해도 됨
}
export { ProcessViewCount, ProcessUploadDate };
🔽 src/components/shared/HorizontalCard.js
import styles from './HorizontalCard.module.css';
import 'moment/locale/ko';
//import moment from 'moment';
import { ProcessUploadDate, ProcessViewCount } from '../../utils';
function HorizontalCard({ data }) {
return (
<a href={`https://www.youtube.com/watch?v=${data.id}`}>
<div className={styles.card}>
<img
className={styles.thumbnail}
src={data.thumbnail}
alt={`${data.title}의 썸네일`}
/>
<div className={styles.info}>
<div className={styles.title}>{data.title}</div>
<div className={styles.meta}>
<a
href={`https://www.youtube.com/channel/${data.channelId}`}
className={styles.uploader}
>
{data.channelTitle}
</a>
<div className={styles.view}>{ProcessViewCount(data.viewCount)}</div>
<div className={styles.time}>{ProcessUploadDate(data.date)}</div> // 수정된 부분
</div>
<div className={styles.desc}>{data.description}</div>
</div>
</div>
</a>
);
}
export default HorizontalCard;
📌 15일차 호스팅
✅ 오늘의 문제 | 호스팅 페이지 업로드
➕ 추가 기능 구현 <검색 기능>
-
HOME page
- 검색 기능 추가
-> setFilter 함수를 Home - Layout - Header 컴포넌트 간에 props 로 넘겨주고 넘겨준 setFilter 함수를 활용하여 검색어를 입력하면 filter 값이 해당 검색어로 바뀔 수 있도록 함 - 검색 버튼을 클릭할 때 뿐만 아니라 검색어 입력 후 엔터 키를 눌렀을 때에도 검색이 이루어질 수 있도록 함
-> Header 컴포넌트의 input 태그에 발생한 키 이벤트가 "Enter"일 시에 검색 버튼 클릭 시 실행되는 함수가 동일하게 실행될 수 있도록 onKeyPress 속성 추가 - 기존의 Home 화면과 달리 검색 결과 화면은 Explore 컴포넌트의 레이아웃을 활용하여 필터링된 결과를 표시
-> useState 활용하여 isSearchMode 변수(초기값은 false) 추가한 후 해당 변수의 참/거짓 값에 따라 Home 페이지를 다른 레이아웃으로 출력 - Menu에서 [홈] 메뉴 클릭 시 원래의 Home 컴포넌트 레이아웃으로 화면 초기화
-> setFilter, setIsSearchMode 함수를 Home - Layout - Menu 컴포넌트 간에 props 로 넘겨주고 넘겨준 함수들을 활용하여 각각 filter, isSearchMode 값 초기화
- 검색 기능 추가
-
EXPLORE & SUBSCRIPTION page
- 검색 기능 미지원
-> activeMenu !== "home"일 경우 Header의 input 비활성화(disabled 속성)+검색 기능을 지원하지 않는다는 문구 출력(placeholder 속성)
- 검색 기능 미지원
Author And Source
이 문제에 관하여([코뮤니티 모각코] 웹 리액트 과정 - 3주차), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@dianestar/코뮤니티모각코-웹리액트과정-3주차저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)