iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 27
0
Modern Web

RRRR的世界 (Ruby on Rails + React + Redux)系列 第 27

Day 27, Example: Reading List

Title很愛學Redux

SharedReduxStore, rails, react都認識他

//client/app/bundles/stores/SharedReduxStore.jsx
import { combineReducers, applyMiddleware, createStore, compose } from 'redux';
import thunk from 'redux-thunk';

import reducers from '../reducers';
import { initialStates } from '../reducers';

export default (props, railsContext) => {
  const combinedReducer = combineReducers(reducers);
  const newProps = { ...props, railsContext };
  const { $$readingListState } = initialStates;
  const initialState = {
    $$readingListStore: $$readingListState.merge({
      ...props,
			railsContext
    }),
  };
	const store = createStore(combinedReducer, 
							  initialState, 
							  compose(
                                applyMiddleware(thunk), 
							    window.devToolsExtension ? window.devToolsExtension() : f => f
                              )
							)
  return store;
  //return applyMiddleware(middleware,)(createStore)(combinedReducer, initialState);
};

前端Javascript進入點

//client/app/bundles/startup/HelloWorldApp.jsx
import React from 'react';
import ReactOnRails from 'react-on-rails';
import { Provider } from 'react-redux';
import ReadingList from '../containers/ReadingList';
import SharedReduxStore from '../stores/SharedReduxStore';
import ReadingListContainer from '../containers/ReadingListContainer';
const ReadingListApp = (props, _railsContext) => {
  const store = ReactOnRails.getStore('SharedReduxStore');
  const reactComponent = (
    <Provider store={store}>
			<ReadingListContainer />
    </Provider>
  );
  return reactComponent;
};
ReactOnRails.register({ ReadingListApp });
//註冊(介紹)SharedReduxStore給Rails認識
ReactOnRails.registerStore({
	SharedReduxStore,
});

Reducer,限制誰該改什麼State

//client/app/bundles/reducers/index.jsx
import readingListReducer from './readingListReducer';
import { $$initialState as $$readingListState } from './readingListReducer';

export default {
  $$readingListStore: readingListReducer,
};

export const initialStates = {
  $$readingListState,
};

//client/app/bundles/reducers/readingListReducer.jsx
import Immutable from 'immutable';
import actionTypes from '../constants/readingListConstants';

export const $$initialState = Immutable.fromJS({
  books: [], // this is the default state that would be used if one were not passed into the store
  railsContext: {}, // this is the default state that would be used if one were not passed into the store
});

export default function readingListReducer($$state = $$initialState, action) {
  const { type, books } = action;
  switch (type) {
    case actionTypes.LOAD_BOOKS:
      return $$state.set('books',Immutable.fromJS(books));
    case actionTypes.UPDATE_BOOK:
    case actionTypes.DELETE_BOOK:
    case actionTypes.UPDATE_BOOK_STATUS:
    default:
      return $$state;
  }
}

Action,有哪些動作然後動作長怎樣

//client/app/bundles/constants/readingListConstants.jsx
import mirrorCreator from 'mirror-creator';

const actionTypes = mirrorCreator([
  'LOAD_BOOKS',
  'DELETE_BOOK',
  'UPDATE_BOOK',
  'UPDATE_BOOK_STATUS',
]);

// actionTypes = {HELLO_WORLD_NAME_UPDATE: "HELLO_WORLD_NAME_UPDATE"}
// Notice how we don't have to duplicate HELLO_WORLD_NAME_UPDATE twice
// thanks to mirror-creator.
export default actionTypes;

//-----------------------------------------------------------------

//client/app/bundles/actions/ReadingListActionCreators.jsx
import actionTypes from '../constants/readingListConstants';
import {DOM as RxDOM} from 'rx-dom';

export function allBooks(settings){
	return (dispatch) => {
		return dispatch(sendAJAX(settings, actionTypes.LOAD_BOOK))
	}
//  return {
//    type: actionTypes.LOAD_BOOKS,
//    books,
//  };
}
export function updateBook(settings){
	return (dispatch) => {
		return dispatch(sendAJAX(settings, actionTypes.UPDATE_BOOK))
	}
//  return {
//    type: actionTypes.UPDATE_BOOK,
//    books,
//  };
}
export function updateBookStatus(settings){
	return (dispatch) => {
		return dispatch(sendAJAX(settings, actionTypes.UPDATE_BOOK_STATUS))
	}
//  return {
//    type: actionTypes.UPDATE_BOOK_STATUS,
//    books,
//  };
}
export function deleteBook(settings){
	return (dispatch) => {
		return dispatch(sendAJAX(settings, actionTypes.DELETE_BOOK))
	}
}
function sendAJAX(settings, type){
    return (dispatch) => {
			return RxDOM.ajax(settings)
				 .subscribe(
				   (data) => {
						 return dispatch(loadBooks(data.response.books));
					 }
					 ,
					 () => {}
				 );
		}
}
function loadBooks(books){
	return {type: actionTypes.LOAD_BOOKS, books: books};
}

Container元件,把收到的資料改成props

ReadngListContainer.jsx

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Immutable from 'immutable';

import ReadingList from './ReadingList';

import * as readingListActions from '../actions/ReadingListActionCreators';

const ReadingListContainer = ({ actions, books, railsContext }) => {
  return <ReadingList {...{ actions, books, railsContext }} />
};
ReadingListContainer.propTypes = {
  actions: PropTypes.object.isRequired,
  books: PropTypes.array.isRequired,
  railsContext: PropTypes.object.isRequired,
};

function mapStateToProps(state) {
	let { $$readingListStore } = state
  return {
    books: $$readingListStore.get("books").toJS(),
    railsContext: $$readingListStore.get("railsContext").toObject(),
  };
}

function mapDispatchToProps(dispatch) {
  return { actions: bindActionCreators(readingListActions, dispatch) };
}

// Don't forget to actually use connect!
export default connect(mapStateToProps, mapDispatchToProps)(ReadingListContainer);

ReadingList.jsx

import React, { PropTypes } from 'react';
import { connect } from 'react-redux'; 
import ReadingListWidget from '../components/ReadingListWidget';
import {DOM as RxDOM} from 'rx-dom';
import * as ReadingListActions from '../actions/ReadingListActionCreators'
let propTypes = {
  books: PropTypes.array.isRequired,
};
class ReadingList extends React.Component {
  constructor(props, context) {
    super(props, context);
		console.log(props)
    this.allBooks = this.allBooks.bind(this);
  }
  componentDidMount(){
  }
  allBooks(bookAttributes) {
    let settings = { url: Routes.books_path(), responseType: 'json'}
		this.props.allBook(settings)
  }
  updateBook(bookAttributes, id) {
    let settings = id == 0? { url: Routes.books_path(), responseType: 'json', method: 'POST', body: bookAttributes} :
                          { url: Routes.book_path(id), responseType: 'json', method: 'PUT', body: bookAttributes}
		this.props.updateBook(settings)
  }
  updateBookStatus(id, status) {
    let settings = {
      url: Routes.status_book_path(id),
      responseType: 'json',
      method: 'POST',
      headers: {
        'X-Requested-With': 'RxJS',
        'Content-Type': 'application/json;charset=utf-8'
      },
      body: JSON.stringify({book:{status: status}})
    }
		this.props.updateBookStatus(settings)
  }
  deleteBook(id) {
    let settings = { url: Routes.book_path(id), method: 'DELETE'}
		this.props.deleteBook(settings)
  }
  render() {
    return (
      <div>
        <ReadingListWidget books={this.props.books}
                           updateBook={(attributes, id=0) => this.updateBook(attributes, id)}
                           updateBookStatus={(id, status) => this.updateBookStatus(id, status)}
                           deleteBook={(id) => this.deleteBook(id)}/>
      </div>
    );
  }
}
ReadingList.propTypes = propTypes;
export default connect(null, ReadingListActions)(ReadingList);

因為其他像是Books.jsx, ReadingListWidget.jsx, BookInputWidget.jsx這些component屬於Presentational Components,不太需要知道Redux的存在,大致上就沒有調整了,都跟之前React的部分一樣。

到了今天,RRRR主題順利結束。
剩下的三天會講一下很多人在討論的state該放在哪、deploy上heroku,然後做個總結。
先這樣啦! 要範例可以看我Github喔~


上一篇
Day 26, Redux Store 中控室
下一篇
Day 28, State in Redux or React? 搞混了嗎~
系列文
RRRR的世界 (Ruby on Rails + React + Redux)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言