一篇文章介绍redux、react-redux、redux-saga总结
本篇主要将react全家桶的产品非常精炼的提取了核心内容,精华程度堪比精油。各位大人,既然来了,客官您坐,来人,给客官看茶~~
redux
前言
首先,本篇文章要求您对js,react等知识有一定的了解,如果不曾了解,建议您先看一下:react精髓!一篇全概括(急速)
react有props和state:
- props意味着父级分发下来的属性
- state意味着组件内部可以自行管理的状态,并且整个react没有数据向上回溯的能力,这就是react的单向数据流
这就意味着如果是一个数据状态非常复杂的应用,更多的时候发现react根本无法让两个组件互相交流,使用对方的数据,react的通过层级传递数据的这种方法是非常难受的,这个时候,迫切需要一个机制,把所有的state集中到组件顶部,能够灵活的将所有state各取所需的分发给所有的组件,是的,这就是redux
简介
- redux是的诞生是为了给 react 应用提供「可预测化的状态管理」机制。
- redux会将整个应用状态(其实也就是数据)存储到到一个地方,称为store
- 这个store里面保存一棵状态树(state tree)
- 组件改变state的唯一方法是通过调用store的dispatch方法,触发一个action,这个action被对应的reducer处理,于是state完成更新
- 组件可以派发(dispatch)行为(action)给store,而不是直接通知其它组件
- 其它组件可以通过订阅store中的状态(state)来刷新自己的视图
使用步骤
创建reducer
- 可以使用单独的一个reducer,也可以将多个reducer合并为一个reducer,即:combinereducers()
- action发出命令后将state放入reucer加工函数中,返回新的state,对state进行加工处理
创建action
- 用户是接触不到state的,只能有view触发,所以,这个action可以理解为指令,需要发出多少动作就有多少指令
- action是一个对象,必须有一个叫type的参数,定义action类型
创建的store,使用createstore方法
- store 可以理解为有多个加工机器的总工厂
- 提供subscribe,dispatch,getstate这些方法。
按步骤手把手实战。
上述步骤,对应的序号,我会在相关代码标出
npm install redux -s // 安装 import { createstore } from 'redux' // 引入 const reducer = (state = {count: 0}, action) => {----------> ⑴ switch (action.type){ case 'increase': return {count: state.count + 1}; case 'decrease': return {count: state.count - 1}; default: return state; } } const actions = {---------->⑵ increase: () => ({type: 'increase'}), decrease: () => ({type: 'decrease'}) } const store = createstore(reducer);---------->⑶ store.subscribe(() => console.log(store.getstate()) ); store.dispatch(actions.increase()) // {count: 1} store.dispatch(actions.increase()) // {count: 2} store.dispatch(actions.increase()) // {count: 3}
自己画了一张非常简陋的流程图,方便理解redux的工作流程
react-redux
刚开始就说了,如果把store直接集成到react应用的顶层props里面,只要各个子组件能访问到顶层props就行了,比如这样:
<顶层组件 store={store}> <app /> </顶层组件>
不就ok了吗?这就是 react-redux。redux 官方提供的 react 绑定库。 具有高效且灵活的特性。
react redux 将组件区分为 容器组件 和 ui 组件
- 前者会处理逻辑
- 后者只负责显示和交互,内部不处理逻辑,状态完全由外部掌控
两个核心
provider
看我上边那个代码的顶层组件4个字。对,你没有猜错。这个*组件就是provider,一般我们都将顶层组件包裹在provider组件之中,这样的话,所有组件就都可以在react-redux的控制之下了,但是store必须作为参数放到provider组件中去
<provider store = {store}> <app /> <provider>
这个组件的目的是让所有组件都能够访问到redux中的数据。
connect
这个才是react-redux中比较难的部分,我们详细解释一下
首先,先记住下边的这行代码:
connect(mapstatetoprops, mapdispatchtoprops)(mycomponent)
mapstatetoprops
这个单词翻译过来就是把state映射到props中去 ,其实也就是把redux中的数据映射到react中的props中去。
举个栗子:
const mapstatetoprops = (state) => { return { // prop : state.xxx | 意思是将state中的某个数据映射到props中 foo: state.bar } }
然后渲染的时候就可以使用this.props.foo
class foo extends component { constructor(props){ super(props); } render(){ return( // 这样子渲染的其实就是state.bar的数据了 <div>this.props.foo</div> ) } } foo = connect()(foo); export default foo;
然后这样就可以完成渲染了
mapdispatchtoprops
这个单词翻译过来就是就是把各种dispatch也变成了props让你可以直接使用
const mapdispatchtoprops = (dispatch) => { // 默认传递参数就是dispatch return { onclick: () => { dispatch({ type: 'increatment' }); } }; }
class foo extends component { constructor(props){ super(props); } render(){ return( <button onclick = {this.props.onclick}>点击increase</button> ) } } foo = connect()(foo); export default foo;
组件也就改成了上边这样,可以直接通过this.props.onclick,来调用dispatch,这样子就不需要在代码中来进行store.dispatch了
react-redux的基本介绍就到这里了
redux-saga
如果按照原始的redux工作流程,当组件中产生一个action后会直接触发reducer修改state,reducer又是一个纯函数,也就是不能再reducer中进行异步操作;
而往往实际中,组件中发生的action后,在进入reducer之前需要完成一个异步任务,比如发送ajax请求后拿到数据后,再进入reducer,显然原生的redux是不支持这种操作的
这个时候急需一个中间件来处理这种业务场景,目前最优雅的处理方式自然就是redux-saga
核心讲解
1、saga 辅助函数
redux-saga提供了一些辅助函数,用来在一些特定的action 被发起到store时派生任务,下面我先来讲解两个辅助函数:takeevery 和 takelatest
takeevery
takeevery就像一个流水线的洗碗工,过来一个脏盘子就直接执行后面的洗碗函数,一旦你请了这个洗碗工他会一直执行这个工作,绝对不会停止接盘子的监听过程和触发洗盘子函数
例如:每次点击 按钮去fetch获取数据时时,我们发起一个 fetch_requested 的 action。 我们想通过启动一个任务从服务器获取一些数据,来处理这个action,类似于
window.addeventlister('xxx',fn)
当dispatch xxx的时候,就会执行fn方法,
首先我们创建一个将执行异步 action 的任务(也就是上边的fn):
// put:你就认为put就等于 dispatch就可以了; // call:可以理解为实行一个异步函数,是阻塞型的,只有运行完后面的函数,才会继续往下; // 在这里可以片面的理解为async中的await!但写法直观多了! 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) }
注意:上面的 takeevery 函数可以使用下面的写法替换
function* watchfetchdata() { while(true){ yield take('fetch_requested'); yield fork(fetchdata); } }
takelatest
在上面的例子中,takeevery 允许多个 fetchdata 实例同时启动,在某个特定时刻,我们可以启动一个新的 fetchdata 任务, 尽管之前还有一个或多个 fetchdata 尚未结束
如果我们只想得到最新那个请求的响应(例如,始终显示最新版本的数据),我们可以使用 takelatest 辅助函数
import { takelatest } from 'redux-saga' function* watchfetchdata() { yield* takelatest('fetch_requested', fetchdata) }
和takeevery不同,在任何时刻 takelatest 只允许执行一个 fetchdata 任务,并且这个任务是最后被启动的那个,如果之前已经有一个任务在执行,那之前的这个任务会自动被取消
2、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的使用方式还是比较简单的,相比较之前的redux框架的counterapp,多了一个sagas的文件,reducers文件还是之前的使用方式
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 的更新。
ok,故事到这里就接近尾声了,以上主要介绍了redux,react-redux和redux-saga目前redux全家桶主流的一些产品,接下来,主要会产出一下根据源码,手写一下redux和react-redux的*
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。