项目中用到的React、React-Redux、Redux-saga简介

  1. React有props和state:
    props意味着父级分发下来的属性
    state意味着组件内部可以自行管理的状态,并且整个React没有数据向上回溯的能力,这就是react的单向数据流。这就意味着如果是一个数据状态非常复杂的应用,更多的时候发现React根本无法让两个组件互相交流,使用对方的数据,react的通过层级传递数据的这种方法是非常难受的,这个时候,迫切需要一个机制,把所有的state集中到组件顶部,能够灵活的将所有state各取所需的分发给所有的组件,这就是redux
  2. Redux的诞生是为了给 React 应用提供「可预测化的状态管理」机制。
  • Redux会将整个应用状态(其实也就是数据)存储到到一个地方,称为store(就是一个数据池,一个应用只有一个),这个store里面保存一棵状态树(state tree),组件改变state的唯一方法是通过调用store的dispatch方法,触发一个action,这个action被对应的reducer(reducer就是改变state的处理层,它接收action和state,通过处理action来返回新的state)处理,于是state完成更新
    组件可以派发(dispatch)行为(action)给store,而不是直接通知其它组件,其它组件可以通过订阅store中的状态(state)来刷新自己的视图
  • 使用步骤
    1.创建reducer
    可以使用单独的一个reducer,也可以将多个reducer合并为一个reducer,即:combineReducers()
    action发出命令后将state放入reducer加工函数中,返回新的state,对state进行加工处理
    2.创建action
    用户是接触不到state的,只能由view触发,action可以理解为指令,需要发出多少动作就有多少指令,action是一个对象,其中的type属性是必须的,定义action类型,其他属性可以自由设置
    3.创建store,使用createStore方法。store 可以理解为有多个加工机器的总工厂,提供subscribe,dispatch,getState这些方法。


  1. 如果把store直接集成到React应用的顶层props里面,只要各个子组件能访问到顶层props就可以了,比如这样:
<顶层组件 store={store}>
 <App />
</顶层组件>

这就是 react-redux,是为了让redux更好的适用于react而生的一个库。react-redux将组件区分为容器组件 和 UI 组件,前者会处理逻辑,后者只负责显示和交互,内部不处理逻辑,状态完全由外部掌控。

  • 两个核心
    Provider
    一般我们都将顶层组件包裹在Provider组件之中,这样,所有组件就都可以在react-redux的控制之下了,但是store必须作为参数放到Provider组件中。
 <Provider store = {store}>
 <App />
<Provider>
// 这样,所有组件都能够访问到Redux中的数据

connect
这个方法可以从UI组件生成容器组件,但容器组件的定位是处理数据、响应行为,因此,需要对UI组件添加额外的东西,即mapStateToProps和mapDispatchToProps,也就是在组件外加了一层state,用法如下:

connect(mapStateToProps, mapDispatchToProps)(MyComponent)

mapStateToProps
把Redux中的数据映射到React中的props中去,举例:

const mapStateToProps = (state) => {
  return {
    foo: state.bar
  }
}

然后渲染的时候就可以使用this.props.foo

class Foo extends Component {
 constructor(props){
  super(props);
 }
 render(){
  return(
   <div>this.props.foo</div>
  )
 }
}
Foo = connect()(Foo);
export default Foo;

然后这样就可以完成渲染了
mapDispatchToProps
把各种dispatch也变成了可以直接使用的props,举例:

const mapDispatchToProps = (dispatch) => { 
 return {
   onClick: () => {
    dispatch({type: 'increatment'});
   }
 }
}
class Foo extends Component {
 constructor(props){
  super(props);
 }
 render(){
  return(
    <button onClick = {this.props.onClick}>click increase</button>
  )
 }
}
Foo = connect()(Foo);
export default Foo;

组件可以直接通过this.props.onClick来调用dispatch,就不需要在代码中来进行store.dispatch了

  1. 如果按照原始的redux工作流程,当组件中产生一个action后会直接触发reducer修改state,reducer又是一个纯函数,也就是不能在reducer中进行异步操作;而往往实际中,组件中发生的action在进入reducer之前需要完成一个异步任务(比如发送ajax请求),拿到数据后再进入reducer,这个时候就需要一个中间件来处理这种业务场景,目前最优雅的处理方式是redux-saga中间件,它通过 Generator 函数来创建,可以用同步的方式写异步的代码,目的是更好、更易地解决异步操作(把所有异步请求集中处理)。
    redux-saga提供了一些辅助函数,用来在一些特定的action 被发起到Store时派生任务,我们先来看两个辅助函数:takeEvery 和 takeLatest
    takeEvery
    takeEvery,同一个action多次触发,每个都会执行
    例如:每次点击按钮去Fetch数据时,我们发起一个 FETCH_REQUESTED 的 action,想通过启动一个任务从服务器获取一些数据,来处理这个action
    首先创建一个将执行异步 action 的任务
// put:你就认为put就等于 dispatch就可以了
// call:可以理解为实行一个异步函数,是阻塞型的,只有运行完后面的函数,才会继续往下
import { call, put } from 'redux-saga/effects'
export function* fetchData(action) {
 try {
  const apiAjax = (params) => fetch(url, params);
  const data = yield call(apiAjax);
  yield put({type: "FETCH_SUCCEEDED", data});
 } catch (error) {
  yield put({type: "FETCH_FAILED", error});
 }
}

然后在每次 FETCH_REQUESTED action 被发起时启动上面的任务,也就相当于每次触发一个名字为 FETCH_REQUESTED 的action就会执行上边的任务,代码如下

import { takeEvery } from 'redux-saga'
function* watchFetchData() {
 yield* takeEvery("FETCH_REQUESTED", fetchData)
}

takeLatest
在上面的例子中,takeEvery 允许多个 fetchData 实例同时启动,在某个特定时刻,我们可以启动一个新的 fetchData 任务, 尽管之前还有一个或多个 fetchData 尚未结束,如果我们只想得到最新那个请求的响应(例如,始终显示最新版本的数据),我们可以使用 takeLatest

import { takeLatest } from 'redux-saga'
function* watchFetchData() {
 yield* takeLatest('FETCH_REQUESTED', fetchData)
}

和takeEvery不同,在任何时刻 takeLatest 只允许执行一个 fetchData 任务,并且这个任务是最后被启动的那个,如果之前已经有一个任务在执行,那之前的这个任务会自动被取消
Effect Creators
redux-saga框架提供了很多创建effect的函数,下面我们就来简单的介绍下开发中最常用的几种

  • take(pattern)
  • put(action)
  • call(fn, ...args)
  • fork(fn, ...args)
  • select(selector, ...args)

take(pattern)
take函数可以理解为监听未来的action,它创建了一个命令对象,告诉middleware等待一个特定的action,Generator会暂停,直到一个与pattern匹配的action被发起,才会继续执行下面的语句,也就是说,take是一个阻塞的 effect,用法:

function* watchFetchData() {
 while(true) {
 // 监听一个type为 'FETCH_REQUESTED' 的action的执行,直到等到这个Action被触发,才会接着执行下面的 yield fork(fetchData) 语句
  yield take('FETCH_REQUESTED');
  yield fork(fetchData);
 }
}

put(action)
put函数是用来发送action的 effect,你可以简单的把它理解成为redux框架中的dispatch函数,当put一个action后,reducer中就会计算新的state并返回,put 也是阻塞 effect,用法:

export function* toggleItemFlow() {
 let list = []
 // 发送一个type为 'UPDATE_DATA' 的Action,用来更新数据,参数为 `data:list`
 yield put({
  type: actionTypes.UPDATE_DATA,
  data: list
 })
}

call(fn, ...args)
call函数简单的理解为可以调用其他函数的函数,它命令 middleware 来调用fn 函数,args为函数的参数,注意:fn 函数可以是一个 Generator 函数,也可以是一个返回 Promise 的普通函数,call 函数也是阻塞 effect,用法:

export const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
export function* removeItem() {
 try {
 // 这里call 函数就调用了 delay 函数,delay 函数为一个返回promise 的函数
 return yield call(delay, 500)
 } catch (err) {
 yield put({type: actionTypes.ERROR})
 }
}

fork(fn, ...args)
fork 函数和 call 函数很像,都是用来调用其他函数的,但是fork函数是非阻塞函数,也就是说,程序执行完 yield fork(fn, args) 这一行代码后,会立即接着执行下一行代码语句,而不会等待fn函数返回结果后再执行下面的语句,用法:

import { fork } from 'redux-saga/effects'
export default function* rootSaga() {
 // 下面的四个 Generator 函数会一次执行,不会阻塞执行
 yield fork(addItemFlow)
 yield fork(removeItemFlow)
 yield fork(toggleItemFlow)
 yield fork(modifyItem)
}

select(selector, ...args)
select 函数是用来指示 middleware调用提供的选择器获取Store上的state数据,可以简单的把它理解为redux框架中获取store上的 state数据一样的功能 :store.getState(),用法:

export function* toggleItemFlow() {
  // 通过 select effect 来获取 全局 state上的 `getTodoList` 中的 list
  let tempList = yield select(state => state.getTodoList.list)
}

一个具体的实例

  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootSaga from './sagas'
import Counter from './Counter'
import rootReducer from './reducers'

const sagaMiddleware = createSagaMiddleware() // 创建了一个saga中间件实例

// 下边这句话和下边的两行代码创建store的方式是一样的
// const store = createStore(reducers,applyMiddlecare(middlewares))
 
const createStoreWithMiddleware = applyMiddleware(middlewares)(createStore)
const store = createStoreWithMiddleware(rootReducer)
sagaMiddleware.run(rootSaga)
 
const action = type => store.dispatch({ type })
 
function render() {
 ReactDOM.render(
 <Counter
  value={store.getState()}
  onIncrement={() => action('INCREMENT')}
  onDecrement={() => action('DECREMENT')}
  onIncrementAsync={() => action('INCREMENT_ASYNC')} />,
 document.getElementById('root')
 )
}
render()
store.subscribe(render)
  • sagas.js
import { put, call, take,fork } from 'redux-saga/effects';
import { takeEvery, takeLatest } from 'redux-saga'
 
export const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
 
function* incrementAsync() {
 // 延迟 1s 在执行 + 1操作
 yield call(delay, 1000);
 yield put({ type: 'INCREMENT' });
}
 
export default function* rootSaga() {
 // while(true){
 // yield take('INCREMENT_ASYNC');
 // yield fork(incrementAsync);
 // }
 
 // 下面的写法与上面的写法上等效
 yield* takeEvery("INCREMENT_ASYNC", incrementAsync)
}
  • reducer.js
export default function counter(state = 0, action) {
 switch (action.type) {
 case 'INCREMENT':
  return state + 1
 case 'DECREMENT':
  return state - 1
 case 'INCREMENT_ASYNC':
  return state
 default:
  return state
 }
}

redux-saga基本用法总结:

  • 使用 createSagaMiddleware 方法创建 saga 的 Middleware ,然后在创建的 redux 的 store 时,使用 applyMiddleware 函数将创建的 saga Middleware 实例绑定到 store 上,最后可以调用 saga Middleware 的 run 函数来执行某个或者某些 Middleware 。
  • 在 saga 的 Middleware 中,可以使用 takeEvery 或者 takeLatest 等 API 来监听某个 action ,当某个 action 触发后, saga 可以使用 call 发起异步操作,操作完成后使用 put 函数触发 action ,同步更新 state ,从而完成整个 State 的更新。
  • ui->action1(异步请求)->redux-saga->action2->reducer(更新state)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343