Redux实例—简单的待办事项列表

CodeRoverPro
• 阅读 1758

写在前面

"待办事项列表"这个例子是redux文档推荐的,但是对于新人来说,官方文档和网上博客的相关解释都不是很好,没有思路分析到代码实现整个个过程,这令我在学习的时候非常头疼。看着不分文件、不梳理数据流向、不解释代码思路的各种文章,我决定自己写一篇redux入门实例——简单的待办事项列表。
github

效果展示

Redux实例—简单的待办事项列表

开始之前

redux的基本原则

  • 整个应用的state被储存一个object tree中,并且这个object tree只存在于唯一的store中。
  • 唯一改变state的方法是通过dispatch触发actionaction是一个描述修改意图的普通对象,例如:{ type: add, value }
  • store收到action后,开始在reducer中计算,最后返回一个新的state
  • Redux 入门教程官方文档

命令行

create-react-app redux-todolist
cnpm i redux react-redux -S

目录结构

Redux实例—简单的待办事项列表

准备

使用Provider

在根组件外用provider包裹,可以让所有组件都能拿到state

// App.js
import React from 'react';
import { Provider } from 'react-redux';
import AddTodo from './containers/addtodo'; //注意引用的位置
import ShowList from './containers/showlist'; //注意引用的位置
import Filter from './containers/filter' //注意引用的位置
import store from './redux/store' //注意引用的位置
function App() {
  return (
    <Provider store = {store}>
      <AddTodo />
      <ShowList />
      <Filter />
    </Provider>
  );
}
export default App;

触发Reducer自动执行

实际应用中,dispatch方法需要触发 reducer 的自动执行,从而对state进行修改。为此,store 需要知道 reducer 函数,做法就是在生成 store 的时候,将 reducer 传入createStore方法。

// redux/store
import {createStore} from 'redux'
import reducers from './reducers/reducers'
// createStore接受 reducers 作为参数,生成一个新的Store。
// 以后每当dispatch发送过来一个新的 action,就会自动调用reducers(多个reducer的组合),得到新的 State。
const store = createStore(reducers)
export default store 

添加事项

UI组件AddTodo

新建AddTodo组件,当点击添加时,将输入框内的值value通过调用props接收到的addTodoText方法,传递到containers下的容器组件addtodo中。

//components/addtodo/AddTodo
import React, { Component } from 'react';
class AddTodo extends Component {
  handleAdd() {
    if(this.refs.inputText.value) {
      this.props.addTodoText(this.refs.inputText.value)
      // 调用接受到的addTodoText方法
      // 这个方法会在containers下的容器组件addtodo中与组件AddToDo连接
      this.refs.inputText.value = ''
    }
  }
  render() { 
    return ( 
      <div>
        <input type="text" ref="inputText" />
        <button onClick={this.handleAdd.bind(this)}>添加</button>
      </div>
    );
  }
}
export default AddTodo;

容器组件addtodo

connect方法会将UI组件AddTodo进行包装,并添加业务逻辑:输入逻辑mapStateToProps、输出逻辑mapDispatchToProps,最后得到一个容器组件 addtodo。这里只用到了输出逻辑(用户在组件上的操作如何变为action对象,点击的‘添加事项’会从这里传出去)。

// containers/addtodo
import AddTodo from '../components/addtodo/AddTodo';
import { connect } from 'react-redux'
import { addTodo } from '../redux/actions'
// 这是一个重点,一定要理解
const mapDispatchToProps = (dispatch) => {
  return {
    addTodoText: (text)=> {
      //console.log('传递成功',text);
      dispatch(addTodo(text))
      // dispatch 会发出action-addTodo
      // 在redux/actions下存储了多个action
    }
  }
}
export default connect(null, mapDispatchToProps)(AddTodo)
// redux/actions
import * as actionTypes from './actionTypes'
export function addTodo(text) {
  return { type: actionTypes.ADD, text, completed: false}
  // 在actionTypes 下存储了多个用来描述修改意图的参数
}
//   redux/actionTypes
export const ADD = "ADD"
// 添加事件

触发reducer

在容器组件addtodo中触发了dispatch并传递一个新的action,自动调用reducers中的todolist

 // reducers/todolist
 function NewList(state = [], action) {
 //调用时的state内容是上次添加完成后的内容
 //新内容在action中
  switch(action.type){
    case 'ADD': 
      state = [{text: action.text, completed: action.completed}, ...state] 
      return state
    default: return state
  }
}
export default NewList
// reducers/reducers
import { combineReducers } from 'redux'
import NewList from './todolist'
export default combineReducers({
  NewList,
  // 有多个reducer要在这里引入
})

获取reducer处理完成后的数据

这里将会使用UI组件ShowList的容器组件showlistshowlist通过输入逻辑 mapStateToProps获取reducer处理完成后的state,并将其映射到UI组件ShowListprops中。

//  containers/showlist
// 只有容器组件才能获取state
import { connect } from 'react-redux';
import ShowList from '../components/showlist/ShowList';
const mapStateToProps = (state) => {
  return {
    list: state.NewList
  }
}
export default connect (mapStateToProps, null)(ShowList)

组件ShowList渲染数据

state经过容器组件的传递,可在UI组件的this.props中获取。

import React, { Component } from 'react';
class ShowList extends Component {
  render() {
    let { list } = this.props //终于拿到点击添加后的事项
    return (
      <ul>
        {
          list.map((item, index) => (
            <li key={index}>
              {item.text}
            </li>
          ))
        }
      </ul>
    );
  }
}
export default ShowList;

完成事项

实现:点击事项,出现删除线,表示已完成

UI组件ShowList

为每条事项添加点击事件,将点击的事项id传给容器组件上的dispatch,从而触发reducer进行修改。

class ShowList extends Component {
  handleDone(index) {
    return () => {
      this.props.completedThing(index)
    }
  }
  render() {
    let { list } = this.props
    return (
      <ul>
        {
          list.map((item, index) => (
            <li onClick={this.handleDone(index)} key={index}
              className={item.completed ? 'line-through' : ''}
              ref='node'>
                 // 在css文件中更改样式
              {item.text}
            </li>    
          ))
        }
      </ul>
    );
  }
}
export default ShowList;

容器组件showlist

通过UI组件的触发,在mapDispatchToProps中发起dispatch请求(与添加事项相似)。

// containers/showlist
import { connect } from 'react-redux';
import ShowList from '../components/showlist/ShowList';
import { completed } from '../redux/actions'   
//  引入action
const mapStateToProps = (state) => {
  return {list: state.NewList}    // 之前写过的
}
const mapDispatchToProps=(dispatch)=>{
  return {
    completedThing:(index)=>{
      // console.log('传递成功',index);
      dispatch(completed(index))
      }
  }
}
export default connect (mapStateToProps, mapDispatchToProps)(ShowList)
// actions
export function completed(index) {
  return { type: actionTypes.DONE, index}
  //将点击的事项的id传给reduce
}

// actionTypes
// 完成事件
export const DONE = 'DONE'

触发reducer

同样调用reducers中的todolist

//  reducers/todolist 
function NewList(state = [], action) {
  ......
    case 'DONE':
      return (() => {
        state = state.slice(0)
        state[action.index].completed = !state[action.index].completed; 
        // 修改事项中的complete参数 再返回数据
        return state
      })()
    default: return state
  }
}
export default NewList

获取和渲染

修改过state,UI组件ShowList会重新渲染,相关办法不改变。

筛选事项

UI组件Filter

添加三个按钮的点击事件,分别对应容器组件上的方法。

// components/filter/Filter
import React, { Component } from 'react';
class Filter extends Component {
  handleAll() {
   this.props.renderAll()
  }
  handleActive() {
    this.props.renderActive()
  }
  handleGone() {
    this.props.renderGone()
  }
  render() { 
    return ( 
      <div>
        <button onClick={this.handleAll.bind(this)}>全部</button>
        <button onClick={this.handleActive.bind(this)}>未完成</button>
        <button onClick={this.handleGone.bind(this)}>已完成</button>
      </div>
    );
  }
}
export default Filter;

容器组件filter

通过UI组件的触发,在mapDispatchToProps中发起dispatch请求。

import { connect } from 'react-redux';
import Filter from '../components/filter/Filter';
import { selectAll, selectActive, selectGone } from '../redux/actions'

const mapDispatchToProps = (dispatch) => {
  return {
    renderAll: () => {
      //console.log('加载全部');
      dispatch(selectAll())
    },
    renderActive: () => {
      //console.log('加载未完成');
      dispatch(selectActive())
    },
    renderGone: () => {
      //console.log('加载已完成');
      dispatch(selectGone())
    }
  }
}
export default connect(null, mapDispatchToProps)(Filter)
// actions
export function selectAll() {
  return { type: actionTypes.ALL }
  //注意这里传递的是点击的按钮参数 ‘ALL’
}
export function selectActive() {
  return { type: actionTypes.ACTIVE }
}
export function selectGone() {
  return { type: actionTypes.GONE }
}

// actionTypes
// 加载全部事件
export const ALL = 'ALL'
// 加载未完成事件
export const ACTIVE = 'ACTIVE'
// 加载已完成事件
export const GONE = 'GONE'

触发reducer

调用reducers下的filter,返回对应的参数放在 FilterTtpe中。

// reducers/filter
function FilterType(state, action) {
  switch(action.type) {
    case 'ACTIVE':
      return 'ACTIVE'
    case 'GONE':
      return 'GONE'
    default:
      return 'ALL'
      // 默认点击‘全部’,加载全部事项
  }
}
export default FilterType

获取和渲染

在容器组件showlist中通过接收到的NewListFilterType,对list进行筛选,返回给UI组件筛选完成后的新表。UI组件ShowList重新渲染。因为在点击筛选按钮的过程中没有添加新的事项,所以stateNewList一直是最后一次添加完成后的内容。

import { connect } from 'react-redux';
import ShowList from '../components/showlist/ShowList';
import { completed } from '../redux/actions'

const mapStateToProps = (state) => {
  //console.log(state.NewList);
  let fileList = []
  switch(state.FilterType) {
    case 'ACTIVE':
      fileList = state.NewList.filter(item => item.completed === false)
      return { list: fileList}
    case 'GONE':
      fileList = state.NewList.filter(item => item.completed === true)
      return { list: fileList}
    default:
      return { list: state.NewList}
  }
}
const mapDispatchToProps=(dispatch)=>{
  return {
    completedThing:(index)=>{
      //console.log('传递成功',index);
      dispatch(completed(index))
      }
  }
}
export default connect (mapStateToProps, mapDispatchToProps)(ShowList)

总结

  • components文件夹下的UI组件只负责渲染数据,不负责业务逻辑;参数都由 this.props提供,不使用 this.state
  • containers文件夹下的容器组件负责数据管理和业务逻辑,主要通过 mapStateToProps, mapDispatchToProps来获取或处理数据。
  • react-redux提供的connect方法用于生成容器组件。
  • mapStateToProps负责输入逻辑,将reducer返回的state映射到UI组件的 props中。
  • mapDispatchToProps负责输出逻辑,将用户的反馈参数映射在 action中,并通过发起 dispatch来传递给 reducer进行修改。
点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
Jira Epic在完成状态时,如何让Epic在Scrum面板待办事项中不显示?
遇到的问题:Epic在完成状态时,仍旧在Scrum面板待办事项中显示,需要手动将其【标记完成】而想要的效果是:Epic到完成状态时,Epic自动标记完成!(https://oscimg.oschina.net/oscnet/03911d609eda7627b34e9cf9ef0a4c25ab9.jpg)通过Chrome控制台查看【标记完成】
Stella981 Stella981
3年前
Redux入门实战——todo
1.前言在之前的博客中,我写了一篇关于todolist实现的博客,一步一步详细的记录了如何使用基础的React知识实现一个React单页面应用,通过该篇文章,能够对React入门开发有一个直观的认识和粗浅的理解。近期,个人学习了一下Redux,又将该项目使用ReactRedux的方式进行了实现。本片内容记录以下实践的过程。通过本实例,
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
MySQL数据库InnoDB存储引擎Log漫游(1)
作者:宋利兵来源:MySQL代码研究(mysqlcode)0、导读本文介绍了InnoDB引擎如何利用UndoLog和RedoLog来保证事务的原子性、持久性原理,以及InnoDB引擎实现UndoLog和RedoLog的基本思路。00–UndoLogUndoLog是为了实现事务的原子性,
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
3年前
Hibernate纯sql查询结果和该sql在数据库直接查询结果不一致
问题:今天在做一个查询的时候发现一个问题,我先在数据库实现了我需要的sql,然后我在代码中代码:selectdistinctd.id,d.name,COALESCE(c.count_num,0),COALESCE(c.count_fix,0),COALESCE(c
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这