Title很愛學Redux
//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);
};
//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,
});
//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;
}
}
//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};
}
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);
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喔~