- 一、常用的 redux 中间件
- 二、TodoList 案例
- 2.1 项目结构如下
- 2.2 代码如下:
- 2.2.1 src/index.js
- 2.2.2 sr/store/index.js
- 2.2.3 src/store/action-types.js
- 2.2.4 src/store/action/index.js 【actionCreator】
- 2.2.5 src/store/reducer/index.js
- 2.2.5 src/components/App.js
- 2.2.5 src/components/TodoHeader.js
- 2.2.7 src/components/TodoList.js
- 2.2.8 src/components/TodoItem.js
- 2.2.9 src/components/TodoFooter.js
一、常用的 redux 中间件
- 我们以 Counter 为例;组件代码如下:
import React, { Component } from 'react'import actions from '../store2/action'import { connect } from 'react-redux'class Counter extends Component {render () {return (<div className="container"><button onClick={() => this.props.add(1)}>+</button><span>{this.props.num}</span><button onClick={() => this.props.minus(1)}>-</button></div>)}}export default connect(state => ({...state}), actions)(Counter)
1.1 redux-logger
当 store 中的状态发生变化时,redux-logger 会把改变之前的状态和改变之后的状态输出到控制台;
1.1.1 安装 redux-logger
所有的中间件在使用前都需要安装;
yarn add redux-logger --save
1.1.2 使用中间件
使用中间件,需要借助 redux 中的另一个方法 applyMiddleware,从 redux 中导出它,同时导入中间件
import { createStore, applyMiddleware } from 'redux'import reduxLogger from 'redux-logger'
然后,在调用 createStore 时传入第二个参数:applyMiddleware(中间件名字)
示例:
import { createStore, applyMiddleware } from 'redux'import reducer from './reducer'import reduxLogger from 'redux-logger'export default createStore(reducer, applyMiddleware(reduxLogger))
接着,在控制台修改状态试试看,控制台会输出修改前的状态和修改后的状态;
1.2 redux-thunk
- 需求,点击 Counter 的加,2s 后再执行加的操作
我们有时候需要一些异步的操作,例如定时器、ajax 等,这些异步的操作无法直接在 actionCreator 中进行,但是这个 redux-thunk 函数,可以让我们的 actionCreator 返回一个 function ,这个函数的参数是 dispatch 和 getState就是 store.dispatch、store.getState;
它把 dispatch 的控制权交给了我们 actionCreator 返回的函数,在这个函数中,我们可以在任意位置 dispatch,例如定时器中;
1.2.1 安装 redux-thunk
yarn add redux-thunk --save
1.2.2 导入,并且作为第二个参数传入 createStore 时的 applyMiddleware
import { createStore, applyMiddleware } from 'redux'import reducer from './reducer'import reduxLogger from 'redux-logger'import reduxThunk from 'redux-thunk'export default createStore(reducer, applyMiddleware(reduxLogger, reduxThunk))
1.2.3 修改后的 actionCreator 函数
import * as Types from '../action-types'export default {add (amount) {return (dispatch, getState) => {// 返回一个 函数,在我们可以在任意位置 dispatch action,异步的也可以setTimeout(() => {dispatch({type: Types.ADD,amount})}, 2000)}},minus (amount) {return {type: Types.MINUS,amount}}}
1.3 redux-promise
我们在日常开发中,尤其是处理异步的数据时,常用到 Promise;对此 redux-promise 中间件专门用来处理 redux 中的 Promise;
1.3.1 安装 redux-promise
yarn add redux-promise --save
1.3.2 导入并使用
import { createStore, applyMiddleware } from 'redux'import reducer from './reducer'import reduxLogger from 'redux-logger'import reduxThunk from 'redux-thunk'import reduxPromise from 'redux-promise'export default createStore(reducer, applyMiddleware(reduxLogger, reduxThunk, reduxPromise))
1.3.3 使用 redux-promise 后,在 actionCreator 中有两种用法:
- 直接在某个 actionCreator 中返回一个 Promise 实例,但是这种用法,只能处理 resolve,并在 resolve 时传入一个 action 对象;如果 reject 直接抛出错误
- 示例:
import * as Types from '../action-types'export default {add (amount) {return (dispatch, getState) => {setTimeout(() => {dispatch({type: Types.ADD,amount})}, 2000)}},minus (amount) {return new Promise((resolve, reject) => {setTimeout(() => {resolve({type: Types.MINUS,amount})}, 2000)})}}
- 如果失败成功都要处理,我们不能直接在 actionCreator 函数中返回 Promise 实例,而是在返回的 action 对象中增加 payload 属性,payload 的属性值是一个 Promise,如果 Promise resolve 那么 payload 代表的就是 resolve 时传入的值,如果 reject 时,payload 代表的就是 reject 时传入的值,同时 reducer 函数也需要修改;
示例:
- action/index.js 【actionCreator】
import * as Types from '../action-types'export default {add (amount) {return (dispatch, getState) => {setTimeout(() => {dispatch({type: Types.ADD,amount})}, 2000)}},minus (amount) {return {type: Types.MINUS,payload: new Promise((resolve, reject) => {// resolve(1)reject(10)})}}}
- reducer/index.js
import * as Types from '../action-types'export default function (state = {num: 0}, action) {switch (action.type) {case Types.ADD:return {num: state.num + action.amount}case Types.MINUS:return {num: state.num - action.payload // actionCreator 函数使用 Promise 对应修改为减去 action.payload}}return state}
二、TodoList 案例
效果如图:
2.1 项目结构如下
-src│ index.css│ index.js│├─components│ App.js│ TodoFooter.js│ TodoHeader.js│ TodoItem.js│ TodoList.js│├─store│ action-types.js│ index.js│├─action│ index.js│└─reducerindex.js
2.2 代码如下:
2.2.1 src/index.js
import React from 'react';import ReactDOM from 'react-dom';import './index.css';import { Provider } from 'react-redux'import App from './components/App';import store from './store'ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
2.2.2 sr/store/index.js
import { createStore } from 'redux'import reducer from './reducer'export default createStore(reducer)
2.2.3 src/store/action-types.js
export const ADD_TODO = 'ADD_TODO'export const CHANGE_SELECTED = 'CHANGE_SELECTED'export const DELETE_TODO = 'DELETE_TODO'export const CHANGE_CURRENT_TYPE = 'CHANGE_CURRENT_TYPE'
2.2.4 src/store/action/index.js 【actionCreator】
import * as Types from '../action-types'// 在组件中使用 connect 的时候我们需要传递一个 actionCreator 对象,所以这里我们直接导出一个 actionCreator 对象export default {addTodo (todo) { // 添加 todo 内容return {type: Types.ADD_TODO,todo}},changeSelected (id) {return {type: Types.CHANGE_SELECTED,id}},deleteTodo (id) {return {type: Types.DELETE_TODO,id}},changeType (curType) {return {type: Types.CHANGE_CURRENT_TYPE,curType}}}
2.2.5 src/store/reducer/index.js
import * as Types from '../action-types'let initState = {type: 'all',todos: [{isSelected: false,title: '今天吃药了吗',id: 1},{isSelected: true,title: '今天吃饭了吗',id: 2}]}export default function todo(state= initState, action) {switch (action.type) {case Types.ADD_TODO:return {...state,todos: [...state.todos,action.todo]}case Types.CHANGE_SELECTED:return {...state,todos: state.todos.map(item => {if (item.id === action.id) {item.isSelected = !item.isSelectedreturn item}return item})}case Types.DELETE_TODO:return {...state,todos: state.todos.filter(item => item.id !== action.id)}case Types.CHANGE_CURRENT_TYPE:return {...state,type: action.curType}}// 写 reducer 第一件事返回默认状态return state}
2.2.5 src/components/App.js
import React, { Component } from 'react'import TodoHeader from './TodoHeader'import TodoFooter from './TodoFooter'import TodoList from './TodoList'import 'bootstrap/dist/css/bootstrap.min.css'export default class App extends Component {render () {return (<div className="container"><div className="col-md-6 col-md-offset-3"><div className="panel panel-danger"><div className="panel-heading"><TodoHeader /></div><div className="panel-body"><TodoList /></div><div className="panel-footer"><TodoFooter /></div></div></div></div>)}}
2.2.5 src/components/TodoHeader.js
import React, {Component} from 'react'import {connect} from 'react-redux'import actions from '../store/action'class TodoHeader extends Component {getUnfinishedCount = () => this.props.todos.filter(i => !i.isSelected).lengthrender () {return (<div><h3>亲,你有 {this.getUnfinishedCount()} 件事没有完成</h3><input type="text" className="form-control" onKeyDown={(e) => {if (e.keyCode === 13) {this.props.addTodo({id: this.props.todos[this.props.todos.length - 1].id + 1,title: e.target.value,isSelected: false})e.target.value = ''}}}/></div>)}}export default connect(state => ({...state}), actions)(TodoHeader)
2.2.7 src/components/TodoList.js
import React, {Component} from 'react'import actions from '../store/action'import TodoItem from './TodoItem'import { connect } from 'react-redux'class TodoList extends Component {filterData = () => {switch (this.props.type) {case 'all':return this.props.todoscase 'unfinished':return this.props.todos.filter(i => !i.isSelected)case 'finished':return this.props.todos.filter(i => i.isSelected)}}render() {return (<ul className="list-group">{this.filterData().map((item, index) => {return <TodoItem item={item}key={index}changeSelected={this.props.changeSelected}deleteTodo={this.props.deleteTodo} />})}</ul>)}}export default connect(state => ({...state}), actions)(TodoList)
2.2.8 src/components/TodoItem.js
import React, { Component } from 'react'export default class TodoItem extends Component {render () {let { item, changeSelected, deleteTodo } = this.propsreturn (<li className="list-group-item"><input type="checkbox"checked={item.isSelected}onChange={() => changeSelected(item.id)} />{item.title}<button className="btn btn-xs pull-right" onClick={() => deleteTodo(item.id)}>×</button></li>)}}
2.2.9 src/components/TodoFooter.js
import React, { Component } from 'react'import actions from '../store/action'import { connect } from 'react-redux'class TodoFooter extends Component {render () {let type = this.props.typereturn (<div><nav className="nav nav-pills" onClick={(e) => {// h5 新增的属性,元素对象有一个 dataset 属性,值是对象,其中包含了以 data- 开头的行内属性及对应的值;注意取值时不带 data-this.props.changeType(e.target.dataset.type)}}><li className={type === 'all' ? 'active' : ''}><a data-type="all">全部</a></li><li className={type === 'unfinished' ? 'active' : ''}><a data-type="unfinished">未完成</a></li><li className={type === 'finished' ? 'active' : ''}><a data-type="finished">已完成</a></li></nav></div>)}}export default connect(state => ({...state}), actions)(TodoFooter)
