7/4
오늘은 저번에 배운 git 기초에 대해 간단하게 복습하고, branch, merge 에 대해 배웠다.
솔직히 정확히 알아듣진 못했지만 내가 작성한 코드를 귀찮게 직접 올리지 않고 내가 수정한 기록도 남기면서 전에 했던 것으로 되돌릴 수 있다니 굉장히 매력적인 기능이라고 생각했다.
branch 는 나뭇가지라는 뜻인데 나무의 가지처럼 기능별로 여러 갈래로 나눠서 작성한 코드를 github 에 올릴 수 있다. 그리고 나뉜 branch 를 합쳐서 미리 한번 구동해보는 것을 develop branch 에서 하고 최종적으로 작동하는 branch 를 main 이라고 한다. 
merge 는 각 나뉜 branch 를 합쳐주는 작업을 말한다. 이 과정에서 오류도 많이 나고 문제가 많이 생겨서 보통 주니어 개발자는 하지 않는다고 한다. 확실히 다른 교육생들도 오류가 많이 나서 힘들어 했다. 나는 어찌저찌 강사님의 코드를 잘 따라쳐서 운좋게도 오류가 나지 않았지만 후에 오류가 났을 때 해결하지 못할까봐 걱정이다.
cbp 프로젝트 모임에서는 각 탭에 들어갈 내용과 레퍼런스를 찾았다. 생각보다 정리할 내용도 방대하고 전문적이라 어려움을 겪었다. 주요 타깃도 30~40대나 기업으로 생각해서 깔끔하고 직관성있는 레퍼런스를 찾기 힘들었다. 양이 많아서 그런지 다들 중간마다 늘어지고 힘들어해서 시간이 오래 걸렸다. 오래 걸릴수록 힘들어지긴 마찬가지일텐데 협업할 때는 나 혼자의 힘만으로는 빠르게 진행이 되지 않는다는 것을 깨달았다. 

7/5
오늘은 3-way-merge, switch, rebase, stashing 에 대해서 배웠다.
브랜치를 합칠 때는 두가지 방법이 있는데 merge 와 rebase 가 있다. 그 중 merge 의 종류는 fast-foward 와 3-way-merge 가 있다. 단순히 포인터를 최신 커밋으로 옮기는 것을 fast-forward 방식이라고 하고 3-way-merge는 각 브랜치가 가리키는 커밋 2개와 공통의 조상 1개(base)를 사용하여 새 commit 을 만들어내어 비교하며 병합한다. 즉, 3-way-merge 는 변경된 것은 변경된 사항으로 적용되고 내용이 다 같으면 당연히 그냥 두고 양쪽에서 변경을 하는 것은 conflict 가 뜬다.
여기서 merge 와 rebase 의 차이점은 Merge 의 경우 쉽고 안전하지만 커밋히스토리가 지저분할 수 있다. 반면 Rebase 는 잘 모르고 사용할 경우 위험할 수 있어 까다롭지만 커밋히스토리를 깔끔하게 관리할 수 있다. rebase 는 base를 새롭게 설정한다는 의미로 이해하면 된다고 한다. 예를 들어 main 브랜치와 other 브랜치가 다른 commit 을 가리킬 때 main 브랜치가 가리키는 commit 을 base 를 삼아 비교하여 main 브랜치를 새로운 commit 으로 fast-forward 시키는 것이 rebase 이다. 따라서 다시 가지는 일정한 한 줄로 만들어진다. 다시 말해, merge 는 commit 기록과 merge 기록까지 다 남는 반면 rebase 는 한 가지로 병합되어 기록이 깔끔하다.
switch 는 간단하게 checkout 과 비슷한 명령어이다. checkout 과 똑같이 $ git switch -c newbranch 하면 브랜치가 만들어짐과 동시에 이동한다.
stashing 은 임시 저장을 뜻하는데 잠깐 해당 브랜치를 commit 하지 않고 잠시 나와야 할 때 쓰는 기능이다. 임시 저장소에 넣고, 빼고, 삭제하는 등 여러 기능을 사용할 수 있다.
cbp 프로젝트 모임에서는 어제 이어서 메인과 서브 페이지에 대한 내용정리와 레퍼런스를 계속해서 찾았다. 디자인에 좀 힘을 쓰다보니 진행이 잘 되지 않는 것 같다. 하지만 보다 나은 결과를 위해 각자 노력을 하는 것이니 


7/6
json 파일 계속 만듦

7/7
상담버튼 레퍼런스, 세진님 언니분 깃헙 강의

7/8
아이콘 찾음

7/9
맡은 서브 페이지 xd 파일로 내용, 디자인 얹어보기

1. 포트폴리오 쓰는 팁

바닐라 자스 - 어떤 코드로 어떻게 작성했다고 기술해야 함

리액트 - 기존 것 정리 안된 기능들 정리해야 더 효율적

어떤 걸 썼고 구현했다는 구체적 기술 설명 필요

 

1) 메모장 todo-list

- 로컬 스토리지, lodash 라이브러리, debounce 기능, getset 함수 사용

 

2) 쇼핑몰 페이지

- 장바구니, 로그인, 리덕스, 리덕스 toolkit, 리덕스 persist 사용, 라우팅, params, 리다이렉션

- 사이트처럼 수정해서 만들어보기

 

3) 리덕스 메모장

- json server, get post put delete 구현

 

4) 유튜브 클론

- api, 리덕스 데이터 관리(store)

 

5) 뮤비앱 api 기획해서 포트폴리오에 추가할 것

 

 

 

 

2. 고차 컴포넌트

훅이 아닌 컴포넌트, 컴포넌트를 가져와 새 컴포넌트를 반환해주는 함수

리액트 메모라는 higherOrderComponent 가 있음 조금 더 성능이 좋은 컴포넌트로 반환해주는 것을 고차 컴포넌트라고 함

 

1) react.memo

전체 컴포넌트를 싸서 리턴하면 컴포넌트가 리액트 메모라는 함수의 매개변수로 들어가서 성능이 좀 더 좋게 변경됨

 

2) 메모이제이션

동일한 값을 저장했다가 사용, 복잡한 수학의 문제를 푼 후 다음에 다시 문제를 풀었을 때 푸는 과정 없이 값만 반환, 그래서 성능이 좀 더 좋아짐

 

3) 성능이 느려지는 가장 큰 이유?

- 여러 컴포넌트들이 재 랜더링이 된다는 것이 문제: 다시 그리게 하기 때문에 불필요한 랜더링이 문제가 됨 / 컴포넌트가 재 랜더링되는 것은 컴포넌트가 최초로 마운팅되었을 때, state 값이 변경되었을 때, 상위컴포넌트로부터 새로운 props 를 전달 받았을 때 가 있음

 

4)

- state 선언할 때 선언 위치 : state 를 만들 때 상위에 설계하는 것이 맞지만

- state 값을 어떤 식으로 분할해서 만들 것인지

- map key index 번호로 사용하지 않는다 : index로 하면 컴포넌트들이 리랜더될 수 있음, 중간에 요소 추가되면 index 값이 변경되고 키값도 변경되어 리랜더링됨

 

 

 

 

3. react.memo 라는 고차 컴포넌트를 이용해서 메모이제이션을 하는 방법

1) src > component > index.js

- <UserList /> 컴포넌트 불러들이고 같은 위치에 UserList.js 만들기

- <UserDetail/> 컴포넌트 불러들이고 같은 위치에 UserDetail.js 만들기

 

useMemo 는 메모이제이션 된 값을 반환함

- use Memo(() => comuteExpensiveValue(a,b),[a,b]); → 의존성 값이 변경될 때만 리랜더링

 

reduce( ) 메서드

https://sodevly.github.io/javascript-reduce/ 참고

- 배열 메서드로 평균값을 낼 때 유용하게 사용

- 배열을 순회하면서 각 요소를 특정 함수를 실행한 결과를 누적하여 하나의 결과값을 만들어줌

- 콜백에 들어갈 수 있는 값: 이전 반환값, 처리할 현재요소로 이전 처리값은 첫 인덱스 값, 현재 반환된 값은 다음 인덱스 값

- 누적 결과로 점점 값이 누적 변경되어 처리 가능

 

이 메서드를 이용해 평균값을 내볼 것임

 

usememo 쓰지 않으면 변경될 때마다 불필요한 리랜더링이 생김

- import { useMemo } from 'react';

  // useMemo 사용 : 즉시실행함수 필요없음

  const avg = (function (){

    console.log('평균을 구하는 함수가 호출되었습니다.')

    return users.reduce((result, user)=>{

      return result + user.engScore / users.length

    },0)

  })();

 

 const avg = useMemo ( () => {

    console.log('평균을 구하는 함수가 호출되었습니다.')

    return users.reduce((result, user)=>{

      return result + user.engScore / users.length

    },0)

  },[users]);

→ 이렇게 변경해주면 text 라는 state 를 변경할때는 리랜더 되지 않음

 

1. 주말 숙제 상세페이지 채널 정보 넣기

강사님이 보내준 api 주소 :

https://youtube.googleapis.com/youtube/v3/channels?part=statistics&part=snippet&id=channelId&key=[YOUR_API_KEY]

 

1) api.js

export function channelUrl(channelId){

  return `https://youtube.googleapis.com/youtube/v3/channels?part=statistics&part=snippet&id=${channelId}=${API_KEY}`

}

channelId를 매개변수로 url 하나 export

 

2) VideoItem

- api 주소 정보에 snippet안에 channelId가 있었음 그걸 꺼내서 쿼리값으로 넘겨줄 것임

- navigate(`/watch?id=${id}&channelId=${item.channelId}`) 로 수정 → id 는 비디오 아이디, channelId 는 내가 선택한 비디오의 채널 아이디가 들어가도록

 

3) VideoView

- {id} props 로 받아준 것처럼 상위 컴포넌트인 Watch 에서 channelId 도 쿼리값 받아주기

 

4) Watch

- const channelId=query.get('channelId') 추가

- <VideoView id={id} channelId={channelId} /> → channelId={channelId} 추가해서 VideoView 에 내려주기

 

5) VideoView

- props channelId 추가

- import { channelUrl } from '../../lib/api'; → 채널 정보 받아올 api url import

- 정보 받아서 store 에 저장해서 쓸 것임

 

6) store > videoSlice

export const getChannelInfo = createAsyncThunk(

    "GET_CHANNEL_LIST",

    async(url)=>{

     try{

         const res=await axios.get(url)

         return res.data.items

     }catch(err){

         console.log(err)

     }

    }

 )

getChannelInfo 라는 뭐 하나 더 추가????

- initialState channel 초기값으로 '' 빈스트링 추가

builder.addCase(getChannelInfo.fulfilled,(state,action)=>{

            console.log('비디오채널정보', action.payload)

            state.channel=action.payload;

        })

→ extraReducers 에 추가

 

7) VideoView

import { getChannelInfo } from '../../store/video/videoSlice';

import { useSelector, useDispatch } from 'react-redux/es/exports';

 

const dispatch = useDispatch();

  useEffect(()=>{

    const channelIdInfo = channelUrl(channelId)

    dispatch(getChannelInfo(channelIdInfo))

  },[])

??????

 

- const {channel} =useSelector((state)=>state.channel) 추가

- return 안에 채널 정보 들어갈 곳에

{

        channel && (

          <div className='descriptionContainer'>

            <div className='channel-img'>

              <img src={channel[0].snippet.thumbnails.default.url} alt="" />

            </div>

            <div className='channel-data'>

              <h3 className='channel-title'>

                {channel[0].snippet.title}

              </h3>

              <p className='channel-des'>

                {channel[0].snippet.description}

              </p>

            </div>

          </div>

        )

      }

→ 각 정보 넣어서 마크업

 

 useEffect(()=>{

    const channelIdInfo = channelUrl(channelId)

    dispatch(getChannelInfo(channelIdInfo))

  },[channelId, dispatch])

→ 의존 잡아줘야 상세페이지 목록을 클릭해도 채널정보가 맞게 뜸

 → 동영상 하단에 채널 정보가 맞게 뜸 완성! (스타일링은 알아서)

 

 

 

 

2. 빌딩??? 최적화??? !!!다시!!!

1) 설치

- npm run build

- build 폴더가 생겼을 것임

- 빌딩된 걸 로컬에서 보려면? : npm 사이트 가보면 서버가 없어서 서버를 하나 설치해야함, npm install -g serve

- 서버 실행시키는 방법? serve -s build

- npm start 는 개발자 모드, 빌드는 배포?

- 디플로이 배포?

- git 에 먼저 올리기

- netlify????

- heroku?????: Config Vars 에 환경변수 설정, 키랑 밸류 넣기 .env 에 해준 것 복붙

커넥트 가서 깃이랑 연결 확인하고 디플로이 클릭,  

1. 로딩 추가

1) VideoList

- useEffect 는 컴포넌트를 불러올 때 api 호출, 시간에 딜레이가 생기면 마운팅이 됐을 때 api 를 못 불러오는 경우가 생김 따라서 로딩 화면이 뜨는 게 필요함

- npm react-spinners 라이브러리 설치, npm install --save react-spinners

- npm 사이트에 검색해보면 Demo Page 에 로딩 화면에 뜰 다양한 아이콘 제공, Storybook 에서는 해당 아이콘의 구체적 코드 제공

 

import ClipLoader from "react-spinners/ClipLoader";

 

if(loading){

    return <ClipLoader color={color} loading={loading} cssOverride={override} size={150} />

   }

- return 문 밖에 if 문으로 로딩이 없으면 컨텐츠가 나오고 로딩이 있으면 로딩 컴포넌트가 보이도록

- loading 값은 리덕스로 가져올 것임

- ClipLoader react-spinners 의 기능적인 것이 아니고 모양임 따라서 스토리북에서 원하는 모양을 가져다가 쓰면 됨

- 스토리북에서 가져올 때 control 에서 내가 원하는 값으로 설정해서 코드를 가져올 수도 있음 (색깔, 애니메이션 시간 등)

- 컴포넌트는 따로 클래스 네임을 지정하지 못하기 때문에 인라인 스타일로 줘야 함

- 스토리북에서 cssOverride 가 그것인데 control 에서 raw 해주면 스타일을 지정할 수 있고 상단에 show code 하면 해당 스타일의 코드가 나옴, 복사 붙여넣기

 

2) videoSlice

- initialState 에 기본값으로 loading: true, 넣어놓기

- extraReducers 에다가 fulfilled 말고 값을 판단하고 있는 상태를 추가

builder.addCase(getVideoList.pending,(state,action)=>{

            state.loading=true;

        })

pending 상태에서 loading true

builder.addCase(getVideoList.fulfilled,(state,action)=>{

            state.data=action.payload;

            state.loading=false;

        })

→ 기존에 있던 것 수정, 나오면 loading false

builder.addCase(getVideoList.rejected,(state,action)=>{

            console.log('액션페이로드',action.payload)

            state.loading=true;

        })

rejected 거부됐을 때 loading true

 

3) VideoList

- loading 값 가져오기

- 기존에 있던 데이터 불러온 것과 축약해서 설정

- const {data, loading} = useSelector((state) => state.video)

- 이렇게 하면 잘 뜸

 

 

 

2. 동영상 현재 날짜 기준 몇일 전 올렸는지 날짜 표시

- 가져온 데이터 보면 snippet publishedAt 에 시간이 나오는데 현재 시간과 비교해서 빼고 시분초로 나눠서 표시해줄 것

 

1) VideoItem

- 그냥 <p className='date'>{item.publishedAt}</p> 로 추가하면 2019-06-03T23:00:05Z 이렇게만 나옴

 

2) lib > common.js 생성

- export function convertDate(dateValue) 하나 생성

- const publishedDate = new Date(dateValue); → publish 된 시간의 객체 받아옴

- const currentDate = new Date(); → 현재 시간의 객체 받아옴

- const seconds = (currentDate.getTime()-publishedDate.getTime()) / 1000 → 1970년을 기준으로 지정한 날짜까지의 초를 반환하는 getTime 메서드로 현재시간 빼기 publish 시간

- 따라서 뺀 사이 간격으로 1000 으로 나눠서 초, 60 으로 나눠서 분, 몇으로 나눠서 시 로 만들 것

let result;

if (seconds<10){

  result = `방금 `;

}else if (seconds < 60) {

  result = `${seconds} `;

}else if (seconds < 3600) {

  result =  `${Math.floor(seconds / 60)} `;

}else if (seconds < 86400) {

  result = `${Math.floor(seconds / 3600)}시간 `;

} else if (seconds < 604800) {

  result = `${Math.floor(seconds / 86400)} `;

} else if (seconds < 2592000) {

  result = `${Math.floor(seconds / 604800)} `;

} else if (seconds < 31536000) {

  result = `${Math.floor(seconds / 2592000)} `;

} else {

  result = `${Math.floor(seconds / 31536000)} `;

}

return result;

 

3) VideoItem

- 위에 것 import, import { convertDate } from '../../lib/common';

- 아까 만들었던 <p className='date'>{item.publishedAt}</p> 부분을

- <p className='date'>{convertDate(item.publishedAt)}</p> 로 수정

 

 

 

3. 상세페이지 동영상 목록 일렬로 수정

1) Watch

- videoData 부분을 data 로 바꿔줘서 통일시켜주기

- const {data} = useSelector((state) => state.video);

2) Home

- const display = useSelector((state) => state.video.listLayout) 부분을

- const {listLayout} = useSelector((state) => state.video) 로 수정

- <VideoList display={display} /> 부분을

- {listLayout &&  <VideoList display={listLayout} />} 로 수정

3) Search

- const display = useSelector((state)=>state.video.listLayout); 부분을

- const {listLayout} = useSelector((state)=>state.video.listLayout); 로 수정

- <VideoList display={display} /> 부분을

- {listLayout &&  <VideoList display={listLayout} />} 로 수정

 

4) Watch

- <ul className='watchList' > 클래스 네임에 VideoRowList 추가해서 style 설정

 

 

 

4. 주말 숙제 : 동영상 하단에 채널 정보 뜨도록 설정하기

1) 하는 방법

- youtube.api 사이트로 가서 Reference > video > list

- part 부분에 statistics 라고 적기, + 눌러서 snippet 도 추가

- show 코드

https://youtube.googleapis.com/youtube/v3/videos?part=statistics&part=snippet&chart=mostPopular&maxResults=30®ionCode=kr&key=본인키

- 주소에 복붙하면 statistics 가 있는 걸 볼 수 있음

- 서치가 있을 때만 저 값이 있도록 해줘야 함

- 채널 정보 부분은 channels > list part : snippet, ~ channelId 꼭 추가해서 보면 채널 정보가 포함되어 있음

- 그래서 상세페이지 아래에 넣을 컴포넌트에 위 정보를 뿌려서 나오도록 만들기

1. lib 폴더 > api.js

export const videoUrl = `https://youtube.googleapis.com/youtube/v3/search?part=snippet&chart=mostPopular&maxResults=30&q=css3®ionCode=kr&type=video&key=본인키`

→ 인증키 노출되면 안됨, 나의 고유한 정보라서 해킹되거나 도용될 수 있어서 그 때 생기는 문제는 자기 책임, 따라서 환경 변수로 처리해줘야 함

 

 

 

2. 환경변수 만드는 법

react 공식 문서에 환경변수 설정 부분 있는데 못 찾음 나중에 첨부해주신다고 함

- 어떤 폴더 안에 말고 아예 밖에 .env 파일 만들기

- API_KEY=내 키값노드에서 사용하는 키값

- REACT_APP_API_KEY=내 키값 리액트에서 사용하는 키값

- 둘 다 띄어쓰기 안됨, 띄어쓰기 까지 환경 변수로 인식

- api.js 로 가서 const API_KEY=process.env.REACT_APP_API_KEY → import 해줄 필요 없고 이렇게 넣어주면 불러온 거임

- 그리고 위에 videoUrl 변수 지정해줬던 주소에 있는 키값 없애고 ${API_KEY} 이렇게 넣어주면 키 값이 들어가는 거임

- videolist 로 가서 import { videoUrl } from '../../lib/api'; 해주고 dispatch(getVideoList(videoUrl)) 로 넣어주면 쿼리값으로 넘어감

- 환경변수 생성 후에는 꼭 서버 껐다가 다시 동작해야 적용이 됨

 

- searchform 부분의 api 도 가져와서 환경변수 처리해주기 ( url 안에 변수가 있음 )

export function searchUrl(input){

  return `https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=30&q=${input}&regionCode=kr&type=video&key=${API_KEY}`

}

api.js 에서 url 안의 변수를 인자로 넣은 함수를 생성

- searchform index 파일로 가서 import { searchUrl } from '../../../lib/api';

const onSearch = (input) =>{

        const url =searchUrl (input)

        dispatch(getVideoList(url))

        navigate('/')

    }

app.js 에서 가져온 searchUrl input 을 넣은 변수로 설정한 뒤 쿼리값으로 해당 변수를 인자로 넘겨주도록 수정

 

 

 

3. pages > search.js 생성해서 검색하면 아예 다른 페이지로 이동하도록

- app.js 에서 <Route path="/search" element={<Search />} />

- search.js에 기본 세팅

import React from 'react';

import SideMenu from '../components/SideMenu';

import VideoList from './../components/VideoList/index';

 

const Search = () => {

  return (

    <>

      <SideMenu />

      <section className='main-content'>

        <VideoList />

      </section>

    </>

  );

};

 

export default Search;

home.js 와 똑같이 구성

 

1) 레이아웃 관리를 redex

- videoSlice 로 가서

- initialState listLayout: 'grid', 추가

 reducers:{

        videoListLayout: (state, action) =>{

            state.listLayout=action.payload

        }

    },

→ 해당 grid 레이아웃 나오도록 만드는 것

- 맨 하단에 export const {videoListLayout} = videoSlice.actions

 

2) grid 라는 값을 reducer , url 뿐 아니라 레이아웃도 dispatch

- SearchForm 으로 와서 onSearch 하면 dispatch(videoListLayout('list')) 가 쿼리값 넘어가도록 설정

- videoListLayout import

- list 인지 gird 인지 읽어서 해야 하니까 Search.js 로 와서

- import { useSelector } from 'react-redux';

- const display = useSelector((state)=>state.video.listLayout);

display onsearch 해서 Search 페이지로 넘어가면 레이아웃을 list css 를 변경해줄 것임

 

- VideoList 로 와서 display props 받아주고

- <ul className={display==='grid'?'videoList VideoGrid':'videoList VideoRowList'} > 로 수정

display gird 일 경우 VideoGrid' , 아니면 VideoRowList 를 적용

 

- css 만들기

 

- Home.js 로 가서

import { useEffect } from 'react';

import { useSelector,useDispatch } from 'react-redux';

import { videoListLayout } from '../store/video/videoSlice';

 

const dispatch = useDispatch();

    const display = useSelector((state) => state.video.listLayout)

 

    useEffect(()=>{

        dispatch(videoListLayout('grid'))

    },[])

 

<VideoList display={display} />

 

 

 

!!!! 주의 !!!!

환경 변수 파일 무시하고 올려야 함

ignore 파일 안에 #API_KEY .env 해주기

 

1. 클릭하면 넘어갈 페이지 만들기

1) watch.js 파일 생성

import VideoView from './../components/VideoView'

<section className='list content'>

      <VideoView />

</section>

→ 기본 틀 작성

 

2) VideoView 폴더 > index.js index.css 파일 생성

- 영상 재생은 불러와야 함

- youtube.api 페이지로 들어가서 or 유튜브 퍼가기로 iframe ~ 어쩌고 코드 가져오기

- src, title, allowfullscreen 만 남기고 다 삭제

 

3) 내가 선택한 li 가 재생되게 설정

- src id 가 변수로 지정되어야 해당 썸네일만 재생됨

- src={`https://www.youtube.com/embed/${id}`} → 수정

- 설명 처리할 div 박스 하나 만들어서 iframe 감싸기

- 그 아래 descriptionContainer 클래스 네임을 가진 div 박스 하나 더 생성

 

4) 해당 id 에 맞는 영상 재생되도록 설정

- app.js 로 가서 <Route path='/watch' element={<Watch />} /> 라우팅 처리

- header 폴더 > index.js 로 가서 link 태그로 로고 부분 감싸서 홈으로 이동하도록 처리

- 서치를 했을 때나 플레이 리스트에서 그냥 화면이 나왔을 때 id 를 찾을 수 없음 /  id > items > 어쩌구~

- VideoList 로 가서 VideoItemvalue={item} 을 내려주기

- VideoItem 에 가서 value 도 받아주기 : props {item} itemssnippet 이고 props {value} items

- import {useNavigate} from 'react-router-dom'

let id;

  if(typeof value.id==='string'){

  id=value.id

  }else if(typeof value.id==='object'){

    id=value.id.videoId;

  }

  const navigate=useNavigate();

  const goToWatch = ()=>{

      navigate(`/watch?id=${id}`)

    }

쿼리 값으로 넘어감

- 그래서 watch.js 에서 쿼리값 받을 것임

import {useSearchParams} from 'react-router-dom'

const[query,setQuery] = useSearchParams();

  const id =query.get('id') // id video.id 들어가 있을 것임

 

  return (

    <section className='list content'>

      <VideoView id={id} />

    </section>

 

- 이렇게 하면 섬네일 클릭하면 영상 있는 상세페이지로 넘어감

- 그리고 해당 상세페이지 css 처리해주기

 

5) 상세페이지에 동영상 목록도 표시되도록 설정 watch.js

- import { useSelector } from 'react-redux';

- const videoData = useSelector((state)=>state.video.data)

- const lte15VideoData=videoData.filter((item,idx)=>idx<=15) → 15개 이하만 동영상 나오도록

- ul 안에 VideoItem 맵으로 똑같이 돌려주기

<ul>

        {

          lte15VideoData.map((item,idx)=>(

            <VideoItem key={item.snippet.thumbnails.default.url} item={item.snippet} value={item} />

          ))

        }

      </ul>

 

- app.css style 처리

 

6) 상세페이지에서 검색하면 다시 홈으로 가서 검색이 되도록 설정

- SearchForm 으로 가서

- import { useNavigate } from 'react-router-dom';

- const navigate=useNavigate();

- 변수 onSearch 안에 navigate('/') 추가 홈으로 나가도록 주소 추가

 

7) 상세페이지에서 새로고침하면 목록이 사라짐스토리지 필요, redux-persist 사용

- store 폴더 > index.js

- redux-persist 설치

const persistConfig ={

    key:'root',

    storage:storageSession,

}

 

import persistReducer from 'redux-persist/es/persistReducer';

 

const persistedReducer = persistReducer(persistConfig,reducer)

 

middleware:(getDefaultMiddleware)=>getDefaultMiddleware({

        serializableCheck:false

    })

- 여러 개 리듀서를 했을 때 적용시킬 것만 부르고 싶을 때 : persistConfig whitelist:['video'] 추가, blacklist가 그 반대

- src 폴더 > index.js persist 수정 :

다시하기 다 넘어감

 

8) samesite 에러 수정

- 요소검사 가보면 문제 파트에 samesite 에러가 오조오억

- 쿠키 어쩌구?? 보안 때문인듯??? 무슨 말인지 모르겠음 ㅠㅠ

- iframe 은 리소스에 요청된 쿠키가 틀림

- 도메인을 바꿔야 samesite 에러 안뜸

- VideoView 에 가서 iframe 링크에 https://www.youtube-nocookie.com/embed/${id} nocookie 넣어서 수정

1. api

오픈 api 는 키가 있음, 구글 api 키를 받아야 인증 정보를 넣어야지만 사용가능

구글 api : https://console.cloud.google.com/apis/dashboard?pli=1 구글 api console

- 인증키를 받을 수 있는 곳

- 프로젝트 만들기→이름→생성

- 라이브러리로 가서 youtube data api v3 사용 버튼 누르기

- 그럼 첫화면으로 넘어감

- 상단 오른쪽에 사용자 인증 정보 만들기 버튼 누르기

- 공개 데이터로 체크, 다음 버튼

- 그럼 api 키 라고 나올 텐데 그 키를 이용해서 가져올 수 있음, 자기 키 복붙해놓기

 

- youtube data api 들어가보면 가져올 수 있는 방법 등 여러가지 적혀있음

- 한국어의 경우 안되는 것들이 있음 영어로 바꿀 것

- API Reference videos → list 로 들어가기

- part snippet, chart mostPopular(인기순), maxResults(최대 몇 개) 30, regionCode kr, oauth 는 체크 풀기

- 그러면 결과물이 나옴, show code 들어가서 http 누르면 상단에 주소가 나오는데 그걸 복붙해서 쓰면 내가 위에서 선택한대로 설정값을 가져옴

 

GET https://youtube.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&maxResults=30®ionCode=kr&key=본인키

accept 은 서버에 요청할 때 이런 데이터 타입을 허용해줄 것이라 알려주면 서버에서 그에 맞는 응답을 해줌

[YOUR_API_KEY] HTTP/1.1

Authorization: Bearer [YOUR_ACCESS_TOKEN]

Accept: application/json 이건 필요 없고 아까 받아온 키를 key 뒤로 붙여넣기

 

- 위의 get 어쩌구를 브라우저 주소창에 넣어보기

- 우리가 필요한 정보는 snippet 안에 다 있음

 

 

 

2. app.js 로 돌아와서 주소를 fetch 로 뿌리기?

- import {useState,useEffect} from 'react';

const [videos, setVideos] =useState([])

  useEffect(()=>{

    getMostPopularVideos();

  },[])

  const getMostPopularVideos = async()=>{

    const url=`https://youtube.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&maxResults=30®ionCode=kr&key= 본인키`

    const res = await fetch(url);

    const data = await res.json();

    console.log('인기동영상목록', data)

  }

api 정보 불러와주기

→ 그럼 콘솔에 : 인기동영상목록 {kind: 'youtube#videoListResponse', etag: 'ctotuwi6huGd-Q_Z-Ya1OPPCQis', items: Array(30), nextPageToken: 'CB4QAA', pageInfo: {…}} 이렇게 들어와 있는 걸 볼 수 있음

- getMostPopularVideos 안에 setVideos(data.items) 해줘서 items 만 들어오도록

- return 문 안에

{

   videos.map(item=><div key={item.id}>{item.snippet.title}</div>)

}

→ 이렇게 해주면 title 이 주루룩 다 나열됨

- 개발자 검사에 네트워크 들어가보면 요청이 어떻게 들어가 있는지 나와있음

- 요청헤더, 응답헤더에 나와 있는 건 다시 검색해보기, origin 이 계속 나오는데 그건 리소스 출처를 뜻함(이후 다시 설명)

- scheme 은 프로토콜이 어떤 걸로 잡혀있는지 나옴

 

 

 

3. 날씨api 불러와보기

- weather api https://openweathermap.org/api

- 상단 오른쪽에 본인 이름 누르고 my api keys 들어가보면 key 가 나옴

- api : e87e991da99c74ba6aad50150b10c85c

- api 메뉴로 들어가보면 Current Weather Data api doc 버튼 누르기

- API call https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API key} 복붙

- 주소의 {api key} 부분에 본인 키 붙여넣기

- 구글 지도에 들어가서 위도 경도 받아오기

- {lat} 부분에 위도경도 넣어주기 : 마우스 오른쪽 클릭 주변 검색 누르면 검색창에 표시됨

- https://api.openweathermap.org/data/2.5/weather?lat=37.543365&lon={lon}&appid=e87e991da99c74ba6aad50150b10c85c

 

 

 

4. app.js 돌아와서 데이터 뿌려주기

const [weather, setWeather] =useState(null)

  useEffect(()=>{

    getCurrentWeatherData();

  },[])

  const getCurrentWeatherData = async() =>{

    const url=`https://api.openweathermap.org/data/2.5/weather?lat=37.5468&lon=126.9498&appid=e87e991da99c74ba6aad50150b10c85c`

    const response = await fetch(url);

    const data = await response.json();

    console.log('날씨데이터', data)

    setWeather(data)

  }

→ 이렇게 해주면 콘솔 : 날씨데이터 {coord: {…}, weather: Array(1), base: 'stations', main: {…}, visibility: 10000, …}

- 날씨 섭씨로 바꿔주기 : weather api 홈페이지→ api Current Weather Data api doc 버튼 → celsius 찾기 → units=metric 넣어주면 된다고 뜸 → app.js url 주소 맨 뒤에 &units=metric 붙여주면 됨

- 지역 이름이랑 온도 출력하기 :

<h1>{weather && weather.name}</h1>

<h2>{weather && weather.main.temp}</h2>

 

 

 

 

5. 넷플릭스나 왓챠 클론할 때 쓰는 api

movie database api : https://www.themoviedb.org/

이것도 한번 불러와보기

! 오늘 할 것 !

- 로그인 로그아웃 : 로그인하면 상품명 페이지 뜨도록

- 저번에 장바구니에서 삭제 잘 안된거 수정

- 라우팅 설명

- 상세페이지 설명

 

 

1. 링크를 거는 방법

첫번째 방법) link 컴포넌트 사용 : 화면에서는 a 태그로 변환 그냥  a 태그처럼 사용하는 상황일 때

두번째 방법) 함수로 호출해주는 방법 : useNavigate사용 함수를 호출해줘야 하는 상황일 때

- routes 하는 건 app.js , index.js 둘 다 상관없음

 

1) main.js 에서 회사소개 페이지 누르면 그 페이지로 넘어가도록

- import { Link } from 'react-router-dom';

- <Link to='/company'>회사소개 페이지</Link>

 

2) company.js 에서 메인페이지로 이동 누르면 그 페이지로 넘어가도록

- import { useNavigate } from 'react-router-dom';

- <span onClick={goToMain}>메인페이지로 이동</span>

const navigate = useNavigate();

const goToMain = () => {

        navigate('/')

    }

 

 

 

2. ? : 쿼리 값

- uses서치팜 : query 값 가져올 수 있고 셋쿼리

- query : url 뒤에 추가적인 정보를 전달하고 싶을 때 사용

- usestate 와 비슷하게 사용

 

1) app.js 에서 product.js 라우트

2) product.js

- import { useSearchParams } from 'react-router-dom'

- let [query,setQuery]=useSearchParams();

- console.log('query ', query.get('page'))

- 여기서 get 은 어떤 걸 가져오는 메서드 page 에 해당하는 키값을 가져오겠다는 뜻

- http://localhost:3000/product?page=1&num=10 → 페이지는 1, num 10 인데 이걸로 주소 입력했을 때 콘솔에 query 1 이라고 뜸

 

3) main.js

- import useNavigate

- const navigate = useNavigate();

const goToProduct = () =>{

        navigate('/product?page=1&num=10&name=dd') // 보통은 변수로 들어감

        /* navigate(`/product?page=${id}&num=${num}&name=${name}`) -> 이런 식으로 들어감 */

    }

- <button onClick={goToProduct}>상품페이지 가기</button>

 

 

 

3. 상세페이지 만들기

1) ProductDetail.js 파일 생성

- app.js ProductDetail route

- useNavigate import 해주기

- const navigate = useNavigate();

- <button onClick={goToProduct}>상품페이지 가기</button>

const goToProduct = () =>{

        navigate('/product?page=1&num=10&name=dd') // 보통은 변수로 들어감

        /* navigate(`/product?page=${id}&num=${num}&name=${name}`) -> 이런 식으로 들어감 */

    }

 

 

2) restfull routes : https://woojinger.tistory.com/32

- api 디자인 패턴

- https://learn.co/lessons/sinatra-restful-routes-readme : Routes Overview 보기, url 디자인 권고사항

- 상세페이지 표시로 :id 쓸거임, id 는 라우트의 파라미터로 변수 같은 개념, 어떤 값이 들어와도 무조건 상세페이지를 표시해줌

- /product/:id 는 이 아이디 값을 가진 아이템을 보여준다는 뜻

 

3) app.js restfull routes 적용

- <Route path='/product:id' element={<ProductDetail />} />

- import ProductDetail from './pages/ProductDetail';

 

4) ProductDetail.js

- import { useSearchParams } from 'react-router-dom'

- const params = useParams();

- id key , value id

 

 

 

4. 로그인 페이지 구현

1) 기본틀 만들기

- User.js 컴포넌트 생성

- route > RedirectsRoute.js 생성

 

2) Redirects

방향을 다른 페이지로 돌려 주는 것

 

3) app.js

- <Route path='/login' element={<Login />} /> 추가

- <Route path='/product/:id' element={<UserRedirects />} /> 추가

 

4) RedirectsRoute.js

import React from 'react';

import Detail from '../components/Detail';

import { Navigate } from 'react-router-dom'

import { useSelector } from 'react-redux/es/exports';

 

const UserRedirects = () => {

    const user = useSelector((state)=>state.user.value)

    return user === true ? <Detail /> : <Navigate to='/login' />;

};

 

export default UserRedirects;

 

 

 

5. 에러 났던 장바구니 react12 에서 이어서

1) CartItem.js

const restItems = cart.filter((ele, index) => {

        let indexNum;

        if (ele.id === item.id) {

            indexNum = index;

        }

        return indexNum;

 

    })

→ 지우고 다시

- const restItems = cart.filter((ele, index) => ele.id === item.id) 로 수정

- ele.id === item.id : 일치하는 것은 하나이기 때문에 계속 index0 번일 것임

 

2) cartSlice.js

- deleteCart 에서 const num = state.findIndex (어레이의 메서드) 로 검사할 것임

- const num = state.findIndex((ele)=>ele.id===action.payload[0].id)

console.log(action.payload)

            /* 0: {id: 1, product_name: '검정칼라 스웨터', price: 35000, product_img: 'https://picsum.photos/200', is_checked: 'false'}

            length: 1

            [[Prototype]]: Array(0) */

 

- state.splice(num, 1) 로 수정

- 지난 번에 왜 안 됐는지 알아야 함

 

장바구니에 같은 애 못 담게

- state = state.push(action.payload); 수정

- console.log(action.payload.id) : 클릭한 id 가 뜸

- const equalItem = state.findIndex(ele=>ele.id===action.payload.id);

if(equalItem>=0){

                alert ('장바구니에 동일한 상품이 있습니다.')

            }else{

                state = state.push(action.payload);

            }

 

→ 과제 : 장바구니 총 합산 구하기         

 

 

 

 

6. redux-persist : 로컬스토리지 사용할 수 있게 만드는 라이브러리

- https://redux-toolkit.js.org/tutorials/overview 참고

- npm install redux-persist : 설치 먼저

 

1) store index.js

import storage from 'redux-persist' //로컬

import storageSession from 'redux-persist/lib/storage/session' //세션

 

- import { persistReducer } from 'redux-persist' : reducer 가 실행될 때 persist 를 같이 사용하게 묶어서 사용할 수 있게 하는 것

- import { configureStore, combineReducers } from '@reduxjs/toolkit' : combineReducers 여러가지 reducer 를 묶어서 사용할 수 있게 하는 것(login reducer 도 만들 것이고 persist 도 만들 것이기 때문에 사용함)

- persistConfig 변수 만들고 key: 'root', storage, 적기

- 여기서 key 값이란 localstorage 에 저장될 때의 key

- const rootReducer = combineReducers({ cart : cartSlice, })

const persistedReducer = persistReducer(persistConfig, rootReducer)

// persistConfig rootReducer 묶어서 사용할 있게 해줌,

redux-persist 사용해서 로컬스토리지에 state 저장하면 페이지가 새로고침 되어도 초기값(initialState) 로컬스토리지에 저장된 state 값으로 대체되기 때문에 초기화되지 않는다.

 

- const store = configureStore({ reducer : persistedReducer, }) 적은 후

export default configureStore({

    reducer: {

        cart: cartSlice

    }

})

→ 지우고 export default store; 로 수정

 

2) src 폴더의 index.js 파일

- import { PersistGate } from 'redux-persist/integration/react';

- let persistor = persistStore(store);

<PersistGate persistor={persistor}>

        <App />

</PersistGate>

→ 수정

- import { persistStore } from "redux-persist";

 

3) store 폴더의 index.js 파일

- middelware : 사가, 텅크? 두가지가 있음. redux 에서의 비동기처리. 사가는 따로 설치해야 하고 텅크는 toolkit 에 있음

 

 

 

 

7. 로그인

1) Login.js 파일 생성

<form>

     <div>

        <div><label htmlFor='userId'>아이디</label></div>

         <div><input type="text" id="userId" placeholder='아이디입력' /></div>

     </div>

     <div>

        <div><label htmlFor='userPass'>비밀번호</label></div>

        <div><input type="password"  id="userPass" /></div>

     </div>

     <div>

         <input type="submit" value="로그인" />

     </div>

</form>

→ 기본틀 입력

- app.js <Route path='/login' element={<Login />} />

- Detail.js 파일 생성, 기본틀 작성

- route 폴더 생성해서 밑에 UserRedirects.js 파일 생성

 

2) UserRedirects.js

import React from 'react';

import Detail from '../components/Detail';

import { Navigate } from 'react-router-dom'

import { useSelector } from 'react-redux/es/exports';

 

const UserRedirects = () => {

    const user = useSelector((state)=>state.user.value)

    return user === true ? <Detail /> : <Navigate to='/login' />;

};

 

export default UserRedirects;

 

3) app.js

- <Route path='/product/:id' element={<UserRedirects />} />

- import UserRedirects from './route/UserRedirects';

 

4) store > user > userSlice.js

import { createSlice } from '@reduxjs/toolkit'

 

const initialState = {

  value: false,

}

 

export const userSlice = createSlice({

  name: 'user', // reducer 이름

  initialState, // 데이터의 초기값

  reducers: { // 상태가 변하면 어떻게 실행될지 지정

    login: (state, action) => {

      state.value = action.payload;

      action.type='SET_USER_LOGIN'

    },

    logout: (state, action) => {

        state.value = action.payload;

        action.type='SET_USER_LOGOUT'

    },

  },

})

 

export const { login, logout } = userSlice.actions

export default userSlice.reducer

→ 초기 세팅

 

5) store > index.js

- user:userSlice 추가

- import { userSlice } from './user/userSlice'; 추가

 

6) login.js

- <form onSubmit={(e)=>loginUser(e)}> 수정

- 변수 loginUser 추가

const loginUser = (e)=>{

        e.preventDefault();

        dispatch(login(true))

        navigate('/')

    }

 

7) nav.js

- import useNavigate, useDispatch, logout

const user=useSelector((state) => state.user.value)

const dispatch = useDispatch()

const navigate = useNavigate()

→ 추가

{

       user ?

       ( <span className="user" onClick={()=>{dispatch(logout(false))}}>로그아웃</span>) :

       (<span className="user"  onClick={() =>

navigate("/login")}>로그인</span>)

}

→ 출력되어야 하는 곳에 추가

 

 

 

8. 프라이빗페이지 리다이렉트

1) ProductItem

const navigate = useNavigate();

    const goToDetail=()=>{

        navigate(`/product/${item.id}`)

    }

→ 추가

- import { useNavigate } from 'react-router-dom';

- <div className="item-name" onClick={goToDetail}>{item.product_name}</div> 으로 수정

 

2) Detail

- import {useParams} from 'react-router-dom'

- let params = useParams();

- console.log('params', params.id)

- <h2>제품아이디는 {params.id}</h2>

1. redux 연습, 쇼핑몰

1) 구조

product list : 상품 목록 전체

product item : 상품 목록 하나

nav : 상단의 nav bar 로고와 카트(몇 개 아이템 담겼는지), 로고랑 카트 link to 로 연결되게 해놓음

cartlist : 카트에 들어가면 보일 목록 전체

cartitem : 카트 목록 하나

app : nav 바 맨 위에, productlist cart 라우트 해줌

 

2) 구동

장바구니+ 버튼 누르면 nav bar 의 카트 아이콘에 아이템 몇 개 담겼는지 표시,

 

 

 

2. store 폴더 index.js

- store 기본 구조 가져오기

- store 파일로 쓸 미리 구조 잡아놓기 : exprot 안에 reducer: { cart: cartSlice }, cartSlice import

 

 

 

3. store 폴더 안에 cart 폴더 안에 cartSlice.js

- redux 페이지의 퀵스타트에서 Create a Redux State Slice 부분 카피해서 구조 잡기

- 초기값 수정

import { createSlice } from '@reduxjs/toolkit'

 

const initialState= []

 

export const cartSlice = createSlice({

    name: 'cart',

    initialState,

    reducers: {

        addCart: (state) => {

 

        },

        deleteCart: (state) => {

           

        }

       

    }

})

 

// Action creators are generated for each case reducer function

export const { addCart, deleteCart } = cartSlice.actions

 

export default cartSlice.reducer

 

- 내가 선택한 product item state 값이 길이만큼 카트 아이콘에 표시, 배열 안에 있는 것들을 리스트로 출력, 그래서 initialState(초기값)을 빈 배열로 만들어 놓을 것임,

- addCart, deleteCart 에 인자로 action 도 추가

 

 

 

4. product list

- 보고 해석할 것

const [products, setProducts] = useState([]);

    useEffect(() => {
        fetch("/data/productList.json").then(response => response.json())
            .then(response => setProducts(response.productList)) // productList 라고 키값을 지정해둬서 response.productList 라고 한 것임 키값 지정 안하면
    }, []);

    const itemList = products.map(item => {
        return <ProductItem key={item.id} item={item} />
    })

    return (
        <section className="product-list">
            <h2 className="product-list-title">상품 목록</h2>
            <div className="product-item-container">
                {itemList}
            </div>
        </section>
    );

 

 

5. Product Item

- toLocaleString ,

- 장바구니에 담기 버튼 누르면 누른 길이만큼 카트 아이콘에 숫자 표시되도록

- 전역 관리?

- useDispatch import 해주기 :

- addCart import 해주기 :

- 카트 아이콘을 onClick 하면 dispatch 불러오게

- const dispatch = useDispatch();

- dispatch 인자로 props 로 받은 item 을 설정, item 은 상위 컴포넌트에서 json 데이터를 받은 것 중에 item 이라는 데이터

 

 

 

6. 전체 index.js

- import Provider, store

- <Provider store={store}> <App /> 을 감싸도록 설정

 

 

 

7. cartSlice

- addCart 안에 state = state.push(action.payload); , action.type = 'SET_ADD_ITEM' !!!다시!!!

- deleteCart 안에 action.type = 'SET_DELETE_ITEM' !!!다시!!!

 

 

 

8. nav

- useSelector import : state 구독

- const cart = useSelector(state => state.cart)

- 표시될 아이콘에 안에 {cart.length} 해서 클릭한 수만큼 state 변화가 표시되도록 설정

 

 

 

9. cart list

- 클릭한 배열이 그만큼 들어와서 리스트 업 되도록

- useSelector import : state 구독

- const cart = useSelector(state => state.cart)

- 구독하고 있는 state 값이 카트에 들어와 있으니 map 을 돌릴거임

const cartItem = cart.map((item, index) => {

        return (

            <CartItem key={item.id} item={item} />

        )

    })

item index 를 인자로 잡고, state 로 구독한 map 을 돌려 cart 배열 만큼 리스트 업 될거임

 

 

 

삭제하면 삭제되도록, 카트 아이콘 숫자도 업데이트

10. cart item                                                                   

- import useDispatch

- const dispatch = useDispatch();

- onClick={()=>dispatch(deleteCart())}

- useSelector import : state 구독

- const cart = useSelector(state => state.cart)

- 카트에서 걸러낼 것임, restItem 변수를 지정해서 cartfilter로 걸러냄 인자는 ele, index로 설정하고 어떻게 걸러낼 것이냐? 만약 ele.id item.id 가 같다면 걸리는 애의 index 번호만 리턴해서 걸러내기 !!!다시!!!

- deleteCart import

- 4번째, 3번째, 2번째 아이템을 선택하면 카트 아이템으로 index 안에 순서대로 id4, id3, id2 가 들어와 있고 index는 순서대로 0,1,2 가 들어갈 것, 그래서 클릭한 아이템의 id

- dispatch(deleteCart(restItem))

 

 

 

11. cartSlice

- deleteCart state.splice(action.payload,1)

- 기존의 state 값에서 하나를 잘라냄

요소검사 redux 가보면 cart 의 인덱스 길이가 삭제 버튼 누를 때마다 없어지는 것을 볼 수 있음

- 클릭한 애 인덱스 번호번째부터 하나를 잘라내서 삭제

 

 

 

장바구니가 비었으면 비었다고 표시

12. CartList

- 삼항연산자로 수정

const cartItem = cart.map((item, index) => {

        return (

            <CartItem key={item.id} item={item} />

        )

    })

→ 원래 이렇게 있던 부분을

const cart = useSelector(state => state.cart);

    const cartItem = cart.length >= 1 ? cart.map((item, index) => {

        return (

            <CartItem key={item.id} item={item} />

        )

    }) : <div className="cart-emty">장바구니가 비어 있습니다.</div>

→ 이렇게 수정

React-Redux

 

Redux 페이지의 Usage Guides : https://ko.redux.js.org/usage/index (참고)

 

- redux ? 전역상태를 관리하는 라이브러리

- 전역상태를 저장하는 store 가 있음

- 하나의 프로젝트에 store 는 하나, 그걸 담는 state 는 여러 개.

- setState 역할을 하는 것이 action(데이터를 수정하기 위한 요청, 미리 지정해둬야 함)

- redux 는 리액트에 종속되는 라이브러리가 아니고 다른 라이브러리와 프레임워크, 바닐라 자바스크립트와 함께 사용할 수 있음

 

 

1. Redux 가 작동하는 순서

- 컴포넌트에서는 store 에서 필요한 state 값을 구독해서 사용 (원래는 전역적으로 state 를 관리를 할 수 없음, 하지만 redex는 사용 가능)

- 상태를 변경시켜야 할 때 변경해야 할 정보를 담아서 action 이라는 객체로 생성

- action dispatch(처리될 작업을 실행시키는 것, 보내는 것)해서 reducer 에 넘겨줌

- reducer 는 그 정보(action)를 보고 state 를 정해진 규칙에 따라 변경해줌

- 최종적으로 전역 state 가 변경되고, 해당 state 를 구독하고 있던 컴포넌트들은 변경된 새로운 state 값을 받음, 해당 컴포넌트는 값이 변경되었기 때문에 리랜더링됨.

 

 

2. redux 의 세가지 원칙

https://velog.io/@seungmini/Redux%EC%9D%98-%ED%8A%B9%EC%A7%95-%EC%84%B8%EA%B0%80%EC%A7%80 (참고)

 

  1) single source of truth : 신뢰가능한 단일 출처

데이터(state)는 신뢰가능한 단일 출처를 가져야 한다.

데이터(state)는 여러 곳에 두지 말고 하나의 store 에 두고 관리해야 한다는 뜻

중복된 데이터에 의한 오류가 줄이고 디버깅을 쉽게 하기 위해서

 

  2) state is read-only : 상태는 읽기 전용

데이터의 흐름이 양방향이 아닌 단방향이어야 한다.

state 를 구독하다가 갑자기 컴포넌트를 바꾸는 것이 아닌 일정 방향이 있음

action 이라는 객체를 통해 데이터(state) 가 변경되어야 함

이점 : 이 데이터(state)가 어떤 것에 의해 변경되었는지 명확하게 알 수 있음

 

  3) changes are made with pure functions : reducer 는 순수한 함수

순수한 함수란 어떤 입력 값이 정해져 있을 때 결과 값을 예측할 수 있는 함수

예를 들면 2x2 를 넣으면 4라는 값이 나오는 함수

random 함수는 결과값을 예측할 수 없기 때문에 예측 불가. date 함수는 그때그때 날짜가 달라지기 때문에 예측 불가. 따라서 둘은 순수 함수가 아님

입력값이 있을 때 (action 이 정해졌을 때) 일관된 결과 (random 하지 않은 결과) 를 가지고 와야 함

 

 

3. Redux 설치 : redux toolkit

- npm install @reduxjs/toolkit react-redux : reduxjs/toolkit react-redux 두 개를 설치하는 것

- Redux DevTools : 크롬 확장프로그램, action 이 어떻게 작동하는지 볼 수 있도록 하는 것, 요소검사에서 볼 수 있음

 

 

4. Redux 실행 준비

- store 폴더의 index.js 파일 하나 만들고 Quick Start Create a Redux Store 의 코드 복사해서 붙여넣기 → store 역할 하는 파일

- toolkit 으로 설치했기 때문에 src 폴더의 index.js 파일에 import 해서 사용할 것임

- src 폴더의 index.js 파일에 가서 import store from './store'; import { Provider } from 'react-redux'; 해주기 → store index.js 파일

- <Provider store={store}> <App /> </Provider> → app.js 컴포넌트가 store 에 접근할 수 있도록 Provider로 감싸서 store 내려주기

- store 폴더 안에 counter 폴더 안에 counterSlice.js 파일 생성

- Redux 페이지의 Create a Redux State Slice 의 코드 복사해서 counterSlice.js 파일에 붙여넣고 수정해서 쓸 것임

 

 

5. counterSlice.js 파일

- incrementByAmount : 안 쓸거니까 지우기

- 미리 action 을 요청해놓기 : increment, decrement 라는 action 을 요청. 따라서 state.value += 1 state.value -= 1 라는 요청을 할 수 있다는 뜻

- 그런 요청을 받아 실질적으로 값을 처리해주는 함수는 reducers 함수

- createSlice : 툴킷에서 사용하는 함수, reducers(state를 조작하는 함수) 를 담고 있음

- const initialState~ 라고 되어 있는 함수를 따로 빼고 export 안에는 initialState 라고 불러와 주기

- increment state 라는 인자를 갖고 있는 것에 (state, action) 이라고 하나 더 만들어주기

- reducers 에 어떻게 함수를 조작할 것인지 넣어줘야 함

- 인자 state 는 현재값(기존 value), 인자 action는 데이터가 전달됨(수정 요청을 받게 됨)

- state.value1 action.payload; 로 수정, 그리고 action.type='SET_VALUE_INCREMENT' 적어주기 / decrement 에도 인자 action 넣고, action.payload 수정, action.type='SET_VALUE_DECREMENT' 적어주기 보통 type 은 대문자로 만듬

 

기본 세팅 끝

 

 

6. Quick Start 예제로 만들어보기

- src 파일에 component 폴더에 Counter 폴더 안에 index.js 파일 생성

- app.js Counter import 해주기

- 만든 파일 안에 rsc 해주고 div 태그 안에 Redux 페이지의  Use Redux State and Actions in React Components button 태그 두개 복사 붙여넣기 해주기

- 만든 파일 안에 import { useSelector, useDispatch } from 'react-redux'; 해주기 : react-redux 에서 제공하는 훅

- 만든 파일 안에 const dispatch = useDispatch(); 해주기 : dispatch 만 해주면 useDispatch 훅 사용 가능

- action 인자를 받아 dispatch 로 변화를 요청 : onClick={() => dispatch(변화)} / 액션 직접 만들 수 있지만 매번하면 귀찮기 때문에 액션을 생성하는 함수 호출, action creator 호출

- action creator 는 이미 만들어져 있음, counterSlice.js 에 가보면 하단에 action 생성 함수가 export 되고 있어 action 을 생성할 수 있음

- counter index.js 파일에 decrement, increment import 해주기

- 가져온 decrement, increment 를 각각 onclick dispatch 함수에 넣어주기

- dispatch(increment(1)) dispatch(decrement(1)) : 1로 값을 설정해주면 payload 로 값이 전달됨

 

 

7. counterSlice.js 파일

- createSlice : create reducers create action 을 합쳐놓은 훅, redux toolkit 에서 제공됨

- Immer 라이브러리 : react 에서 불변성을 유지하는 코드를 작성하기 쉽게 해주는 라이브러리로, 지금 우리는 tool kit 으로 설치했기 때문에 쓰지 않아도 됨

- type action 에서 꼭 들어가야 하는 속성, type 은 어떤 reducer 로 가야 하는지 경로를 정해주기 때문에 꼭 있어야 함

 

 

8. store 폴더의 index.js 파일 (store 역할 하는 파일)

- counterSlice.js 파일을 import

- reducer counter : counterSlice 입력

- counter 라는 reducer, 그리고 counterSlice 를 연결한 것

 

 

9. Counter 폴더의 index.js 파일

- {count} 가 빨간 줄 쳐 있을 거임

- count 는 최종적으로 가져오는 state 값임

- store 파일에서 보낸 state 값을 구독해야 연결이 됨

- useSelector 라는 훅은 store 에서 구독하는 역할을 해줌

- const count = useSelector(state => state.counter.value); → counter 라는 값은 어디서 왔는지? store 파일의 counter 값에서 옴

 

 

10. 확장프로그램 dev tools 에서 보면서 확인해볼 것

- toolkit 설치 않은 채로 dev tool 사용하려면 npm 으로 설치해서 다시 설정해줘야 함

 

요소 검사가서 Redux 란으로 가보면

- action : 어떻게 연결되어 들어는지 보임

- diff : 어떻게 값이 변경되는지 보임

- state : 어떻게 state 가 작동되는지 보임

- 하단의 화살표 go back, go forward : 눌러보면 이전, 이후로 타임라인 돌아감

- chart : type payload 가 가지 형태로 나옴

- raw : type payload 가 객체 형태로 나옴

 

 

11. payload

- 고정하지 않은 값으로 어떤 변경 사항을 추가해주기 위해 설정한 것

- payload 값을 꼭 넣어야 하는 것은 아님, 변경이나 추가 사항이 없으면 state.value 에 값을 고정해놓고 onclick 보낼 때 increment 소괄호 안에 아무것도 안넣어도 됨

 

 

 

 

** 과제 : 오늘 redux 예제 푼 것 연습해보기

+ Recent posts