728x90
반응형

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;

 

 

 

 

 

 

728x90
반응형
728x90
반응형

components/SongList.js

import React, { Component } from 'react';

import { connect } from 'react-redux';

import { selectSong } from '../actions';

 

class SongList extends Component {

  renderList() {

    return this.props.songs.map(song => {

      return (

        <div className="item" key={song.title}>

          <div className="right floated content">

            <button

              className="ui button primary"

              onClick={() => this.props.selectSong(song)}

            >                                                           // selectSong()mapDispatchToProps함수를 통해

                                                                              현 클래스의 property에 추가된다 

              Select

            </button>

          </div>

          <div className="content">{song.title}</div>

        </div>

      );

    });

  }

 

  render() {

    return <div className="ui divided list">{this.renderList()}</div>;

  }

}

 

const mapStateToProps = state => {

  return { songs: state.songs };

};

 

export default connect(

  mapStateToProps,

  { selectSong }

)(SongList);

 

728x90
반응형
728x90
반응형

 

actions/index.js

import _ from 'lodash';

import jsonPlaceholder from '../apis/jsonPlaceholder';

 

export const fetchPostsAndUsers = () => async (dispatch, getState) => {

  await dispatch(fetchPosts());

 

  _.chain(getState().posts)

    .map('userId')

    .uniq()

    .forEach(id => dispatch(fetchUser(id)))

    .value();

};

 

export const fetchPosts = () => async dispatch => {

  const response = await jsonPlaceholder.get('/posts');

 

  dispatch({ type: 'FETCH_POSTS', payload: response.data });

};

 

export const fetchUser = id => async dispatch => {

  const response = await jsonPlaceholder.get(`/users/${id}`);

 

  dispatch({ type: 'FETCH_USER', payload: response.data });

};

 

 

 

728x90
반응형

+ Recent posts

Powered by Tistory, Designed by wallel