Redux使用总结

xiaohesong edited this page Nov 13, 2017 · 14 revisions

react-redux

使用前得需要安装

npm i --save react-redux redux redux-thunk
  1. 先创建actions 获取用户列表的相关操作.
// src/actions/users/index.js
import * as usersTypes from '../../constants/users/ActionTypes';

export const queryUsersSuccess = users => ({ type: usersTypes.QUERY_USERS_SUCCESS, users })

export const queryUsers = () => {
  return dispatch => {
    MyFetch.get(`v1/users`).then(data => {
      dispatch(queryUsersSuccess(data.users))
    })
  }
}
// MyFetch.js
import { message } from 'antd';
const API_URL = process.env.REACT_APP_DEV_API_URL
var Fetch = {
    get(path) {
        return new Promise((resolve, reject) => {
            fetch(`${API_URL}/${path}`, {
                headers: new Headers({
                    'token': localStorage.getItem("my-custom-token"),
                    'Accept': 'application/json',
                    'Content-Type': 'application/json',
                })
            }).then(res => {
                return handleStatus(res);
            })
                .then(json => {
                    resolve(json);
                })
                .catch(err => {
                    reject(err);
                });
        });
    },
    post(params) {},
    ...
}

function handleStatus(res) {
    let errors;
    switch (res.status) {
        case 200:
            return res.json();
        case 500:
            console.log("500错误");
            message.error('服务器内部错误', 5)
            errors = `${res.status}, ${res.statusText}`
            throw errors
        case 404:
            message.error("资源不存在", 5)
            errors = `${res.status}, ${res.statusText}`
            throw errors
        case 401:
            message.error("登录会话过期,请重新登录", 5)
            localStorage.removeItem("my-custom-token")
            window.location.href = '/login'
            break;
        case 403:
            message.error("无权限访问", 5)
            errors = `${res.status}, ${res.statusText}`
            throw errors
      default:
    }
}
  1. 创建constants
// src/constants/users/ActionTypes.js
export const QUERY_USERS = 'QUERY_USERS'
export const QUERY_USERS_SUCCESS = "QUERY_USERS_SUCCESS"
  1. 创建 reducers相关
// src/reducers/users.js
import {QUERY_USERS_SUCCESS} from "../constants/users/ActionTypes";

const initState = {
  users: []
}

export default function users(state = initState, action) {
    console.log("Welcome to reducer users");
    switch (action.type) {
        case QUERY_USERS_SUCCESS:
          return {
            ...state,
            users: action.users
          }
        default:
            return state
    }
}
// src/reducers/index.js
import { combineReducers } from 'redux'

//BASE API
import users from './users'

const rootReducer = combineReducers({
  users,
})

export default rootReducer

这里创建了相关的reducers.

  1. 在根组件store
// src/index.js
import React from 'react';
import { Provider } from 'react-redux'
import configureStore from './configureStore';
import ReactDOM from 'react-dom';
import App from './App';
const store = configureStore()

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
// src/configureStore.js
import {createStore, applyMiddleware} from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from './reducers';

// let store = createStore(combineReducers);

export default function configureStore() {
    const store = createStore(
        rootReducer,
        applyMiddleware(thunkMiddleware)
    );

    return store;
}
  1. connect
// src/components/User.js
import * as UserActions from '../../actions/users';
...
class User extends Component{
  componentDidMount(){
       this.props.actions.queryUsers();
  }
}

const mapStateToProps = state => ({
  company: state.companies,
	users: state.users //这个是reducers定义的
})

const mapDispatchToProps = dispatch => ({
    actions: bindActionCreators(UserActions, dispatch)
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Company)

这样就可以使用啦

action里的函数返回不是type,纠结症容易范, 想干掉.想像这样的纯净

所以想尝试换一个.打算使用

2017.11.13

thunk-saga

背景: 刚开始学习前端以及react.之前粗略的对比了下thunk以及saga.发现thunksaga总体差不多,对我来说都够用,再考虑到学习成本,我还是选择使用了thunk. 但是使用thunk重构几个模块之后发现登录流程很麻烦,需要promise或者async/wait的支持才可以很好的完成登录流程,我的解决办法是在回调里调用(尝试过async/promis不可以,里面的步骤比较繁琐),这个很low逼,我也很不喜欢,以后维护起来会很吃力,所以决定切换到saga.

两者的对比,先从简单的获取数据说起(获取用户列表) 一: 大体相同的部分

...
import * as actions from '../actions/users
import {connect} from 'react-redux';
import { bindActionCreators } from 'redux'
class User extends React.Component {
  ...
  
}
const mapStateToProps = (state, ownProps) => ({
  users: state.users,
})

const mapDispatchToProps = dispatch => ({
    actions: bindActionCreators(actions, dispatch)
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(User)

上面这部分就是大致相同的.

二: 不同点

  • 配置不同 saga的store配置如下
// ./configureStore.js
import {createStore, applyMiddleware} from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from './reducers';

import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';
const sagaMiddleware = createSagaMiddleware()
// let store = createStore(combineReducers);

export default function configureStore() {
    const store = createStore(
        rootReducer,
        applyMiddleware(sagaMiddleware)
    );

    sagaMiddleware.run(rootSaga)

    return store;
}

可以发现初始化的时候就去运行 saga.

我们来看看saga:

// ./sagas/index.js
import {watchFetchUsers} from './users';

function* rootSaga() {
  /*The saga is waiting for a action called LOAD_DASHBOARD to be activated */
  yield all(
    [
      fork(watchFetchUsers),
    ]
  )
}

export default rootSaga

我们来看看watchFetchUsers

import {put, takeEvery, call} from 'redux-saga/effects';
import {queryUsers} from './Api';
import * as actions from '../../actions/users';
import {GET_USER_REQUEST} from '../../constants/users/ActionTypes'

export function * watchFetchUsers() {
  yield takeEvery(GET_USER_REQUEST, getUsers)
}

export function* getUsers() {
  const data = yield call(queryUsers);
  yield put(actions.queryUsersSuccess(data.users));
}
// ./Api
import MyFetch from '../../tools/MyFetch'

export const queryUsers = () => {
  return MyFetch.get(`v1/users`);
};

可以发现,saga里有 watchFetchUsersgetUsers.我们在rootSagas里是有fork这个watchFetchUsers的.然后通过watchFetchUsers去触发getUsers. 那么如何触发watchFetchUsers呢?我们需要改变下用户的actions.

// actions/users/index.js
import * as types from constants/users/ActionTypes
export const getUserRequest = () => ({type: types.GET_USER_REQUEST})

现在我们有了这个action, 那么我们就可以去使用他发起一个请求.

// components/User.js

class User extends React.Component {
  ...
  componentDidMount() {
    this.props.actions.getUserRequest()
  }
}

这样子他就会去执行getUserRequest方法,这样就会被watchFetchUsers给监听到,再去通过type(GET_USER_REQUEST)去匹配getUsers方法. 再getUsers方法最后有 yield put(actions.queryUsersSuccess(data.users));这个put就是相当于thunk的dispatch.

写了一天之后给我的感觉就是: thunk需要你自己去匹配需要的动作,saga是写一个监听方法,他自己去分发对应的action. 或许我这样的写法是不规范的,但是我还是决定先切换到saga

下面是一个关于登录发送短信倒计时的对比.

  1. thunk
export const sendAuthCodeToPhone = (self, phone) => {
    return dispatch => {
      dispatch(sendCodeStart())
      MyFetch.post(`v1/verification_code`, {phone: phone}).then(data => {
        if(data.status === 200){
          dispatch(snedCodeSuccess())
          self.timer = setInterval(() => {
            dispatch(tick(self))
          }, 1000);
        }else {
          dispatch(snedCodeFail())
          message.error(data.message)
        }
      })
    }
}

const tick = (self) => {
  return dispatch => {
    let counter = self.props.login.count
    if (counter < 1) {
      clearInterval(self.timer)
      dispatch(timerStart())
    } else {
      dispatch(timerStop(counter))
    }
  }
}
  1. saga
import { eventChannel, END } from 'redux-saga'
import {put, takeEvery, call, take, fork, takeLatest} from 'redux-saga/effects';

export function* sendCode(action) {
  let {self} = action
  const result = yield call(sendCodeToPhone, action)
  if(result.status === 200){
    yield put(actions.snedCodeSuccess())
    const timeChannel = yield call(tick, self)
    try {
      while (true) {
        // take(END) will cause the saga to terminate by jumping to the finally block
        let seconds = yield take(timeChannel)
        yield put(self.props.actions.timerStop(seconds))
      }
    } finally {
      yield put(self.props.actions.timerStart())
    }
  }else {
    yield put(actions.snedCodeFail())
    message.error(result.message)
  }
}

function tick(self) {
  return eventChannel(emitter => {
      const timer = setInterval( function() {
        let counter = self.props.login.count
        if (counter < 1) {
          emitter(END)
          clearInterval(timer)
        } else {
          emitter(counter)
        }
      }, 1000);

      return () => {
        clearInterval(timer)
      }
    }
  )
}
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.