프로젝트 하고 타입스크립트도 본격적으로 쓰고 하니까
함부로 손도 대면 안될 것 같고 (충돌 나고 할까봐...) 정말 정말 조심히 코드 치려고 하고 있다 ㅠ0ㅠ......
타입 정의는 언제 해줘야 하며, 어느걸 해줘야하는지 아직 감도 안잡히는데..
플젝에서 쓰려고 하니 나 최큼 힘들어 ... ⭐️
구현 해야 할 UI ↓
코드 치기 전, 내가 궁금했던 건
1. 타입 지정은 언제 어디서부터 어떻게 ? 그리고 공통 컴포넌트의 타입은 또 언제?
2. 그냥 js로 먼저 작성한 뒤, 타입 지정 해주면 안되는건가 ?
3. 예를 들어, 회원 가입 페이지를 만들 때 어떤 흐름으로 만드는걸까? 공통 컴포넌트 먼저 제작?
3번 흐름 먼저 잡고 가보자. 지피티 사마의 답변이다.
타입스크립트 + 리액트 처음 할 때 "순서" 헷갈리는 건 정말 당연해!
보통 회원가입 페이지 작업 순서 (실제 개발자들도 이렇게 해!) !!
- 대략적인 레이아웃부터 만든다
→ 전체 구조부터 먼저 만들어
→ 페이지에 어떤 요소가 들어가는지, 어떤 게 반복될지 눈으로 확인하는 단계 - 중복되거나 재사용될 UI 요소를 발견하면
→ 그때 공통 컴포넌트로 분리
→ 이 시점에서 props랑 타입도 정의 - 상세 기능 붙이기
→ 입력값 검증, API 연동, 에러 처리 등 기능 추가
예를 들어 이런식으로 개발자들은 개발한다.
1단계: 초안 구성 (레이아웃)
const SignUpPage = () => {
return (
<div>
<h1>회원가입</h1>
<input placeholder="이메일" />
<input placeholder="비밀번호" />
<button>회원가입</button>
{/* 소셜 로그인 */}
<button>카카오 로그인</button>
<button>네이버 로그인</button>
</div>
);
};
💡 근데, 이 때는 타입 지정을 안해줘도 될까? 싶었다.
지피티 사마의 답변 ↓
정확히 거기서 제일 많이 헷갈려 하는 부분이야 —
"초안 만들 때부터 타입을 써야 하나?" 라는 고민.
1단계: 대략적인 레이아웃을 만들 때는 꼭 타입 정의 안 해도 돼!
이 시점엔 타입 없이 JS처럼 자유롭게 UI 먼저 구성해도 좋아.
왜냐하면?
- UI 구조를 빠르게 확인하는 게 더 중요해서
→ 레이아웃이 어떻게 생겼는지, 어떤 컴포넌트가 필요한지 판단이 먼저야. - 컴포넌트를 분리할지 말지도 아직 모르기 때문에
→ 타입은 컴포넌트를 만들 때 또는 props를 넘겨줄 때 정의하면 충분해!
예시 (초안 단계 - 타입 안 씀)
const SignUpPage = () => {
return (
<div>
<h1>회원가입</h1>
<input placeholder="이메일" />
<input placeholder="비밀번호" />
<button>회원가입</button>
{/* 소셜 로그인 버튼들 */}
<button>카카오 로그인</button>
<button>네이버 로그인</button>
</div>
);
};
타입 하나도 없어도 전혀 문제 없지?
이렇게 UI 구조만 먼저 확인해보는 거야.
이후: "공통 컴포넌트로 뺄까?" 생각이 들면 이때부터 타입을 작성
type SocialLoginButtonProps = {
provider: string;
onClick: () => void;
};
💡 방금 설명에서.. 이후: 공통 컴포넌트에서는 타입을 지정해줘야 한다 !? 무슨말이지..
지피티 사마의 답변 ↓
"공통 컴포넌트로 뺄 때는 왜 타입 지정을 꼭 해야 해?"
공통 컴포넌트는 “props(속성)”를 받아서 동작하기 때문에, 그 props의 구조를 정확히 알려줘야 해.
그걸 알려주는 게 바로 **타입(Type)**이야.
비유로 쉽게 설명해볼게!
Jane이 직접 만든 소셜 로그인 버튼이 있다고 해보자.
<SocialLoginButton provider="kakao" onClick={handleKakao} />
지금 이 SocialLoginButton 컴포넌트는 외부에서 값을 받아야 동작해.
그 "받는 값"이 바로 props고,
그 props가 어떤 모양인지 설명하는 설명서가 바로 **타입(Type)**이야.
비유: 택배 상자 받는 컴포넌트
type SocialLoginProps = {
provider: string;
onClick: () => void;
};
- provider: string (ex. 'kakao', 'naver')
- onClick: 클릭했을 때 실행할 함수
→ 이렇게 정의해줘야, 나중에 누가 실수로 숫자를 넘기거나 이상한 걸 넘기면
→ 타입스크립트가 바로 에러로 알려줘! ❌
예를 들어, 타입 없이 실수하면?
<SocialLoginButton provider={false} /> // ❌ boolean 넘겼는데 에러 안남 (JS일 경우)
<SocialLoginButton /> // ❌ 필수 값 빠졌는데도 동작함 (근데 화면 깨짐)
이런 걸 미리 막아주는 역할이 바로 타입이야.
이런 식으로 이상한 값이 들어와도 JS에서는 바로 에러가 안 나.
타입스크립트는 이런 실수를 막아주기 위해서 props에 타입을 지정해줘야 해.
한 줄 요약
공통 컴포넌트는 "남이 쓸 수 있는 함수"처럼 동작하니까, 정확한 설명서(= 타입)가 꼭 필요하다!
다른 건 나중에 해도 되지만
공통 컴포넌트는 타입 지정이 거의 필수!
2단계: 반복되는 요소 분리
"어? 이 로그인 버튼, 로그인 페이지에서도 쓰이잖아!"
→공통 컴포넌트로 분리
type SocialLoginProps = {
provider: 'kakao' | 'naver';
onClick: () => void;
};
const SocialLoginButton = ({ provider, onClick }: SocialLoginProps) => {
return (
<button onClick={onClick}>
<img src={`/icons/${provider}.svg`} />
{provider === 'kakao' ? '카카오 로그인' : '네이버 로그인'}
</button>
);
};
그리고 이렇게 씀:
<SocialLoginButton provider="kakao" onClick={handleKakao} />
<SocialLoginButton provider="naver" onClick={handleNaver} />
3단계: 기능 추가
- form 입력값 상태 관리
- 회원가입 API 연결
- 에러 처리 등
💡 한마디 요약💡
레이아웃부터 만들고 → 중복 발견되면 공통 컴포넌트로 분리 → 그때 타입도 같이 작성
절대 처음부터 공통 컴포넌트 다 만들려고 하지 않아도 된다.
"반복되는 것 같다?" → 그때 분리
이게 효율적이고 자연스러운 흐름
그리고 Join을 만들다보니.. 내가 만든 페이지의 상황을 체크 해보고 싶은데
라우터를 섣불리 등록했다가 충돌 생길 것 같고
우선 페이지라도 잘 작동되는지 app.tsx에 넣어서 실험해보고 싶은데
그러다가 커밋 할 때 문제생길까봐
(나는 깃이란게 내가 작성하는 모~~든 흐름을 다 추적하는 줄 알았는데.)
만만의 오산!
깃의 커밋은.. 그러니까, 내가 최종적으로
그럼 결국 맨 마지막 코드 시점에 add 할 때 그 코드만 저장 되는 것!
그말은 결국 내가 뭔 난리를 다 쳐도..
뭐 지웠다가 저장했다가 하더라도, 그게 커밋에 들어가는게 아니라
맨 마지막 커밋 시점에 그 때의 최종 코드가 딱 커밋! 되는 거임
새로 알게된 사실이
실험하는 동안 app.tsx에 Join / 페이지를 넣으니까
바뀌는 부분에 초록색으로 깃이 감지를 했고 git status로 상황을 보니까, app. tsx가 수정되었다고 떴음
근데 실험 하고 난 뒤, app.tsx에서 Join을 없애니까 초록색 부분도 사라졌고 git status로 상황을 보니까 그 내용도 지워져있었음
그러니까 아무튼 결론!
내가 뭔 난리를 치든 ! 아무튼 커밋은 커밋의 시점은 커밋 시점의 최종 코드!!
마치 블로그 글 적을 때 임시저장이 커밋같은 것 같음..
맨 마지막에 내가 적고 임시저장 누르면 그 때 글이 저장~
임시 저장 누르기 전에 내가 뭘 막 지우고 썼다가 지워도 그런 기록들이 저장되진 않고, 최종 임시저장 ! 누르는 순간의 글만 저장되는..
(물론 자동 저장 기능 없단 전제하에)
그에따라, 우선 회원가입 Join 페이지의 대략적인 레이아웃은 완성
레이아웃을 구성하다보니 또 여러가지 궁금증...
카카오톡 저 박스는 div로 해야할까 버튼으로 해야할까?↓
좋은 질문입니다! 실무에서는 아래 기준에 따라 button으로 쓸지, div로 쓸지를 명확하게 나눕니다.
정답은 "기능에 따라 다르게 처리한다" 입니다.
실무 기준: button vs div 사용 기준
용도 | 태그 | 설명 |
실제로 클릭해서 로그인/이동/제출 등의 동작이 있는 경우 | button 또는 a | "사용자 액션"이 있는 요소는 반드시 버튼 계열로 처리해야 함 |
단순한 스타일 박스, 레이아웃용 정렬 | div | 클릭 불가능한 구조용 박스는 div로 처리 |
카카오 로그인 버튼은?
// ✅ 실무에서는 무조건 <button> 사용!
<button onClick={handleKakaoLogin}>카카오로 3초만에 가입하기</button>
- 이유: 카카오 로그인은 사용자 액션을 트리거하는 인터랙티브 요소니까 div로 하면 접근성(A11y) 및 시멘틱 구조에 어긋나요.
- 보조기기(Screen Reader)에서도 button은 "클릭 가능한 요소"로 인식되고, div는 인식 못 합니다.
실수 예시 (안 좋은 사례)
// ❌ 클릭 동작이 있는 걸 div로 처리하면 안 됨
<div onClick={handleKakaoLogin}>카카오 로그인</div>
문제점
- 시멘틱 웹 규칙 위반
- 접근성 문제
- 키보드 접근 (Tab, Enter) 안 됨
- 유지보수 시 혼동 가능
팁: 시멘틱 + 접근성을 고려한 버튼 작성법
팁
- 로그인, 회원가입, 제출 등 사용자 동작 트리거 → 반드시 <button>
- 스타일만 입히고 싶을 땐 버튼에 style, className만 쓰면 됨 (굳이 div 필요 없음)
- 아이콘/이미지도 모두 버튼 내부에 넣으면 OK (스타일만 잘 정리)
정리하자면
로그인 버튼, 소셜 버튼 | ✅ button |
페이지 이동 링크 | ✅ a |
단순 스타일 박스 | div |
"사용자가 클릭할 수 있는 기능이 있다"면 무조건 button이나 a 태그를 써야 합니다.
이렇게 코드를 치다보니,,
얼떨 결에 ui도 완성은 됐는데, (아직 라우터나 이런건 등록 전)
카카오톡이랑 네이버 부분이 공통된 부분이 많아서, 조금 코드도 길어지고 복잡해져서.
공통 버튼 컴포넌트로 분리해보려고 함
import React from "react";
function Join() {
return(
<div className="flex flex-col items-center pt-20">
<img src="src/assets/logo_black.png" alt="오즈코딩스쿨 로고" className="w-[180px] mb-4"/>
<div className="flex gap-3 text-[16px]">
<p className="text-[#4D4D4D]"> 회원 이신가요?</p>
<p className="text-[#6201E0]"> 로그인 하기</p>
</div>
<br/>
<br/>
{/* 카카오톡 회원 가입 버튼 */}
<button
style={{backgroundColor: "#FEE500",
width: "348px",
height: "52px",
borderRadius: "4px",
color: "#391C1A",
padding: "8px",
marginBottom: "12px",
display: "flex", // ✅ 필수!
alignItems: "center", // ✅ 수직 정렬
justifyContent: "center", // ✅ 가운데 정렬
gap: "10px", // ✅ 간격 조정
}}>
<img src="src/assets/kakao_brown.svg" alt="카카오" />
카카오로 3초만에 가입하기</button>
{/* 네이버 회원 가입 버튼 */}
<button
style={{backgroundColor: "#03C75A",
width: "348px",
height: "52px",
borderRadius: "4px",
color: "#FFFFFF",
padding: "8px",
marginBottom: "25px",
display: "flex", // ✅ 필수!
alignItems: "center", // ✅ 수직 정렬
justifyContent: "center", // ✅ 가운데 정렬
gap: "10px", // ✅ 간격 조정
}}>
<img src="src/assets/naver_white.svg" alt="네이버" />
네이버로 가입하기 </button>
<p className="text-[#4D4D4D] underline"> 일반회원 가입</p>
</div>
)
}
export default Join
그 때, 팀원분 Exellent님께서 이럴 때 버튼은 공통으로 빼는게 좋고
그 안에서 공통되는 디자인 부분은 상수로 또 빼는 것도 알려주셔서 공부 겸 짚고 넘어가기 .!
스타일 상수와, 공통 컴포넌트 차이 ?
좋은 질문이에요! 스타일 상수와 공통 컴포넌트가 어떤 차이가 있는지, 좀 더 쉽게 설명해볼게요.
1. 스타일 상수 (Style Constants)
- 말 그대로 '스타일 값'만 변수로 빼놓은 것이에요.
- 예를 들어, 버튼 배경색, 글자색, 폰트 크기, 여백 같은 걸 변수로 미리 정해놓고, 여러 군데서 같은 값을 쓸 때 재사용하는 거예요.
예시
// styleConstants.js
export const COLORS = {
kakaoYellow: "#FEE500",
naverGreen: "#03C75A",
textDark: "#391C1A",
};
export const FONT_SIZES = {
button: "16px",
};
// 버튼 스타일에 변수 쓰기
const kakaoButtonStyle = {
backgroundColor: COLORS.kakaoYellow,
color: COLORS.textDark,
fontSize: FONT_SIZES.button,
padding: "12px 24px",
borderRadius: "4px",
};
- 위처럼 색상이나 글꼴 크기, 간격 같은 걸 변수로 빼놓고 버튼 스타일에 사용해요.
- 이렇게 하면 스타일 변경할 때 변수 값만 바꾸면 되니 편해요.
2. 공통 컴포넌트 (Common Component)
- UI 요소 전체를 재사용할 수 있게 컴포넌트로 만드는 것이에요.
- 버튼 모양, 텍스트, 클릭 동작 등 완성된 UI 단위를 하나로 묶는 거예요.
예시 (리액트 컴포넌트)
import React from "react";
import { COLORS, FONT_SIZES } from "./styleConstants";
function SocialButton({ provider, onClick }) {
const styles = {
kakao: {
backgroundColor: COLORS.kakaoYellow,
color: COLORS.textDark,
},
naver: {
backgroundColor: COLORS.naverGreen,
color: "#fff",
},
};
return (
<button
style={{
...styles[provider],
fontSize: FONT_SIZES.button,
padding: "12px 24px",
borderRadius: "4px",
border: "none",
cursor: "pointer",
}}
onClick={onClick}
>
{provider === "kakao" ? "카카오 로그인하기" : "네이버 로그인하기"}
</button>
);
}
export default SocialButton;
- 이렇게 버튼을 컴포넌트로 만들면
- 버튼 모양, 크기, 동작(클릭 이벤트 등)을 한 군데서 관리 가능
- 나중에 버튼을 추가하거나 디자인을 바꿀 때 아주 편해요
- 버튼을 여러 곳에서 동일하게 사용할 수 있어요
차이점 정리
구분 스타일 상수 공통 컴포넌트역할 | 색상, 폰트 크기, 간격 등 스타일 값 저장 | UI 요소(버튼, 카드 등) 전체를 재사용 가능하게 만듦 |
대상 | 디자인 속성 (숫자, 문자열) | UI 기능과 구조가 포함된 컴포넌트 코드 |
예시 | const primaryColor = "#6201E0"; | <SocialButton provider="kakao" /> |
장점 | 스타일 변경 시 유지보수 용이 | UI 일관성 유지, 코드 재사용성 증가 |
보통 같이 쓰임? | 네, 스타일 상수는 공통 컴포넌트 안에서 자주 사용됨 | 공통 컴포넌트 안에 스타일 상수를 활용 |
필요하면 스타일 상수만 먼저 만들고, 그걸 활용해서 공통 컴포넌트를 만드는 식으로도 많이 해요.
더 궁금한 점 있으면 편하게 물어봐 주세요!
그런데
결국 어찌 됐든.. 스타일 상수만 썼을 때는, 여전히 코드가 복잡해질 것 같아서
우선 공통 컴포넌트로 빼보겠습니다
아 그리고, 상수는 같은 파일내만 쓸 수 있어서,,
상수는 공통 컴포넌트로 안뺄때 반복 안될때 한 파일에서 쓸 때 유용 하겠다 ~
맞아요!
상수로 스타일만 빼면 색깔, 폰트 크기 같은 값은 재사용되지만,
버튼을 만드는 코드(구조, 텍스트, 이벤트 등)는 여전히 중복될 수 있거든요.
그래서 버튼 모양이 비슷하고 역할도 같으면
컴포넌트로 쭉 빼서 재사용하는 게 코드도 훨씬 깔끔해지고 관리하기 편해요.
스타일 상수랑 공통 컴포넌트 둘 다 잘 활용하면
유지보수성과 생산성이 확실히 올라가니까 좋은 습관이에요!
필요하면 내가 간단한 컴포넌트 예시도 바로 만들어 줄게요~
공통 컴포넌트로 만들 때 타입 지정에 대해 또 고민이었다.
어디서 부터 어디까지 타입을 지정해야 하나..
1. 공통된 부분 (버튼 공통 컴포넌트 내부 고정 값)
- 버튼 사이즈 (높이, 너비)
- 기본 테두리, 모서리 둥글기
- 기본 폰트 스타일 등
→ 이 부분은 컴포넌트 내부에 하드코딩하거나 스타일 시트에 고정으로 두고, 타입 지정 필요 없음!
2. 달라질 부분 (프롭스로 받는 부분)
- 배경 색상 (bgColor)
- 내부 글자 (children 또는 label)
- 내부 로고 (iconSrc 또는 icon)
- 바닥 마진 (marginBottom)
→ 이 부분만 props로 받고 타입 지정해줘야 함!
type ButtonProps = {
bgColor?: string;
children: React.ReactNode;
iconSrc?: string;
marginBottom?: string | number;
onClick?: () => void;
};
이렇게 하면 재사용도 쉽고, 고정 부분은 유지하면서 변할 부분만 유연하게 바꿀 수 있어!
type ButtonProps = {
bgColor?: string;
children: React.ReactNode;
iconSrc?: string;
marginBottom?: string | number;
onClick?: () => void;
};
여기서, children 부분:
children: React.ReactNode는 React 컴포넌트에서 특별한 타입이다
children 이란?
- React 컴포넌트 안에 들어가는 중첩된 요소나 텍스트를 의미해.
- 예를 들어, <Button>Click me</Button>에서 "Click me"가 바로 children이야.
React.ReactNode란?
- React에서 렌더링할 수 있는 모든 걸 포함하는 타입이야.
- 문자열, 숫자, JSX 요소, 배열, null, undefined, false 등 모두 포함돼.
예를 들면:
<Button>Click me</Button> // 문자열
<Button><span>Click</span> me</Button> // JSX 요소 섞인 것
<Button>{condition && <Icon />}</Button> // 조건부 렌더링도 가능
이 모든 경우를 아우르는 타입이 바로 React.ReactNode야.
정리하자면
- children: React.ReactNode라고 하면
- 버튼 안에 텍스트, 아이콘, 여러 JSX 요소 등 어떤 형태든 다 넣을 수 있다는 뜻!
그렇다면.. 왜 <SocialButton>내용</SocialButton> 이렇게 썼을까?
children이 있어서!
- React에서 컴포넌트 태그 사이에 넣은 내용이 바로 children prop으로 전달
- 그래서 <SocialButton>카카오로 가입하기</SocialButton> 처럼
태그 사이에 글자, JSX 요소, 아이콘 등 뭐든 넣으면 그게 children이 돼서 컴포넌트 내부에서 렌더링
반면에 <Button /> 처럼 닫히는 태그는?
- 컴포넌트에 전달할 내용(children)이 없을 때 쓰는 문법.
- <Button /> 은 children이 없다는 뜻!
- 만약 children을 기대하는 컴포넌트인데 빈 태그로 닫으면, 내부에 표시할 내용이 없으니까 화면에 아무 것도 안 나올 수 있어.
정리하자면
- <SocialButton>내용</SocialButton> = children에 내용이 전달됨
- <SocialButton /> = children이 없어서 내부에 아무 것도 없음
예시
// children이 있는 경우
<SocialButton>로그인</SocialButton>
// children이 없는 경우
<SocialButton /> // 내부에 내용이 없으니까 버튼 안에 아무 것도 안 나옴
onclick 부분!
onClick?: () => void; 이건,
- onClick이라는 이름의 prop이고,
- 함수 타입이라는 뜻이야.
- ?는 이 prop이 선택적(optional) 이라는 뜻. 즉, 전달 안 해도 된다는 의미고.
자세히 풀면:
- () => void 는
- 매개변수(parameter)는 없고,
- 반환값(return value)도 없는 함수 타입이야.
- 즉, 아무 인자도 받지 않고, 아무것도 반환하지 않는 함수를 의미해.
왜 쓰는지?
- 버튼이 클릭됐을 때 실행할 함수를 부모 컴포넌트가 넘겨줄 수 있게 하기 위해서!
- 예를 들어:
<Button onClick={() => alert("버튼 클릭됨!")}>클릭</Button>
버튼을 누르면 alert가 실행돼.
정리
- onClick은 클릭 이벤트 핸들러를 받는 프롭이고,
- 전달 안 해도 되니까 ? 붙음,
- 함수는 인자 없이 아무것도 반환하지 않는 타입이야.
그랬을 때 코드는 다음과 같다.
//공통 버튼 컴포넌트
import React from "react";
type SocialButtonProps = {
bgColor: string;
iconSrc: string;
iconAlt: string;
children: React.ReactNode
marginBottom?: string | number;
onClick?: () => void;
txtColor: string
}
export default function SocialButton({
bgColor,
iconSrc,
iconAlt,
children,
marginBottom,
onClick,
txtColor
}: SocialButtonProps) {
return (
<button
onClick={onClick}
style={{
backgroundColor: bgColor,
width: "348px",
height: "52px",
borderRadius: "4px",
padding: "8px",
color: txtColor,
marginBottom: marginBottom,
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "10px",
}}
>
<img src={iconSrc} alt={iconAlt} />
{children}
</button>
);
}
//직접 사용하기 코드
: 추후에, 백엔드랑 연결 후, onclick 변경해줘야 함!
네, 맞아요!
지금은 onClick에 간단히 alert 함수 넣어서 테스트하고 있는데,
나중에 백엔드 API랑 연동할 때는 onClick 안에 실제 소셜 로그인/회원가입 로직을 넣으면 돼요.
예를 들면:
- 카카오 로그인 API 호출
- 네이버 로그인 API 호출
- 로그인 성공 시 받은 토큰 처리
- 사용자 정보 받아서 상태 관리 (Redux, Context API 등)
아래처럼 onClick 함수만 변경하면 됩니다:
<SocialButton
bgColor="#FEE500"
txtColor="#391C1A"
iconSrc="src/assets/kakao_brown.svg"
iconAlt="카카오"
marginBottom="12px"
onClick={handleKakaoLogin}
>
카카오로 3초만에 가입하기
</SocialButton>
const handleKakaoLogin = async () => {
try {
// 카카오 로그인 API 호출 및 처리
} catch (error) {
console.error("카카오 로그인 실패", error);
}
};
필요하면 로그인 성공 후 리다이렉트나 사용자 상태 업데이트도 같이 해주면 되고요!
지금처럼 UI만 먼저 구현해놓고, 나중에 기능 연결하는 방식이 보통이에요.
import SocialButton from "@/components/common/SocialButton";
import React from "react";
function Join() {
return(
<div className="flex flex-col items-center pt-20">
<img src="src/assets/logo_black.png" alt="오즈코딩스쿨 로고" className="w-[180px] mb-4"/>
<div className="flex gap-3 text-[16px]">
<p className="text-[#4D4D4D]"> 회원 이신가요?</p>
<p className="text-[#6201E0]"> 로그인 하기</p>
</div>
<br/>
<br/>
{/* 카카오로 회원 가입 버튼 */}
<SocialButton
bgColor="#FEE500"
txtColor="#391C1A"
iconSrc="src/assets/kakao_brown.svg"
iconAlt="카카오"
marginBottom="12px"
onClick={() => alert("카카오 로그인 클릭됨")}>
카카오로 3초만에 가입하기
</SocialButton>
{/* 네이버로 회원 가입 버튼 */}
<SocialButton
bgColor="#03C75A"
txtColor="#FFFFFF"
iconSrc="src/assets/naver_white.svg"
iconAlt="네이버"
marginBottom="25px"
onClick={() => alert("네이버 로그인 클릭됨")}>
네이버로 가입하기
</SocialButton>
<p className="text-[#4D4D4D] underline"> 일반회원 가입</p>
</div>
)
}
export default Join
2. 페이지를 따로 만들어서 (일반 회원가입용) 만들기
다음 게시글에서...