- React-router로 주소에 따라 다른 페이지를 보여줄 수 있다.
- 미리 정해놓은 주소가 아닐 때, '앗! 잘못 찾아오셨어요!' 페이지를 보여줄 수 있다.
- Redux로 상태관리를 해보고 아래의 상태관리 흐름을 이해한다.
(기본 값이 있고 → 어떤 동작을 하면("어떤 동작을 하는 지 정하자! → 하면 뭐가 바뀌는 지 정하자!" 과정이 사전에 필요하다!) → 값이 변했잖아? → 컴포넌트한테 알려주자!)- Redux hook을 사용해본다.
yarn add react-router-dom
...
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
...
<Route path="주소[/home 처럼 /와 주소를 적어요]" component={[보여줄 컴포넌트]}/>
<Route path="주소[/home 처럼 /와 주소를 적어요]" render={(props) => (<BucketList list={this.state.list} />)} />
...
import { Route } from "react-router-dom";
...
render(){
return (
<div className="App">
<Route exact path="/" component={Home} />
{/* '/' 이 기호가 /cat에도 있고 home path도 / 라서 메인으로 들어가면 컴포넌트가 중복 호출됨 -> exact 추가 */}
<Route path="/cat" component={Cat} />
<Route path="/dog" component={Dog} />
</div>
);
}
...
//App.js
...
// 파라미터 주기
<Route path="/cat/:cat_name" component={Cat}/>
...
//Cat.js
...
const Cat = (props) => {
console.log(props.match.params.cat_name);
return (
<div>고양이 화면이에요.</div>
)
}
...
// App.js
...
import { Link } from "react-router-dom";
...
<div>
<Link to="/">Home으로 가기</Link>
<Link to="/cat">Cat으로 가기</Link>
<Link to="/dog">Dog으로 가기</Link>
</div>
// App.js
...
import { withRouter } from "react-router";
...
<button onClick={() => {
// props에 있는 history를 사용합니다.
// push([이동할 주소])는 페이지를 이동시켜 줍니다.
this.props.history.push('/cat');
}}>
/cat으로 가기
</button>
<button onClick={()=>{
// goBack()은 뒤로가기 예요.
this.props.history.goBack();
}}>뒤로가기
</button>
...
export default withRouter(App); // 내보내는 부분에서 withRouter로 감싸줍니다
// App.js
...
import { Route, Switch } from "react-router-dom";
...
render() {
return (
<div className="App">
...
<Switch> // Switch가 모든 Route를 감싸야 정상 동작함.
<Route
path="/"
exact
render={(props) => (
<BucketList
list={this.state.list}
history={this.props.history}
/>
)}
/>
<Route path="/detail" component={Detail} />
<Route component={NotFound} /> // path를 안적음으로써 없는 path에 갔을때 NotFound 컴포넌트가 불러와짐.
</Switch>
...
</div>
);
}
...
yarn add redux react-redux
리덕스 구성 요소
State, Action, ActionCreator, Reducer, Store, dispatch
Redux의 3가지 특징
store는 하나만 쓴다.
store의 state(데이터)는 오직 action으로만 변경할 수 있다!
어떤 요청이 와도 리듀서는 같은 동작을 해야한다!
리듀서는 순수한 함수여야 한다는 말입니다.
순수한 함수라는 건,
// /src/redux/modules/bucket.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) {
switch (action.type) {
// do reducer stuff
case "bucket/LOAD":
return state;
case "bucket/CREATE":
const new_bucket_list = [...state.list, action.bucket];
return {list: new_bucket_list};
case "bucket/DELETE":
const bucket_list = state.list.filter((l, idx) => {
return idx !== action.bucket
});
return {list: bucket_list};
default:
return state;
}
}
// /redux/configStore.js
import { createStore, combineReducers } from "redux";
import bucket from './modules/bucket';
import { createBrowserHistory } from "history";
// 브라우저 히스토리를 만들어줍니다.
export const history = createBrowserHistory();
// root 리듀서를 만들어줍니다.
// 나중에 리듀서를 여러개 만들게 되면 여기에 하나씩 추가해주는 거예요!
const rootReducer = combineReducers({ bucket });
// 스토어를 만듭니다.
const store = createStore(rootReducer);
export default store;
// index.js
...
// 우리의 버킷리스트에 리덕스를 주입해줄 프로바이더를 불러옵니다!
import { Provider } from "react-redux";
// 연결할 스토어도 가지고 와요.
import store from "./redux/configStore";
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("root")
);
1) 리덕스 모듈과 connect 함수를 불러옵니다.
2) 상태값을 가져오는 함수와 액션 생성 함수를 부르는 함수를 만들어줍니다.
3) connect로 컴포넌트와 스토어를 엮어줍니다.
4) 콘솔에 this.props를 찍어봅니다. (스토어에 있는 값이 잘 나왔는지 볼까요!)
5) this.state에 있는 list를 지우고 스토어에 있는 값으로 바꿔봅시다!
6) setState를 this.props.create로 바꿔봅시다!
// App.js
...
// 리덕스 스토어와 연결하기 위해 connect라는 친구를 호출할게요!
import {connect} from 'react-redux';
// 리덕스 모듈에서 (bucket 모듈에서) 액션 생성 함수 두개를 가져올게요!
import {loadBucket, createBucket} from './redux/modules/bucket';
// 이 함수는 스토어가 가진 상태값을 props로 받아오기 위한 함수예요.
const mapStateToProps = (state) => ({
bucket_list: state.bucket.list,
});
// 이 함수는 값을 변화시키기 위한 액션 생성 함수를 props로 받아오기 위한 함수예요.
const mapDispatchToProps = (dispatch) => ({
load: () => {
dispatch(loadBucket());
},
create: (new_item) => {
dispatch(createBucket(new_item));
}
});
...
addBucketList = () => {
const new_item = this.text.current.value;
this.props.create(new_item); // Redux의 create type을 적용해준 모습!
};
render() {
return (
...
<Switch>
<Route
path="/"
exact
render={(props) => (
<BucketList
list={props.bucket_list} // store의 bucket_list를 props로 보내줌. (자식 컴포넌트에서도 store로 불러주면 되긴하지만, 아직 해당 작업을 안해줌.)
history={this.props.history}
/>
)}
/>
...
</Switch>
...
</div>
);
}
}
...
// withRouter 적용
// connect로 묶어줬습니다!
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(App));
Redux도 함수형 컴포넌트에서 쓰는 Hook이 있음
useSelector(), useDispatch()
// BucketList.js
...
// redux hook을 불러옵니다.
import {useDispatch, useSelector} from 'react-redux';
const BucketList = (props) => {
// 버킷리스트를 리덕스 훅(useSelector)으로 가져오기
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");
}}
>
{list}
</ItemStyle>
);
})}
</ListStyle>
);
};
...
// Detail.js
...
// 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);
return (
<div>
<h1>{bucket_list[bucket_index]}</h1>
<button onClick={() => {
dispatch(deleteBucket(bucket_index)); // delete type Action
}}>삭제하기</button>
</div>
);
};
...
// App.js
<Route
path="/"
exact
render={(props) => (
<BucketList
// list={props.bucket_list} // 자식 컴포넌트에서도 store로 불러주면 되기때문에 더 이상 props로 bucket_list를 보내주지 않아도 됨.
history={this.props.history}
/>
)}
/>