- 一、常用的 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
│
└─reducer
index.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.isSelected
return 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).length
render () {
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.todos
case '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.props
return (<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.type
return (<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)