over 3 years ago

React Redux 使用 redux-thunk 做 Async Actions

準備 package.json
使用 react-redux 及 redux-thunk

{
  "name": "reduxthunk",
  "version": "0.0.1",
  "description": "a todo app that you'd never want to use",
  "main": "index.js",
  "scripts": {
    "test": " ",
    "start": "webpack-dev-server --devtool eval --progress --colors --hot --inline"
  },
  "keywords": [
    "test"
  ],
  "author": "Jason Wang",
  "license": "ISC",
  "dependencies": {
    "react": "^0.14.2",
    "react-dom": "^0.14.2",
    "react-redux": "^4.0.0",
    "redux-thunk": "^2.0.1",
    "redux": "^3.0.4",
    "webpack": "^1.12.6",
    "webpack-dev-server": "^1.12.1"
  },
  "devDependencies": {
    "babel-core": "^6.1.21",
    "babel-loader": "^6.2.0",
    "babel-preset-es2015": "^6.1.18",
    "babel-preset-react": "^6.1.18"
  }
}

配置 webpack.config.js

var path = require("path");
module.exports = {
  entry: './components/app.js',
  output: {
    path: path.resolve(__dirname, "build"),
    publicPath: "/",
    filename: "bundle.js"
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /(node_modules)/,
        loader: 'babel',
        query: {
          presets: ['react', 'es2015']
        }
      }
    ]
  },
  resolve: {
    extensions: ['', '.js', '.jsx']
  }
};

安裝

npm install

建立 action \actions\index.js

export function addReposAction(jsonResult) {
  return {
    type: "ADD_TWEETS",
    repos: jsonResult
  }
}
export function userChangedAction(value) {
  return {
    type: "USER_CHANGED",
    value: value
  }
}
export function loadingChangedAction(isLoading) {
  return {
    type: "IS_LOADING",
    isLoading: isLoading
  }
}
export function loadReposAction() {
  return function(dispatch, getState) {
    var state = getState();
    var url = "https://api.github.com/users/" + state.user + "/repos";
    dispatch(loadingChangedAction(true));

    return fetch(url)
      .then(function(result) {
        dispatch(loadingChangedAction(false));

        if (result.status === 200) {
          return result.json();
        }

        throw "request failed";
      })
      .then(function(jsonResult) {
        dispatch(addReposAction(jsonResult));
      })
      .catch(function(err) {
        alert("Oops...Couldn't fetch repos for user: " + state.user, "error");
      });
  }
}

thunk function -> loadReposAction()
使用 fetch 如果瀏覽器不支援可改用 isomorphic-fetch
呼叫 Api "https://api.github.com/users/" + state.user + "/repos" 取得 user 的 github repos
並於於內部執行 dispatch(loadingChangedAction(...)) 及 dispatch(addReposAction(jsonResult)) 來更新 component

建立 \reducers\rootReducer.js

function initialState() {
  return {
    user: "",
    repos: [],
    isLoading: false
  }
}
export default (state, action)  => {
  var previousState = (state ? state : initialState());
  switch (action.type) {
    case "ADD_TWEETS":
      return addTweets(previousState, action);
      break;
    case "USER_CHANGED":
      return userChanged(previousState, action);
      break;
    case "IS_LOADING":
      return isLoadingChanged(previousState, action);
    default:
      return previousState;
  }
}

function addTweets(state, action) {
  return {
    isLoading: state.isLoading,
    user: state.user,
    repos: action.repos
  }
}

function userChanged(state, action) {
  return {
    user: action.value,
    repos: [],
    isLoading: state.isLoading
  };
}

function isLoadingChanged(state, action) {
  return {
    user: state.user,
    repos: state.repos,
    isLoading: action.isLoading
  };
}

建立 store.js

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/rootReducer';

let store =  applyMiddleware(thunk)(createStore)(rootReducer);
export default store;

使用 applyMiddleware 綁定 reducers

建立 \components\rootComponent.js

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

import * as Actions from '../actions';

class rootComponent extends Component {
  constructor(props) {
    super(props);
    this.loadRepos = this.loadRepos.bind(this);
  }
  loadRepos() {
     const {state, actions} = this.props;
     let user = this.refs.user.value;
     actions.userChangedAction(user);
     actions.loadReposAction();
  }
  render() {
    const {state, actions} = this.props;
    return (
      <div>
        <input ref="user" id="user" type="text"  />
        <input type="button" value="load" onClick={this.loadRepos} />
        {(state.isLoading) ? <div>load....</div> : state.repos.map((repo, index) => <div key={index}>{repo.name}</div>)}
      </div>
    )
  }
}
function mapStateToProps(state) {
  return {
    state
  }
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Actions, dispatch)
  }
}
export default connect(mapStateToProps, mapDispatchToProps)(rootComponent);

於 rootComponent->loadRepos() 中依序執行
actions.userChangedAction(user);
actions.loadReposAction();

建立 \components\app.js

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import store from '../store'
import RootComponent from './rootComponent'

let reactElement = document.getElementById('react')
render(
  <Provider store={store}>
    <RootComponent />
  </Provider>,
  reactElement
)

執行

npm start

結果




參考網址
http://nojaf.com/2015/12/06/redux-thunk/
https://davidwalsh.name/fetch
https://camsong.github.io/redux-in-chinese/docs/advanced/AsyncActions.html

← React Redux 使用 Chrome Redux DevTools Extension 監測 state React 使用 babel-plugin-transform-react-inline-elements 優化 runtime performance →
 
comments powered by Disqus