reducers / PostsReducers.js
export default (state = [], action) => {
switch (action.type) {
case 'FETCH_POSTS' :
console.log(action.payload)
return action.payload;
default:
return state;
}
};
reducers / usersReducers.js
export default (state = [], action) => {
switch (action.type) {
case 'FETCH_USERS' :
return [ ...state, action.payload] -- es6문법으로 해체
default: 할당이다
return state;
//state[]에 action.payload를 추가하고 배열을 만든다
}
};
reducers / index.js
import { combineReducers } from 'redux';
import PostsReducers from './PostsReducers';
import usersReducers from './usersReducers';
export default combineReducers({
posts: PostsReducers, // 두개의함수를 가지고와 리덕스를 활용하여 state로 저장한다
users: usersReducers // 그렇게 combine된 함수들은 react-redux에서 connect해서 활용한다
});
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import App from './components/App';
import reducers from './reducers';
const store = createStore(reducers, applyMiddleware(thunk));
// createStore을 하면 store를 생성할 수 있으며 미들웨어인 thunk를 같이 인자값으로 생성하면
// 액션함수를 선언할 때 dispatch가 가능하여 자유롭게 리듀서의 정보를 액션에 맞게 변경할 수 있다
ReactDOM.render( // Privider 컴포넌트에 store을 주면, 그 자식컴포넌트에서 store의 정보들을 편하게 사용이 가능하다
// 이때 중요한 점은 applyMiddleware(thunk)도 같이 넣어주면서 store에 저장하게된다
<Provider store={store}>
<App />
</Provider>,
document.querySelector('#root')
);
// 이번 프로젝트는 redux-thunk를 활용하여 비동기식 처리 방법이다
// Thunk는 특정작업을 나중에 할 수 있도록 미루기 위해 함수 형태로 감싼 것을 의미합니다.
// 그리고 보통의 액션생성자는 그냥 하나의 액션객체를 생성할 뿐이지만 redux-thunk를 통해 만들게 되면 그 내부에서 여러가지 작업을 할 수도 있습니다
// 총정리
// react-redux의 Provider로 App를 묶어주면 App에 적용될 태그들은 store의 reducers 정보를 적용할 수 있다
// 적용하려고 하는 클래스를 connect함수로 연결하면 사용할 수 있다
// redux는 액션 함수들을 한 번에 묶어주어 리듀서를 만들어주어 관리해준다. 이때 redux의 combineReducers 함수를 사용하여야만 한다
Thunk와 Saga의 차이점
두 방식의 가장 큰 차이점을 문장 구조라고 생각을 할 것입니다. 그것이 가장 큰 사실이지만 더 큰 차이점이 존재하는데,
Thunks는 절대로 action에 응답을 줄 수 없습니다.
반면 Redux-Saga는 store를 구독하고 특정 작업이 디스패치 될 때 saga가 실행되도록 유발할 수 있습니다.
actions / index.js
import _ from 'lodash';
import jsonPlaceholder from '../apis/jsonPlaceholder'
export const fetchPostsAndUsers = () => async (dispatch, getState) => {
await dispatch(fetchPosts());
// thunk의 장점중 하나는 dispatch를 함수로 할수 있다는 것이다 그렇게 전체적인 fetchPostsAndUsers 를 만들어서 호출하여 fetchPosts(), fetchUser(id) 두개를 dispatch하는 과정에서 데이터를 조작하여 id를 주입하게된다
// const userIds = _.uniq(_.map(getState().posts, 'userId'));
// userIds.forEach(id => dispatch(fetchUser(id)));
_.chain(getState().posts) // lodash의 chain()를 사용하면 가독성에도 좋다 한번 참조하자
.map('userId')
.uniq()
.forEach(id => dispatch(fetchUser(id)))
.value()
};
지금의 문제점은 userid의 중복된 호출이다
이것을 보다 성능을 향상시키기 위해 _.memozie()를 활용하는 것과 각종 중복배열을 제거하고
새로운 배열을 만들어 dispatch한다 위의 코드분석은 아래의 블로그를 참조하자
http://kbs0327.github.io/blog/technology/lodash/ , https://gracefullight.dev/2016/12/25/Lodash-활용법/
_.uniq() 배열의 중복제거,
_.map() 특정 값들의 제 배치,
forEach() 배열 나열
// export const fetchUser = id => dispatch => _fetchUser(id, dispatch);
// const _fetchUser = _.memoize(async (id, dispatch) => {
// const response = await jsonPlaceholder.get(`/users/${id}`);
// dispatch({ type: 'FETCH_USERS', payload: response.data });
// });
그 변수는 매개변수로 지정된 함수와 같은 기능을 하는 동시에 이 함수는 메모리에 저장되어 동일한 인자값을 받을 때에는
네트워크에 거치지않고 자체적으로 메모리에 저장하여 request를 하지 않는다는 것이 장점이다
const getUser(int v){
return " hello world";
}
//const memoizedGetUser = _.memoize(getUser);
//memoizedGetUser(3)
//-> " hello world"
위의 예문처럼 최초 한번 네트워크에 요청하면 로그가 남는다. 하지만 그 다음에는 로그가 남지 않음을 알 수 있다
지금처럼 유저 정보가 반복될 때 네트워크에 요청하지 않고 재사용할 수 있다
export const fetchPosts = () => async dispatch => {
console.log(dispatch) // api는 개별적으로 관리를 하고 그것을 접근하는 것은 비동기로 처리하는 것이 중요하다
const response= await jsonPlaceholder.get('/posts');
// 그 이유는 JSX문법으로 태그를 넘겨받고 움직이는 컴퓨터의 실행력과 서버에서
// 데이터를 받아오는 시간차가 있기때문에 동기식으로는 절대 받아올 수 없다
dispatch({ type: 'FETCH_POSTS', payload: response.data });
// 그래서 비동기식인 async 와 await을 사용하여야 한다
// dispatch를 하려는 액션을 수동으로 불러낸다.
}; 즉, 액션을 직접적으로 돌려주는 것이 아니라 dispatch를 호출하여 액션 오브젝트를 건네어 주게 된다
export const fetchUser = id => async dispatch => {
// redux-thunk의 장점은 액션함수에서도 dispatch를 함수를 사용할 수 있다는 점이다
const response = await jsonPlaceholder.get(`/users/${id}`);
// 그렇게 axios로 비동기화 시켜 받아온 정보를 dispatch시켜 액션과 데이터를 건네줄수 있다
dispatch({ type: 'FETCH_USERS', payload: response.data });
};
// 이 예제를 잘 이용하면 어떠한 정보를 CURD하여 그 정보를 액션 타입에 맞게 행동하는 코드를 작성하면 괜찮을 듯 하다
// 그리고 dispatc메서드에 매개변수를 지정하는데 이때 함수를 지정하여 다른 action 파일에 있는 함수도 호출을 할 수 있게 된다
// https://velog.io/@dongwon2/Redux-Thunk-vs-Redux-Saga를-비교해-봅시다- 여기에 들어가면 상세정보를 얻을 수 있다
당연히 밑의 예제처럼 일반적인 action 함수도 사용이가능하다
export const selectPost =()=> {
return {
type: 'SELECT_POST' // 하지만 서버에서 DATA를 가져올때에는 비동기식으로 가져올 것을 생각해야만한다
}
})
apis / jsonPlaceholder.js
import axios from 'axios';
export default axios.create({
baseURL: 'https://jsonplaceholder.typicode.com'
}); // 단지 api를 보관하기위한 파일여러개의 export해서 사용이가능할 것이다
components / PostList.js
import React from 'react';
import { connect } from 'react-redux'; // react에서 리덕스로 combined된 state를 사용하기 위해서는 react-redux가 필요하고
// connect 메소드라는 함수로 연결을 해주어야한다
import { fetchPostsAndUsers } from '../actions';
import UserHeader from './UserHeader';
class PostList extends React.Component {
componentDidMount() {
this.props.fetchPostsAndUsers();
}
renderList(){
return this.props.posts.map(post => {
return (
<div className="item" key={post.id}>
<i className="large middle aligned icon user"></i>
<div className="content">
<div className="description">
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
<UserHeader userId={post.userId}/>
</div>
</div>
);
});
}
render() {
return <div className="ui relaxed divided list">{this.renderList()}</div>;
}
}
const mapStateToProps = (state) => {
return { posts : state.posts}
}
export default connect( // 일반적으로 첫 번째 인자값에는 store의 state를 인수로 받습니다.
mapStateToProps, // store의 state는 props와 맵핑되는 방식을 만들 객체를 반환하는
mapStateToProps메서드를 사용한다
{ fetchPostsAndUsers } )(PostList);
// 두 번째 인자값은 mapDispatchToProps는 dispatch action이 props와 맵핑되는 방식을 만들 유사한 객체를 반환한다
// fetchPostsAndUsers 로 정보를 받아와 componentDidMount에서 fetchPostsAndUsers ()를 실행시켜 액션을 취한다
//두 번째 괄호는 이 정보들을 사용할 클래스명을 쓴다
components / UserHeader.js
import React from 'react';
import { connect } from 'react-redux';
class UserHeader extends React.Component {
// componentDidMount() { // componentDidMount는 부모태그에서 넘겨주는 state만을 받아서 사용가능하다
// this.props.fetchUser(this.props.userId); // 그 이유는 componentDidMount는 랜더링이 끝나고 나서 처음 한번만 실행되기때문이다
// lodash추가로 DidMount 삭제 // 그래서 지금 한 번 실행될 때 fecthuser을 실행함으로써 액션을 실행하게 되고
그렇게 그 후 데이터를 불러온다
render() {
const { user } = this.props;
// const user = this.props.users.find(user => user.id ===this.props.userId);
이렇게 장문으로 해서 비교해서 id를 찾는 방법이있지만
// 부모에서 받아온 props와 dispatch된 props가 혼동될수 있으니 확인하여야한다
if (!user) { // 그래서 밑에 처럼 connect후 mapstateToprops를 통해 기존 ownProps와 state를 구분해서 작업이 가능하다
return null;
}
return <div className="header">{user.name}</div>
}
}
const mapStateToProps = (state, ownProps) => {
return { user: state.users.find(user => user.id === ownProps.userId) }; // 참고합시다
}
export default connect(mapStateToProps)(UserHeader);
// mapstateProps와 그 fetchuser 함수를 연결함으로써 정보를 userHeader에서 사용이가능
components / App.js
import React from 'react';
import PostList from './PostList';
const App = () => { // App은 모든 컴포넌트를 받아 전달하는 의미가 있다
return (
<div className="ui container">
<PostList />
</div>
);
}
export default App;