반응형

Prisma 란?

prisma는 Node.js와 TypeScript ORM이다.

ORM은 Object Relational Mapping이라는 뜻이고 기본적으로 번역기의 역할을 한다고 생각하면된다.

JavaScript 혹은 TypeScript 코드와 DB사이에 다리를 놓아준다.

(mongo DB의 Mongoose를 떠올리면 쉬울것 같다.)

 

Prisma를 사용하기전에 데이터베이스가 어떻게 생겼는지를 schema.prisma라는 파일에 설명해줘야한다.

예를 들면 아래와 같다.

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider     = "mysql"
  url          = env("DATABASE_URL")
  relationMode = "prisma"
}

model User {
  id        Int      @id @default(autoincrement())
  name      String
  phone     Int?     @unique
  email     String?  @unique
  avatar    String?
  createAt  DateTime @default(now())
  updatedAt DateTime @updatedAt
}

이러한 타입에 관한 정보를 알고 있으면, Clinet를 생성해 줄 수 있다.

Client를 이용하면 타입스크립트로 데이터베이스와 직접 상호작용할 수 있다. 

prisma에게 DB가 어떻게 생겼느지 설명해주면 Prisma는 Client를 생성해 줄 거고 그 Client 안에 필요한 모든 name이 들어있을 것이다.

 

더불어  Prisma Studio라는 Visual Database Browser있는데 이것은 데이터베이스를 위한 관리자 패널(Admin panel) 같은 것이다.

데이터베이스는 PostgreSQL / MySQL / SQL Server / SQLite / MongoDB에 사용할 수 있다.  

Prisma Setup

1. VSCode에서 PRISMA EXTENTIONS찾아서 설치

2. Prisma 설치

npm i prisma -D

3. 설치 후 Prisma 불러오기 (Prisma를 사용할 때면 항상 npx prisma 라는 명령어 사용)

npx prisma init

위 코드를 입력시 application 안에 prisma 폴더가 생겼고 .env 파일도 생성이됨

생성 후에 VSCode 터미널에 다음으로 해야할 사항들이 나열되어있는데 

  1. .env파일에 있는 DATABASE_URL을 설정해줘야함 . 초기설정에 postgresql이 설정되어 있는데 자신이 사용하는 DB URL을 기입
  2. schema.prisma 파일에서 datasource의 provider를 설정 해야함
// This is your Prisma schema file,

datasource db {
  //provider는 우리가 사용할 데이터베이스를 말한다.
  provider     = "mysql"
  url          = env("DATABASE_URL")
  relationMode = "prisma"
}


//사용할 user에 대한 schema를 만들어준다. 만들때 model이라고 작성 후 이름을 붙어주고 사항들을 붙어준다.

model User {
  id        Int      @id @default(autoincrement())
  //@id는 모델의 id라는 걸 알려주는 역할을 하며 유니크한 식별자이다.
  //@default로 기본값을 지정해주는데 autoincrement로 자동으로 증가하는 field로 설정함.
  
  name      String
  phone     Int?     @unique
  //?의 표시는 값이 들어올수도 아닐수도 있다는 표시이며 @unique라는 걸 붙이게 되면 값이 중복될수가 없다.
  
  email     String?  @unique
  avatar    String?
  
  createAt  DateTime @default(now())
  //user가 만들어지고 난 시점(now())을 넣어준다.
  
  updatedAt DateTime @updatedAt
  // @updateAt은 유저가 업데이트 될 때마다 이 field값이 변할 거라고 prisma에게 알려줄거다.
}

 

 

PlanetScale

(이하 pscale)

pscale은 MySQL과 호환되는 Serverless 데이터베이스 플랫폼이다. 

이 말은 즉슨, 데이터베이스를 제공해주며 , serverless는 정말로 서버가 없다는 것이 아니라 , 서버를 우리가 유지보수할 필요가 없다는 뜻이다.

예를 들어 백만명이 작업자의 데이터베이스에 연결되면 직접 scaling(확장)시켜줘야하는데 이런 플랫폼을 사용하면 그런 작업을 대신 해준다. 

 

vitess - pscale에 따르면 가장 scaling 기능이 뛰어난 오픈 소스데이터베이스이다. vitess는 유튜브를 scale하기 위해 구글이 만든 것.

vitess는 대기업들이 규모에 맞게 MySQL을 scale하기 위해 쓰는 방법이다. 

 

pscale은 CLI가 있다.  CLI로 데이터베이스를 다룰 수 있다. 어떠한 Model을 수정한다면 모두가 사용하는 메인 데이터베이스를 직접 수정하는 대신 데이터베이스에 brach를 만들수가 있다. 그 brach에서 schema도 바꾸고 새로 model도 만들고 field도 수정할 수 있다.  수정 후에는  schema를 옮기는 건데 그걸 알아서 합쳐줄것이다.

 

PlanetScale Connecting

**pscale을 연결하기 위해서는 일단 회원가입을하고 카드를 등록해줘야 원할히 연결할 수 있다. 

 

1 . CLI를 사용을 위해 터미널에 명령어를 입력해준다. (mac 사용자)

brew install planetscale/tap/pscale

brew install mysql-client

 

window 유저라면 scoop을 통해 다운로드 (scoop은 콘솔을 통해서 쉽게 다운로드 받게 해줄수 있는 도구 https://scoop.sh)

설치가 완료됬다면 VSCode 터미널에서 pscale를 입력하면 그와 관련된 명령어들이 나온다.

 

2. 설치 후에는 pscale auth login 을 통해 로그인 해준다. 이때 카드 등록은 필수이며 vscode의 터미널에 나오는 숫자와 브라우저에 나오는 숫자를 잘 대조한다.

 

3. vscode로 다시 돌아와서 pscale region list를 터미널에 작성하면 slug의 태그 이름을 복사해서 

pscale database create 프로젝트이름 --region "region slug"

이러면 데이터베이스가 만들어질것이다.

 

4. pscale의 관리자 패널로 가보면 만들어져 있는 것을 볼 수 있을 것이다. (브라우저에서 로그인 후 확인)

 브라우저에서도 데이터베이스를 만들 수 있다. 

 

5.  prisma와 연결하기 위해 .env에 있는 DATABASE_URL를 바꿔줘야하는데 보통 데이터베이스 플랫폼에서는 데이터베이스를 하난 만들면 암호를 생성해야한다. 암호를 환경파일에 집어넣어야한다. 이렇게 되면 내 장치에 암호가 저장되는 꼴이 되어서 보안에 취약할 수 있다.

따라서 사람들이 보통하는 방식은 컴퓨터로 작업할 때 직접 진짜 데이터베이스를 건들지 않고  가짜와 진짜로 두 개의 데이터베이스를 운영한다. 

대신에 pscalse에서는 일종의 보안 tunnel을 이용할 수 있다. 컴퓨터와 pscale 사이에 암호를 알 필요가 없어진다. 

pscale 보안연결을 하기 위해서는 CLI를 활용해야한다. 

pscale connect 데이터베이스이름

이렇게 작성하면 터미널에 URL이 나온다.

Local address to connect your application: 127.0.0.1:3306 (press ctrl-c to quit)

콘솔을 절대 종료하지 않고  URL을 복사하여  env파일에 작성해준다.

DATABASE_URL="데이터베이스관리시스템이름://콘솔창에 나온 URL/프로젝트명"

이렇게 하면 pscalse와 연결이 되었다.

 

6. 연결 후 해야하는게 있는데 MySQL은 하는데 vitess는 하지 않는 몇가지가 있는데 그중에 foreign key 제약이다.

 

 

/* 

foreign key는 예를 들어

User DB: [
{id:1 , userName :"hoho"}
]

게시판 DB:[
{게시물의 id:1 , text:"hahahoho", 작성자: userID(1)}
]

이렇게 되면 게시판의 게시글의 작성자가 누가 누군지 알게 된다.
이러한 방식을 foreign key라고 한다.
MySQL이나 postgreSQL은 이러한 방식을 사용하지만 vitess는 이러한 방식을 사용하지 못한다.

만약 게시물의 작성자 id가 5가 되었다면 작동되지 않았겠지만 pscale에서는 아무 문제 없이 작동이 된다.
이게 작동되는 이유는 MySQL과 vitess의 차이점 때문이다.
vitess는 Scalability에 특화되어 있다.
vitess는 데이터베이스를 잘게 쪼개서 여러 서번에 분산시키는데에 특화되어 있다.
vitess는 게시물을 생성하기 전에 사요자가 존재의 유무를 확인하지 않는다.

그래서 보통은 데이터베이스에게 도움을 받아 존재의 유무를 확인한다. 하지만 우리는 prisma를 사용해 도움을 받을 수 있다. 

*/

//schema.prisma

//... 기존 코드들

datasource db {
  provider     = "mysql"
  url          = env("DATABASE_URL")
  // 아래 코드 추가
  relationMode = "prisma"
}

코드 추가 후에 VSCode 콘솔 창에 작성

npx prisma db push

후에 pscale의 해당 데이터베이스 Brach로가면 schema.prisma에서 작성한 모델이 올라가있는것을 볼 수 있다.

npx prisma studio

위의 명령어를 입력하게 되면 데이터 베이스 관리자 패널을 볼 수 있다.

 

Prisma Client

prisma가 prisma client라는걸 제공해준다.

npm i @prisma/client

 

typescript 파일을 만들고

import {PrismaClient} from "@prisma/client";

export default new PrismaClient();
npx prisma generate

client를 생성했다는 메세지를 볼 수 있다.

prisma client는 브라우저에서 실행할 수 없다. 프론트 단에서 실행할 수 없다.

DB에 접근하는 것은 프론트단에 작성하면 안된다.

 

API ROUTES (next.js)

prisma client를 접근하기 위해 서버, 즉 백엔드 코드를 작성할 곳이 필요하다.

보통같으면 백엔드로 nodejs를 사용해서 백엔드 코드를 작성할텐데 NextJS는 API를 만들기 위해 다른 서버를 구축할 필요가 없을 정도로 좋다.

app 폴더에 api 폴더를 만들고 route.ts를 만든다. 이렇게 만들면 api서버가 완성된다.

import {NextResponse} from "next/server";
import client from "../../../../util/client";
//Next JS에서 req,res object를 제공하는데 TS사용한다면 다음과 같이 지정
export async function GET() {
  await client.user.create({
    data: {
      name: "hahah",
      email: "11@22.22",
    },
  });

  return NextResponse.json({
    ok: true,
    data: "xxx",
  });
}

이후 Prisma studio에 들어와서 확인하면 유저가 생성된걸 볼 수 있다. 이때 prisma와 pscale은 계속 연결이 되어야 한다.

728x90
반응형

React-Hook-Form

React Hook Form라이브러리를 사용하면 따로 이벤트 헨들러, state를 만들 필요가 없다.

// 기존

export default function Mainpage() {
  const [val, setVal] = useState("");
  const onChange = (event: React.FormEvent<HTMLInputElement>) => {
    const {
      currentTarget: {value},
    } = event;
    setVal(value);
  };
  const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
     console.log("submit")
  };
  return (
    <div>
      <h1>React / Recoil / StyledComponent / TypeScript / To Do List</h1>
      <form onSubmit={onSubmit}>
        <input type="text" value={val} onChange={onChange} />
        <button>Add</button>
      </form>
    </div>
  );
}

1-1. register()

register 함수를 사용하면 onChange 이벤트 핸들러, props, state 가 필요가 없다.

register 함수에는 

  • name
  • onChnage
  • onBlur
  • ref

가 내포되어 있다.  react input의 attribute라고 생각되면 됨

1-2. watch()

watch는 form의 입력값들의 변화를 관찰 할 수 있게 해주는 함수

1-3. handleSubmit( onValid(required), onInValid(optional))

validation을 담당함 .  handleSubmit에는 2개의 인자를 받는데 하나는 데이터가 유효할 때 호출되는 함수

다른 하나는 실패했을 때 호출 되는 함수

1-4.formState

console.log(formState.errors)를 하게 되면 해당부분에 떤 에러가 났는지 디버깅 할수 있다.

interface IFormData {
  todo: string;
  email: string;
  errors: {
    email: {
      message: string;
    };
  };
}
export default function Mainpage() {
  const {
    register,
    watch,
    handleSubmit,
    formState: {errors},
  } = useForm<IFormData>();
  const onValid = (data: any) => {
    console.log(data);
  };

  return (
    <div>
      <h1>React / Recoil / StyledComponent / TypeScript / To Do List</h1>
      <form onSubmit={handleSubmit(onValid)}>
        {/* // register안에 required를 작성해 html의 소스코드를 보호할수 있다. */}
        <input
          {...register("todo", {
            required: true,
            minLength: {
              value: 5,
              message: "length is too short",
            },
          })}
          type="text"
          placeholder="Write a to do"
        />
        <input
          {...register("email", {
            required: "emai is required",
            minLength: 5,
            pattern: {
              value: /^[A-Za-z0-9._-]*@[A-za-z0-9]*\.[a-zA-Z]{2,3}$/,
              message: "only email",
            },
          })}
          type="email"
          placeholder="Write Email"
        />
        <span>{errors?.email?.message}</span>
        <button>Add</button>
      </form>
    </div>
  );
}

2. Custom Validation

  const onValid = (data: IFormData) => {
    if (data.pw !== data.pw1)
      setError("pw1", {message: "pw are not same"}, {shouldFocus: true});
    setError("extraError", {message: "server offline"});
  };
  
  // 코드 생략
  
   <input
          {...register("name", {
            required: true,
            minLength: {
              value: 5,
              message: "length is too short",
            },
            validate: {
              badName: (value) =>
                value.includes("fuck") ? "bad word is not allowd" : true,
            },
          })}
          type="text"
          placeholder="Write a name"
        />
        <span>{errors?.name?.message}</span>

 

728x90

'React > React Hook Form' 카테고리의 다른 글

React Hook Form  (0) 2023.11.15
반응형

React Query 공식 홈은 -> 여기다   TanStack Query

 

먼저 npm i @tanstack/react-query 을 해준다.

다음으로

index.tsx에 가서

import {QueryClient, QueryClinetProvider} form '@tanstack/react-query'

const queryClient = new QueryClient()

function App(){
	retrun (
     <QueryClientProvider client={queryClient}>
      <Todos />
    </QueryClientProvider>
  )
}

작업을 해준다.

 

Api가 있는 template 파일의 문장을 토대로 스크립트 파일을 하나 만든다

// api.ts

export const functionName = async()=>{
	return fetch('').then((response)=>response.json())
}

 

후에

Api가 있는 template 파일로가서 useEffect가 있는 문장을 없애주고

import {api} from './api.ts'
import {useQuery} from '@tanstack/react-query'


export default FunctionName (){
	const {isLoading, data} = useQuery(['keyName'], functionApi)
    // 비동기 방법
    const {isLoading, data} = useQuery(['keyName'], async () => {
    	const data = await functionApi
        return data
        },
        //5초마다 refetch
        {
        	refetchInterval:5000;
         }
        )
	return(
    <div>
    {isLoading ? <span> loading </span> : data.map()//문장 구성}
    </div>
    )
}
728x90
반응형

API로 불러온 데이터를 쉽게 interface 작업을 하는 방법

useEffect(() => {
    (async () => {
      const infoData = await (
        await fetch(`http://~~~/${params.id}`)
      ).json();

      setInfo(infoData);
      console.log(info);
    
    })();
  }, [params]);

이렇게 API를 불러오고 console.log하여 콘솔창에 데이터를 출력한다

/* 콘솔창에서 불러와진 데이터를 오른쪽 클릭후 전역 변후 Object로 저장을 누르면 temp1로 저장이 되는데 이때
콘솔창에 다시 아래의 커멘드를 입력한다.*/

Object.keys(temp1).join()

/* 이렇게 하면 key값들이 한문장으로 나열이된다.*/


/* key 값의 타입을 구하기 위해서는 다음과 같이 작성한다.*/
Object.values(temp1).map(v=>typeof v).join()

이렇게하면 많은 데이터가 있을 때 쉽게 처리할 수 있다.

 

데이터 처리 후 데이터 안에 Array Object 가 있을 경우 그 부분에 대한 interface를 만들고 해당 key에 interfaceName[]을 적어준다.

interface ITAG{
	//keyname : typeof
    hello : string
    hi : boolean
    hoho : number
}

interface keys{
// key : ITAG[]

// 위의 방법이나 아래방법을 사용하면 됨

	key : {
    hello : string
    hi : boolean
    hoho : number
    }
}
728x90

'CODING PRACTICE > Typescript' 카테고리의 다른 글

TypeScript__Classes & Interfaces  (0) 2023.11.27
TypeScript__Typescript 개요 및 타입들  (1) 2023.11.21
Typescript__With React(001 -- Basic)  (0) 2023.08.25
반응형

Typescript

Typescript는 Javascript를 기반으로 한 프로그래밍 언어이다.

서로는 다른 언어이지만 엄청 다른 언어는 아니고 새로운 기능이 몇몇 추가된 언어이다. 그래서 문법도 같다.

//Javascript 일때...

const plus = (a,b) => a+b

plus(1,3)
// console.log(plus(1,3)) === 4

plus(1,"hello")
// console.log(plus(1,"hello")) === "1hello"
// TypeScript

const plus = (a:number,b:number) => a + b;

plus(1,3)
plus(a,2) => 오류 표시가 나옴
plus("3",2) => 오류 표시가 나옴

Typescript 설치

// React와 함께 사용할경우
npx create-react-app 앱이름 --template typescript

// 기존 문서에 추가할 경우
npm i --save typescript @types/node @types/react @types/react-dom @types/jest


/* 어떤 라이브러리나 패키지는 TypeScript로 만들어진게 아니여서 불만 표시가 있다면 type definition인 
@types/라이브러리이름 을 찾아 설치 하는 방법을 고려해보자*/

Typescript가 React에게 설명하는 방법

기존에는 Prop Types를 사용해 prop이 거기 있는지 없는지를 확인해주었다. 하지만 PropTypes는 브라우저 콘솔에 경고표시를 하고 코드를 실행한 후에만 확인가능했다. 우리는 코드가 실행전 확인하려한다.

//App.tsx
import React from "react";
import styled from "styled-components";
import Circle from "./Circle";

export default function App() {
  return (
    <div>
      <Circle bgColor={"teal"} />
      <Circle bgColor={"tomato"} />
    </div>
  );
}
//Circle.tsx
import styled from "styled-components";

//Container의 div에게 bgColor prop에 대한 type을 알려주는 방법
interface ContainerProps {
  bgColor: string;
}
const Container = styled.div<ContainerProps>`
  width: 200px;
  height: 200px;
  background-color: ${(props) => props.bgColor};
`;

//function Circle의 bgColor의 type을 알려주는 방법
interface CircleProps {
  bgColor: string;
}

export default function Circle({bgColor}: CircleProps) {
  return <Container bgColor={bgColor} />;
}

Optional Props

선택적으로 타입의 유무에 대해 확인하는 방법

interface의 object에 물음표(?)를 달아 선택적으로 확인할 수 있다 

물음표는 ? === type | undefine

??는 null 병합연산자 

??앞에 값이 null이거나 undefined라면 오른쪽 아니면 왼쪽

//App.tsx
import React from "react";
import Circle from "./Circle";

function App() {
  return (
    <div>
      <Circle borderColor="red" bgColor={"teal"} />
      <Circle bgColor={"tomato"} text={"문구가 있으면 이 문구로 바뀜"}/>
    </div>
  );
}

export default App;


//Circle.tsx

import styled from "styled-components";

//Container의 div에게 bgColor prop에 대한 type을 알려주는 방법
interface ContainerProps {
  bgColor: string;
  borderColor?: string;
}
const Container = styled.div<ContainerProps>`
  width: 200px;
  height: 200px;
  background-color: ${(props) => props.bgColor};
  border: 1px solid ${(props) => props.borderColor};
`;



//function Circle의 bgColor의 type을 알려주는 방법
interface CircleProps {
  bgColor: string;
  borderColor?: string;
  text?: string;
}

export default function Circle({bgColor, borderColor, text= '내용이 없으면 이 문구가 default임'}: CircleProps) {
  return(
   <Container bgColor={bgColor} borderColor={borderColor ?? bgColor} >
     {text}
   </Container>
  )
}

State Type

export default function Circle({bgColor, borderColor}: CircleProps) {
  // typescript가 counter의 type을 number로 자동으로 추론한다.
  const [counter, setCounter] = useState(1);
  // 만약 setCounter를 통해 string 값을 줘야한다면 다음과 같이 작성 하지만 그리 자주 사용은 안됨
  const [counter, setCounter] = useState<number|string>(0);
  return <Container bgColor={bgColor} borderColor={borderColor ?? bgColor} />;
}

Form Type

import React, {useState} from "react";

function App() {
  const [val, setVal] = useState("");
  const onChange = (event: React.FormEvent<HTMLInputElement>) => {
    /** event의 타입을 주기위해서는 위와 같이 작성해야하는데 저것을 찾기 위해서는 어떤곳에서 이벤트가 이루어졌는지 생각해보면 쉽게 다가갈수 있음 혹은
     * 공식문서나 구글링을 통해 찾는 걸 추천함 */
    // console.log(event.currentTarget.value);
    /** 또한 보통 자바스크립트 같은 경우엔 target이라고 작성하는데 타입스크립트와 리액트를 같이 사용하는데 있어서 currentTarget으로 작성해야한다.*/
    const {
      currentTarget: {value},
    } = event;
    setVal(value);
  };

  const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log(val);
  };
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input
          value={val}
          onChange={onChange}
          type="text"
          placeholder="UserName"
        />
        <button>Log In</button>
      </form>
    </div>
  );
}

export default App;
728x90
반응형

Connect Typescript with styled-components

1. 설치

npm install @types/styled-components

2.  선언파일 만들기 (Create a declarations file)

src폴더 안에 styled.d.ts 생성 후 styled-components의 공식문서에서 선언파일 만드는 방법을 찾아 붙이기

//styled.d.ts

// import original module declarations
import "styled-components";

// and extend them!
declare module "styled-components" {
  export interface DefaultTheme {
    textColor: string;
    bgColor: string;
    btnColor: string;
  }
}
//theme.ts
import {DefaultTheme} from "styled-components/dist/types";

export const lightTheme: DefaultTheme = {
  bgColor: "white",
  textColor: "black",
  btnColor: "teal",
};
export const darkTheme: DefaultTheme = {
  bgColor: "black",
  textColor: "white",
  btnColor: "tomato",
};
//App.tsx

import React, {useState} from "react";
import styled from "styled-components";

const Btn = styled.button`
  background-color: ${(props) => props.theme.btnColor};
`;

function App() {
  const [val, setVal] = useState("");
  const onChange = (event: React.FormEvent<HTMLInputElement>) => {
    /** event의 타입을 주기위해서는 위와 같이 작성해야하는데 저것을 찾기 위해서는 어떤곳에서 이벤트가 이루어졌는지 생각해보면 쉽게 다가갈수 있음 혹은
     * 공식문서나 구글링을 통해 찾는 걸 추천함 */
    // console.log(event.currentTarget.value);
    /** 또한 보통 자바스크립트 같은 경우엔 target이라고 작성하는데 타입스크립트와 리액트를 같이 사용하는데 있어서 currentTarget으로 작성해야한다.*/
    const {
      currentTarget: {value},
    } = event;
    setVal(value);
  };

  const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log(val);
  };
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input
          value={val}
          onChange={onChange}
          type="text"
          placeholder="UserName"
        />
        <button>Log In</button>
      </form>
      <Btn>눌러뽕</Btn>
    </div>
  );
}

export default App;

 

Styled-Components 의 Style - Reset 하기

  • npm i styled-reset
  • 아니면 styled-reset의 깃에 들어가 src/index.ts를 복붙하면됨
import React from "react";
import styled, {createGlobalStyle} from "styled-components";
import Mainpage from "./views/Mainpage";

const GlobalStyle = createGlobalStyle`
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+KR:wght@100;200;300;400;500;600;700&display=swap');
  html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, menu, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
main, menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, main, menu, nav, section {
  display: block;
}
/* HTML5 hidden-attribute fix for newer browsers */
*[hidden] {
    display: none;
}
body {
  line-height: 1;
  font-family: 'IBM Plex Sans KR', sans-serif;
  color: ${(props) => props.theme.textColor};
  background-color: ${(props) => props.theme.bgColor};
}
menu, ol, ul {
  list-style: none;
}
blockquote, q {
  quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
  content: '';
  content: none;
}
table {
  border-collapse: collapse;
  border-spacing: 0;
}
*{
  box-sizing:border-box;
}
a{
  text-decoration:none;
}
`;

const Wrapper = styled.div`
  display: flex;
`;
function App() {
  return (
    // Fragment - ghost component
    <>
      <GlobalStyle />
      <Wrapper>
        <Mainpage />
      </Wrapper>
    </>
  );
}

export default App;
728x90

'React > Styled-Components' 카테고리의 다른 글

2023/08/24__Styled-Components(2)  (0) 2023.08.24
2023/08/23__Styled-Components(1)  (0) 2023.08.23
반응형

Styled-Components

React에서 style을 주기위한 방법은 몇가지가 있다.

  • 첫 번째로 직접 엘리멘탈에 스타일을 주는방법
return(
	<div style={{backgroundColor:'red'}}>
    	<h1>hello</h1>
    </div>
)
  • 두 번째로 CSS 모듈 파일을 만든다
  • 파일이름.module.css
import styles from '파일이름.module.css'

export default function 컴포넌트이름 (){
	return (
    	<div className={styles.hello}>
        	<h1 className={styles.text}> hello </h1>
        </div>
    )
}


/** 
파일이름.module.css

.hello{
background-color:teal;
}

.text {
color:white;
}

*/
  • 세 번째로는 프레임워크를 사용하는것
  • 네 번째로는 stlyed-component라이브러리를 사용

설치

리액트 프로젝트 생성 후

npm i styled-components
import styled from 'styled-compoenets'

//백틱에 스타일이 들어가야 한다.
const SayHello = styled.h1`
	background-color:teal;
	color:red;
    width:100px;
    height:100px
    `;
    
//props로도 스타일을 줄 수 있다.    
const SayMeetYou = styled.div`
	background-color: ${(props)=>props.bgColor};
    width:100px;
    heigth:100px;
    `;
    
// SayMeetYou의 요소를 그대로 사용하며 border-radius의 값을 추가하여 사용할 수 있다.
const SayBye = styled(SayMeetYou)`
    border-radius:50px;
    `;


// 예로 버튼태그에 스타일을 주었는데 링크테그에도 똑같은 스타일을 주고 싶을 때 사용하는 방법
const Btn = styled.button`
	border: 0;
    border-radius: 25px
    background-color: tomato:
    color:white;
    `;
    
//어트리뷰트 값도 지정해줄 수 있다.
const Input = styled.input.attrs({required:true, minLength:'10'})`
	background-color: cornflowerblue;
`;

export default function App (){
  return(
   <div>
    <div
     style={{ backgroundColor:'teal', width:100, height:100 }}>
				<h1> hello </h1>
    </div>
    <div>
     <SayHello> hello </SayHello>
    </div>
    <SayMeetYou bgColor={"orange"}}></SayMeetYou>
    <SayBye bgColor={"Yellow"}}></SayBye>
    <Btn>Click me</Btn>
    // Btn 스타일일을 쓰고 싶지만 링크로써 사용하고 싶다면 as라는 props를 줘서 원하는 html을 부여할 수 있다.
    <Btn as='a' href='URL LINK'>Click me</Btn>
    <Input/>
   </div>
  )
}

 

728x90

'React > Styled-Components' 카테고리의 다른 글

2023/08/25__Styled-Components with Typescript(3)  (0) 2023.08.25
2023/08/24__Styled-Components(2)  (0) 2023.08.24
반응형

07. 리스트와 아이콘 사용법

앱UI제작

  • 리스트와 아이콘의 사용법 학습
  • 실전과 비슷한 예제를 통해서 사용법 학습

data :{}는 단일 데이터

data(){}는 return은 필수이며 return으로 값을 호출 할 수 있다. 재사용 다시 렌더링하려면 함수형식으로 사용

  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <!-- |Material+icon 아이콘 같이 사용하려면 기입 -->
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900&display=swap|Material+icon"
      rel="stylesheet"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/@mdi/font@7.0.96/css/materialdesignicons.min.css"
      rel="stylesheet"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css"
      rel="stylesheet"
    />
    <link
      href="https://fonts.googleapis.com/css?family=Material+Icons"
      rel="stylesheet"
    />

    <title>vuetify practice</title>
    <style></style>
  </head>
  <body>
    <div id="app">
      <v-app>
        <v-app-bar color="orange" dark app>
          <!-- 좌측에 메뉴 아이콘 추가     -->
          <v-app-bar-nav-icon></v-app-bar-nav-icon>
          <v-toolbar-title>Virtual Company</v-toolbar-title>
        </v-app-bar>
        <v-main>
          <v-container>
            <v-card>
              <v-list two-line v-for="item in aList" :key="item.title">
                <!-- 항목을 하나씩 가져와서 item 단위로 표시 -->

                <v-list-item @click="">
                  <!-- 좌측에 대표 아이콘 먼저 표시 -->
                  <v-list-item-avatar>
                    <v-icon :class="item.icon_style">
                      {{item.icon_name}}
                    </v-icon>
                  </v-list-item-avatar>
                  <!-- 제목 렌더링 -->
                  <v-list-item-content>
                    <v-list-item-tilte>{{item.title}}</v-list-item-tilte>
                  </v-list-item-content>
                  <v-list-item-action>
                    <v-btn icon>
                      <v-icon class="material-symbols-outlined"
                        >keyboard_arrow_right</v-icon
                      >
                    </v-btn>
                  </v-list-item-action>
                </v-list-item>
              </v-list>
            </v-card>

            <v-btn fab dark color="red">
              <v-icon>add</v-icon>
            </v-btn>
          </v-container>
        </v-main>
        <v-footer></v-footer>
      </v-app>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.js"></script>
    <script>
      new Vue({
        el: "#app",
        vuetify: new Vuetify(),
        data() {
          // 반복되는 항목들은 JSON 배열 데이터로 만들어 반환
          return {
            aList: [
              {
                icon_style: "red white--text ",
                icon_name: "account_balance",
                title: "Create List",
              },
              {
                icon_style: "green white--text ",
                icon_name: "photo",
                title: "Use Material Icon",
              },
              {
                divider: false,
                icon_style: "yellow white--text",
                icon_name: "movie",
                title: "List & Icons",
              },
            ],
          };
        },
      });
    </script>
  </body>

 

08.하단 네비게이션

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <!-- |Material+icon 아이콘 같이 사용하려면 기입 -->
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900&display=swap"
      rel="stylesheet"
    />

    <link
      href="https://fonts.googleapis.com/css?family=Material+Icons"
      rel="stylesheet"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/@mdi/font@7.0.96/css/materialdesignicons.min.css"
      rel="stylesheet"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css"
      rel="stylesheet"
    />

    <title>vuetify practice</title>
    <style></style>
  </head>
  <body>
    <div id="app">
      <v-app>
        <v-main>
          <v-card
            height="100%"
            class="d-flex display-2 align-center justify-center"
          >
            선택:{{sSelect}}
          </v-card>
        </v-main>
        <v-footer>
          <!-- 선택된 메뉴는 sSelect 데이터를 연동시켜 바인딩 -->
          <v-bottom-navigation absolute v-model="sSelect" dark>
            <!-- 아이콘 버튼 3개 표시 -->
            <v-btn color="red" text value="자전거"
              >자전거
              <v-icon>directions_bike</v-icon>
            </v-btn>
            <v-btn color="red" text value="지하철"
              >지하철
              <v-icon>subway</v-icon>
            </v-btn>
            <v-btn color="red" text value="버스"
              >버스
              <v-icon>directions_bus</v-icon>
            </v-btn>
          </v-bottom-navigation>
        </v-footer>
      </v-app>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.js"></script>
    <script>
      new Vue({
        el: "#app",
        vuetify: new Vuetify(),
        data() {
          return {
            sSelect: "자전거",
          };
        },
      });
    </script>
  </body>

09. 탐색 서럽

탐색 서랍(Navigation drawer)UI

  • 앱바의 메뉴 아이콘을 누르면 탐색 서랍표시
  • 링크를 클릭하면 해당 사이트로 이동
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <!-- |Material+icon 아이콘 같이 사용하려면 기입 -->
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900&display=swap"
      rel="stylesheet"
    />

    <link
      href="https://fonts.googleapis.com/css?family=Material+Icons"
      rel="stylesheet"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/@mdi/font@7.0.96/css/materialdesignicons.min.css"
      rel="stylesheet"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css"
      rel="stylesheet"
    />
    <script
      src="https://kit.fontawesome.com/86e4e86e0d.js"
      crossorigin="anonymous"
    ></script>
    <title>vuetify practice</title>
    <style></style>
  </head>
  <body>
    <div id="app">
      <v-app>
        <v-app-bar app color="primary" dark>
          <!-- 메뉴 아이콘을 누르면 클릭기능 비활성화 -->
          <v-app-bar-nav-icon
            @click.stop="bDrawer = !bDrawer"
          ></v-app-bar-nav-icon>
          <v-toolbar-title>Header입니다</v-toolbar-title>
          <!-- bDrawerrk True 이면 탐색 서랍 사용하도록 바인딩 -->
        </v-app-bar>
        <v-navigation-drawer
          absolute
          temporary
          v-model="bDrawer"
        ></v-navigation-drawer>
      </v-app>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.js"></script>
    <script>
      new Vue({
        el: "#app",
        vuetify: new Vuetify(),
        data() {
          return {
            //버튼을 누르면 비활성화되도록 하는 토글 변수
            bDrawer: false,
            //배열로 메뉴의 제목, 아이콘, 링크 준비
            menu_items: [],
          };
        },
      });
    </script>
  </body>

라우터로 멀티페이지 관리하는 SPA 만들기

본격적인 실전 프로젝트 시작

  • 뷰티파이에서 제공하는 Vue-CLI 템플릿
  • 메인페이지와 서브페이지로 이동하는 라우터 기능 구현

 

public  정적파일만 담겨질수있음

src 개발한 파일들이 담겨져있는 폴더

dist build를 통한 결과 폴더

 

vue add vuetify

 

** 공부

seo

mpa - 페이지마다 한번씩 로딩 

spa - 처음로딩이 느림 그후에는 계속 

 

04. Vuex로 상태값 관리하는 SPA 만들기

Vuex로 상태값 관리

  • 뷰티파이로 디자인한 메인페이지에서 입력한 내용을 Vuex에 저장
  • 그 값을 서브페이지에서 전달받아 관리
728x90

+ Recent posts