body {
position: relative;
background: #f5f6f7;
}
a {
text-decoration: none;
cursor: pointer;
}
h1 {
display: block;
font-size: 2em;
margin-block-start: 0.67em;
margin-block-end: 0.67em;
margin-inline-start: 0px;
margin-inline-end: 0px;
font-weight: bold;
margin: 0;
padding: 0;
-webkit-text-size-adjust: none;
}
h3 {
display: block;
font-size: 1.17em;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
font-weight: bold;
}
form{
display: block;
margin: 0;
padding: 0;
-webkit-text-size-adjust: none;
}
div{
display: block;
}
label {
cursor: pointer;
}
input{
border-radius: 0;
appearance: none;
outline: 0;
text-decoration: none;
cursor: pointer;
-webkit-text-size-adjust: none;
}
option {
font-weight: normal;
display: block;
white-space: nowrap;
min-height: 1.2em;
padding: 0px 2px 1px;
}
select{
border-radius: 0;
border: none;
background: 0 0;
appearance: none;
outline: 0;
text-decoration: none;
cursor: pointer;
-webkit-text-size-adjust: none;
}
ul {
margin: 0 0 9px 0;
-webkit-text-size-adjust: none;
}
li {
text-align: -webkit-match-parent;
}
#container, #footer, #header {
margin: 0 auto;
max-width: 768px;
min-width: 460px;
}
#header {
position: relative;
overflow: hidden;
padding: 60px 0 54px;
box-sizing: border-box;
}
#container {
width: auto;
max-width: 768px;
margin: 0 auto;
/* max-width: 768px; */
min-width: 460px;
}
#footer {
clear: both;
margin: 0 auto;
padding: 30px 0 15px 0;
text-align: center;
}
#footer * {
font-size: 12px;
font-style: normal;
line-height: normal;
margin: 0;
padding: 0;
list-style: none;
color: #333;
}
#footer ul li:first-child {
background: 0 0;
}
#footer ul li {
font-size: 12px;
position: relative;
display: inline;
padding: 0 3px 0 7px;
white-space: nowrap;
background: url(https://static.nid.naver.com/images/join/pc/bu_bar_2x.gif) left 50% no-repeat;
background-size: 1px 10px;
}
/* @media (max-width: 1024px) */
#footer a, #footer a:visited {
color: #7e7e7e;
}
#footer address {
font: 9px/14px Verdana;
}
:root #footer address .logo {
top: 2px;
margin-left: 1px;
}
#footer address .logo {
position: relative;
display: inline-block;
width: 63px;
height: 12px;
background: url(https://static.nid.naver.com/images/ui/login/pc_sp_login_190522.png) no-repeat;
background-position: -242px 0;
background-size: 460px auto;
}
#footer address span {
font: 9px/14px Verdana;
padding-left: 2px;
}
#footer address em {
font: 9px verdana;
padding-left: 4px;
}
#footer address a {
font: bold 9px Verdana;
color: #333;
}
#footer a {
text-decoration: none;
}
.join_membership {
padding-bottom: 20px;
}
.h_logo {
display: block;
margin: 0 auto;
width: 240px;
height: 44px;
background-image: url(https://static.nid.naver.com/images/ui/join/m_naver_logo_20191126.png);
background-repeat: no-repeat;
background-position: 0 0;
background-size: 240px auto;
color: transparent;
font-size: 0;
}
.blind {
position: absolute;
overflow: hidden;
clip: rect(0 0 0 0);
margin: -1px;
width: 1px;
height: 1px;
}
.row_group {
overflow: hidden;
width: 100%
}
.row_group+.row_group {
margin-top: 20px;
}
.join_title {
margin: 19px 0 8px;
font-size: 14px;
font-weight: 700;
}
.join_content{
width: 460px;
height: 995.60px;
}
.step_url {
position: absolute;
top: 16px;
right: 13px;
font-size: 15px;
line-height: 18px;
color: #8e8e8e;
}
.ps_box, .ps_box_disable {
display: block;
position: relative;
width: 100%;
height: 51px;
border: solid 1px #dadada;
padding: 10px 110px 10px 14px;
background: #fff;
box-sizing: border-box;
/* vertical-align: top; */
}
.ps_box.int_id {
background: #fff;
outline: 0;
}
.ps_box.int_pass{
padding-right: 40px;
}
.ps_box.int_pass_check {
padding-right: 40px;
}
.int {
display: block;
position: relative;
width: 100%;
height: 29px;
padding-right: 25px;
line-height: 29px;
border: none;
background: #fff;
font-size: 15px;
box-sizing: border-box;
z-index: 10;
}
.ps_box.int_pass:after{
content: '';
display: inline-block;
position: absolute;
top: 50%;
right: 13px;
width: 24px;
height: 24px;
margin-top: -12px;
background-image: url(https://static.nid.naver.com/images/ui/join/m_icon_pw_step.png);
background-repeat: no-repeat;
background-position: 0 0;
background-size: 125px 75px;
cursor: pointer;
}
.ps_box.int_pass_check:after{
content: '';
display: inline-block;
position: absolute;
top: 50%;
right: 13px;
width: 24px;
height: 24px;
margin-top: -12px;
background-image: url(https://static.nid.naver.com/images/ui/join/m_icon_pw_step.png);
background-repeat: no-repeat;
background-size: 125px 75px;
background-position: -27px 0;
cursor: pointer;
}
.box_right_space {
padding-right: 14px;
box-sizing: border-box;
}
.join_row.join_birthday{
padding: 0;
}
.bir_wrap{
display: table;
width: 100%;
}
.bir_dd, .bir_mm, .bir_yy {
display: table-cell;
table-layout: fixed;
width: 147px;
vertical-align: middle;
}
.join_birthday .ps_box {
padding: 11px 14px;
}
.bir_mm+.bir_dd, .bir_yy+.bir_mm {
padding-left: 10px;
}
:root .sel {
background: #fff url(https://static.nid.naver.com/images/join/pc/sel_arr_2x.gif) 100% 50% no-repeat;
background-size: 20px 8px;
}
.sel {
width: 100%;
height: 29px;
padding: 7px 8px 6px 7px\9;
font-size: 15px;
line-height: 18px;
color: #000;
border: none;
border-radius: 0;
}
.join_sex {
overflow: hidden;
}
.join_sex .gender_code {
display: block;
width: 100%;
padding-right: 7px;
box-sizing: border-box;
}
.terms_choice {
color: #8e8e8e;
}
.join_email .terms_choice {
font-size: 12px;
font-weight: 400;
}
.row_group+.join_mobile {
margin-top: 20px;
}
.join_mobile {
position: relative;
overflow: hidden;
}
.join_mobile .int_mobile_area {
position: relative;
margin-top: 10px;
padding: 0 125px 0 0;
}
.gender_nationality .gender_code, .join_mobile .country_code, .join_sex .gender_code {
display: block;
width: 100%;
padding-right: 7px;
box-sizing: border-box;
}
.country_code {
position: relative;
overflow: hidden;
}
.join_mobile .int_mobile {
display: inline-block;
width: 100%;
padding: 10px 15px 10px 14px;
vertical-align: top;
}
.ps_box .lbl {
left: 0;
padding: 0 14px;
width: 100%;
box-sizing: border-box;
}
.lbl {
display: block;
position: absolute;
top: 50%;
margin-top: -9px;
font-size: 15px;
line-height: 18px;
color: #8e8e8e;
z-index: 9;
}
.join_mobile .int_mobile_area .btn_verify {
position: absolute;
top: 0;
right: 0;
width: 115px;
height: 51px;
padding: 18px 0 16px;
font-weight: 700;
text-align: center;
box-sizing: border-box;
text-decoration: none;
}
.btn_verify {
display: block;
font-size: 15px;
cursor: pointer;
}
.btn_primary {
color: #fff;
border: solid 1px rgba(0,0,0,.08);
background-color: #03c75a;
}
.join_mobile .ps_box_disable, .join_mobile .ps_box_disable input {
background: #f7f7f7;
outline: 0;
}
.int_mobile_area+.ps_box, .int_mobile_area+.ps_box_disable, .ps_box_disable+.ps_box {
margin-top: 10px;
}
.wa_blind {
position: absolute;
overflow: hidden;
clip: rect(0 0 0 0);
margin: -1px;
width: 1px;
height: 1px;
font-size: 0;
}
.btn_area {
margin: 30px 0 9px;
}
.btn_type {
display: block;
width: 100%;
padding: 15px 0 15px;
font-size: 18px;
font-weight: 700;
text-align: center;
cursor: pointer;
box-sizing: border-box;
}
.copy{
display: none;
}
address .u_cri {
display: inline-block;
font-size: 12px;
font-family: Arial,Helvetica,sans-serif;
font-weight: 400;
color: #7e7e7e;
margin: 0;
padding-left: 1px;
}
#footer address .u_cra{
display: inline-block;
font-size: 12px;
font-family: Arial,Helvetica,sans-serif;
font-weight: 400;
color: #7e7e7e;
}
/* #footer .u_cr, #footer address .all_r, #footer address .copy, #footer address .logo {
display: none;
} */
정렬이랑 폰트가 조금 다른것 빼고는 다 구색은 맞췄다...
정렬 왜 안될까 이것도 class이름이 좀 다르게 설정되어있는지 확인해봐야함...
App.tsx의 코드들을 각각 Component와 View폴더를 만들어서 분리를 해주 었는데 그 이유를 Chat-GPT에서 찾아봤다
React에서 컴포넌트는 UI를 만드는 데 사용됩니다. 컴포넌트는 일반적으로 작고 독립적이며, 여러 번 재사용될 수 있습니다. 대규모 React 애플리케이션에서는 수백 개 이상의 컴포넌트를 가질 수 있으며, 이러한 컴포넌트들을 유지 보수하고 관리하기 위해 구조화된 디렉토리 구조가 필요합니다.
이러한 이유로 React 개발자들은 컴포넌트와 관련된 파일을 별도의 폴더로 구성하곤 합니다. 이 구조는 프로젝트의 규모가 커질수록 더 유용하며, 컴포넌트 파일의 위치를 파악하기 쉽게 만들어 줍니다.
View 폴더는 일반적으로 애플리케이션의 레이아웃과 스타일링을 관리하는 데 사용됩니다. 이 폴더 안에는 컴포넌트의 스타일링 파일, 이미지 파일 및 기타 관련 파일을 포함할 수 있습니다. 이는 컴포넌트와 뷰가 서로 분리되어 있음을 의미합니다. 따라서 애플리케이션의 레이아웃을 변경하려면 뷰 폴더의 파일을 수정하면 됩니다. 이는 유지 보수와 개발 과정을 간단하게 만들어 줍니다.
결론적으로, React에서 컴포넌트와 뷰 폴더를 사용하여 프로젝트를 구성하는 것은 애플리케이션의 유지 보수성과 개발자의 생산성을 향상시키는 방법 중 하나입니다.
example-react/
node_modules/
public/
index.html
favicon.ico
src/
components/
Component1.tsx
Component2.tsx
...
views/
Home.tsx
About.tsx
...
App.tsx
index.tsx
types/
types.d.ts
tsconfig.json
package.json
README.md
스니펫(Snippet)은 코드 조각이나 템플릿 등을 미리 작성해두고, 필요할 때 불러와서 사용하는 기능을 말합니다. 주로 코드 에디터나 IDE에서 지원하며, 자주 사용되는 코드나 반복적으로 작성해야 하는 코드를 미리 등록해두고, 해당 코드를 자동으로 완성하거나 불러와서 사용할 수 있습니다. 스니펫은 코드 작성 시간을 절약하고, 오타나 문법 오류를 줄여줘서 개발 생산성을 높일 수 있습니다. 대부분의 코드 에디터와 IDE에서 스니펫 기능을 제공하며, 사용자가 직접 스니펫을 추가하거나 수정할 수도 있습니다.
스니펫은 주로 일정한 형식을 갖는 코드 블록을 자주 사용해야 할 때 유용합니다. 예를 들어, 함수 선언문이나 반복문 등의 코드 블록을 자주 사용해야 할 경우, 스니펫을 이용하여 미리 작성해두면 해당 코드 블록을 반복해서 작성할 필요 없이, 스니펫을 호출하여 쉽게 사용할 수 있습니다. 스니펫은 코드 블록뿐만 아니라, 템플릿이나 라이브러리 등을 포함한 여러 가지 형태로 등록할 수 있습니다.
스니펫은 대개 키워드나 축약어 등의 단축키를 이용하여 호출합니다. 예를 들어, for라는 단축어를 입력하면 for문의 스니펫이 자동완성되는 등의 기능이 제공됩니다. 각 에디터나 IDE마다 지원하는 스니펫의 종류와 사용 방법은 다를 수 있으며, 각각의 사용자 매뉴얼을 참고하여 사용할 수 있습니다.
VS Code에서 스니펫 자동완성 명령어 = rfc
export와 export default는 모듈 시스템에서 사용되는 키워드로, 모듈 내부의 함수, 클래스, 객체 등을 다른 모듈에서 사용할 수 있도록 내보내는 역할을 합니다.
export는 모듈에서 여러 개의 변수, 함수, 클래스 등을 내보낼 때 사용합니다. 내보내려는 변수, 함수, 클래스 등 앞에 export 키워드를 붙이면 다른 모듈에서 해당 변수, 함수, 클래스 등을 불러와 사용할 수 있습니다. export로 내보낸 항목을 불러올 때는 중괄호({})를 사용하여 해당 항목의 이름을 명시해야 합니다.
// 모듈에서 변수, 함수, 클래스 등을 내보내는 예시
export const name = "Alice";
export function greet() {
console.log(`Hello, ${name}!`);
}
export class Person {
constructor(name) {
this.name = name;
}
}
export default는 모듈에서 기본으로 내보내는 항목을 지정할 때 사용합니다. 내보내려는 항목 앞에 export default를 붙이면 다른 모듈에서 해당 항목을 불러올 때 이름을 지정하지 않아도 됩니다. 즉, 기본으로 내보내는 항목은 이름이 없고, 내보내려는 항목을 불러올 때는 원하는 이름을 지정하여 사용할 수 있습니다. export default는 하나의 모듈에서 한 번만 사용할 수 있습니다.
// 모듈에서 기본으로 내보내는 항목을 지정하는 예시
export default function add(a, b) {
return a + b;
}
다른 모듈에서 export로 내보낸 항목을 불러올 때는 중괄호({})를 사용하여 이름을 지정해야 하지만, export default로 내보낸 항목은 이름을 지정하지 않아도 됩니다. 다음은 export와 export default로 내보낸 항목을 불러올 때의 차이점을 보여줍니다.
// 다른 모듈에서 export로 내보낸 항목을 불러올 때
import { name, greet, Person } from "./module.js";
// 다른 모듈에서 export default로 내보낸 항목을 불러올 때
import add from "./module.js";
import Header from './view/HeaderView';
import Footer from './view/FooterView';
import Main from './view/MainView';
react를 통해서 기존의 html의 반복되는 코드들을 component로
나눠서 (java에서 for반복문처럼)간단하게 표현할수 있다.
<div class="id-password-wrapper">
<div class="input-row">
<div class="icon-shell">
<span class="icon-id">
<span class="blind">아이디</span>
</span>
</div>
⭐<input type="text" class="input-text" maxlength="41" placeholder="아이디" name="id" id="id" />
</div>
<div class="input-row">
<div class="icon-shell">
<span class="icon-pw">
<span class="blind">비밀번호</span>
</span>
</div>
⭐<input type="password" class="input-text" maxlength="16" placeholder="비밀번호" name="pw" id="pw" />
</div>
</div>
<ul class="find-wrapper">
<li>
🌟<a class="find-text" href="https://nid.naver.com/user2/api/route?m=routePwInquiry&lang=ko_KR">비밀번호 찾기</a>
</li>
<li>
🌟<a class="find-text" href="https://nid.naver.com/user2/api/route?m=routeIdInquiry&lang=ko_KR">아이디 찾기</a>
</li>
<li>
🌟<a class="find-text" href="https://nid.naver.com/user2/V2Join?m=agree&lang=ko_KR">회원가입</a>
</li>
</ul>
⭐,🌟의 부분은 반복이 되게 된다. 이런 반복되는 코드를 component폴더를 만들어서 따로 분리를 진행했다.
InputComponent
import { Dispatch, SetStateAction } from 'react'
interface Props{
label: string;
type: string;
name: string;
setter: Dispatch<SetStateAction<string>>;
maxLength: number;
iconClass: string;
}
export default function 💡NaverInput(props: Props) {
const {label, type, name, maxLength, iconClass, setter} = props;
return ( //반복되는 값 빼주고, 어떤 변수 써야할지 정한다
//: 아이디, type ... 6개의 변수 잡기
<div className="input-row">
<div className="icon-shell">
<span className={iconClass}>
<span className="blind">{label}</span>
</span>
</div>
<input type={type}
className="input-text"
maxLength={maxLength}
placeholder={label}
name= {name}
onChange={(event) => setter(event.target.value)}/>
</div>
)
}
MainView
<div className="id-password-wrapper">
<NaverInput label = "아이디" type="text" name = "id" maxLength={41} iconClass="icon-id" setter={setId}/>
<NaverInput label = "비밀번호" type="password" name = "pw" maxLength={16} iconClass="icon-pw" setter={setPassword}/>
</div>
FindComponent
import React from 'react'
interface Props{
title: string;
link: string;
}
export default function NaverFind({title, link}: Props) {
return (
<li>
<a className={"find-text" }
href={link}>
{title}</a>
</li>
)
}
MainView
FIND_LIST 생성
const FIND_LIST = [
{
title: '비밀번호 찾기',
link: 'https://nid.naver.com/user2/api/route?m=routePwInquiry&lang=ko_KR',
},
{
title: '아이디 찾기',
link: 'https://nid.naver.com/user2/api/route?m=routeIdInquiry&lang=ko_KR',
},
{
title: '회원가입',
link: 'https://nid.naver.com/user2/V2Join?m=agree&lang=ko_KR',
},
];
만들어진 리스트를 함수를 통해 찾아서 가져와 반환하는 코드를 작성
<ul className="find-wrapper">
{
FIND_LIST.map((findItem) => (<💡NaverFind title = {findItem.title} link={findItem.link}/>))
//요소를 하나씩 꺼내온걸 함수로 처리한 다음 반복되는 값을 뿌려주게 된다.
}
</ul>
Header, Main, Footer을 각각 View폴더를 만들어 나눠서 분리했다.
HeaderView
export default function Header() {
return (
<div className="header">
<<div className="header-inner">
<a href="https://naver.com" className="logo">
<h1 className="blind">NAVER</h1>
</a>
<div className="lang">
<select className="select">
<option>한국어</option>
<option>English</option>
</select>
</div>
</div>
</div>
);
}
MainView
import NaverFind from "../../component/FindComponent";
import NaverInput from "../../component/InputComponent";
//export default한 항목을 불러와서 {}각각 요소 적지않음
import {useRef, useState} from 'react'
const FIND_LIST = [
{
title: '비밀번호 찾기',
link: 'https://nid.naver.com/user2/api/route?m=routePwInquiry&lang=ko_KR',
},
{
title: '아이디 찾기',
link: 'https://nid.naver.com/user2/api/route?m=routeIdInquiry&lang=ko_KR',
},
{
title: '회원가입',
link: 'https://nid.naver.com/user2/V2Join?m=agree&lang=ko_KR',
},
];
export default function Main() {
const formRef = useRef<HTMLFormElement | null>(null);
const[id,setId]=useState<string>(''); //이거 위치 여기 맞니
const [password, setPassword] = useState<string>(''); //이거 언제 추가했대
const [isIdCheck, setIdCheck] = useState<boolean>(false);
const onSubmitHandler = () => {
if(id.trim()){
setIdCheck(true);
return;
}
setIdCheck(false);
if(formRef.current) formRef.current.submit();
}
return (
<div className="main">
<div className="content">
<div className="sign-in-wrapper">
<form ref={formRef} id="form" action="https://nid.naver.com/nidlogin.login" method="POST">
<ul className="panel-wrapper">
<li className="panel-item">
<div className="panel-inner">
<div className="id-password-wrapper">
<NaverInput label = "아이디" type="text" name = "id" maxLength={41} iconClass="icon-id" setter={setId}/>
<NaverInput label = "비밀번호" type="password" name = "pw" maxLength={16} iconClass="icon-pw" setter={setPassword}/>
</div>
<div className="sign-in-keep-wrapper">
<div className="keep-check">
<input type="checkbox" className="input-keep" value="off" />
<label className="keep-text">로그인 상태 유지</label>
</div>
<div className="ip-check"></div>
</div>
{isIdCheck && (<div id="sign-in-error" className="sign-in-error">
<div className="error-message">
<strong>아이디</strong>를 입력하세요
</div>
</div>)}
<div className="sign-in-button-wrapper">
<button type="button" className="sign-in-button" onClick = {() => onSubmitHandler()}>
<span className="button-text">로그인</span>
</button>
</div>
</div>
</li>
</ul>
</form>
</div>
<ul className="find-wrapper">
{
FIND_LIST.map((findItem) => (<NaverFind title = {findItem.title} link={findItem.link}/>))
//요소를 하나씩 꺼내온걸 함수로 처리한 다음 반복되는 값을 뿌려주게 된다.
}
</ul>
<div className="banner-wrapper">
<div className="banner-content">
<img className="banner-img" src="https://ssl.pstatic.net/melona/libs/1378/1378592/fe1b4bd9453e84b57ed7_20230407151920279.jpg" />
</div>
</div>
</div>
</div>
);
}
FooterView
export default function Footer() {
return(
<div className="footer">
<div className="footer-inner">
<ul className="footer-link">
<li><a className="footer-item"><span className="text">이용약관</span></a></li>
<li><a className="footer-item"><span className="text"><strong>개인정보처리방침</strong></span></a></li>
<li><a className="footer-item"><span className="text">책임의 한계와 법적고지</span></a></li>
<li><a className="footer-item"><span className="text">회원정보 고객센터</span></a></li>
</ul>
<div className="footer-copy">
<a href="https://naver.com">
<span className="footer-logo">
<span className="blind">네이버</span>
</span>
</a>
<span className="text">Copylight</span>
<span className="copy">© NAVER Corp.</span>
<span className="text">All Rights Reserved.</span>
</div>
</div>
</div >
);
}
모르는 함수 다시 분석해보기
map관련