React.JS - master class

SOO·2022년 10월 4일

basic

목록 보기
4/16

Why Styled Components

style components를 쓰면 모든 style은 컴포넌트를 사용하기 전에 미리 컴포넌트에 포함될거임

import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;
const BoxOne = styled.div`
  background-color: teal;
  width: 100px;
  height: 100px;
`;
const BoxTwo = styled.div`
  background-color: tomato;
  width: 100px;
  height: 100px;
`;

const Text = styled.span`
  color: white;
`;
function App() {
  return (
    <Father>
      <BoxOne>
        <Text>Hello</Text>
      </BoxOne>
      <BoxTwo />
    </Father>
  );
}

export default App;

유효한 태그만 가능
백틱 사이에 들어가는건 css여야함!

react font

index.html에

<link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400&display=swap"
    />

Adapting and Extending

import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;
const Box = styled.div`
  background-color: ${(props) => props.bgColor};
  width: 100px;
  height: 100px;
`;

function App() {
  return (
    <Father>
      <Box bgColor="teal" />
      <Box bgColor="tomato" />
    </Father>
  );
}

export default App;

중복코드 활용하기

import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;
const Box = styled.div`
  background-color: ${(props) => props.bgColor};
  width: 100px;
  height: 100px;
`;

const Circle = styled(Box)`
  border-radius: 50px;
`;

function App() {
  return (
    <Father>
      <Box bgColor="teal" />
      <Circle bgColor="tomato" />
    </Father>
  );
}

export default App;

컴포넌트 확장하기

'As' and Attrs

As

const Btn = styled.button`
  color: white;
  background-color: tomato;
  border: 0;
  border-radius: 15px;
`;
<Btn>Log in</Btn>
<Btn as="a" href="/">
        Log in
</Btn>

하면

<a href="/" class="sc-fEXmlR eQdzdV">Log in</a>

일케된다 html 요소가 바뀜 그치만 style은 공유하는

Attrs

const Input = styled.input.attrs({ required: true })`
  background-color: tomato;
`;

후에 input으로 전달될 모든 속성을 가진 오브젝트를 담을 수 있음

Quiz

Can styled components receive custom props? Yes.

If I send a textColor prop to my styled component, how can I use it on the styles?

color: ${props => props.textColor};

How can I extend a styled component?

const B = styled(A)``;

What does the ‘as’ prop do? It changes the component's resulting HTML tag

Is doing this:

span {
    color:blue;
    &:hover {
        color:red;
    }
}

the same as:

span {
    color:blue;
}
span:hover {
    color:red;
}

Yes.

Animations and Pseudo Selectors

스타일 컴포넌트에서는 keyframes helper를 사용시 앱 전체에서 사용할 수 있는 고유한 인스턴스를 생성 (다른 파일에서 같은 이름의 keyframes가 존재하더라도 이름 충돌이 나지 않도록 해줌)

import styled, { keyframes } from "styled-components";

const Wrapper = styled.div`
  display: flex;
`;

const rotationAnimation = keyframes`
  0% {
    transform:rotate(0deg);
    border-radius:0px;
  }
  50% {
    border-radius:100px;
  }
  100%{
    transform:rotate(360deg);
    border-radius:0px;
  }
`;

const Box = styled.div`
  height: 200px;
  width: 200px;
  background-color: tomato;
  display: flex;
  justify-content: center;
  align-items: center;
  animation: ${rotationAnimation} 1s linear infinite;
  span {
    font-size: 36px;
    &:hover {
      font-size: 48px;
    }
    &:active {
      opacity: 0;
    }
  }
`;

function App() {
  return (
    <Wrapper>
      <Box>
        <span>🤩</span>
      </Box>
    </Wrapper>
  );
}

export default App;

component안에 element 선택 가능

import styled, { keyframes } from "styled-components";

const Wrapper = styled.div`
  display: flex;
`;

const rotationAnimation = keyframes`
  0% {
    transform:rotate(0deg);
    border-radius:0px;
  }
  50% {
    border-radius:100px;
  }
  100%{
    transform:rotate(360deg);
    border-radius:0px;
  }
`;

const Emoji = styled.span`
  font-size: 36px;
`;

const Box = styled.div`
  height: 200px;
  width: 200px;
  background-color: tomato;
  display: flex;
  justify-content: center;
  align-items: center;
  animation: ${rotationAnimation} 1s linear infinite;
  ${Emoji}:hover {
    font-size: 98px;
  }
`;

function App() {
  return (
    <Wrapper>
      <Box>
        <Emoji>🤩</Emoji>
      </Box>
      <Emoji>😡</Emoji>
    </Wrapper>
  );
}

export default App;

tag name(span이나 p)에 의존하지 않고 싶다면 우리만의 이모티콘인 styled component를 만들어줄거임

Themes

theme: 기본적으로 모든 색상들을 가지고 있는 object
모든 색깔을 하나의 object안에 넣어놨기 떄문에 매우매우 유용함
왜냐면 나중에 색깔을 바꿀 때 그냥 그 object만 바꿔주면 되거든
component의 색을 일일이 바꾸는게 아니라 말야

import React from "react";
import ReactDOM from "react-dom/client";
import { ThemeProvider } from "styled-components";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
const darkTheme = {
  textColor: "whitesmoke",
  backgroundColor: "#111",
};

const lightTheme = {
  textColor: "#111",
  backgroundColor: "#whitesmoke",
};

root.render(
  <React.StrictMode>
    <ThemeProvider theme={darkTheme}>
      <App />
    </ThemeProvider>
  </React.StrictMode>
);
import styled, { keyframes } from "styled-components";

const Title = styled.h1`
  color: ${(props) => props.theme.textColor};
`;

const Wrapper = styled.div`
  display: flex;
  height: 100vh;
  width: 100vw;
  justify-content: center;
  align-items: center;
  background-color: ${(props) => props.theme.backgroundColor};
`;

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

export default App;

object를 themeProvider에 전달하고 ThemeProvider안에 있는 모든 component들은 이 objcet에 접근할 수 있게 됨

dark/light mode를 가지고 싶다면 property들의 이름이 똑같아야함!

TypeScript

js를 기반으로 한 프로그래밍 언어
strongly-typed; 프로그래밍 언어가 작동하기 전에 type을 확인한다는 거임
typeScript는 프로그램을 돌리기도 전에 뭐가 잘못됐다고 말해주는거임
TypeScript는 브라우저가 이해 못함

Typescript 플레이그라운드 (타입스크립트 테스트)
https://www.typescriptlang.org/play

@types란 아주 큰 github repository, 모든 유명한 npm 라이브러리를 가지고 있는 저장소임

DefinitelyTyped

npx create-react-app 내 앱 이름 --template typescript
npm i --save-dev @types/styled-components
npm i styled-components

Typing the Props

interface: 객체모양을 TypeScript에게 설명해주는 TypeScript의 개념

interface안에다가 TypeScript에게 object shape을 설명해줄거임

interface CircleProps {
  bgColor: string;
}

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

CircleProps의 타입이 뭔지 component에게 말해줘야함
bgColor의 타입은 CircleProps의 object이다라고 말해주고 있는거임

interface ContainerProps {
  bgColor: string;
}

TypeScript에게 bgColor를 styled-component에게도 보내고 싶다고 말할거임

const Container = styled.div<ContainerProps>``;

TypeScript에게 Container가 bgColor를 받을거라 얘기하면 이렇게 ContainerProps를 붙여주면 됨.

interface는 typescript와 코드가 실행되기 전에 확인해준다는거임

ex)

const sayHello=(playerObj)=>`Hello`;
interface PlayerShape{
name:string;
age:number;}

const sayHello=(playerObj:PlayerShape)=>`Hello ${playerObj.name} you are ${playerObj.age} years old.`;

sayHello({name:"nico", age:12})

Optional Props

  borderColor?: string;

? (optional)

 bgColor={bgColor} borderColor={borderColor ?? bgColor}

borderColor는 사용자가 만든 borderColor값이며, 만약 undefined 된 상태라면 값으로 bgColor을 보낸다 !

?? (Null 병합 연산자 (Nullish coalescing operator))
??앞에 값이 null이거나 undefined이면 오른쪽 값을, 그렇지 않으면 왼쪽 값을 반환하는 논리연산자

State

useState < number > ( )

state의 type을 지정하려면 Generics안에 타입을 지정
일반적으로는 초기값을 지정하면 타입스크립트가 자동으로 타입을 유추하기 때문에 굳이 지정해주지 않아도 되지만 상태가 undefined또는 null이 될 수도 있거나 객체 또는 배열일 때는 지정해주는 것이 조음

ex) const [ value, setValue ] = useState< Value | null >(null);

typeScript를 쓰지 않았더라도, default값으로 여러분이 어떤 타입을 쓸건지 알거임ㅇㅇ

const [value, setValue] = useState(0);이라 쓴면 알아서 const value: number이라고 알고
setValue(2)를 했을 때 오류가 생기지 않음 근데 만약 setValue("dd")라고 하면 오류 발생!

Forms

any는 typescript의 type임
우린 any type을 배제하려고 노력해야함

ReactJS Typescript 사람들은 currentTarget 사용을 택함

const {
currentTarget: { value },
} = event;

의미

event안 curentTarget안에 value의 값을 기존 이름 그대로 value 라는 변수 만들기

const value = event.currentTarget.value;
const tagName = event.currentTarget.tagName;
const width = event.currentTarget.width;
const id = event.currentTarget.id;

->

const {
currentTarget: {value, tagName, width, id}
} = event;
 const onChange = (event: React.FormEvent<HTMLInputElement>) => {
    const {
      currentTarget: { value },
    } = event;
    setValue(value);
  };

Themes

내 테마가 어케 보일지 설명하는 부분

ThemeProvider는 하나의 컴포넌트. styled-components로부터 오는.

Quiz

Typescript and Javascript are the same programming language. No.

Javascript is strongly typed. No.

This is valid Typescript syntax.

const user = {
  firstName: "Angela",
  lastName: "Davis",
  role: "Professor",
}
console.log(user.name)

This is valid Typescript syntax.

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

No.

Modern browsers like Google Chrome understand Typescript code. No.

Why did we install types/styled-components? To teach Typescript the types of styled-components

DefinitelyTyped has the type definitions of all npm packages. No.

Using PropTypes and Typescript to check props is the same.
No. Typescript checks before the code runs.

Why do we use interfaces?
To explain to Typescript the type of the properties on an object.

In this interface, how can I make the property fontSize optional?

interface A {
    color:string;
    fontSize:string;
}

fontSize?:string;

Does color?:string has the same result as color: string | undefined; on Typescript? Yes.

Typescript will complain about this code:

const [value, setValue] = useState(true);
setValue("hello")

Yes.

Styles

createGlobalStyle

한 컴포넌트를 만들 수 있게 해주는데, 렌더링 될 때 그 컴포넌트는 전역 스코프에 스타일들을 올려줌

<>
우리에게 부모 없이 서로 붙어있는 많은 것들을 리턴할 수 있게 해줌

Home Part

max width와 margin o auto도 추가했음 -> 화면을 크게 했을 때도 모바일화면처럼 가운데에 위치하게됨.

useEffect function을 사용하면 너의 코드가 component의 시작할 때 실행될 지, 아니면 component가 끝날 때 실행될 지, 그것도 아니면 뭐든 변화가 일어날 때마다 실행할 지 고를 수 있음

Nested Routes

useMatch()
React Router 5버전 => 6버전
useRouteMatch() => useMatch()

useRouteMatch는 너가 특정한 URL에 있는지의 여부를 알려주게 될거임

<Tab isActive={chartMatch !== null}>

우리가 찾고 있는 URL에 들어와 있다면 우리는 object를 받고, 그렇지 않다면 null을 받게 될거임

React Query

우리는 API를 fetch하고 json을 return하는 함수를 만들었음
useQuery라고 불리는 hook을 사용할거임

useQuery는 2가지 argument를 필요로함

첫번째는 query key.
query의 고유 식별자
우리의 경우 이걸 allCoins라고 부름

두번ㄴ째 argument는 fetcher함수인데 fetcher 함수는 여기 fetchCoins로 존재하고 있음

export function fetchCoins() {
    return fetch("https://api.coinpaprika.com/v1/coins").then((response) =>
      response.json()
    );
  }

useQuery의 멋진 점 중 하나는 isLoading이라고 불리는 boolean 값을 return하는데
isLoading은 true 혹은 false인 boolean인데

const [loading, setLoading] = useState(true);
setLoading(false);

를 대체

reactquery가 멋진게 useQuery라는 hook이 fetcher함수 fetchCoins를 불러오고

그리고 fecther 함수가 isLoading이라면 react query가 말해줄거임

fecthCoins는 json을 return 했음
fetchCoins가 끝나면 react query는 그 함수의 데이터를 여기 data에 넣어줄거임

정리
useQuery hook은 너의 fetcher 함수를 부르고, fetcher함수가 loading 중이라면 react query는 여기서 그걸 알려줄거임

const { isLoading, data } = useQuery<ICoin[]>("allCoins", fetchCoins);

useQuery는 fetcehr 함수를 부르고 fetcher함수가 끝나면 react query는 이 json을 여기에 넣을거임!

Loading.. 뜨자나
이건 내가 비트코인화면이 들어갈 때마다 API에 접근한다는 것을 의미함
비트화면에서 돌아올 때는 Loading이 발생하지 않는 걸 볼 수 있음

이건 fetching을 하는 Coin screen과 React query가 하는 것이 다르기 때문에 발생하는거임
React Query는 이 response를 caching하고 있음

React Query는 API로부터 response를 받고 있어서 우리가 화면을 바꿨다가 돌아오더라도 react query는 우리가 원하는 data가 이미 캐시에 있다는 것을 알고 있음

그래서 React Query는 API에 접근하지 않음
이게 React Query를 사용하느냐 안 하느냐의 아주 큰 차이임

정리

react query는 기본적으로 fetcher함수와 연결시켜서
isLoading 같은 함수가 불렸는지 아닌지를 우리에게 알려줌
그리고 함수가 끝났을 때는 결과 값을 여기에 넣어줘서 그 data에 아주 쉬운 방법으로 접근할 수 있게 도와줌

react query는 아주 강력한 캐싱 매케니즘을 갖고 있음
만약 네 query의 고유한 key값을 react query에 넘겨 주었다면 react query는 유저에게 Loading을 다시 보여주지 말라고 알게 됨

react query는 이미 캐시에 어떠한 데이터가 있다는 걸 알고 있으니까
react query는 우리가 지난 강의에서 설치했던 ReactQueryDevtools라는 걸 갖고 있는데
여기 보여지는게 ReactQueryDevtools인데 캐시에 어떤 query가 있는지 보여주고 결과가 무엇인지도 보여주고 ~

key는 react query 캐시 시스템에서 저장되고 작동하기 위해서 고유한 값이어야 해
array를 만들어서 첫번째 key가 카테고리의 역할을 하도록 하고, 두번째 key가 우리의 URL에 있는 coinId가 되어서 고유한 부분이 되도록 했음

그리고 우리는 2가지의 query를 가지고 있기 떄문에 useQuery의 return 값이 같았음
isLoading 과 data

하지만 우리는 2개의 query를 가지고 있기 때문에 이름을 바꿔야 했음

Price Chart

apex chart
: npm install --save react-apexcharts apexcharts

Dark Mode

단순 react js로 state management를 할때 생기는 불편함:
App에서 컴포넌트로 내려가는 계층구조로 인해서 state와 state manipulation을 일일이 각 component들에 전달해줘야함. (App-Router-Coins // App-Router-Coin-Chart)

Recoil은 state를 따로 buble에 담아서 각 컴포넌트들이 필요할때 buble에 접속해서 사용할 수 있게 함. (App -> (isDark)

단순 react js로 state management를 할때 생기는 불편함:
App에서 컴포넌트로 내려가는 계층구조로 인해서 state와 state manipulation을 일일이 각 component들에 전달해줘야함. (App-Router-Coins // App-Router-Coin-Chart)

Recoil은 state를 따로 buble에 담아서 각 컴포넌트들이 필요할때 buble에 접속해서 사용할 수 있게 함. (App -> (isDark)

Recoil

atom은 두 가지를 요구함
첫 번째는 key, 유일해야함
그리고 기본값 필요

atom은 딱 하나 있는데, 그 기본값은 true이고 우리의 어플리케이션에 연결되어있음
chart도 이 atom에 연결되어있음

atom 형성

export const isDarkAtom = atom({ key: "isDark", default: true });

한 component가 atom의 value를 감지하고 있음

const isDark = useRecoilValue(isDarkAtom);

다른 compoenet도 atom의 value를 감지하고 있음

const isDark = useRecoilValue(isDarkAtom);

useRecoilValue(state)

Recoil state값을 반환합니다.
이 hook은 암묵적으로 주어진 상태에 컴포넌트를 구독합니다.
이 hook는 읽기 전용 상태와 쓰기 가능 상태에서 모두 동작하므로 컴포넌트가 상태를 읽을 수만 있게 하고 싶을 때에 추천하는 hook입니다. 이 hook을 React 컴포넌트에서 사용하면 상태가 업데이트 될 때 리렌더링을 하도록 컴포넌트를 구독합니다.
ex) const names = useRecoilValue(namesState);

https://recoiljs.org/ko/docs/api-reference/core/useRecoilValue/

useSetRecoilState(state)

Recoil state의 값을 업데이트하기 위한 setter 함수를 반환합니다.
상태를 변경하기 위해 비동기로 사용될 수 있는 setter 함수를 리턴합니다.
setter는 새로운 값이나 이전 값을 인수로 받는 updater 함수를 넘겨줍니다.
ex) const setNamesState = useSetRecoilState(namesState);

https://recoiljs.org/ko/docs/api-reference/core/useSetRecoilState/

RECAP

atom은 우리 앱의 파편들

  1. atoms들의 값을 받아올 수 있음
    app에서도 isDarkAtom의 값을 받아오고, Chart에서도 isDarkAtom의 값을 받아왔음
    -> atoms와 연결하고 atoms의 값들을 반영하기 위해서임

atom의 값을 얻으려 할 때는 useRecoilValue를 썼음

const isDark = useRecoilValue(isDarkAtom);

이게 너가 써야할 함수고, 이 함수의 매개변수로는 atom을 넣음

  1. atoms를 수정하는 법
    두 가지 다른 함수를 씀
    atom의 값을 얻으려 할 때는 useRecoilValue를 썼음
const isDark = useRecoilValue(isDarkAtom);

이게 너가 써야할 함수고, 이 함수의 매개변수로는 atom을 넣음

useSetRecoilState

const setDarkAtom = useSetRecoilState(isDarkAtom);

매개변수로 atom을 받고, atom을 변경하는 함수를 반환할거임
atom의 값을 변경

TO-DO-APP

to-do-setUp

Forms

React Hook Form

import { useForm } from "react-hook-form";
const {register, watch} = useForm();

register 함수를 사용하면 여기 onChange evnet handler가 필요 없음

<input {...register("email")} placeholder="Email" />

우리는 register함수가 반환하는 객체를 가져다가 input에 prop로 주는거임

useForm함수는 많은 것들을 제공하는데, 그 중 하나는 watch

watch는 너가 form의 입력 값들의 변화를 관찰할 수 있게 해주는 함수임
console.log(watch())

Form Validation

handleSubmit이 바로 validation, preventDefault 를 담당하게 될거임
그리고 나서 handleSubmit은 우리가 작성한 코드가 진행될 수 있게 해줄거임

handleSubmit도 useForm hook에서 제공하는 함수

모든 validation을 마쳤을 때 호출되는 것

const onValid=()=>{}

onSubmit event안에 handleSubmit을 호출하고, handleSubmit은 2개의 인자를 받아

하나는 데이터가 유효할 때 호출되는 함수, 또 다른 하나는 데이터가 유효하지 않을 때 호출되는 함수

onValid는 필수 ㅇㅇ onInvalid는 필수 ㄴㄴ

onSubmit={handleSubmit(onValid)}

유저가 실제로 submit을 하면, handleSubmit은 해야하는 모든 validation이나, 다른 일들을 전부 끝마친 후에 우리의 데이터가 유효할 때만 우리의 함수를 호출할거임

 <input
          {...register("lastName", { required: true })}
          placeholder="Last Name"
        />

react-hook-form은 입력하지 않은곳에 커서를 옮겨죽기도 함

formstate

console.log(formState.errors);

validation과 error 표시를 자동으로 해줌

Form Errors

 pattern: {
              value: /^[A-Za-z0-9._%+-]+@naver.com$/,
              message: "Only naver.com emails allowed",
            },

또 다른 validation 방법이 있는데 그건 바로 정규식 사용(RegExp)

  <input
          {...register("password1", {
            required: "Password is required",
            minLength: {
              value: 5,
              message: "Your password is too short.",
            },
          })}
          placeholder="Password1"
        />
        <span>{errors?.password1?.message}</span>

Custom Validation

errors?.항목이름?.message
?를 붙이면 그 항목이 undefined면 그 뒤를 실행하지 않음

<span>{errors?.extraError?.message}</span>

errors와 extraError가 존재할 때만 message를 찾아볼거임

validate:(value)=>"hello",

react-hook-form에서 문자열을 리턴하면 그건 즉 너가 에러 메시지를 리턴한다는 뜻임

validate: {
              noSoo: (value) =>
                value.includes("soo") ? "no soo allowed" : true,

내가 원하는 규칙으로 검사하기

Recap

setValue: (name: string, value: unknown, config?: Object) => void
필드 값을 업데이트
이 함수을 사용하면 등록된 필드의 값을 동적으로 설정하고 form state를 확인하고 업데이트하는 옵션을 가질 수 있습니다. 동시에 불필요한 rerender를 피하려고 합니다.
 const { register, handleSubmit, setValue } = useForm<IForm>();
  const handleValid = (data: IForm) => {
    console.log("add to do", data.toDo);
    setValue("toDo", "");
  };

data가 유효하다면 setValue로 값 다시 설정

{shouldFocus: true}
sholdFoucs라는 건 사용자가 form을 submit할 때 에러를 발생시키면,
커서를 해당 input에 focus시켜주는거임

Add To Do

const[value,modFn]= useRecoilState(toDoState);

반환되는 배열의 첫 번째 항목은 데이터의 value, 두번째는 value를 변경하기 위해 사용되는 함수

value만 가져오고 싶다면 useRecoilValue를 value를 바꾸고싶다면 useSetRecoilState를 사용!

interface IToDo{
text:string;
category:"TO_DO"|"DOING"|"DONE"}

이제 우리가 ToDo를 만들면 이건 명시된 3개 중 하나의 string만을 가져야함

Categories

function ToDo({ text, category, id }: IToDo) {
  const setToDos = useSetRecoilState(toDoState);
  const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
    const {
      currentTarget: { name },
    } = event;
  };

event를 통해서 버튼의 name 받아올 수 있음

{category !== "DOING" && (
        <button name="DOING" onClick={onClick}>
          Doing
        </button>
      )}

Immutability

[...front,"감",...back]
...은 front안에 있는 모든 원소를 푼다

const food = ["pizza","mango","kimchi","kimbab"]
[...food.slice(0,target),"감",...food.slice(target+1)]

['pizza','감','kimmchi','kimbab']

Selectors

selector는 atom의 output을 변형시키는 도구

xport const toDoSelector = selector({
  key: "toDoSelector",
  get: ({ get }) => {
    const toDos = get(toDoState);

get function이 있어야 atom을 받을 수 있음

Selector는 파생된 state(derived state)의 일부를 나타낸다.
즉, 기존 state를 가져와서, 기존 state를 이용해 새로운 state를 만들어서 반환할 수 있다. 기존 state를 이용만할 뿐 변형시키지 않는다. derived state는 다른 데이터에 의존하는 동적인 데이터를 만들 수 있기 때문에 강력한 개념이다.

const filteredTodoListState = selector({
key: 'filteredTodoListState',
get: ({get}) => {
const filter = get(todoListFilterState);
const list = get(todoListState);

switch (filter) {
case 'Show Completed':
return list.filter((item) => item.isComplete);
case 'Show Uncompleted':
return list.filter((item) => !item.isComplete);
default:
return list;
}
},
});

filteredTodoListState는 내부적으로 2개의 의존성 todoListFilterState와 todoListState을 추적한다. 그래서 둘 중 하나라도 변하면 filteredTodoListState는 재 실행된다.

Enums

열거형은 TypeScript가 제공하는 기능 중 하나
enum은 열거형으로 이름이 있는 상수들의 집합을 정의할 수 있고,
열거형을 사용하면 의도를 문서화 하거나 구분되는 사례 집합을 더 쉽게 만들 수 있음
TypeScript는 숫자와 문자열-기반 열거형을 제공

*숫자형 enum
"TO_DO" ,
"DOING" ,
"DONE" ,

setCategoryList(+event.currentTarget.value as Categories);

value={0}
value={1}
value={2}

*문자형 enum
"TO_DO" = "TO_DO",
"DOING" = "DOING",
"DONE" = "DONE",

setCategoryList(event.currentTarget.value as Categories)

value={"TO_DO"}
value={"DOING"}
value={"DONE"}

Get Selectors

selector는 get 함수를 가지고 있고, get함수는 첫번째 인자로 option이라는 object를 가져오는데, 우리가 중괄호로 열면 get이라는 함수를 다시 불러 state를 가져올 수 있는데 그러면 우리가 여기서 어떤 값을 return하던지 간에 그 값은 hourSelector의 값이 될거임

export const hourSelector = selector({
key:"hours",
get:({get})=>{
	}
})

만약 selector 안에서 atom에 접근하고 싶다면 여기 get함수를 사용하면 되는데 이 함수는 atom의 값을 가져오게끔 하는 함수임

export const minuteState = atom({
  key: "minutes",
  default: 0,
});

export const hourSelector = selector({
  key: "hours",
  const minutes = get(minuteState);
    return minutes / 60;
  },
});

Set Selectors

set은 state를 set하는 것을 도와주는 속성
get 함수에서도 우리는 get함수로 접근할 수 있었음

set함수는 첫 번째 argument로 option이라는 object를 주는데 그건 setRecoilState를 가지고 있고 이건 우리가 원하던 바
두번째 argument는 우리가 보내는 새로운 값을 주고 있음

recoilState는 결과로 array를 주는데 array의 첫번째 item은 atom의 값이고
두번째 argument는 atom을 수정하는 함수

trello

Drag and Drop part One

drag-and-drop context는 기본적으로 드래그 앤 드롭을 가능하게 하고 싶은 앱의 한 부분

onDragEnd라는 함수는 유저가 드래그를 끝낸 시점에 불려지는 함수
그럼 여기에 onDragEnd를 두고, onDragEnd라는 함수를 만들어

DragDropContext는 children이라는게 필요로함

Children은 그냥 여기 있는 요소 중 하나

const onDragEnd = () => {};
  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <div></div>
    </DragDropContext>
  );

Droppable은 droppableld라고 불리는 prop을 필요로 함 그리고 children을 필요로함
근데 droppable의 children은 함수여야함

<Droppable droppableId="one">{() => <ul></ul>}</Droppable>

이렇게 해야하는 이유는 Droppable안에 component를 넣으면 바로 사용할 수 있는 무언가를 얻음

<Draggable draggableId="first" index={0}>
                {() => <li>One</li>}
              </Draggable>

draggableprovided에서 draggbleProps는 전체화면을 드래그,, dragHandleProps는 원하는 곳만 드래그에서 옮기고 싶을때,,

{(magic) => (
            <ul ref={magic.innerRef} {...magic.droppableProps}>
              <Draggable draggableId="first" index={0}>
                {(magic) => (
                  <li ref={magic.innerRef} {...magic.draggableProps}>
                    <span {...magic.dragHandleProps}>🔥</span>
                    One
                  </li>
                )}

style

{magic.placeholder}

Draggable 엘리먼트를 드래그하는 동안 position: fixed(영역을 고정시킴)를 적용
Draggable을 드래그할 때 Droppable 리스트가 작아지는 것을 방지
Draggable 노드의 형제로 렌더링하는 것이 좋음

Recording

atom의 값을 가져오고 싶으면 useRecoilValue 사용하면 됨
근데 atom의 값 뿐만 아니라 atom을 수정하는 함수까지 부르고 싶다면 useRecoilState를 써야함

onDragEnd; 함수 드래그가 끝났을 때 실행되는 함수

onDragEnd는 어떤 일이 일어났는지에 대한 정보로 많은 argument를 준다는거임

const onDragEnd =(args:any)=>{console.log(args)};

드래그 앤 드롭을 할 때마다 그게 어디서 왔고 어디로 가는지 알려줌

const onDragEnd = ({ destination, source }: DropResult) => {};

splice(start,deletecount, ...items )
ex)x.splice(0,1) splice는 index 0으로 가서 1개의 item을 삭제할거임
ex)x.splice(3,0,"a"); index 3에서 시작해서 아무것도 지우지 않고 a를 집어넣음

setToDos((oldToDos) => {
      const toDosCopy = [...oldToDos];
 // 1) Delete item on source.index
toDosCopy.splice(source.index, 1);
// 2) Put back the item on the destination.index
toDosCopy.splice(destination?.index, 0, draggableId);
return toDosCopy;
    });

우리 예전의 oldToDos를 복사해서 source.index에 있는 item을 삭제하고(움직이고 있는 item을 삭제하고) 그 item을 다시 item이 도착한 지점에 넣어주기

 const onDragEnd = ({ draggableId, destination, source }: DropResult) => {
    if (!destination) return;
    setToDos((oldToDos) => {
      const toDosCopy = [...oldToDos];
      // 1) Delete item on source.index
      console.log("Delete item on", source.index);
      console.log(toDosCopy);
      toDosCopy.splice(source.index, 1);
      console.log("Deleted item");
      console.log(toDosCopy);
      // 2) Put back the item on the destination.index
      console.log("Put back", draggableId, "on ", destination.index);
      toDosCopy.splice(destination?.index, 0, draggableId);
      console.log(toDosCopy);
      return toDosCopy;
    });
  };
<Draggable key={toDo} draggableId={toDo} index={index}>

< Draggable /> list의 키
< Draggable /> list를 렌더링하는 경우 각 < Draggable />에 key prop을 추가

key랑 draggableId가 무조권 같아야함
key는 list 내에서 고유해야하고, key에 item의 index가 포함되어서는 안 됩니다. (map의 index사용 X) 일반적으로 draggableId를 key로 사용하면 됨.
주의! list에 key가 없으면 React가 경고하지만 index를 key로 사용하는 경우 경고하지 않음

return items.map((item, index) => (
< Draggable
// adding a key is important!
key={item.id}
draggableId={item.id}
index={index}
>

Performance

지금문제점: react.js에서 component의 State가 변하면 해당 component의 모든 children은 다시 렌더링 돼

a는 바뀌지않고 e랑 f만 바뀌었는데도! rendering 됨!

왤까? Droppable,Board, DrapDropContext등 부모 State가 바뀌면 우리의 Card는 다시 렌더링 될거임
state가 바뀌면 모든ㄷ게 change됨!

export default React.memo(DragabbleCard);

react memo는 react.js한테 prop이 바뀌지 않는다면 컴포넌트(여기선 DragabbleCard)를 렌더링 하지 말라고 함

component들의 prop이 바뀌었으면 걔네만 그 item들만 다시 rendering되는것

Multi Boards

interface IToDoState{
[key: string]: string[];
}

이 state는 string으로써의 property와 string array로 이루어져있다고

Object.keys(obj)

Object.keys() 메소드는 주어진 객체의 속성 이름들을 일반적인 반복문과 동일한 순서로 순회되는 열거할 수 있는 배열로 반환합니다.
ex) Object.keys(obj).map((item)=>obj[item])

const object1 = {
a: 'somestring',
b: 42,
c: false
};
console.log(Object.keys(object1)); // Array ["a", "b", "c"]

Same Board Movement

setToDos((allBoards) => {
        const boardCopy = [...allBoards[source.droppableId]];
        boardCopy.splice(source.index, 1);
        boardCopy.splice(destination?.index, 0, draggableId);
        return {
          ...allBoards,
          [(source.droppableId)]: boardCopy,
        };
      });

여기서 우리가 하는 건 복사하는거
예를 들어, doing board를 복사해서, 그 복사본을 대체하고 변형하고, 다른 board들을 모두 return하면서 이제 doing은 board의 복사본이라고 말해주는거!

Droppable Snapshot

isDraggingOver은 snapshot에서 나옴

isDraggingOver: boolean
현재 선택한 Draggable이 특정 Droppable위에 드래깅 되고 있는지 여부 확인

draggingOverWith: ?DraggableId
Droppable 위로 드래그하는 Draggable ID

draggingFromThisWith: ?DraggableId
현재 Droppable에서 벗어난 드래깅되고 있는 Draggable ID

isUsingPlaceholder: boolean

Refs

references: 우리의 react 코드를 이용해 HTML 요소를 지정하고, 가져올 수 있는 방법
다시 말해 자바스크립트로부터 HTML 요소를 가져오고 수정하는 방법

const inputRef = useRef<HTMLInputElement>(null);
<input ref={inputRef} placeholder="grab me" />

Creating Tasks

아래쪽으로 생성

return {
        ...allBoards,
        [boardId]: [...allBoards[boardId],newToDo],
      };

맨위로 생성

return {
        ...allBoards,
        [boardId]: [newToDo, ...allBoards[boardId]],
      };

localstorage

npm i recoil-persist

const { persistAtom } = recoilPersist({
key: "todoLocal",
storage: localStorage,
});

effects_UNSTABLE: [persistAtom],

Code challenge

  1. 다크/라이트 토글 버튼
    우선, 다크 / 라이트 테마에 적용될 styled-components theme을 만들어 주어야 합니다. theme.tsx에 아래와 같이 lightTheme과 darkTheme을 생성합니다.

우리는 두 Theme에 대한 타입을 선언 해주어야 합니다. 강의에서 사용된 방식은 Ambient Modules방식입니다. 우선 styled.d.ts 파일을 생성해준 뒤, 아래와 같이 styled를 import하고 DefaultTheme이라는 interface를 선언해줍니다.


createGlobalStyle을 통해 현재의 theme을 전역적으로 적용하도록 합시다. body(혹은 theme을 적용하고자 하는 컴포넌트)의 background와 color에 각각 props.theme.변수명을 연결해줍니다.
다크모드를 확인하기 위한 atom 생성합니다. 우리가 구현하려고 하는 다크모드는 Dark/Light라는 두 개의 테마를 가집니다. 따라서 boolean값을 통해 true인 경우 dark모드, false인 경우 light모드를 적용하시면 됩니다. Atom의 이름은 isDark와 같은 직관적인 이름을 사용하는 것이 좋습니다.
토글 버튼의 onClick에 clickHandler를 달아줍시다. 클릭 이벤트가 발생할 경우, recoil의 state를 반대로 바꾸어주시면 됩니다. ()=>{setIsDark((prev) => !prev)}
ThemeProvider의 theme 프로퍼티 내부에 삼항연산자를 사용해봅시다.theme = { isDark? darkTheme : lightTheme } recoil의 값이 true일 경우 dark모드를, false일 경우 light모드가 적용됩니다.

  1. 홈으로 돌아가는 버튼
    Anchor 대신 Link 태그를 사용하는 것은 그리 어려운 일이 아닙니다.
<Link to={"/"}>Home &rarr;</Link>

다만, 그 이유를 아는 것이 중요하다고 생각됩니다. React는 엄청난 효율성을 자랑합니다. state가 변할 경우, 해당 컴포넌트만을 re-render하기 때문입니다. 반면,

<a>

는 페이지 이동 간에 새로고침을 유발합니다. 새로고침에 발생함에 따라, 모든 컴포넌트는 re-rendering되며 모든 state가 초기화됩니다. 따라서, React를 사용하는 우리는 anchor대신

<Link>

라는 친구에게 익숙해지는 것이 좋습니다.

3.중첩 라우팅(Nested Routing)
중첩 라우팅을 구현하는 방식 크게 Nested Route 방식과 Descendant Routes 방식으로 나뉩니다.
Nested Route 방식(V6)은 하나의 Switch(또는 Routes) 안의 Route 내부에 또 다른 Route를 넣음으로써 모든 Route의 경로를 사전에 구성하는 방식입니다. 해당 방식은

<Outlet />

을 이용해 컴포넌트를 렌더링 할 위치를 결정합니다.
한편, 수업에서는 Descendant Route 방식을 채택합니다. 해당 방식은 하위 Route가 없는 Route들로 구성된 여러 Switch(또는 Routes)를 생성한 뒤, Route의 컴포넌트 내부에 다시 Switch(Routes)를 할당하는 방식입니다. 대신 Route의 하위 컴포넌트에 또 다른 Switch(Routes)를 사용하기 위해, 상위 Route의 path의 마지막에 “/*”를 명시해주어야 합니다.

4.ApexChart(CandleStick)
CandleStick은 아래의 형식대로 데이터를 받습니다.

series 내부에서 데이터를 가공하기엔 너무 복잡해지는 경향이 있기 때문에, 컴포넌트 외부에서 data를 가공 및 예외 처리를 진행한 뒤, 전달해주도록 합시다.

예외 처리를 위해, 널 병합 연산자와 옵셔널 체이닝을 사용합니다. “??”라는 연산자는 널 병합 연산자(Nullish Coalescing Operator)라고 합니다.
데이터 가공을 완료했다면, 아래와 같이 넣어주시면 됩니다. :)

code challenge

  1. 카테고리 추가

우선 Board를 추가할 수 있는 input과 form을 생성해줍니다. Form은 input의 부모 컴포넌트여야 합니다. 이후, react-hook-form의 register을 적용하기 위해, input의 프로퍼티에 register을 스프레드 연산자로 전달해줍시다.
<AddBoardInput {...register("add")} />
onValid 콜백함수를 작성해줍시다. 해당 함수는 onSubmit 이벤트가 발생할 때, 사용됩니다. 해당 콜백함수는 input에 작성된 text를 받아와 Recoil State의 Object의 key값으로, value로는 빈 Array를 할당해주어야 합니다. 또한, input의 값이 없는 경우(값을 입력하지 않고 submit을 한 경우) if문으로 기본 예외 처리를 해주도록 합시다.

유효한 값이 들어왔다면, Object의 값을 수정하기 위해서, 아래의 코드와 같이 spread 연산자를 사용합니다. spread 연산자를 사용해 이전 Object 내부의 값을 복사하여 새 Object를 만들고, 새롭게 추가할 key값과 value 값을 추가해줍시다. Input으로 받은 text는 key값을, value로는 빈 Array를 할당해줍시다.

  1. recoil-persist

npm i recoil-persist
Recoil-persist 설치 후, atoms.tsx에서 import 해줍니다.
import { recoilPersist } from 'recoil-persist'
const { persistAtom } = recoilPersist() 이후 영속성을 부여할 atom 내부에 아래의 속성을 추가해줍니다.
effects_UNSTABLE: [persistAtom],

GATSBY

Framework vs Library

gatsby는 리액트 기반의 프레임워크
gatsby는 리액트를 이용하면서도, gatsby는 리액트에 아주 약간의 복잡함을 더해주고 있음
리액트에 새로운 규칙 몇 가지를 추가하는거

라이브러리는 우리 개발자들이 불러오는 어떤 대상이야.
우리는 라이브러리를 임포트하기도 하고, 우리가 필요로 할 때 쓰게 돼
라이브러리는 불러오기 될 대상인거야! 어떤 파일에서든 임포트해서 사용하는 어떤거야! 그걸 우린 함수 안에서, 컴포넌트 안에서 임포트해서 사용하기도 해. 어디서인지는 상관 없어. 왜냐면 말그대로 라이브러리니까, 우리가 그냥 사용하면 되는거

반면 프레임워크는 좀 달라!
우리는 프레임워크를 임포트하지도, 불러오지도 않아.
대신에 프레임워크 속에다 우리가 코드를 집어넣으면, 프레임워크가 그 코드를 불러다 줄 거야

개츠비가 켜지면, 네 코드 안에 들어가서 그 코드를 작성할 거고, 불러올 거고,
개츠비의 룰만 따르면, 즉 적합한 디렉토리 속에, 맞는 파일을 파일명까지 맞게 생성하면, 개츠비가 네 코드를 불러와서 우릴 도와주는 프레임워크가 작동하는거야.
개츠비는 임포트 되는 대상이 아니야,,
개츠비를 임포트해서 개츠비가 구동을 시작한다? 그런 일은 일어나지 않아..

0개의 댓글