redux 使用
2017-12-05 本文已影响17人
日不落000
package.json
"dependencies": {
"react-redux": "^5.0.6",
"redux": "^3.7.2",
"redux-thunk": "^2.2.0",
}
"devDependencies": {
"remote-redux-devtools": "^0.5.12",
"remote-redux-devtools-on-debugger": "^0.8.3",
},
import { Provider } from "react-redux";
import store from "./store/store.js";
<Provider store={store}>
<MyRootComponent />
</Provider>
./store/store.js
/**
* Created by nick on 2017/12/4.
*/
import allReducers from "../reducers/index.js";
import { applyMiddleware, createStore } from "redux";
/**
* Logs all actions and states after they are dispatched.
*/
const logger = store => next => action => {
console.group(action.type);
console.info('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
console.groupEnd(action.type);
return result;
};
/**
* Sends crash reports as state is updated and listeners are notified.
*/
// const crashReporter = store => next => action => {
// try {
// return next(action)
// } catch (err) {
// console.error('Caught an exception!', err)
// Raven.captureException(err, {
// extra: {
// action,
// state: store.getState()
// }
// })
// throw err
// }
// }
/**
* Schedules actions with { meta: { delay: N } } to be delayed by N milliseconds.
* Makes `dispatch` return a function to cancel the timeout in this case.
*/
const timeoutScheduler = store => next => action => {
if (!action.meta || !action.meta.delay) {
return next(action);
}
let timeoutId = setTimeout(
() => next(action),
action.meta.delay
);
return function cancel() {
clearTimeout(timeoutId);
};
};
/**
* Schedules actions with { meta: { raf: true } } to be dispatched inside a rAF loop
* frame. Makes `dispatch` return a function to remove the action from the queue in
* this case.
*/
const rafScheduler = store => next => {
let queuedActions = [];
let frame = null;
function loop() {
frame = null;
try {
if (queuedActions.length) {
next(queuedActions.shift());
}
} finally {
maybeRaf();
}
}
function maybeRaf() {
if (queuedActions.length && !frame) {
frame = requestAnimationFrame(loop);
}
}
return action => {
if (!action.meta || !action.meta.raf) {
return next(action);
}
queuedActions.push(action);
maybeRaf();
return function cancel() {
queuedActions = queuedActions.filter(a => a !== action);
};
};
};
/**
* Lets you dispatch promises in addition to actions.
* If the promise is resolved, its result will be dispatched as an action.
* The promise is returned from `dispatch` so the caller may handle rejection.
*/
const vanillaPromise = store => next => action => {
if (typeof action.then !== 'function') {
return next(action);
}
return Promise.resolve(action).then(store.dispatch);
};
/**
* Lets you dispatch special actions with a { promise } field.
*
* This middleware will turn them into a single action at the beginning,
* and a single success (or failure) action when the `promise` resolves.
*
* For convenience, `dispatch` will return the promise so the caller can wait.
*/
const readyStatePromise = store => next => action => {
if (!action.promise) {
return next(action);
}
function makeAction(ready, data) {
let newAction = Object.assign({}, action, { ready }, data);
delete newAction.promise;
return newAction;
}
next(makeAction(false));
return action.promise.then(
result => next(makeAction(true, { result })),
error => next(makeAction(true, { error }))
);
};
/**
* Lets you dispatch a function instead of an action.
* This function will receive `dispatch` and `getState` as arguments.
*
* Useful for early exits (conditions over `getState()`), as well
* as for async control flow (it can `dispatch()` something else).
*
* `dispatch` will return the return value of the dispatched function.
*/
const thunk = store => next => action =>
typeof action === 'function'
? action(store.dispatch, store.getState)
: next(action);
// You can use all of them! (It doesn't mean you should.)
let store = createStore(
allReducers,
applyMiddleware(
rafScheduler,
timeoutScheduler,
thunk,
vanillaPromise,
readyStatePromise,
logger
)
);
module.exports = store;
todoActionCreators.js
/**
* Created by nick on 2017/12/4.
*/
export const ADD_TODO = 'ADD_TODO';
export const SET_INPUT_TEXT = 'SET_INPUT_TEXT';
/*
* action creators
*/
export function addTodo(text) {
return { type : ADD_TODO, text };
}
export function setInputText(text) {
return { type : SET_INPUT_TEXT, text };
}
//todo async
export const REQUEST_POSTS = 'REQUEST_POSTS';
function requestPosts(subreddit) {
return {
type : REQUEST_POSTS,
subreddit
};
}
export const RECEIVE_POSTS = 'RECEIVE_POSTS';
function receivePosts(subreddit, json) {
return {
type : RECEIVE_POSTS,
subreddit,
posts : json.data.children.map(child => child.data),
receivedAt : Date.now()
};
}
// 来看一下我们写的第一个 thunk action creator!
// 虽然内部操作不同,你可以像其它 action creator 一样使用它:
// store.dispatch(fetchPosts('reactjs'))
export function fetchPostsOld(subreddit) {
// Thunk middleware 知道如何处理函数。
// 这里把 dispatch 方法通过参数的形式传给函数,
// 以此来让它自己也能 dispatch action。
return function (dispatch) {
// 首次 dispatch:更新应用的 state 来通知
// API 请求发起了。
dispatch(requestPosts(subreddit))
// thunk middleware 调用的函数可以有返回值,
// 它会被当作 dispatch 方法的返回值传递。
// 这个案例中,我们返回一个等待处理的 promise。
// 这并不是 redux middleware 所必须的,但这对于我们而言很方便。
return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
.then(response => response.json())
.then(json =>
// 可以多次 dispatch!
// 这里,使用 API 请求结果来更新应用的 state。
dispatch(receivePosts(subreddit, json))
)
// 在实际应用中,还需要
// 捕获网络请求的异常。
}
}
export function fetchPosts(subreddit) {
// Thunk middleware 知道如何处理函数。
// 这里把 dispatch 方法通过参数的形式传给函数,
// 以此来让它自己也能 dispatch action。
// OtherApi.postInfoNormalNoAccessTokenTestRedux(`http://www.subreddit.com/r/${subreddit}.json`,{},(jsonObj)=>{
// console.log(jsonObj);
// })
//
// return;
return async function (dispatch) {
// 首次 dispatch:更新应用的 state 来通知
// API 请求发起了。
dispatch(requestPosts(subreddit));
// thunk middleware 调用的函数可以有返回值,
// 它会被当作 dispatch 方法的返回值传递。
// 这个案例中,我们返回一个等待处理的 promise。
// 这并不是 redux middleware 所必须的,但这对于我们而言很方便。
// return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
// .then(response => response.json())
// .then(json =>
//
// // 可以多次 dispatch!
// // 这里,使用 API 请求结果来更新应用的 state。
//
// dispatch(receivePosts(subreddit, json))
// );
//
let responseJson = await postAsync(`http://www.subreddit.com/r/${subreddit}.json`, {}, subreddit, dispatch)
dispatch(receivePosts(subreddit, responseJson))
return;
// 在实际应用中,还需要
// 捕获网络请求的异常。
};
}
async function postAsync(url, bodyObject, subreddit, dispatch) {
let headers = {
'Accept' : 'application/json',
'Content-Type' : 'application/json',
};
let response
= await
fetch(url, {
method : 'POST',
headers : headers,
body : JSON.stringify(bodyObject),
});
console.log(response);
let responseJson
= await
response.json();
console.log(responseJson);
// console.log(StringUtil.object2Json(responseJson));
return responseJson;
};
./reducers/todoReducer.js
/**
* Created by nick on 2017/12/5.
*/
import { combineReducers } from "redux";
import { ADD_TODO, RECEIVE_POSTS, SET_INPUT_TEXT, } from "../actions/todoActionCreators";
function header_input_value(state = 'header_input_value', action) {
switch (action.type) {
case SET_INPUT_TEXT:
return action.text;
default:
return state;
}
}
function receivePost(state = {}, action) {
switch (action.type) {
case RECEIVE_POSTS:
return action;
default:
return state;
}
}
function todos(state = [ { text : "Use Redux11", completed : false } ], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text : action.text,
completed : false
}
];
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed : !todo.completed
});
}
return todo;
});
default:
return state;
}
}
const todoApp = combineReducers({
header_input_value,
receivePost,
todos
});
export default todoApp;
./reducers/index.js
import { combineReducers } from "redux";
import todoReducer from "./todoReducer.js";
const allReducers = combineReducers({
todoReducer,
});
export default allReducers;
ZZTestReduxTodoListScreen.js
/**
* Created by nick on 2017/4/20.
* https://redux.js.org/docs/basics/ExampleTodoList.html
*/
import React, { Component } from "react";
import { Container, Content } from "native-base";
import ConstantUtil from "../utils/ConstantUtil";
import { InteractionManager, Text, View } from "react-native";
import BaseCommon from "../common/BaseCommon";
import HeaderNormalWithRightButtonAndSearch from "../components/HeaderNormalWithRightButtonAndSearch";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as todoActionCreators from "../actions/todoActionCreators";
import MyButton from "../components/MyButton";
import * as StyleUtil from "../utils/StyleUtil";
class TodoList extends Component {
render() {
let { todos } = this.props;
let _view = null;
_view = (
<View>
{todos && todos.length > 0 ? todos.map(
(item, i) => {
return (
<Text key={`key_${i}`}>{item.text}</Text>
);
}
)
: null
}
</View>
);
return _view;
}
}
class ZZTestReduxTodoListScreen extends Component {
static propTypes = {
header_title : React.PropTypes.string, //
};
static defaultProps = {
header_title : '',
};
constructor(props) {
super(props);
const { dispatch } = props;
// Here's a good use case for bindActionCreators:
// You want a child component to be completely unaware of Redux.
// We create bound versions of these functions now so we can
// pass them down to our child later.
this.boundActionCreators = bindActionCreators(todoActionCreators, dispatch)
console.log(this.boundActionCreators)
// {
// addTodo: Function,
// removeTodo: Function
// }
this.state = {
};
}
componentDidMount() {
console.log(this);
let { actions } = this.props
// Note: this won't work:
// TodoActionCreators.addTodo('Use Redux')
// You're just calling a function that creates an action.
// You must dispatch the action, too!
// This will work:
actions.addTodo('Use Redux')
}
componentWillMount() {
}
componentWillUnmount() {
}
componentWillReceiveProps(nextProps) {
}
render() {
let { header_input_value, receivePost } = this.props;
let { setInputText, fetchPosts } = this.props.actions;
let _viewHeader = null;
_viewHeader = (
<HeaderNormalWithRightButtonAndSearch
_rightBtnShouldShow={true}
_textBtn={'添 加'}
_onPressBtn={() => {
let { actions } = this.props
// Note: this won't work:
// TodoActionCreators.addTodo('Use Redux')
// You're just calling a function that creates an action.
// You must dispatch the action, too!
// This will work:
actions.addTodo(header_input_value)
}}
_inputPlaceHolder={this.props.header_input_place_holder}
_inputDefaultValue={header_input_value}
_inputValue={header_input_value}
_onChange={(value) => {
setInputText(value);
}}
/>
);
let { todos } = this.props
return (
<Container style={{ backgroundColor : ConstantUtil.colors.bgGray }}>
{_viewHeader}
<Content
keyboardShouldPersistTaps='always'
// keyboardDismissMode={'on-drag'}
showsVerticalScrollIndicator={false}
contentContainerStyle={{
justifyContent : 'center',
alignItems : 'stretch',
flex : 1,
}}>
<MyButton
textStyle={[ StyleUtil.gStyles.gButtonTextWhiteDefault, ]}
buttonStyle={[ StyleUtil.gStyles.gButtonBlueDefault, {}, ]}
onClick={() => {
fetchPosts(header_input_value);
}}
>
<Text> 网络请求 </Text>
</MyButton>
<Text>{receivePost.receivedAt}</Text>
<Text style={{ width : StyleUtil.size.width, height : 100, }}>{JSON.stringify(receivePost)}</Text>
<TodoList todos={todos} {...this.boundActionCreators} />
</Content>
</Container>
);
}
}
function mapStateToProps(state) {
return {
todos : state.todoReducer.todos,
header_input_value : state.todoReducer.header_input_value,
receivePost : state.todoReducer.receivePost,
};
}
function mapDispatchToProps(dispatch) {
return { actions : bindActionCreators(todoActionCreators, dispatch) };
}
export default connect(mapStateToProps, mapDispatchToProps)(ZZTestReduxTodoListScreen);