all is well!!
[ 항해 4주차 회고록] 주특기숙련 본문
⚡️ 4주차 시작!
왕초보인 내가 알고리즘 5일치 문제만으로 React 벼랑에 등 떠밀린지 2주차..!
이번엔 감을 잡기위해서 컴포넌트도 내가 원하는데로 쪼개서 조립하고 싶었다.
그래서 이번엔 참고예시 페이지를 거의 참고하지않고 컴포넌트 쪼개기와 포장지(html+css)는 내가 원하는대로
만들었고 이번에 새로 배우는 전역 상태관리 관련(redux,firebase)은 항해에서 준 강의에 의존해서 만들었다.
💾 주특기 React 숙련
이번 과제도 팀과제와 개인과제가 같이 주어졌다.
* 4주차 개인과제 내용
1) 나만의 사전 완성, 2) 파이어베이스 or S3로 배포 를 완성해야 합니다.
- 게시글 목록 페이지
- 게시글 목록을 화면에 그리기 (각각 뷰는 카드 뷰로 만들기)
- 게시글 내의 예시는 파란 글씨로 보여주기
- 게시글 목록을 리덕스에서 관리하기
- 게시글 목록을 파이어스토어에서 가져오기
- 게시글 작성 페이지
- 게시글 작성에 필요한 input 3개를 ref로 관리하기
- 작성한 게시글을 리덕스 내 게시글 목록에 추가하기
- 게시글 목록을 파이어스토어에 저장하기
- 추가로 해보면 좋을 기능 (필수 X)
- 무한 스크롤 붙이기
- 게시글 수정해보기
1. App 컴포넌트 (최상위 컴포넌트)
1. redux 묶음과 미들웨어 묶음이 담겨있는 store을 Provider로 App.js에 주입시켜서 컴포넌트에 붙였다.
2. 나만의 미니 단어장 제목이 들어있는 Maintop 컴포넌트는 어떤 페이지로 이동하던 항상 위에 있어야 하기 때문에 Routers위에
붙여주었다.
3. Route안에는 처음에 사이트 들어가면 보이는 단어장 Main 컴포넌트와 단어장을 추가해주는 Update 컴포넌트를 넣었다.
import React from 'react';
import './App.css';
import { Route, Routes, BrowserRouter } from "react-router-dom";
import Main from './components/Main';
import UpDate from './components/UpDate';
import MainTop from './components/MainTop';
import {Provider} from "react-redux"
import store from "./redux/configStore.js";
function App() {
// const [list, setList] = useState([ // redex쓰기전까지의 useState 데이터. 이제 필요없음!!!
// {"word":"star","mean":"별","ex":"twinkly star"},
// {"word":"sun","mean":"해","ex":"twinkly sun"},
// {"word":"moon","mean":"달","ex":"twinkly moon"},
// ]);
return (
<div className="App">
<Provider store = {store}> {/* Provieder로 store 주입시켜서 컴포넌트에 붙이기 */}
<BrowserRouter>
<MainTop />
<Routes>
<Route exect path='/' element={<Main/>} />
<Route exect path='/update/add' element={<UpDate />} />
<Route exect path='/update/:id' element={<UpDate />} />
</Routes>
</BrowserRouter>
</Provider>
</div>
);
}
export default App;
2. Main 컴포넌트
React Hook? : 함수형 컴포넌트를 관리하는 것을 더 편리하게 해준다. 리액트는 함수형 컴포넌트와 훅을 함께 사용하는 것을 권장하기 때문에 앞으로 과제하면서 Hook을 익숙하게 써보는게 좋을것 같다.
1. React Hook - useNavigate 는 특정 행동을 했을 때 해당 주소로 이동해줄 수 있게 만들어준다.
2. 그래서 메인화면에 뿌릴 단어카드 컴포넌트와 useNavigate를 통해서 단어추가 페이지로 이동할 수 있게 해주는 addBtn 컴포넌트를
import해서 보이고 싶은 위치에 컴포넌트를 불러와서 붙였다.
3. React Hook - useEffect은 렌더링 이후에 실행할 코드를 만들수 있다. 그리고 어떤 변수가 변경될때마다(의존성), 특정기능이 작동하도록 할 수 있다.
import React from 'react';
import '../css/Main.css';
import { useNavigate } from 'react-router-dom';
import WordCard from './WordCard.js';
import AddBtn from './AddBtn';
import {loadDictionFB, addDictionFB} from "../redux/modules/diction";
import { useDispatch } from 'react-redux'; // redux 수정하는 hook
function Main(props) {
const navegate = useNavigate();
const dispatch = useDispatch();
// main에도 firebase db로 화면에 뿌려줘야되니까 여기에도 loadDictionFB(읽어오기), addDictionFB(생성추가) 미들웨어 가져온다.
React.useEffect(()=>{
dispatch(loadDictionFB(), addDictionFB());
}, []);
return (
<div className="main">
<div className='wrap'>
<div className='div_1'>
<WordCard />
</div>
<div className='div_2' onClick={()=>{navegate("/update/add");}}>
<AddBtn/>
</div>
</div>
</div>
);
}
export default Main;
3. Update 컴포넌트
1. 3개의 input창은 ref로 관리한다.
import React from 'react';
import '../css/UpDate.css';
import { createDiction } from '../redux/modules/diction';
import { loadDictionFB, addDictionFB } from '../redux/modules/diction';
import { useDispatch } from 'react-redux'; // redux 수정하는 hook
import { useNavigate } from 'react-router-dom';
import {db} from "../firebase"; //firebase 연결
import {collection, getDoc, getDocs, addDoc, updateDoc, doc, deleteDoc} from "firebase/firestore";
//ref 이해 , 어떻게 참조되는지 이해하기
function UpDate(props) {
const wordRef = React.useRef(null); //ref 객체를 리턴한다.
const meanRef = React.useRef(null); //ref 객체를 리턴한다.
const exRef = React.useRef(null); //ref 객체를 리턴한다.
// firebase data 잘 가져오는지 확인해보는 코드 : doc (밑에랑 같은 코드임)
// React.useEffect(async()=>{ // async & await : 기다릴게 라는 약속이라고 생각하기
// console.log(db); // firebase 잘 연결되었는지 확인
// // firebase 접근할때는 상위데이터부터 차근차근 내려와야됨.
// // collection(db, "dictionary") -> 컬렉션정보 (db, 컬랙션 이름)
// const query = await getDocs(collection(db, "dictionary")); // 컬렉션으로 가져오는 모든 정보 가져올수있음
// console.log(query) // promise로 요청되어지는게 확인가능하다.("야 나 이 데이터좀 줘! 하는 요청")
// query.forEach((doc)=>{
// console.log(doc.id, doc.data());
// });
// }, []);
// firebase data 저장 잘되는지 확인해보는 코드 : addDoc (밑에랑 같은 코드임)
// React.useEffect(async()=>{
// console.log(db);
// // collection(db, "dictionary") // 도큐먼트 추가
// addDoc(collection(db, "dictionarys"), {word: "moon", mean: "달", ex: "twinkly moon"})
// }, [])
// firebase data 도큐먼트 추가 잘되는지 확인해보는 코드 : addDoc (밑에랑 같은 코드임)
// React.useEffect(async()=>{
// console.log(db);
// // collection(db, "dictionary") // 도큐먼트 추가
// addDoc(collection(db, "dictionary"), {word: "moon", mean: "달", ex: "twinkly moon"})
// }, [])
// firebase data 수정 잘되는지 확인해보는 코드 : updateDoc (밑에랑 같은 코드임)
// React.useEffect(async()=>{
// console.log(db);
// const docRef = doc(db, "dictionary", "3FglM1f9inVDJk1NYL9K"); //doc 정보
// updateDoc(docRef, {word: "sun", mean: "해", ex: "twinkly sun"})
// }, [])
// firebase data 삭제 잘되는지 확인해보는 코드 : deleteDoc (밑에랑 같은 코드임)
// React.useEffect(async()=>{
// console.log(db);
// const docRef = doc(db, "dictionary", "3FglM1f9inVDJk1NYL9K"); //doc 정보
// deleteDoc(docRef)
// }, [])
const dispatch = useDispatch();
const navegate = useNavigate();
React.useEffect(()=>{
dispatch(loadDictionFB());
}, []); // 미들웨어 loadDictionFB 정보 확인
// 단어 등록 함수
const addDictionList = () => {
// input 비어있으면 경고창 띄우기
if( !wordRef.current.value || !meanRef.current.value || !exRef.current.value ){
alert("빈칸을 채워주세요!");
return;
}
// current : ref 객체는 참조하기 위해서 그 안에 current 키값의 참조된 타겟값이 할당됨.
const new_word = { // 새로운 단어 형식을 저장하려면 action을 dispatch로 보내야됨.
word : wordRef.current.value,
mean : meanRef.current.value,
ex : exRef.current.value,
} // 리터럴 객체형식
dispatch(addDictionFB(new_word)) //dispatch안에는 diction액션객체 들어가야됨.
console.log(wordRef)
// console.log(meanRef.current.value)
// console.log(exRef.current.value)
}
// html input
// const input = document.querySelector()
// const input = document.querySelector('input')
// input.value
// wordRef.current = input Element
// wordRef.current.value ===
// inputElement.value
return (
<div className="update">
<div className='update_box'>
<p className='title'>단어 추가하기</p>
<hr></hr>
<p>단어</p>
{/* input에 타입이 두개가 정의되어 있는데 첫번째로 type이라는 속성에 text라는 값이 할당되어있고
ref 속성에는 wordRef객체가 할당되어있다. */}
<input type="text" ref={wordRef}></input> {/* ref : dom 직접 참조하기위한 도구 */}
<p>설명</p>
<input type="text" ref={meanRef}></input>
<p>예시</p>
<input type="text" ref={exRef}></input>
<div className='btn_updatd'>
<button onClick={()=>{addDictionList(); navegate(-1);}}>저장하기</button> {/* onClick={createDiction} */}
</div>
</div>
</div>
);
}
export default UpDate;
4. WordCard 컴포넌트
1. React Hook - useSelector은 Provider로 감싼 store에서 state 데이터를 가져올 수 있게 해준다.
import React from 'react';
import '../css/WordCard.css';
import Btns from '../components/Btns';
import {useSelector} from "react-redux"; // 리덕스 데이터 가져오기위한 redux hook
function WordCard(props) {
// (state)=>state : (state)는 스토어가 가지고있는 전체데이터 / =>state는 ()없으니까 그냥 return되는 값
// diction에서 초기값 설정해둔걸 가져오는거임.
// 나는 list만 꺼내오고 싶으니까 diction의 list인 state.diction.list을 => 뒤에 넣어줌.
// const initalState = {
// list : [
// {"word":"star","mean":"별","ex":"twinkly star"},
// {"word":"sun","mean":"해","ex":"twinkly sun"},
// {"word":"moon","mean":"달","ex":"twinkly moon"},
// ]
// }
const my_lists = useSelector((state)=>state.diction.list); // 리덕스 전체데이터 가져오기 ()안에는 어떤데이터를 가져오고 싶은지에 대한 화살표함수
console.log(my_lists);
return (
<div className="word_card">
{/* 위의 리덕스 전체데이터 가져오는 my_list를 card틀에 mapping해서 새로운 내용 추가하면 한개씩 뒤에 card틀에 맞춰서 나오게 함. */}
{my_lists.map((list, index)=>{
return(
<div className='card'
key={index}
>
<div className='btns'><Btns/></div>
<h1>{list.word}</h1> {/* diction(redux)의 초기값의 json배열중 word 정보를 들고와서 뿌려줌 */}
<p>[의미] {list.mean}</p> {/* diction(redux)의 초기값의 json배열중 mean 정보를 들고와서 뿌려줌 */}
<p>[예문] {list.ex}</p> {/* diction(redux)의 초기값의 json배열중 ex 정보를 들고와서 뿌려줌 */}
</div>
)
})}
</div>
);
}
export default WordCard;
5. redux - configstore
//store는 Reducer을 뭉친 rootReducer로 만든다.
import {createStore, combineReducers} from "redux";
import {applyMiddleware, compose} from "redux"; // 비동기 통신을 위한 import
import diction from "./modules/diction.js";
import thunk from "redux-thunk" // 비동기 통신을 위한 미들웨어 thunk import
// 미들웨어도 하나로 묶어야됨
const rootMiddleware = [thunk];
const enhancer = applyMiddleware(...rootMiddleware) // 리듀서 말고 옵셔널하게 추가되는것들을 모음
// 리듀서 하나로 묶기 (지금은 한개밖에 없지만..)
const rootReducer = combineReducers({diction});
const store = createStore(rootReducer, enhancer) // 스토어에 리듀서 묶음과 미들웨어(+옵셔널) 묶음을 넣어서 내보낸다. 이렇게하면 적용 끝!!
export default store;
6. redux - diction
액션타입 생성 -> 액션 생성 함수 -> 미들웨어 -> 리듀서
// diction.js
import { db } from "../../firebase"; // firebase의 db 가져온다.
import { collection, getDoc, getDocs, addDoc, updateDoc, doc, deleteDoc } from "firebase/firestore"; // firebase를 가져오는 내장함수들
import { async } from "@firebase/util";
// Actions(액션 타입)
const LOAD = 'diction/LOAD'; // 가져오는거
const CREATE = 'diction/CREATE'; // 생성하는거
const UPDATE = 'diction/UPDATE'; // 수정하는거
const REMOVE = 'diction/REMOVE'; // 없애는거
// 초기값 (값은 딕셔너리로..)
const initalState = {
list: [
]
}
// Action Creators (액션 객체 만드는 액션 생성 함수)
export function loadDiction(diction_list) {
return { type: LOAD, diction_list: diction_list };
}
export function createDiction(diction) { // create에 대한 액션 생성 함수
return { type: CREATE, diction: diction }; // {생성타입,생성할 어떤값}
}
export function updateDiction(diction) {
return { type: UPDATE, diction: diction };
}
export function removeDiction(diction) {
return { type: REMOVE, diction: diction };
}
// middelware
// firebase 정보를 긁어오는 미들웨어
export const loadDictionFB = () => { // 리덕스 청크는 함수를 리턴한다.
return async function (dispatch) { // 함수 안에는 redux를 수정하는 dispatch를 인자로 가져온다. 그래야 정보를 고칠수 있으니까.
const diction_data = await getDocs(collection(db, "dictionary")); // dictionary_data안에 getDocs로 한 컬렉션안의 정보를 싹다 긁어올수있음.
console.log(diction_data); // 정보가 긁어올수있는 데이터만 있는게 아니어서 배열로 바꿔줘야됨. 그 코드는 밑에...
let diction_list = []; // 1. dictionary_list라는 빈 배열을 만들어서
diction_data.forEach((doc) => { //2. 배열형태로 데이터 나오게끔 설정
console.log(doc.data());
diction_list.push({ ...doc.data() }); // doc.data 안에 있는 모든것을 넣어준다.
}); // getDocsfh 긁어온 파이어베이스 컬렉션값을 담은 dictionary_data로 forEach()를 이용해서 데이터 가져옴
console.log(diction_list)
dispatch(loadDiction(diction_list)) //loadDiction 액션 일으키기("나 이거좀 고쳐줘! 하는 요청").
//이제 실질적으로 데이터를 화면에 뿌려주기 위해서는 밑에 리듀서 설정해줘야됨.
}
}
// firebase 내용 업데이트 미들웨어
export const addDictionFB = (new_word) => {
return async function (dispatch) {
const docRef = await addDoc(collection(db, "dictionary"), new_word); // 첫번째인자 : 데이터 넣을 firebase 컬렉션 / 두번째인자 : 컬렉션에 추가할 새데이터
// const _diction = await getDoc(docRef);
const diction = { id: docRef.id, ...new_word }
console.log(diction);
// dispatch(createDiction(diction)) //createDiction 액션 일으키기("나 이거좀 고쳐줘! 하는 요청").
//이제 실질적으로 데이터를 화면에 뿌려주기 위해서는 밑에 리듀서 설정해줘야됨.
}
}
// Reducer
export default function reducer(state = initalState, action = {}) { // state = {} : 기본값 주는거
switch (action.type) {
case "diction/LOAD": {
return { list: action.diction_list }; // 액션에 넣어놨던 diction_list 데이터 교체
}
case "diction/CREATE": {
const new_diction_list = [...state.list, action.diction];
return { list: new_diction_list }; // 리턴한 새로운 상태값에는 기존에 정보 하나가 추가된 배열 리스트가 들어가야됨.
} //어떨때 뭐를해
default: return state;
}
}
// side effects, only as applicable
// e.g. thunks, epics, etc
// export function getWidget() {
// return dispatch => get('/widget').then(widget => dispatch(updateWidget(widget)))
// }
[ 여기서 알아보는 라이프사이클(클래스형 vs 함수형) ]

리액트 훅이 나오기 전에는 그림처럼 라이프 사이클과 state 관리하기 위해서 주로 클래스형 컴포넌트를 사용했다고 한다.
import React, {component} from 'react;
class App extends component{
super(props),
state:[
]
render(){
return(
<div class="app">
<div/>
)
}
}
이런 형태인 클래스형 컴포넌트를 많이 사용했었지만, 함수형 컴포넌트 맞춤 리액트 훅이 점점 생겨나면서 React에서 class보다는 함수형을 사용하라고 직접 언급할만큼 지금은 class형보다 함수형으로 바뀌고있다고 한다.
import React,from 'react';
function App(){
return(
<div classname="app">
</div>
)
}
export default App;
비교해보면 확실히 class형 보다는 함수형이 코드가 좀더 간결한것 같다.
👏 4주차를 마무리하며 👏
그래도 이번에 내 맘대로 만든 껍데기에 redux와 firebase 부분은 강의 도움을 받아 내 생각보다 빨리 과제를 완성해볼 수 있었다.
아직 redux와 firebase을 처음써봐서 사용이 익숙하지 못하지만 심화때도 왠지 쓸거같아서 좀더 사용해보면 익숙해지지 않을까 싶다.
주특기 심화.. 벌써 무섭지만.. 매주 그랬듯이 일단 부딪혀 봐야겠다ㅠㅠ 어떻게든 되겠지..??ㅋㅋㅋㅋㅋㅋ
다음주도 파이팅!!
'weekly 회고록' 카테고리의 다른 글
| 프론트엔드 3기 WIL - 시작하는 마음 (0) | 2024.06.15 |
|---|---|
| [ 항해 6주차 회고록] 미니 프로젝트 (0) | 2022.08.07 |
| [항해99 3주차] React 입문주차 S.A. (0) | 2022.07.01 |
| 항해1주차 S.A [10팀] (0) | 2022.06.20 |
| 항해준비-0주차(html+css+jsp쬐~끔) (0) | 2022.06.01 |