프로젝트 단계

첫째, 프로젝트를 생성한다.

  • yarn create react-app 프로젝트명

둘째, 뷰를 만든다.

  • styled-components를 사용하여 디자인한다.

셋째, 라우팅을 한다.

  • index.js에서 BrowserRouter로 감싸준다.

넷째, 리덕스를 이용한다.

  • redux폴더 > modules폴더 안에 리덕스 파일을 만들어 준다.

다섯째, 파이어베이스를 이용하여 데이터를 값이 잘 뿌려지는지 확인한다.

프로젝트에 사용한 개념정리

라우팅이란?

URL에 따라서 그에 상응하는 화면을 전송해주는 것을 라우팅이라 한다. 리액트에서 라우팅 기능을 구현하는 것은 쉽지 않지만 React Router는 리액트에서 비교적 쉽게 라우팅이 가능하도록 도와준다.

BrowserRouter : HTML5의 history API를 활용하여 UI를 업데이트한다. 보통 request와 response로 이루어지는 동적인 페이지를 제작하므로 BrowserRouter가 보편적으로 많이 쓰인다.

ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById("root")
);

Route : 현재 주소창의 경로와 매칭될 경우 보여줄 컴포넌트를 지정하는데 사용된다. path prop을 통해서 매치시킬 경로를 지정하고 component prop을 통해서 매치되었을 때 보여줄 컴포넌트를 할당한다.

<Route path="/about" component={About}>

Switch : path의 충동이 일어나지 않게 Route들을 관리한다. Switch 내부에 Route들을 넣으면 요청에 의해 매칭되는 Route들이 다수 있을 때에 제일 처음 매칭되는 Route만 선별하여 실행하기 때문에 충돌 오류를 방지해주며, Route간에 이동 시 발생할 수 있는 충돌도 막아준다. path와 매칭되는 Route가 없을 때에 맨 밑에 default Route의 실행이 보장된다.

// 함수형 컴포넌트
function App() {
  return (
    <div className="App">
      <div>
        <Title>MY DICTIONARY</Title>
        <TwoTitle>HangHae React Dictionary Project</TwoTitle>
        <Line />
        <Switch>
          <Route path="/" exact>
            <DictionaryList />
          </Route>
          <Route path="/add">
            <AddList />
          </Route>
        </Switch>
        <Line />
      </div>
    </div>
  );
}

리덕스란?

리덕스는 가장 사용률이 높은 상태관리 라이브러리로 리액트의 복잡한 컴포넌트 구조속에서 보다 간펼하게 모든 컴포넌트들이 state를 쉽게 공유할 수 있게 해주는 방식이다. 우선 리덕스는 리액트 내부에 있는 기술이 아니며 수순 Html, JavaScript 내에서도 사용이 가능하다. 컴포넌트에 집중된 리액트와 시너지가 좋으니 대체적으로 리액트에 리덕스를 사용할 뿐이다.

리덕스에서 자주 사용되는 키워드

액션(Action) : state에 어떤 변화가 필요할 때 액션이라는 것을 발생시키며 이는 하나의 객체이다. 단어 그대로 어떤 동작에 대해서 선언 되어진 객체라고 생각하면된다. 액션은 반드시 type필드를 가지고 있어야 하며, 그 외의 값은 상황에 따라 넣어줄 수 있다.

// Actions
const Add = "dictionary/ADD";
const LOAD = "dictionary/LOAD";

const initialState = {
  list: [
    {
      word: "개인과제",
      account: "안녕",
      example: "헬로",
    },
  ],
};

액션 생성 함수(Action Creators) : 위에 설명한 Action이 동작에 대해 선언된 객체라면, Action Creator은 Action을 생성해 실제로 객체로 만들어주는 함수이다.

// Action Creators
export const addDictionary = (dictionary) => {
  return { type: Add, dictionary: dictionary };
};

export const loadDictionary = (dictionary) => {
  return { type: LOAD, dictionary: dictionary };
};

리듀서(Reducer) : state에 변화를 일으키는 함수이다. 쉽게 말해 위에 만들어진 Action등의 일거리를 직접 수행하는 것이다. 리듀서는 현재의 State와 Action을 인자로 받아 Store(스토어)에 접근해 Action에 맞춰 State를 변경한다.

export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case "dictionary/LOAD": {
      return { ...state, list: action.dictionary };
    }
    case "dictionary/ADD": {
      const new_dictionary = [...state, action.dictionary];
      return { ...state, list: new_dictionary };
    }
    default:
      return state;
  }
}

스토어(Store) : 스토어는 현재 앱의 State와 Reducer 함수, 그리고 몇 가지 내장 함수등을 가지고 있다. 스토어 State를 수시로 확인해 View한테 변경된 사항을 알려주는 녀석이라 생각하면 된다.

디스패치(dispatch) : 디스패치는 스토어의 내장 함수 중 하나로 리듀서에게 Action을 발생하라고 시키는 것이라고 이해하면 된다. dispatch함수는 dispatch(action)이런 식으로 Action을 인자로 넘긴다. 이렇게 호출을 하면 스토어가 리듀서 함수를 실행해 리듀서 함수가 넘긴 액션을 처리해 새로운 상태를 만들어 준다.

  const dispatch = useDispatch();

App.js

import React from "react";
import styled from "styled-components";

// 라우팅사용하기!
import { Route, Switch } from "react-router-dom";

// 컴포넌트 불러오기!
import DictionaryList from "./DictionaryList";
import AddList from "./AddList";

// 함수형 컴포넌트
function App() {
  return (
    <div className="App">
      <div>
        <Title>MY DICTIONARY</Title>
        <TwoTitle>HangHae React Dictionary Project</TwoTitle>
        <Line />
        {/* 스위치를 쓰는 이유는 주소를 옳바르게 사용하기 위하여 사용한다(ex.주소를 막쳤는데 들어가짐, 이것을 방지하기위해 스위치를 사용) */}
        <Switch>
          {/* exact는 부모만 띄어줄꺼라는 의미 */}
          {/* 라우터는 그냥 이런구조로 쓴다고 생각해라! */}
          <Route path="/" exact>
            {/* DictionaryList 컴포넌트 불러옴 */}
            <DictionaryList />
          </Route>
          <Route path="/add">
            {/* AddList 컴포넌트 불러옴 */}
            <AddList />
          </Route>
        </Switch>
        <Line />
      </div>
    </div>
  );
}

const Title = styled.h1`
  text-align: center;
  color: orange;
`;

const TwoTitle = styled.h3`
  text-align: center;
  color: grey;
`;

const Line = styled.hr`
  width: 95%;
`;

export default App;

DictionaryList.js

// 리액트 패키지를 불러옴.
import React from "react";
import styled from "styled-components";
import { useHistory } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { loadDictionaryFB } from "./redux/modules/dictionary";

const DictionaryList = ({}) => {
  const history = useHistory();
  // const global_lists = list;
  // console.log(global_lists);
  // const global_lists = useSelector((state) => state.dictionary.list);

  // action을 사용하기(발생시키기) 위해 dispatch 선언
  const dispatch = useDispatch();

  // https://ko-de-dev-green.tistory.com/18 파이어베이스에서 값을 불러오는 것이다.
  React.useEffect(() => {
    dispatch(loadDictionaryFB());
  }, []);

  // 데이터베이스에서 불러온 값 // useSelector는 state를 조회하기 위해 사용
  const dictionary_data = useSelector((state) => state.dictionary.list);
  console.log(dictionary_data);

  return (
    <Container>
      {/* &&표시는  dictionary_data가 있을 때 하라는 소리*/}
      {dictionary_data &&
        dictionary_data.map((dictionary, index) => {
          return (
            <ConDiv key={index}>
              <Wrap>
                <SecondTitle>단어</SecondTitle>
                <ItemStyle>{dictionary.word}</ItemStyle>
                <SecondTitle>설명</SecondTitle>
                <ItemStyle>{dictionary.account}</ItemStyle>
                <SecondTitle>예시</SecondTitle>
                <ItemStyle style={{ color: "blue" }}>
                  {dictionary.example}
                </ItemStyle>
              </Wrap>
            </ConDiv>
          );
        })}
      <div>
        <Button
          onClick={() => {
            history.push("/add");
          }}
        >
          <i
            style={{
              fontSize: "30px",
              margin: "12px auto",
              color: "white",
            }}
            className="fas fa-plus"
          ></i>
        </Button>
      </div>
    </Container>
  );
};

const Container = styled.div`
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  left: 0;
`;

const ConDiv = styled.div`
  overflow: scroll;
  width: 300px;
  display: flex;
  margin: 20px;
  border: 1px solid #ddd;
  border-radius: 20px;
  padding: 16px;
  background: orange;
`;

const Wrap = styled.div`
  height: 300px;
  width: 100%;
  margin: 5px;
`;

const SecondTitle = styled.h5`
  text-decoration: underline;
`;

const ItemStyle = styled.div`
  background: #fdf9f3;
  border-radius: 5px;
  padding: 10px;
`;

const Button = styled.div`
  position: fixed;
  bottom: 70px;
  border: 1px solid black;
  border-radius: 50%;
  width: 60px;
  text-align: center;
  right: 50px;
  margin: auto;
  background: black;
  cursor: pointer;
`;

export default DictionaryList;

AddList.js

// AddList.js

import React from "react";
import styled from "styled-components";
import { useHistory } from "react-router-dom";
import { useDispatch } from "react-redux";
import { addDictionaryFB } from "./redux/modules/dictionary";

const AddList = ({}) => {
  const history = useHistory();
  const dispatch = useDispatch();

  const dictionary_index = () => {
    const card = {
      word: word.current.value,
      account: account.current.value,
      example: example.current.value,
    };
    dispatch(addDictionaryFB(card));
  };

  //ref를 선언해준다.
  // input에 있는 값들을 가지고오겠다. 초기값을 null로하겠다.!
  const word = React.useRef(null);
  const account = React.useRef(null);
  const example = React.useRef(null);

  console.log(word);
  console.log(account);
  console.log(example);
  return (
    <Container>
      <Title>단어추가하기</Title>
      <div>
        <SecondTitle>단어</SecondTitle>
        <Input type="text" ref={word} placeholder="단어를 입력해주세요." />
        <SecondTitle>설명</SecondTitle>
        <Input type="text" ref={account} placeholder="설명을 입력해주세요." />
        <SecondTitle>예시</SecondTitle>
        <Input type="text" ref={example} placeholder="예시를 입력해주세요." />
      </div>
      <ButtonWrap>
        <Button
          onClick={() => {
            history.push("/");
            dictionary_index();
          }}
        >
          추가하기
        </Button>
        <Button
          onClick={() => {
            history.push("/");
          }}
        >
          뒤로가기
        </Button>
      </ButtonWrap>
    </Container>
  );
};

const Container = styled.div`
  border: 2px solid #ddd;
  width: 300px;
  margin: auto;
  border-radius: 30px;
  padding: 10px;
`;

const Title = styled.h1`
  text-align: center;
`;

const SecondTitle = styled.h5`
  text-decoration: underline;
`;

const Input = styled.input`
  display: flex;
  justify-content: center;
  width: 280px;
  margin: auto;
  border: 1px solid orange;
  border-radius: 30px;
  padding: 5px;
`;

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

const Button = styled.button`
  width: 140px;
  margin: auto;
  display: flex;
  justify-content: center;
  background: orange;
  border: 2px solid orange;
  border-radius: 30px;
  margin-top: 10px;
  margin-bottom: 10px;
  color: white;
  cursor: pointer;
`;

export default AddList;

dictionary.js

// dictionary.js
// 리덕스(미들웨어 제외!)
import { db } from "../../firebase";
import {
  collection,
  doc,
  getDoc,
  getDocs,
  addDoc,
  deleteDoc,
} from "firebase/firestore";

// Actions
const Add = "dictionary/ADD";
const LOAD = "dictionary/LOAD";

const initialState = {
  list: [
    {
      word: "개인과제",
      account: "안녕",
      example: "헬로",
    },
  ],
};

// Action Creators
export const addDictionary = (dictionary) => {
  return { type: Add, dictionary: dictionary };
};

export const loadDictionary = (dictionary) => {
  return { type: LOAD, dictionary: dictionary };
};

// 미들웨어
export const loadDictionaryFB = () => {
  return async function (dispatch) {
    const dictionary_data = await getDocs(collection(db, "dictionary"));
    let dictionary_list = [];
    dictionary_data.forEach((dictionary) => {
      dictionary_list.push({ ...dictionary.data() });
    });
    console.log(dictionary_list);
    dispatch(loadDictionary(dictionary_list));
  };
};

export const addDictionaryFB = (dictionary_list) => {
  return async function (dispatch) {
    const dictionary_db = await addDoc(
      collection(db, "dictionary"),
      dictionary_list
    );
    console.log(dictionary_list);
  };
};

// Reducer(리듀서)
export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case "dictionary/LOAD": {
      return { ...state, list: action.dictionary };
    }
    case "dictionary/ADD": {
      const new_dictionary = [...state, action.dictionary];
      return { ...state, list: new_dictionary };
    }
    default:
      return state;
  }
}
profile
👍🏻 SeungMai (매 순간 기록을!)🧑🏻‍💻 | WIL만 올리고 있습니다 | 기술블로그는 아래 🏠 의 링크를 이용해주세요.

0개의 댓글