Redux-saga 初探
背景
项目用的是react全家桶, 之前有同事用redux-saga 改进了一波, 一直都没去研究。 前几天趁有空,也去学习了下, 写了个简单的demo练练手, 在这里简单分享一下。
这次的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) 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' // 新加一个check函数,跟之前的input有点类型,如果监测到有数字,会调用CANCEL function* check () { yield takeEvery('INPUT', function* (v) { if (/\d+/.test(v.payload)) { console.log('x ') yield put({type: 'CANCEL'}) } }) }
加入函数main, 首先添加使用fork操作,将input任务保存为i,在碰到cancel的时候,取消该任务,并重新开始新的任务
function* main () { yield fork(check) let i = yield fork(input) while (true) { yield take('CANCEL') console.log('cancel') yield cancel(i) i = yield fork(input) } }
最后导出main
export default main
channel
令函数是依次查询,只有之前的查询完成后,才继续查询后面的
引用actionChannel 的effect,修改函数input:
function* input () { const inputChan = yield actionChannel('INPUT') while (true) { const v = yield take(inputChan) const filterCities = yield call(getData, v) console.log('done') yield put({type: 'SET_LIST', payload: filterCities.slice(0, 10)}) } }
END