手记

redux-saga初探

背景

最近准备切换到react技术栈,需要掌握react,redux和redux-saga,然而我只是简单的写过react。刷了几遍文档还是一头雾水,决定写过小demo练手加深下印象。

就不详细介绍上面的技术了,简单来说redux就是管理状态的,但是对于异步的处理不是很完善,这里就需要用到redux-saga了。redux-saga的异步处理用到了生成器和迭代器的相关知识,不熟悉的同学可以了解下我之前的文章

这次的demo打算写一个输入框,输入拼音会返回对应的城市列表。并尽可能多的使用redux-saga的特性

起步

首先是使用create-react-app创建新的项目,并npm install react-redux redux redux-saga --save

上网随便搜索了一段城市信息的json,保存为city.js

const cities = [{label:"北京Beijing010",name:"北京",pinyin:"Beijing",zip:"010"},
  {label:"重庆Chongqing023",name:"重庆",pinyin:"Chongqing",zip:"023"},
  {label:"上海Shanghai021",name:"上海",pinyin:"Shanghai",zip:"021"},...]

export default cites

接着是构思state,state有两个值,一个是value代表输入的值,另一个是数组list,代表筛选的结果

reducers.js

设置两个action,一个是设置value,一个是设置list

const reducer = (state, action) => {
  switch (action.type) {
    case 'INPUT':
      return {...state, value: action.payload}
    case 'SET_LIST':
      return {...state, list: action.payload}
  }
  return {...state}
}

export default reducer

APP.js

App为纯函数组件,react-redux的connect方法中传入两个参数,mapStateToProps将state传入App的props,mapActionToProps讲handleChange传入App,当调用handleChange时,会调用INPUT这个action

import React, { Component } from 'react';
import './App.css';
import { connect } from 'react-redux'
const App = props =>
  (<div className="App">
     <input type="text" onChange={props.handleChange} value={props.value} />
     <ul>
       {props.list.map(i =>
        <li>{i.name} </li>
       )}
     </ul>
   </div>)

const mapStateToProps = state => ({
  value: state.value,
  list: state.list
})

const mapActionToProps = dispatch => ({
  handleChange: v => dispatch({
    type: 'INPUT',
    payload: v.target.value
  })
})

// export default App
export default connect(mapStateToProps, mapActionToProps)(App);

接着是我们的主角saga.js

takeEvery可以监听对应的action,如果为*号则监听所有的action,如果action.type匹配,调用对应的回调函数。put可以主动去触发action,在这里触发了获取的城市结果

import {takeEvery, put, take} from 'redux-saga/effects'
import cities from './city'

function* input() {
  yield takeEvery("INPUT", function* (v) {
    let filterCities = yield getData(v.payload)
    yield put({type: 'SET_LIST', payload: filterCities.slice(0, 10)})
  });
}

function getData (v) {
  return new Promise(function (res, rej) {
    setTimeout(() => res(cities.filter(i => i.pinyin.toUpperCase().includes(v.toUpperCase()))), 1000)
  })
}

export default input

input函数也可以使用take来替代takeEvery

function* input() {
   while (true) {
    let v = yield take('INPUT')
    let filterCities = yield getData(v.payload)
    yield put({type: 'SET_LIST', payload: filterCities.slice(0, 10)})
  }
}

接下来是最复杂的index.js部分

import React from 'react';
import { render } from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from 'react-redux'
import createSagaMiddleware from 'redux-saga'

import { createStore, applyMiddleware } from 'redux'
import reducers from './reducers'
import saga from './sagas'

const sagaMiddleware = createSagaMiddleware()
const store = createStore(reducers, {value: '', list: []}, applyMiddleware(sagaMiddleware))

sagaMiddleware.run(saga)

render(
 <Provider store={store}>
 <App />
</Provider>,
 document.getElementById('root'));

registerServiceWorker();

到这里,城市拼音输入框就初步完成了

进阶

取消

先加入如果输入数字的话,立刻停止查询并console报错的功能

修改saga.js

首先,导入fork和cancel,redux-saga的cancel只能取消fork的任务

import {takeEvery, put, take, cancel, fork} from 'redux-saga/effects'

修改函数input,在检测到有数字的时候,调用CANCEL的action。


function* input () {
  yield takeEvery('INPUT', function* (v) {
    if (/\d+/.test(v.payload)) {
      yield put({type: 'CANCEL'})
      return
    }
    let filterCities = yield getData(v)
    yield put({type: 'SET_LIST', payload: filterCities.slice(0, 10)})
  })
}

加入函数main, 使用fork操作,将input任务保存为i,在碰到cancel的时候,取消该任务,并重新开始新的任务

function* main () {
  let i
  i = yield fork(input)
  while (true) {
    yield take('CANCEL')
    yield cancel(i)
    i = yield fork(input)
  }
}

最后导出main

export default main

最后

本文章为前端进阶系列的一部分,欢迎关注和star本博客或是关注我的github

0人推荐
随时随地看视频
慕课网APP