redux最佳实践-前世今生1

1,293 阅读5分钟

本篇主要是redux基础使用篇,如果你已经掌握了redux+redux-saga基础,可以直接看下一篇redux最佳实践-前世今生2

本篇理论实际应用于antd-custom框架

大纲:

1.Redux基础知识

redux只有一个store和一个根级reduce(root reducer)函数,通常将reducer拆分成多个小的reducers,就像一个react应用只有一个根级的组件,这个根组件又由多个小组件构成。

reducer管理整个应用的state,每个reducers分别独立操作state tree 的不同部分

redux 三大原则

1)单一数据源:整个应用的state被存储在一棵object tree中,并且这个object tree只存唯一一个store中

2)state是只读:唯一改变state的方法就是触发action,action是一个用于描述已发生事件的普通对象。

3)使用纯函数来执行修改:改变state你需要编写(Reducer),(oldState, action) => newState,每次更改总是返回一个新的 State

tips:纯函数就是输出只依赖于输入,输入值确定后,每次输出值必然相同,纯函数内部不会依赖其他外部变量

redux数据流

redux-data
1)用户在view层调用store.dispatch触发action

2)reducer会修改state状态,即(oldState, action) => newState

3)state状态发生改变后,view层对应的状态也会发生改变

tips:redux本身只是一个状态管理工具,与react并无关联,要想与react联系起来还需要一个中间件react-redux。

react-redux中间件

react-redux主要提供两大功能

1)connect():高阶函数,利用subscribe()和Context原理监听订阅模式传递state

2)Provider:利用ChildContext原理向子组件传递store

具体可看react-redux源码,很短,约百来行代码

2.Redux实例

本例子是redux+redux-saga配置,至于redux-saga是什么,后面会介绍

基础配置

// app.jsx

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import store from './redux/store'
import Home from './home'

const App = (
  <Provider store={store}>
    <Home />
  </Provider>
)

render(App, document.getElementById('root'))

// store.js

import createSagaMiddleware from 'redux-saga'
import { createStore, applyMiddleware } from 'redux'
import reducer from './rootReducer'
import sagas from './rootSaga' 

const sagaMiddleware = createSagaMiddleware()
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(sagas)

export default store

// rootReducer.js

import { combineReducers } from 'redux'
import incrementReducer from './increment.reducer'

const rootReducer = combineReducers({
  incrementReducer,
})

export default rootReducer

// 处理同步不需要saga,这里先返回一个空函数 // rootSaga.js

import { fork, all } from 'redux-saga/effects'
export default function *rootSaga() {
}

以上是redux+redux-saga最基础的配置

1)Redux处理同步

i)redux最原始处理同步,dispatch->action->reducer

// home.jsx

import React from 'react'
import { Button } from 'antd'
import { connect } from 'react-redux'
import { incrementAction } from './redux/increment.action'

const Home = (props) => {
  const onIncrement = (e) => {
    props.dispatch(incrementAction(1))
  }

  return (
    <aside>
      <Button type='primary' onClick={onIncrement}>+1</Button>
      <h4>count: { props.count }</h4>
    </aside>
  )
}

const mapDispatchToProp = (dispatch) => {
  return { dispatch }
}

const mapStateToProp = state => {
  const { incrementReducer } = state
  return incrementReducer
}

export default connect(mapStateToProp, mapDispatchToProp)(Home)

// increment.action.js

const incrementAction = (payload) => {
  return {
    type: 'INCREMENT',
    payload,
  }
}

export default incrementAction

// increment.reducer.js

const incrementReducer = (state = {
  count: 0,
  loading: false,
}, action) => {
  const { type } = action
  switch(type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1,
      }
    case 'INCREMENT_ASYNC':
      return {
        ...state,
        loading: true,
      }
    case 'INCREMENT_ASYNC_SUCCESS':
      const { payload } = action
      return {
        ...state,
        count: state.count + payload,
        loading: false,
      }
    default:
      return state
  }
}

export default incrementReducer

其中,涉及3个文件home.jsx,increment.action.js,increment.reducer.js,即在view层发起dispatch->action->reducer来改变state,state状态发生变化后react-redux监听state状态进而更新view层,以上是redux最原始处理同步行为

效果图:

res1

ii)redux处理同步提取action省略写action,dispatch->reducer

//home.jsx

import React from 'react'
import { Button } from 'antd'
import { connect } from 'react-redux'
// import { incrementAction } from '../../redux/increment.action'

const Home = (props) => {
  const onIncrement = (e) => {
    props.dispatch({
      type: 'INCREMENT',
      payload: 1,
    })
  }
// ...

上述合并action文件和操作,只剩下home.jsx,increment.reducer.js,流程不变,只是简化了aciton 变为dipatch(action)->reducer来改变state,state状态发生变化后react-redux监听state状态进而更新view层,以上是redux进阶处理同步行为

2)Redux处理异步

redux本身只能处理同步,不能处理异步,要处理异步需要引入其他中间件,这里用redux-saga

i)redux异步处理,引入中间件redux-saga处理异步

// rootSaga.js

import { fork, all } from 'redux-saga/effects'

import { watchIncrementAsync } from './increment.saga'

export default function *rootSaga() {
  yield all([
    watchIncrementAsync()
  ])
}

// increment.saga.js

import { put, takeEvery, call, takeLatest } from 'redux-saga/effects'
import { increment } from '../api/increment.api'

export function* incrementAsync({ type, payload }) {
  const resp = yield call(increment, payload)
  console.log("resp", resp)
  yield put({ type: 'INCREMENT_ASYNC_SUCCESS', payload: resp.data })
}

export function* watchIncrementAsync() {
  yield takeLatest('INCREMENT_ASYNC', incrementAsync)
}

export function* watchIncrementAsync() {
  yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}

// increment.api.js

export async function increment(payload) {
  const { data } = payload || {}
  // fetch, axios向后端发起请求,这里用setTimeout模拟异步
  return new Promise(resolve => {
    if (payload) {
      console.log('payload', payload)
      setTimeout(() => {
        resolve(payload)
      }, 1000)
    }
  })
}

// home.jsx

//...
const Home = (props) => {
//...

  const onIncrementAsync = (e) => {
    props.dispatch({
      type: 'INCREMENT_ASYNC',
      payload: {
        data: 10,
      },
    })
  }

  return (
    <aside>
      <Button type='primary' onClick={onIncrement}>+1</Button>
      <Button type='primary' onClick={onIncrementAsync} loading={props.loading}>+10</Button>
      <h4>count: { props.count }</h4>
    </aside>
  )
}
//...

异步处理流程:

在view层发起dispatch(action),redux-saga监听action,sagaMiddleware中间件处理yield,redux-saga调用call进行异步处理,堵塞调用返回api结果,获取返回结果后,sagaMiddleware再次触发yield操作,调用redux-saga的put操作,将action传递给reducer,进而改变state,state状态发生变化后react-redux监听state状态进而更新view层

异步流程:view -> dispatch(action) -> redux-saga -> reducer(state) -> view

同步流程:view -> dispatch(action) -> reducer(state) -> view

整体流程:

redux-saga

效果图:

res2
res3

3.总结 本篇只是简单阐述了redux基础应用,为Redux最佳实践的前世,当项目小一些还好,但当项目慢慢变大,需要在*.reducer.js和*.saga.js文件中来回切换,并不是我们想要的,下一篇将重点讲述redux最佳实践的今生,redux最佳实践-前世今生2

上述例子可正常运行,本篇理论实际应用于antd-custom框架

tips:redux-saga几个重要概念

1)yield作用 Effect 是一个 javascript 对象,里面包含描述副作用的信息,可以通过 yield 传达给 sagaMiddleware 执行

在 redux-saga 世界里,所有的 Effect 都必须被 yield 才会执行,所以有人写了 eslint-plugin-redux-saga 来检查是否每个 Effect 都被 yield。并且原则上来说,所有的 yield 后面也只能跟Effect,以保证代码的易测性。

2)阻塞与非阻塞调用 redux-saga 可以用 fork 和 call 来调用子 saga ,其中 fork 是无阻塞型调用,call 是阻塞型调用

想了解更多redux-saga请点击这里