乐趣区

关于前端:Redux实例简单的待办事项列表

写在后面

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

成果展现

开始之前

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

目录构造

筹备

应用 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进行批改。
退出移动版