一步一步学习 ReactNative + Redux(5:异步Action)

来源:互联网 发布:节拍器软件电脑版 编辑:程序博客网 时间:2024/06/05 14:30

写在开始

经过多天的奋战,我们对 ReactNative 、 Redux 以及之间的数据流已经相当了解,并且我们还能在其中增加中间件,以实现延迟(异步)调用。http://www.jianshu.com/p/d7fadd77cbf5

源码:https://github.com/eylu/web-lib/tree/master/ReactReduxDemo/app_step5

开发

这里,我们将完成真正的异步请求!
我们的任务如下:
1、请求远程数据,并显示 TODO 列表(替代初始数据)
2、TODO 状态的切换,改为远程修改

服务端

服务端(server)使用的 Ruby 、Grape 做为 Api 接口,提供远程数据和操作。
你应该已经写好了类似于下面样式的接口了。

获取 TODO 列表: GET     http://192.168.x.x:3000/api/list_message            参数: 无新建 TODO    : POST    http://192.168.x.x:3000/api/add_message             参数: {message: 'TODO项目'}切换 TODO 状态: PUT     http://192.168.x.x:3000/api/toggle_message_status   参数: {id: TODO的id}

redux-thunk

这是一个已经写好的中间件,我们可以直接使用它。

安装 redux-thunk

npm install redux-thunk --save

使用 redux-thunk

import { createStore, applyMiddleware } from 'redux';import thunk from 'redux-thunk';const finalCreateStore = applyMiddleware(thunk)(createStore);   let store = finalCreateStore(reducers);

它可以代替完成我们自己写的中间件

var thunkMiddleware = function ({ dispatch, getState }) {    ...}

现在,我们来看一下目录结构与依赖包:

|--ReactReduxDemo    |--__tests__    |--android    |--app        |--actions            |--index.js        |--components            |--todo-filter.component.js            |--todo-form.component.js            |--todo-list.component.js        |--config            |--enum.js        |--containers            |--home.container.js        |--reducers            |--index.js        |--index.js    |--ios    |--node_modules    |--index.android.js    |--index.ios.js    |--package.json    |--...

package.json 依赖如下:

{    "name": "ReactReduxDemo",    "version": "0.0.1",    "private": true,    "scripts": {        "start": "node node_modules/react-native/local-cli/cli.js start",        "test": "jest"    },    "dependencies": {        "react": "15.4.1",        "react-native": "0.38.0",        "react-redux": "^4.4.6",        "redux": "^3.6.0",        "redux-thunk": "^2.1.0"    },    "jest": {        "preset": "react-native"    },    "devDependencies": {        "babel-jest": "17.0.2",        "babel-preset-react-native": "1.9.0",        "jest": "17.0.3",        "react-test-renderer": "15.4.1"    }}

请求远程 TODO 数据列表

我们要使用 redux-thunk 这个中间件。
我们把 fetch 封装一下 以便我们更能简单使用,让它来请求远程数据(新添加一个ActionCreator)

1、使用 redux-thunk

ReactReduxDemo/app/index.js 文件,修改如下:

import React, { Component } from 'react';import {    View,    StyleSheet,} from 'react-native';import { createStore, applyMiddleware } from 'redux';        import { Provider } from 'react-redux';import thunk from 'redux-thunk';                                  // 引入 thunkimport { FILITER_KEYS } from './config/enum';import reducers from './reducers/index';import HomeContainer from './containers/home.container';// var thunkMiddleware = function ({ dispatch, getState }) {        // 这里的中间件不再使用//     ...// }// 这是初始数据const initState = {    todos: [        {id:1,title:'吃早饭',status:true},        {id:2,title:'打篮球',status:false},        {id:3,title:'修电脑',status:false},    ],    filter: FILITER_KEYS.ALL,};const finalCreateStore = applyMiddleware(thunk)(createStore);        // 中间件替换为 thunklet store = finalCreateStore(reducers, initState);                   // let store = createStore(reducers, initState);export default class RootWrapper extends Component{    render(){        return (            <Provider store={store}>                <View style={styles.wrapper}>                    <HomeContainer />                </View>            </Provider>        );    }}const styles = StyleSheet.create({    wrapper: {        flex: 1,        marginTop: 20,    },});

2、封装 Fetch

这里是对 fetch 的简单封装,它接收 action 、params(Options) 、method(Options,http method,默认为GET)。
当然,你会有你自己的服务端接口,所以,你应该封装为适合自己服务端的调用方式。

新建文件 ReactReduxDemo/app/utils/fetch-data.js,如下:

export default function fetchData(action, params, method='get'){    return fetch('http://192.168.1.196:3000/api/'+action,{        method: method,        headers: {            'Accept': 'application/json, text/plain, */*',            'Content-Type': 'application/json'        },        body: params ? JSON.stringify(params) : null    })    .then((response)=>{        return response.json()    });}

3、调用远程数据

fetch 工具封装好,中间件 thunk 已使用。我们可以调用远程数据了。数据的提供是在容器组件,所以我们修改容器组件 HomeContainer

ReactReduxDemo/app/containers/home.container.js 文件,修改如下:

import React, { Component } from 'react';import {    View,    Text} from 'react-native';import { connect } from 'react-redux';import { FILITER_KEYS } from '../config/enum';import { initTodoList, changeTodoStatus, addNewTodo, filterTodoList } from '../actions/index';     // 引入新的 ActionCreator :initTodoListimport TodoFormComponent from '../components/todo-form.component';import TodoListComponent from '../components/todo-list.component';import TodoFilterComponent from '../components/todo-filter.component';class HomeContainer extends Component{    constructor(props){        super(props);    }    componentDidMount(){                   // 组件 mount 之后调用数据        let { dispatch } = this.props;        dispatch(initTodoList());          // 执行调用方法    }    addTodo(text){        let { dispatch } = this.props;        dispatch(addNewTodo(text));    }    toggleTodo(id){        let { dispatch } = this.props;        dispatch(changeTodoStatus(id));    }    filterTodo(filter){        let { dispatch } = this.props;        dispatch(filterTodoList(filter));    }    render(){        return (            <View>                <TodoFormComponent addTodo={(text)=>{this.addTodo(text)}} />                <TodoListComponent todoList={this.props.todoList} toggleTodo={(id)=>{this.toggleTodo(id)}} />                <TodoFilterComponent filter={this.props.currentFilter} filterTodo={(filter)=>{this.filterTodo(filter)}} />            </View>        );    }}const getFilterTodos = (todos, filter) => {  switch (filter) {    case FILITER_KEYS.ALL:      return todos;    case FILITER_KEYS.UNDO:      return todos.filter( todo => !todo.status);    case FILITER_KEYS.FINISH:      return todos.filter( todo => todo.status);    default:      throw new Error('Unknown filter: ' + filter);  }}// 基于全局 state ,哪些 state 是我们想注入的 propsfunction mapStateToProps(state){    var list = getFilterTodos(state.todos, state.filter);    return {        todoList: list,        currentFilter: state.filter,    }}export default connect(mapStateToProps)(HomeContainer);

4、添加新的 ActionCreator

ReactReduxDemo/app/actions/index.js 文件,修改如下:

/*********************************** action 类型常量 *************************************/import fetchData from '../utils/fetch-data';            // 引入 fetch 工具export const INIT_TODO_LIST = 'INIT_TODO_LIST';         // 定义新的 action 类型/** * 更改 TODO 状态 * @type {String} */export const TOGGLE_TODO_STATUS = 'TOGGLE_TODO_STATUS';export const ADD_NEW_TODO = 'ADD_NEW_TODO';export const SET_FILTER = 'SET_FILTER';/*********************************** action 创建函数 *************************************/export function initTodoList(){                       // 定义新的 action creator    return function(dispatch){        fetchData('list_message').then((data)=>{      // 使用 fetch 调用远程数据            dispatch({                                // 执行 dispatch(action)                type: INIT_TODO_LIST,                list: data,            })        });    }}/** * 更改 TODO 状态 * @param  {Number} id TODO索引 * @return {Object}       action */export function changeTodoStatus(id){    return function (dispatch){                                setTimeout(()=>{                                           dispatch({type: TOGGLE_TODO_STATUS, id});          }, 2000);    }    // return {type: TOGGLE_TODO_STATUS, id};}export function addNewTodo(text){    return {type: ADD_NEW_TODO, text};}export function filterTodoList(filter){    return {type: SET_FILTER, filter};};

5、修改 reducer

ActionCreator 已经写好,可以执行 dispatch,并且可以获取 fetch 远程数据了。我们还要响应 reducer ,以将数据显示出来。

import { combineReducers } from 'redux';import { INIT_TODO_LIST, TOGGLE_TODO_STATUS, ADD_NEW_TODO, SET_FILTER } from '../actions/index';    // 引入 INIT_TODO_LISTfunction todoList(state=[], action){    switch(action.type){        case INIT_TODO_LIST:                             // 添加新的 action 类型分支,INIT_TODO_LIST            return [                ...state,                ...action.list.map((todo)=>{ return {                    id: todo.id,                    title: todo.title,                    status: todo.status,                }})            ];        case TOGGLE_TODO_STATUS:            var index = state.findIndex((todo)=>{ return todo.id==action.id });            var todo = state.find((todo)=>{ return todo.id==action.id });            return [                ...state.slice(0, index),                Object.assign({}, todo, {                  status: !todo.status                }),                ...state.slice(index + 1)            ];        case ADD_NEW_TODO:            return [                ...state,                {                    id: state.length+1,                    title: action.text,                    status: false,                }            ];        default :            return state;    }}function setFilter(state='', action){    switch(action.type){        case SET_FILTER:            return action.filter;        default :            return state;    }}const reducers = combineReducers({    todos: todoList,    filter: setFilter,});export default reducers;

运行项目,看看是否能够显示新的数据了?如果显示,OK,太棒了。


Paste_Image.png

好了,我们将 store 初始数据 todos 删掉吧

ReactReduxDemo/app/index.js 文件,修改如下:

....// 这是初始数据const initState = {    // todos: [                                     // 这里的数据注释掉    //     {id:1,title:'吃早饭',status:true},    //     {id:2,title:'打篮球',status:false},    //     {id:3,title:'修电脑',status:false},    // ],    filter: FILITER_KEYS.ALL,};....

TODO 状态切换,调用远程接口

记得之前我们的 TODO 状态切换是同步执行,之后,修改为延迟执行(setTimeout)。
现在,我们要把它修改为调用远程接口。

1、修改 ActionCreator

我们要把 changeTodoStatus 这个 ActionCreator 的 setTimeout 修改为 fetchData 来调用远程接口。
我们要调用 toggle_message_status 这个接口,传递的数据为 {id: TODO的id} ,使用的 HttpMethod 为 PUT
远程数据会返回 TODO 的当前 status,所以,我们会将 dispatch 稍微修改一下。

ReactReduxDemo/app/actions/index.js 文件,修改如下:

/*********************************** action 类型常量 *************************************/import fetchData from '../utils/fetch-data';export const INIT_TODO_LIST = 'INIT_TODO_LIST';/** * 更改 TODO 状态 * @type {String} */export const TOGGLE_TODO_STATUS = 'TOGGLE_TODO_STATUS';export const ADD_NEW_TODO = 'ADD_NEW_TODO';export const SET_FILTER = 'SET_FILTER';/*********************************** action 创建函数 *************************************/export function initTodoList(){    return function(dispatch){        fetchData('list_message').then((data)=>{            dispatch({                type: INIT_TODO_LIST,                list: data,            })        });    }}/** * 更改 TODO 状态 * @param  {Number} id TODO索引 * @return {Object}       action */export function changeTodoStatus(id){    return function (dispatch){        fetchData('toggle_message_status', { id: id}, 'PUT').then((data)=>{        // 把 setTimeout 修改为 fetchData            dispatch({type: TOGGLE_TODO_STATUS, status: data, id})        });        // setTimeout(()=>{        //     dispatch({type: TOGGLE_TODO_STATUS, id});        // }, 2000);    }    // return {type: TOGGLE_TODO_STATUS, id};}export function addNewTodo(text){    return {type: ADD_NEW_TODO, text};}export function filterTodoList(filter){    return {type: SET_FILTER, filter};};

2、修改 reducer

我们通过远程接口修改了 TODO 的状态,并返回了最新的状态。
我们要将 reducer 中对应的操作修改,不再只是简单的状态相反操作,而是使用 action中所带来的状态。

import { combineReducers } from 'redux';import { INIT_TODO_LIST, TOGGLE_TODO_STATUS, ADD_NEW_TODO, SET_FILTER } from '../actions/index';function todoList(state=[], action){    switch(action.type){        case INIT_TODO_LIST:            return [                ...state,                ...action.list.map((todo)=>{ return {                    id: todo.id,                    title: todo.title,                    status: todo.status,                }})            ];        case TOGGLE_TODO_STATUS:            var index = state.findIndex((todo)=>{ return todo.id==action.id });            var todo = state.find((todo)=>{ return todo.id==action.id });            return [                ...state.slice(0, index),                Object.assign({}, todo, {                  status: action.status                    // action 所对应 id 的 TODO 状态(status),修改为 action 数据中的 status                }),                ...state.slice(index + 1)            ];        case ADD_NEW_TODO:            return [                ...state,                {                    id: state.length+1,                    title: action.text,                    status: false,                }            ];        default :            return state;    }}function setFilter(state='', action){    switch(action.type){        case SET_FILTER:            return action.filter;        default :            return state;    }}const reducers = combineReducers({    todos: todoList,    filter: setFilter,});export default reducers;

TODO 状态的远程切换到这里就OK了。运行项目,看看是否依然可以切换状态呢?


Paste_Image.png

恭喜你,对异步Action又熟悉了许多!!!

0 0
原创粉丝点击