spartacoding (프론트엔드의 꽃, 리액트) - 3주차

리액트에서 라우팅 처리하기

react-route-dom 설치

yarn add react-router-dom

현재 어느 주소를 보고 있는 지 쉽게 알려주는 BrowserRouter

index.js

import React from 'react';
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

// 이부분이 index.html에 있는 div#root에 우리가 만든 컴포넌트를 실제로 랜더링하도록 연결해주는 부분입니다.
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

세부 화면 만들기
Home.js

import React from 'react';

const Home = (props) => {
    return (<div>메인 화면이에요.</div>);
};

export default Home;

Cat.js

import React from "react";

const Cat = (props) => {

    console.log(props.match);

    return (
        <div>고양이 화면이에요.</div>
    )
}

export default Cat;

Dog.js

import React from 'react';

const Dog = (props) => {
    return (<div>강아지 화면이에요.</div>);
};

export default Dog;

App.js에서 Route 적용하기

1: 넘겨줄 props가 없을 때

<Route path="주소[/home 처럼 /와 주소를 적어요]" component={[보여줄 컴포넌트]}/>

2: 넘겨줄 props가 있을 때

<Route path="주소[/home 처럼 /와 주소를 적어요]" render={(props) => ()} />

App.js에 적용해보기

import React from 'react';
import './App.css';
import {withRouter} from "react-router";
import { Route, Link } from "react-router-dom";

import Home from "./Home";
import Cat from "./Cat";
import Dog from "./Dog";

class App extends React.Component {

  constructor(props){
    super(props);
    this.state={};
  }
  
  componentDidMount() {

  }

  render(){
    return (
      <div className="App">
        <div>
          <Link to="/">Home으로 가기</Link>
          <Link to="/cat">Cat으로 가기</Link>
          <Link to="/dog">Dog으로 가기</Link>
        </div>

        <hr />
        {/* 실제로 연결해볼까요! */}
        <Route path="/" exact component={Home} />
        <Route path="/cat" component={Cat} />
        <Route path="/dog" component={Dog} />

        <button onClick={() => {
          this.props.history.push('/cat/nabi');
        }}>/cat으로 가기</button>
        <button onClick={() => {
          this.props.history.goBack();
        }}>뒤로가기</button>
      </div>
    );
  }
}

export default withRouter(App);

exact를 추가하면 /를 입력했을 때 Home 컴포넌트만 뜰 수 있음
파라미터 주기

<Route path="/cat/:cat_name" component={Cat}/>
파라미터 사용 방법
import React from 'react';
const Cat = (props) => {
	console.log(props.match);
  return ( <div>고양이 화면이에요.</div> )
}
  
export default Cat;
<Link/>사용 방법> <Link to="주소">[텍스트]</Link>

history 객체를 props로 받아오려면, withRouter를 설정해줘야 합니다.

버킷리스트 상세 페이지 만들고 이동시키기

index.js

import React from 'react';
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

// 이부분이 index.html에 있는 div#root에 우리가 만든 컴포넌트를 실제로 랜더링하도록 연결해주는 부분입니다.
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

App.js

import React from "react";
import { withRouter } from "react-router";
import { Route, Switch } from "react-router-dom";
// import [컴포넌트 명] from [컴포넌트가 있는 파일경로];
import BucketList from "./BucketList";
import styled from "styled-components";
import Detail from "./Detail";
import NotFound from './NotFound';

// 클래스형 컴포넌트는 이렇게 생겼습니다!
class App extends React.Component {
  constructor(props) {
    super(props);
  
    this.state = {
      list: ["영화관 가기", "매일 책읽기", "수영 배우기"],
    };
  
    this.text = React.createRef();
  }

  componentDidMount() {
    console.log(this.text);
  }

  addBucketList = () => {
  let list = this.state.list;
    const new_item = this.text.current.value;
    this.setState({ list: [...list, new_item] });
  };
 
  render() {
    return (
      <div className="App">
        <Container>
          <Title>내 버킷리스트</Title>
          <Line />
          <Switch>
            <Route
              path="/"
              exact
              render={(props) => <BucketList list={this.state.list} history={this.props.history}/>}
            />
            <Route path="/detail" component={Detail}/>
            <Route component={NotFound}/>
            <Route render={(props) => {
              <NotFound history={this.props.history}/>
            }}/>
          </Switch>
        </Container>

        <Input>
          <input type="text" ref={this.text} />
          <button onClick={this.addBucketList}>추가하기</button>
        </Input>
      </div>
    );
  }
}
const Input = styled.div`
  max-width: 350px;
  min-height: 10vh;
  background-color: #fff;
  padding: 16px;
  margin: 20px auto;
  border-radius: 5px;
  border: 1px solid #ddd;
`;

const Container = styled.div`
  max-width: 350px;
  min-height: 60vh;
  background-color: #fff;
  padding: 16px;
  margin: 20px auto;
  border-radius: 5px;
  border: 1px solid #ddd;
`;

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

const Line = styled.hr`
  margin: 16px 0px;
  border: 1px dotted #ddd;
`;

export default withRouter(App);

BucketList.js

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

const BucketList = (props) => {
    console.log(props);
    const my_lists = props.list;
    
    return (
        <ListStyle>
            {my_lists.map((list, index) => {
                return (
                    <ItemStyle className="list_item" key={index} onClick={() => {
                        props.history.push('/detail');
                        }}>
                        {list}
                    </ItemStyle>
                );
            })}
        </ListStyle>
    );
};

const ListStyle = styled.div`
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow-x: hidden;
    overflow-y: auto;
`;

const ItemStyle = styled.div`
    padding: 16px;
    margin: 8px;
    background-color: aliceblue;
`;

export default BucketList;

Detail.js

import React from 'react';

const Detail = (props) => {
    return <h1>상세 페이지입니다!</h1>;
};

export default Detail;

NotFound.js

import React from 'react';

const NotFound = (props) => {
    return (
        <div>
            <h1>주소가 올바르지 않아요!</h1>
            <button onClick={() => {props.history.goBack();}}>뒤로가기</button>
        </div>
    );
};

export default NotFound;

리덕스 써보기

리덕스 패키지 설치하기

yarn add redux react-redux

모듈 만들기

bucket.js

// bucket.js

// Actions
const LOAD = "bucket/LOAD";
const CREATE = "bucket/CREATE";

const initialState = {
  list: ["영화관 가기", "매일 책읽기", "수영 배우기"],
};

// Action Creators
export const loadBucket = (bucket) => {
  return { type: LOAD, bucket };
}

export const createBucket = (bucket) => {
  return { type: CREATE, bucket };
}

// Reducer
export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case "bucket/LOAD": {
      return state;
    }

    case "bucket/CREATE": {
      const new_bucket_list = [...state.list, action.bucket];
      return {list: new_bucket_list};
      break;
    }
    // do reducer stuff
    default: return state;
  }
}

configStore.js

import {createStore, combineReducers} from "redux";
import bucket from './modules/bucket';
import { createBrowserHistory } from "history";

export const history = createBrowserHistory();

const rootReducer = combineReducers({bucket});
const store = createStore(rootReducer);

export default store;

리덕스와 컴포넌트 연결하기

index.js

import React from 'react';
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {Provider} from 'react-redux';
import store from './redux/configStore';

// 이부분이 index.html에 있는 div#root에 우리가 만든 컴포넌트를 실제로 랜더링하도록 연결해주는 부분입니다.
ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

컴포넌트에서 리덕스 데이터 사용하기

App.js

import React from "react";
import { withRouter } from "react-router";
import { Route, Switch } from "react-router-dom";
// import [컴포넌트 명] from [컴포넌트가 있는 파일경로];
import BucketList from "./BucketList";
import styled from "styled-components";
import Detail from "./Detail";
import NotFound from './NotFound';

import {connect} from 'react-redux';
import { loadBucket, createBucket } from "./redux/modules/bucket";

const mapStateToProps = (state) => {
  return {bucket_list: state.bucket.list};
}

const mapDispatchToProps = (dispatch) => {
  return {
    load: () => {
      dispatch(loadBucket());
    },

    create: (bucket) => {
      dispatch(createBucket(bucket));
    }
  };
}

// 클래스형 컴포넌트는 이렇게 생겼습니다!
class App extends React.Component {
  constructor(props) {
    super(props);
  
    this.state = {
      
    };
  
    this.text = React.createRef();
  }

  componentDidMount() {
    console.log(this.text);
  }

  addBucketList = () => {
    const new_item = this.text.current.value;
    this.props.create(new_item);
  };
 
  render() {
    return (
      <div className="App">
        <Container>
          <Title>내 버킷리스트</Title>
          <Line />
          <Switch>
            <Route
              path="/"
              exact
              render={(props) => <BucketList list={this.props.bucket_list} history={this.props.history}/>}
            />
            <Route path="/detail/:index" component={Detail}/>
            <Route component={NotFound} render={(props) => {
              <NotFound history={this.props.history}/>
            }}/>
          </Switch>
        </Container>

        <Input>
          <input type="text" ref={this.text} />
          <button onClick={this.addBucketList}>추가하기</button>
        </Input>
      </div>
    );
  }
}
const Input = styled.div`
  max-width: 350px;
  min-height: 10vh;
  background-color: #fff;
  padding: 16px;
  margin: 20px auto;
  border-radius: 5px;
  border: 1px solid #ddd;
`;

const Container = styled.div`
  max-width: 350px;
  min-height: 60vh;
  background-color: #fff;
  padding: 16px;
  margin: 20px auto;
  border-radius: 5px;
  border: 1px solid #ddd;
`;

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

const Line = styled.hr`
  margin: 16px 0px;
  border: 1px dotted #ddd;
`;

export default connect(mapStateToProps, mapDispatchToProps)(withRouter(App));

BucketList.js

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

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

const BucketList = (props) => {
    const bucket_list = useSelector(state => state.bucket.list);
    
    return (
        <ListStyle>
            {bucket_list.map((list, index) => {
                return (
                    <ItemStyle className="list_item" key={index} onClick={() => {
                        props.history.push('/detail/' + index);
                        }}>
                        {list}
                    </ItemStyle>
                );
            })}
        </ListStyle>
    );
};

const ListStyle = styled.div`
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow-x: hidden;
    overflow-y: auto;
`;

const ItemStyle = styled.div`
    padding: 16px;
    margin: 8px;
    background-color: aliceblue;
`;

export default BucketList;

Detail.js

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

const Detail = (props) => {
    const bucket_list = useSelector((state) => state.bucket.list);
    console.log(bucket_list, props);
    const bucket_index = parseInt(props.match.params.index);
    return <h1>{bucket_list[bucket_index]}</h1>;
};

export default Detail;

버킷 리스트 데이터를 삭제해보기
Detail.js

// 리액트 패키지를 불러옵니다.
import React from "react";

// redux hook을 불러옵니다.
import { useDispatch, useSelector } from "react-redux";
// 내가 만든 액션 생성 함수를 불러옵니다.
import {deleteBucket} from "./redux/modules/bucket";

const Detail = (props) => {
    const dispatch = useDispatch();
    
    
  // 스토어에서 상태값 가져오기
  const bucket_list = useSelector((state) => state.bucket.list);
  // url 파라미터에서 인덱스 가져오기
  let bucket_index = parseInt(props.match.params.index);

  console.log(props);
  return (
    <div>
      <h1>{bucket_list[bucket_index]}</h1>
      <button onClick={() => {
        //   dispatch(); <- 괄호안에는 액션 생성 함수가 들어가야겠죠?
        // 예를 들면 이렇게요.
        dispatch(deleteBucket(bucket_index));
        props.history.goBack();
      }}>삭제하기</button>
    </div>
  );
};

export default Detail;

bucketList.js

// widgets.js

// Actions
const LOAD = "bucket/LOAD";
const CREATE = "bucket/CREATE";
const DELETE = "bucket/DELETE";

const initialState = {
  list: ["영화관 가기", "매일 책읽기", "수영 배우기"],
};

// Action Creators
export const loadBucket = (bucket) => {
  return { type: LOAD, bucket };
};

export const createBucket = (bucket) => {
  return { type: CREATE, bucket };
};

export const deleteBucket = (bucket) => {
  return { type: DELETE, bucket };
};

// Reducer
export default function reducer(state = initialState, action) {
    console.log(action);
  switch (action.type) {
    // do reducer stuff
    case "bucket/LOAD":
      return state;

    case "bucket/CREATE":
      console.log(state, action);
      const new_bucket_list = [...state.list, action.bucket];
      return { list: new_bucket_list };

    case "bucket/DELETE":
      const bucket_list = state.list.filter((l, idx) => {
        if(idx !== action.bucket){
            return l;
        }
      });
      return {list: bucket_list};

    default:
      return state;
  }
}

좋은 웹페이지 즐겨찾기