上一节我们说到了redux的基础以及如何理解redux。这一节我们用经典的案例redux-todoList来具体的说一下每一个部分作何解释。
todoList是什么
todolist是一个经典的案例,代办项的意思。一般我们初学一门语言的时候基本都是会做一个todoList来验证自己所学的知识。我们这里用来理解redux也是一个不错的方法。
具体的功能可以查看http://www.todolist.cn/。一个输入框,输入代办事件,刚添加的归类到正在进行,我们可以点击具体的莫一项是他变成已完成。
我自己的代码以及托管到了github上面,之后的代码将会以我自己的代码为例,如果需要,可以去想clone。
梳理
我们回顾一下之前说redux的三要素:action,reducer,store。我们要完成todolist需要做一下什么呢?接下来看看具体的步骤。
入口
入口,即整个项目的入口文件。我们需要把我们需要的代码render(渲染到具体的某一个文件中)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import React, { PureComponent } from 'react' import ReactDOM from 'react-dom' import {Provider} from 'react-redux' import {createStore} from 'redux'
import App from './src/index' import reducer from './src/reducers' require('./src/css/style.scss')
const store = createStore(reducer)
export default class Home extends PureComponent { render() { return <Provider store={store}> <App/> </Provider> } }
ReactDOM.render(<Home/>, document.getElementById('root'))
|
首先是Provider
组件,由react-reudx
提供,作用就是作为整个项目的跟标签,我们把属性store
传递到整个项目中去,供子组件使用。store
就是由redux
提供的createStore
方法创建的。
我们说createStore
方法接收一个参数,所有的reducer,那么reducer在哪里呢?接下来就是reducer。
reducer
我们说reducer是一个函数,给定一个确定的输入必定有一个确定的输出。他的作用就是操作我们需要的state(状态)。
那么在todolist里面有那几个状态呢?
- 所有的代办项,我们用一个数组表示,即todos
- todos的过滤,即我们当前所处一个状态,用
visibilityFilter
表示。
所以就有了以下的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import { combineReducers } from 'redux' let initState = { todos: [], visibilityFilter: "SHOW_ACTIVE" }
const todos = (state=initState.todos, action) => { switch (action.type) { case "ADD_TODO": return [ ...state, { text: action.text, complete: false, index: action.index } ] case "TOGGLE_TODO": return state.map((item, index) => { if( action.index == index ) { return Object.assign({}, item, { complete: !item.complete }) } return item }) default: return state } }
const filterCompleteOrNot = (state=initState.visibilityFilter, action) => { switch (action.type) { case "FILTER_COMPLETE_OR_NOT": return action.filter default: return state } }
const todoApp = combineReducers({ todos, filterCompleteOrNot }) export default todoApp
|
我们给定一个初始的状态initState,里面就有两个属性,todos与visibilityFilter,接下来就是编写纯函数了。
我们根据我们的状态来编写,todos会发生改变,什么情况下面会发生改变呢?
所以我们的纯函数是这样写的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const todos = (state=initState.todos, action) => { switch (action.type) { case "ADD_TODO": return [ ...state, { text: action.text, complete: false, index: action.index } ] case "TOGGLE_TODO": return state.map((item, index) => { if( action.index == index ) { return Object.assign({}, item, { complete: !item.complete }) } return item }) default: return state } }
|
更具我们操作的动作进行switch判断
接下来就是过滤todos,todos里面要显示那些东西呢
1 2 3 4 5 6 7 8
| const filterCompleteOrNot = (state=initState.visibilityFilter, action) => { switch (action.type) { case "FILTER_COMPLETE_OR_NOT": return action.filter default: return state } }
|
这就是我们的reducer。因为我们写了两个纯函数,但是在我们创建store的时候,createStore方法只接受一个参数,即所有纯函数的集合。所以我们需要使用redux
提供的combineReducers
方法把所有的reducer集合起来。
1 2 3 4
| const todoApp = combineReducers({ todos, filterCompleteOrNot })
|
action
再来看看action,和上面的关联起来了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| let initNumber = 0 const addTodo = (text) => ({ text, type: "ADD_TODO", index: initNumber++ })
const toggleTodo = (index) => ({ index, type: 'TOGGLE_TODO' })
const filterCompleteOrNot = (filter) => ({ filter, type: 'FILTER_COMPLETE_OR_NOT' })
const TODOS_TYPE = { SHOW_ALL: "SHOW_ALL", SHOW_COMPLETE: "SHOW_COMPLETE", SHOW_ACTIVE: "SHOW_ACTIVE" }
module.exports = { addTodo, toggleTodo, filterCompleteOrNot, TODOS_TYPE }
|
我们知道action就是我们集体的操作,有哪一些呢?
- 添加todo
- 修改单个todo的state
- 过滤todos
所以我们定义了三个action creater
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const addTodo = (text) => ({ text, type: "ADD_TODO", index: initNumber++ })
const toggleTodo = (index) => ({ index, type: 'TOGGLE_TODO' })
const filterCompleteOrNot = (filter) => ({ filter, type: 'FILTER_COMPLETE_OR_NOT' })
|
最后就是我们具体的展示层了
因为我们使用的是redux,而redux又提倡数据域结构分离,所以在文件目录下面有components
与containers
两个文件夹。但是普通的component与container没有什么关系,普通的组件里面没有store的dispatch等方法。所以我们在其中的几个文件代码中可以看到mapStateToProps
与mapDispatchToProps
两个方法。字面意思就是遍历state(dispatch)到props。意思就是把redux中的state与dispatch方法传递到props中(即组件component)。
1 2 3 4 5 6 7 8 9
| const mapStateToProps = state => { return ({ todos: getVisibleTodos(state.todos, state.filterCompleteOrNot) }) }
const mapDispatchToProps = dispatch => ({ toggleTodo: index => dispatch(toggleTodo(index)) })
|
但是怎么传递呢?
在react-redux中,我们使用react-redux
提供的connect
方法。他的作用就是把component与container链接起来。
1 2 3 4
| export default connect( mapStateToProps, mapDispatchToProps )(TodoList)
|
connect
是一个二阶函数,第一次运行后返回一个函数再次运行。第一个运行的参数就是我们需要传递的两个props,第二次运行的参数就是我们需要传递到的组件。所以在TodoList
文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import React from 'react' import { ListGroup } from 'react-bootstrap'
const TodoList = ({todos, toggleTodo}) => { return ( <ListGroup> { todos.map((item, index) => <ListGroup.Item key={index} onClick={() => toggleTodo(item.index)} variant={item.complete ? 'success' : 'danger'} > <span className='name'>{item.text}</span> <span className='state'>{item.complete ? '已完成' : '未完成'}</span> </ListGroup.Item>) } </ListGroup> ) }
export default TodoList
|
我们才可以使用todos与toggleTodo,因为这两个参数都是我们使用mapStateToProps
与mapDispatchToProps
得到的。
其他的地方理解是一样的。
至于代码中用到的react-bootstrap
,就是bootstrap封装的UI插件,集体可以看react-bootstrap