大前端 - react - redux(数据流)

代码地址:

1.redux核心

1.1什么是redux?

redux是javascript状态容器,提供可预测化的状态管理。

这些状态最终我们都会把它抽象成数据,保存在一个对象中。这个对象就被称之为状态容器。所谓状态容器就是一个javascript对象。在javascript保存了一些数据,这些数据和页面当中的dom元素的状态是一一对应的。

有了状态容器有什么好处呢?
以前操作dom元素都是通过document.getElementById找到这个dom元素,然后再去操作它。有了状态容器我们就不用去页面中查找dom元素,我们直接操作dom元素对于的状态对象就可以了。可以试想一下,是操作dom方便,还是操作js对象方便?肯定是直接操作js对象方便。

页面当中的元素是怎么发生变化呢?
这部分就交给框架来做,例如:react,vue都有自己内部的机制,把状态同步到dom元素中。

提供可预测化的状态管理,什么意思?
在大型项目中,糟糕的状态管理会让项目的维护成本增加。这个时候就希望有一个科学的状态管理的方式。redux的出现就是为了解决这个问题。在redux中提供了一种科学的状态管理方式。通过这个可科学的状态管理方式,当状态发生变化,这个状态变的可以预测。【可以预测】对我们有什么好处?当项目中发生状态的问题时,我们可以很容易定位到这个问题出现在哪里。

1.2 redux 的核心概念和工作流程

1. redux 的核心概念:

store:存储状态的容器,是一个javascript对象。(在redux的应用中,要求我们把所有的状态都存储到store这个对象中)

reducer:是一个函数,这个函数的作用:就是用来向store中存储状态以及更新store中的状态。(也就是说:reducer中返回什么store中就存储什么。)

action:作用是:用来描述store中的状态进行怎样的操作。(是一个javascript对象,需要一个固定的属性,这个属性叫做type,这个type的属性值是一个字符串,这个由开发人员自己定。)

view: 视图,指的就是html页面。

  1. 工作流程
    在这里插入图片描述

2. 工作流程描述
由于所有的状态都存粗在store中,又由于view就是一个视图,他不能直接操作store,所以视图想更改store中的状态,它必须先通过dispatch方法触发一个action,(描述对当前的状态进行怎样的操作)这个action会被reducer接收到,reducer或根据action中的type属性的不同,对状态进行不同的处理,当reducer把这个状态处理完成以后,再通过返回值的方式返回给store,更新store值的状态,当store中的状态被更新之后,通过subscribe方法,就能知道这个状态更新了,然后再去同步视图中的状态。这个就是他的工作流程。

1.3.如何使用redux

首先看下store里面到底有什么?

  • 获取store对象存储的状态。我们发现store里面返回的都是一些方法。
  • getState: 获取store中的数据
  • dispatch: 触发action
  • subscribe:订阅store中的数据变化。参数是一个函数。(得到最新的状态,同步视图)
 // 1创建store。
    var store = Redux.createStore(reducer)
    // 获取store对象存储的状态。我们发现store里面返回的都是一些方法。
    // getState: 获取store中的数据
    // dispatch: 触发action
    // subscribe:订阅store中的数据变化。参数是一个函数。(得到最新的状态,同步视图)
    console.log(store)

在这里插入图片描述

案例:计数器
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button id="plus"> +</button>
  <button id="minus"> -</button>
  <button id="count">0</button>
  <script src= "redux.min.js"> </script>
  <script>
    // 1创建store。
    // 默认状态的存储有2种方式:1.createStore中可以传递第2个参数(默认值,但是一般不这样传递初始化的值。)2 一般直接放在reducer函数的的参数中赋值
    var store = Redux.createStore(reducer)

    // 3. 存储默认的状态
    var inisialState = {
      count: 0
    }
     // 2.创建reducer函数。
    function reducer(state = inisialState, action) {
      switch(action.type) {
        case 'increment' :
          return {
            count: state.count + 1
          }
        case 'decrement' :
          return {
            count: state.count -1
          }
        default :
          return state
      }
    }
    // 4.定义action。数值+1,数值-1
    var increment = {
      type: 'increment'
    }
    var decrement = {
      type: 'decrement'
    }

    // 5.添加点击事件
    document.getElementById('plus').onclick = function() {
      // 6.触发action
      store.dispatch(increment)
    }
    document.getElementById('minus').onclick = function() {
      store.dispatch(decrement)
    }
    // 7. 订阅store。
    store.subscribe(() => {
    // 8.store中的数据同步到页面中。
      // store.getState() 可以看到当点击按钮的时候,数据已经发生了变化。
      document.getElementById('count').innerHTML = state.count
    })

    // 获取store对象存储的状态。我们发现store里面返回的都是一些方法。getState, dispatch
    console.log(store.getState())
  </script>
</body>
</html>
1.4.redux核心api

在这里插入图片描述

2.react+redux

2.1 在react中不使用redux时遇到的问题。
2.2 在react中加入redux的好处
2.3 如何在react中使用redux

npm install create-react-app -g
安装:npm install redux react-redux
创建项目: create-react-app guide

react-redux:提供了2个方法:

  • 1. Provider : 将创建出来的store放在全局位置
  • 2. connect
    1. 在connect方法的内部会帮助我们订阅store,当store中的状态发生变化之后,会帮助我们重新渲染组件。
    2. 通过connect可以拿到store中的状态,将状态通过组件的props属性映射给组件。
    3. 可以获取到dispatch方法
  • 3. connect参数
    第一个参数:mapStateToProps。是一个函数。这个函数的返回一个对象,这个对象中写什么,props就会获取到什么。props中会带有dispatch方法。
    第2个参数:mapDispatchToProps。是一个函数,可以拿到dispatch方法。
2.3.1. 实现计数器案例(react-redux):
  • Provider组件与connect方法的实践:
import React from 'react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'

const initialState = {
  count: 0
}
// 创建reducer
function reducer(state = initialState, action) {
  switch(action.type) {
    case 'increment':
      return {
        state: state.count + 1
      }
      case 'decrement':
      return {
        state: state.count - 1
      }
      default:
        return state
  }
  return state
}
// 创建store
const store = createStore(reducer)
// 通过provider将store放在全局的组件可以获取到的地方
React.renderDOM( <Provider store = {store}></Provider> , document.getElementById('root'))


组件Counter.js

// Counter.js
import React from 'react'
import { connect } from "react-redux"

// function Counter(props) {
// 可以看到打印的props中有一个dispatch方法
// console.log(props)
function Counter({count, increment, decrement}) {
  return (
    <div>
      <button onClick={()=>increment()}>+</button>
      <button onClick={()=>decrement()}>-</button>
      <button>{count}</button>
    </div>
  )
}

/**
 * connect参数:
 *    第一个参数:是一个函数。这个函数的返回一个对象,这个对象中写什么,props就会获取到什么。props中会带有dispatch方法。
 *    第2个参数:是一个函数,可以拿到dispatch方法。
 */ 
const mapStateToProps = (state) => ({
  count: state.count
})
const mapDispatchToProps = (dispatch) => ({
   increment(){
     dispatch({type: 'increment'})
   },
   decrement() {
     dispatch({type: 'decrement'})
   },
})

export default connect(mapStateToProps)(Counter)

在函数式组件中:打印:获取到props,带有dispatch方法:如下图:
在这里插入图片描述

2.3.2. bindActionsCreators方法的使用:

./actions/counter.action.js

// /actions/counter.action.js
export const increment = () => {
  return {type: 'increment'}
}
export const decrement = () => {
  return {type: 'decrement'}
}

Counter.js

// Counter.js
import React from 'react'
import { connect } from "react-redux"
import { bindActionCreators } from 'redux'
import * as counterActions from './actions/counter.action.js'

function Counter({count, increment, decrement}) {
  return (
    <div>
      <button onClick={()=>increment()}>+</button>
      <button onClick={()=>decrement()}>-</button>
      <button>{count}</button>
    </div>
  )
}

/**
 * connect参数:
 *    第一个参数:是一个函数。这个函数的返回一个对象,这个对象中写什么,props就会获取到什么。props中会带有dispatch方法。
 *    第2个参数:是一个函数,可以拿到dispatch方法。
 */ 
const mapStateToProps = (state) => ({
  count: state.count
})

//bindActionCreators 参数解析:
// 第一个参数: 是一个对象。 第2个参数:dispatch。
// 返回值:是一个对象

const mapDispatchToProps = (dispatch) => bindActionCreators(counterActions, dispatch)
export default connect(mapStateToProps)(Counter)

2.3.3. 代码重构:

store目录结构:
在这里插入图片描述
action/counter.action.js

import { INCREMENT, DECREMENT } from "../const/counter.const";

export const increment = payload => ({type: INCREMENT, payload});
export const decrement = payload => ({type: DECREMENT, payload});

const/counter.const.js

// 抽象成常量。因为常量写的时候会有代码提示,避免我们写错代码单词。字符串没有提示。
// 是一个优化操作
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

reducer/counter.reducer.js

import { INCREMENT, DECREMENT } from "../const/counter.const";

const initialState = {
  count: 0
}

export default (state = initialState, action) => {
  switch(action.type) {
    case INCREMENT:
      return {
        ...state,
        count: state.count + 1
      }
    case DECREMENT:
      return {
        ...state,
        count: state.count - 1
      }
    default:
      return state;
  }
}

component/Counter.js

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as couterActions from '../store/actions/counter.actions';

function Counter ({count, increment, decrement}) {
  return <div>
    <button onClick={() => increment()}>+</button>
    <span>{count}</span>
    <button onClick={() => decrement()}>-</button>
  </div>
}

// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(couterActions, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
2.3.4. Action传递参数:

修改代码:
component/Counter.js
传递参数

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as couterActions from '../store/actions/counter.actions';

function Counter ({count, increment, decrement}) {
  return <div>
+    <button onClick={() => increment(5)}>+</button>
    <span>{count}</span>
+    <button onClick={() => decrement(5)}>-</button>
  </div>
}

// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(couterActions, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

action/counter.action.js
接收参数

import { INCREMENT, DECREMENT } from "../const/counter.const";

// action接收参数
+ export const increment = payload => ({type: INCREMENT, payload});
+ export const decrement = payload => ({type: DECREMENT, payload});

reducer修改参数

import { INCREMENT, DECREMENT } from "../const/counter.const";

const initialState = {
  count: 0
}

export default (state = initialState, action) => {
  switch(action.type) {
    case INCREMENT:
      return {
        ...state,
+         count: state.count + action.payload
      }
    case DECREMENT:
      return {
        ...state,
 +       count: state.count - action.payload
      }
    default:
      return state;
  }
}
2.3.5. 案例: redux实现弹出框案例:

components/modal

import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux';
import * as modalActions from '../store/actions/modal.actions';

function Modal({stateShow, show, hide}) {
  const style ={
    width: 200,
    height: 200,
    position:'absolute',
    left: '50%',
    top: '50%',
    marginLeft: -100,
    marginTop: -100,
    background: 'blue',
    display: stateShow ? 'block' : 'none'
  }
  return (
    <div>
      <button onClick={show}>显示</button>
      <button onClick={hide}>隐藏</button>
      <div style={style}></div>
    </div>
  )
}

const mapStateToProps = (state) => ({
  stateShow: state.show
})
const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Modal)

store/action/modal.actions.js

import { HIDEMODAL, SHOWMODAL } from '../const/modal.const'

export const show = () => ({type: 'SHOWMODAL'})
export const hide = () => ({type: 'HIDEMODAL'})

store/const/modal.const.js

export const SHOWMODAL = 'showModal'
export const HIDEMODAL = 'hideModal'

store/reducer.js

import { INCREMENT, DECREMENT } from "../const/counter.const";
import { HIDEMODAL, SHOWMODAL } from "../const/modal.const";

const initialState = {
  count: 0,
+  show: false
}

export default (state = initialState, action) => {
  switch(action.type) {
    case INCREMENT:
      return {
        ...state,
        count: state.count + action.payload
      }
    case DECREMENT:
      return {
        ...state,
        count: state.count - action.payload
      }
+    case SHOWMODAL:
+     return {
+      ...state,
+     show: true
+  }
+    case HIDEMODAL:
+      return {
+        ...state,
+        show: false
+      }  
    default:
      return state;
  }
}
2.3.6 拆分合并reducer
  • 要合并reducer,需要借助redux提供的方法:combineReducers

新建文件:store/reducer/root.reducer.js

  • 用来存放所有的reducer
// 要合并reducer,需要借助combineReducers
import { combineReducers } from 'redux'
import counterReducer from './counter.reducer'
import modalReducer from './modal.reducer'

// store中的状态 :{counter: {count: 0}, modal:{show:false}}


export combineReducers({
  counter: counterReducer,
  modal: modalReducer
})

modal组件的reducer单独写一个文件:modal.reducer.js。
新建文件:store/reducer/modal.reducer.js

import { HIDEMODAL, SHOWMODAL } from "../const/modal.const";

const initialState = {
  show: false
}

export default (state = initialState, action) => {
  switch(action.type) {
    case SHOWMODAL:
      return {
        ...state,
        show: true
      }
    case HIDEMODAL:
      return {
        ...state,
        show: false
      }
    default:
      return state;
  }
}

store/reducer/counter.reducer.js
删除之前在counter.reducer.js中的有关modal的代码。

import { INCREMENT, DECREMENT } from "../const/counter.const";

const initialState = {
  count: 0,
}

export default (state = initialState, action) => {
  switch(action.type) {
    case INCREMENT:
      return {
        ...state,
        count: state.count + action.payload
      }
    case DECREMENT:
      return {
        ...state,
        count: state.count - action.payload
      }
    default:
      return state;
  }
}

components/modal
修改reducer在代码中的使用

import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux';
import * as modalActions from '../store/actions/modal.actions';

function Modal({stateShow, show, hide}) {
  const style ={
    width: 200,
    height: 200,
    position:'absolute',
    left: '50%',
    top: '50%',
    marginLeft: -100,
    marginTop: -100,
    background: 'blue',
    display: stateShow ? 'block' : 'none'
  }
  return (
    <div>
      <button onClick={show}>显示</button>
      <button onClick={hide}>隐藏</button>
      <div style={style}></div>
    </div>
  )
}

const mapStateToProps = (state) => ({
+  stateShow: state.modal.show
})
const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Modal)

store/index.js
store中引入的reducer修改为combineReducers合并的的reducer。

import { createStore } from "redux"
+ import RootReducer from './reducers/root.reducer'

+ export const store = createStore(RootReducer)

3.redux中间件

1. 什么是中间件?
中间件:本质是一个函数。redux允许我们通过中间件的方式扩展redux的应用程序。体现在action的处理上。

之间action是直接被reducer处理的。加入了中间件之后:当组件去触发一个action之后,这个action会优先被中间件处理,当中间件处理完这个action之后,中间件把这个action传递给reducer,reducer继续处理这个action。

2. 在加入中间件以后的的redux的工作流程:
在这里插入图片描述
组件去触发一个action,这个action被store接收后以后,这个store优先去调用了中间件(middleware)函数去处理action,并且把接收到的action传递给中间件,在这个中间件之后可以对action进行处理,当中间件处理完这个action 之后,会把action 传递给reducer,让reducer去处理这个action。这就是加入了中间件的redux工作流程。

它提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。 你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。

4.开发redux中间件

Redux中间件是一个函数,科里化的函数。

4.1. 开发中间件的模版:
export  default store => next => action => {}
/*
通过store.getState()获取当前的状态
通过store.dispatch触发action
action:组件触发的action对象。
next:是一个函数,当中间件的逻辑处理完成之后,调用next,调用next的作用是把这个action传递给reducer,或者传递给下一个中间件。
中间件可以有多个。
*/
4. 2. 注册中间件

中间件在开发完成以后只有被注册才能在Redux的⼯作流程中⽣效。

redux中有一个方法:applyMiddleware,用来注册中间件。

案例:

import { createStore, applyMiddleware } from 'redux'
import logger from './middlewares/logger'

createStore(reducer,applyMiddleware(logger) )
4.3.自己实现中间件:

案例:logger中间件:作用:打印action和store的日志。

4.3.1 新建文件夹 store/middleware

1.store/middleware/logger.js

/*
中间件是一个科里化函数

export default function (store) {
  return function(next) {
    return function(action) {

    }
  }
}
*/

// 写成如下的箭头函数

/*
  参数分析:
    第1个参数store
      store的返回值:getState(), diapatch()
    第2个参数:
      next函数。执行完之后必须调用next方法,不调用代码就卡在这里,action就不能传递给下一个中间件或者是reducer。action作为next的参数
    第3个参数:
       action对象/函数。如果是同步任务:action是一个对象,如果是异步任务,action是函数,这个函数中必须传递dispatch方法。
       action:具体要执行的任务。
*/

export default (store) => (next) => (action) => {
  console.log(store)
  console.log(action)
  // action作为next的参数
  next(action)
}
4.3.2. 使用applyMiddleware注册中间件

store/index.js

import { createStore, applyMiddleware  } from "redux"
import RootReducer from './reducers/root.reducer'
+ import logger from './middleware/logger'
+ import thunk from './middleware/thunk'

// 中间件的执行顺序:取决于注册顺序。
+ export const store = createStore(RootReducer, applyMiddleware(logger, thunk))

查看效果:直接看console控制台,输出的内容。

4.3.3实现thunk中间件:可以执行异步操作:

实现thunk中间件:可以执行异步操作:

  1. 新建文件:store/middleware/thunk.js
//thunk.js
// 如何区分同步操作/异步操作?
// 如果是同步操作action就传递一个对象,如果是异步操作就传递函数。
// 异步操作代码就写在你传递进来的函数中。当前这个中间件函数在调用你传递进来的函数时,要将dispatch方法传递过去。

// export default (store) => (next) => (action) => {
export default ({dispatch}) => (next) => (action) => {
  if (typeof action === 'function') {
    return action(dispatch)
  }
  next(action)
}

在action中使用

import { INCREMENT, DECREMENT } from "../const/counter.const";

// action结收参数
export const increment = payload => ({type: INCREMENT, payload});
export const decrement = payload => ({type: DECREMENT, payload});

/*
异步action:返回一个函数。这个函数中带有dispatch方法。这个dispatch是thunk中间件传递的
payload:是传递的参数
export const increment_async = (payload) => {
  return function (dispatch) => {}
}
*/
// 写成箭头函数如下
export const increment_async = (payload) => (dispatch) => {
  setTimeout(() => {
    dispatch(increment(payload))
  }, 2000)
}
4.3.4 常用的redux的中间件(thunk, saga):

redux- thunk

    1. redux-thunk npm install redux-thunk --save
    1. 使用:redux-thunk
    1. 注册redux-shunk

store/index.js

+ import { createStore, applyMiddleware  } from "redux"
import RootReducer from './reducers/root.reducer'
+ import thunk from 'redux-thunk'

// 中间件的执行顺序:取决于注册顺序。
+ export const store = createStore(RootReducer, applyMiddleware(thunk))


    1. 使用redux-thunk中间件
//  可以直接拿到dispatch。
// 当异步操作完成之后,我们触发另外的action,这个action帮助我们把这个异步操作的结果传递给reducer,reducer对数据进行更新。
const loadPosts = () => async (dispatch) => {
	const posts = await axios.get('/api/posts').then(response => response.data)
	dispatch({type: LOADPOSTSSUCCESS, payload: posts})
}

redux-saga
redux-saga 解决的问题:可以将异步操作从action creator文件中抽离出来,放在一个单独的文件中。

  1. 安装: npm install redux-saga --save
  2. 创建redux-sags中间件
import createSagaMiddleware from 'redux-saga'
const sagaMiddleware = createSagaMiddleware()
  1. 注册sagsMiddleware
createStore(reducer, applyMiddleware(sagaMiddleware))
  1. 使用saga接收action,执行异步操作
/*
takeEvery方法的作用:接收action,当组件触发一个action的时候,在saga文件中可以通过takeEvery这个方法去接收这个action.

put:用来触发另外一个action,当异步操作返回结果以后,我们需要通过put方法去触发一个action,帮助我们把这个异步操作的结果传递给reducer,让reducer把这个数据保存到store中。put方法的作用和dispatch的方法是一样的。

在saga文件中要求我们默认导出一个generator函数。所以在文件的底部通过export default 关键字导出一个函数,在generator这个函数中需要通过takeEvery接收这个action。在调用takeEvery这个方法前面需要加上yield这个关键字。
takeEvery这个方法的参数:1.第一个参数是:action,action的类型字符串,而不是函数。第2个参数是:要执行的方法的名字或者是函数。在这个方法中执行异步操作。
*/

import {takeEvery, put} from './store/saga/post.saga'

function * load_posts() {
	const { data } = yield axios.get('/api/post.json')
	yield put(load_posts_success(data))
}

export default function * postSaga() {
	yield takeEvery(LOAD_POSTS, load_posts)
}
	
  1. 启动saga。调用sagaMiddleware.run启用saga。这样我们写的saga才会加入到redux的工作流程中。
import postSaga from './store/saga/post.saga.js'
sagaMiddleware.run(postSaga)

Redux-saga中间件的使用案例:计算器

  1. 新建文件夹 store/sagas

书写counter.saga.js
store/sagas/counter.saga.js

// store/sagas/counter.saga.js
import { takeEvery, put, delay } from 'redux-saga/effects'
import { increment } from '../actions/counter.actions'
import { INCREMENT_ASYNC } from '../const/counter.const'

// takeEvery:接收action
// put:触发action
// delay:延迟接收的时间
export default function * counterSaga() {
  yield takeEvery(INCREMENT_ASYNC, increment_async_fn)
}

function * increment_async_fn() {
  // 延迟2秒钟
  yield delay(2000)
  // 调用同步代码
  yield put(increment())
}
  1. 书写action
    store/actions/counter.actions.js
import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter.const";

export const increment = payload => ({type: INCREMENT, payload});
export const decrement = payload => ({type: DECREMENT, payload});

// saga
+ export const increment_async = () => ({type: INCREMENT_ASYNC})

store/const/counter.const.js

// 抽象成常量。因为常量写的时候会有代码提示,避免我们写错代码单词。字符串没有提示。
// 是一个优化操作
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

+ export const INCREMENT_ASYNC = 'increment_async'
  1. 在组件中使用:Counter
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as couterActions from '../store/actions/counter.actions';

function Counter ({count, increment, decrement, increment_async}) {
  return <div>
+    <button onClick={() => increment_async()}>+</button>
    <span>{count}</span>
    <button onClick={() => decrement(5)}>-</button>
  </div>
}

// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(couterActions, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

Redux-saga中的action传参:

Counter组件

```javascript
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as couterActions from '../store/actions/counter.actions';

function Counter ({count, increment, decrement, increment_async}) {
  return <div>
+    <button onClick={() => increment_async(20)}>+</button>
    <span>{count}</span>
    <button onClick={() => decrement(5)}>-</button>
  </div>
}

// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(couterActions, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

store/actions/counter.actions.js

import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter.const";

export const increment = payload => ({type: INCREMENT, payload});
export const decrement = payload => ({type: DECREMENT, payload});

// saga
+ export const increment_async = (payload) => ({type: INCREMENT_ASYNC, payload})

store/sagas/counter.saga.js

// store/sagas/counter.saga.js
import { takeEvery, put, delay } from 'redux-saga/effects'
import { increment } from '../actions/counter.actions'
import { INCREMENT_ASYNC } from '../const/counter.const'

// takeEvery:接收action
// put:触发action
// delay:延迟接收的时间
export default function * counterSaga() {
// 注意:takeEvery的第一个参数是字符串,不是action。第2个参数是:函数名称。不加 ().
  yield takeEvery(INCREMENT_ASYNC, increment_async_fn)
}

+ function * increment_async_fn(action) {
  // 延迟2秒钟
  yield delay(2000)
  // 调用同步代码
+  yield put(increment(action.payload))
}

saga文件的拆分与合并
1.新建文件夹 store/sagas
单个saga文件
store/sagas/counter.saga.js

import { takeEvery, put, delay } from 'redux-saga/effects';
import { increment } from '../actions/counter.actions';
import { INCREMENT_ASYNC } from '../const/counter.const';

// takeEvery 接收 action
// put 触发 action

function* increment_async_fn (action) {
  yield delay(2000);
  yield put(increment(action.payload))
}

export default function* counterSaga () {
  // 接收action
  yield takeEvery(INCREMENT_ASYNC, increment_async_fn)
}

单个saga文件
store/sagas/modal.saga.js

import { takeEvery, put, delay } from 'redux-saga/effects';
import { SHOWMODAL_ASYNC } from '../const/modal.const';
import { show } from '../actions/modal.actions';

function* showModal_async_fn () {
  yield delay(2000);
  yield put(show());
}

export default function* modalSaga () {
  yield takeEvery(SHOWMODAL_ASYNC, showModal_async_fn)
}

合并saga
store/sagas/root.saga.js

import { all } from 'redux-saga/effects';
import counterSaga from './counter.saga';
import modalSaga from './modal.saga';

export default function* rootSaga () {
  yield all([
    counterSaga(),
    modalSaga()
  ])
}

store/index.js

import { createStore, applyMiddleware } from "redux";
import RootReducer from "./reducers/root.reducer";
import createSagaMidddleware from 'redux-saga';
import rootSaga from './sagas/root.saga';

// 1.创建 sagaMiddleware
const sagaMiddleware = createSagaMidddleware();

export const store = createStore(RootReducer, applyMiddleware(sagaMiddleware));

// 启动 counterSaga
sagaMiddleware.run(rootSaga)

redux-actions常用中间件

redux-actions:解决的问题:版主我们简化了action和reducer的处理。

  1. 安装 npm install redux-actions --save
  2. 使用
  • 创建action

store/action/counter.action.js

// createAction: 帮助我们生成action函数
// 当传递参数时,createAction('increment'),会自动帮你添加到action的payload上。直接传直接用。
import {createAction} from 'rredux-actions'

export const increment_action = createAction('increment') // 传递的参数是一个字符串,也就是action中的type属性。
export const decrement_action = createAction('decrement')
  • 创建reducer
    store/reducer/counter.reducer.js
// 给handleAction这个方法取一个别名createReducer
import { handleAction as createReducer } from 'redux-actions'

import { increment_action, decrement_action } from '../action/counter.action'

// 初始状态
const initialState = {
  count: 0
}

// createReducer的返回值:就是一个reducer
// 参数解析:1。第一个参数是对象,对象里面是函数,这些函数用来处理state 2.第2个参数是初始值
const counterReducer =  createReducer({
  [increment_action]: (state, action) => ({count: state.count + action.payload}),
  [decrement_action]: (state, action) => {
	return 	state.count - action.payload
  },
}, initialState)

export default counterReducer

components/Counter.js

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as couterActions from '../store/action/counter.action';

function Counter ({count, increment, decrement_action, increment_action}) {
  return <div>
    <button onClick={() => increment_action(5)}>+</button>
    <span>{count}</span>
    <button onClick={() => decrement_action(5)}>-</button>
  </div>
}
const mapStateToProps = state => ({
  count: state.counter.count
})

const mapDispatchToProps = dispatch => bindActionCreators(couterActions, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

5.redux综合案例

6.redux Toolkit

对redux的二次封装,用于高效的redux的开发,使redux的使用变的简单。
yarn add @redux/toolkit@1.6.0 react-redux@7.2.4 --save。当前是没没有单独的安装redux,因为redux/toolkit内部封装的有redux。

6.1. 创建状态切片

对于状态切片,我们可以认为它就是原本redux中的那一个个小的reducer函数。
在redux中,原本reducer函数和action对象需要分别创建,现在通过创建切片替代,它会返回reducer函数和action对象。

1.创建状态切片

// createSlice这个方法,返回两个函数 1. reducer函数 2. actions是一个对象,在这个对象中会存储多个action creator
import {createSlice} from '@reduxjs/toolkit'
const {reducer: TodosReducer,actions} = createSlice()

export const {addTodo} = actions
export default TodosReducer
import {createSlice} from '@reduxjs/toolkit'
// 配置createSlice
// todos.slice.js

const {reducer: TodosReducer,actions} = createSlice({
+	name: TODOS_FEATURE_KEY, // 配置的唯一标识
+	initialState: [], // 初始状态
+	reducers: { // reducers的值是一个对像。里面是reducer函数,函数中默认有参数state,action。state就是状态,action就是通过dispatch方法触发的函数
+		addTodo:(state, action)=>{ // addTodo类似于switch case
+			state.push(action.payload) // action.payload中的payload是工具集自动帮助我们添加的,值就是触发action传递的参数
+		}
+	}
})

export const TODOS_FEATURE_KEY ='todos'

export const {addTodo} = actions
export default TodosReducer
  1. 创建store
// index.js

//configureStore作用:创建store
import { configureStore } from '@reduxjs/toolkit'

import TodosReducer, { TODOS_FEATURE_KEY } from './todos.slice'

export default configureStore({
	
	// reducer作用:合并reducer
	reducer: {
		//格式: [状态的名字]:对应的reducer
		// TODOS_FEATURE_KEY:状态的名字
		// TodosReducer:对应的reducer
		[TODOS_FEATURE_KEY]: TodosReducer
	},
	
	// 是否开启调试模式
	devTools: process.env.NODE_ENV !== 'production'
})

3.配置Provider

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';

+ import { Provider } from 'react-redux'
+ import store from './store/index'

ReactDOM.render(
+  <Provider store={store}>
  <React.StrictMode>
    <App />
  </React.StrictMode>
+  </Provider>,
  document.getElementById('root')
  1. 在组件中触发 Action,获取状态

在这里插入图片描述

案例:todolist

  1. Action预处理

当 Action 被触发后, 可以通过 prepare 方法对 Action 进行预处理, 处理完成后交给 Reducer, prepare 方法必须返
回对象.
在这里插入图片描述

  1. 执行异步操作(方法一)
    1. 创建执行异步操作的 Action 创建函数
      createAsyncThunk作用: 创建执行异步操作的 Action 创建函数。
      createAsyncThunk第一个参数:action.对象中的type属性值。是一个字符串。
      第2个参数:是一个函数,异步操作放到这个函数中,有2个参数,payload,thunkApi,
      thunkApi:就是redux-toolkit提供的一些api,例如dispatch,就是存储在thunkApi中。
      在这里插入图片描述
    1. 创建接收异步操作结果的 Reducer
      在这里插入图片描述
    1. 在组件中触发 Action
      在这里插入图片描述
  1. 执行异步操作(方法二)
    1. 创建执行异步操作的 Action 创建函数
      在这里插入图片描述
    1. 创建接收异步操作结果的 Reducer
      在这里插入图片描述
  1. 配置中间件 - redux-logger
    npm i redux-logger

getDefaultMiddleware:获取内置的中间件。返回值是一个数组。
index.js

在这里插入图片描述

  1. 实体适配器
    实体可以理解为数据

实体适配器 :可以理解为:放入数据的容器。
将状态放入实体适配器 ,实体适配器提供操作状态的各种方法,简化操作。

import { createEntityAdapter } from '@reduxjs/toolkit'
const todosAdapter = createEntityAdapter()

todosAdapter.getInitialState()

// addOne: 向实体适配器中添加一条数据
// addMay: 向实体适配器中添加多条数据
todosAdapter.addOne(state, action.payload)
todosAdapter.addMay(state, action.payload)


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值