Redux實例—簡單的待辦事項列表

寫在前面

"待辦事項列表"這個例子是redux文檔推薦的,可是對於新人來講,官方文檔和網上博客的相關解釋都不是很好,沒有思路分析到代碼實現整個個過程,這令我在學習的時候很是頭疼。看着不分文件、不梳理數據流向、不解釋代碼思路的各類文章,我決定本身寫一篇redux入門實例——簡單的待辦事項列表。
githubjavascript

效果展現

開始以前

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包裹,可讓全部組件都能拿到statecss

// 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方法。html

// 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中。java

//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對象,點擊的‘添加事項’會從這裏傳出去)。node

// 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中的todolistreact

// 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中。git

// 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中獲取。github

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;
複製代碼

完成事項

實現:點擊事項,出現刪除線,表示已完成npm

UI組件ShowList

爲每條事項添加點擊事件,將點擊的事項id傳給容器組件上的dispatch,從而觸發reducer進行修改。redux

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進行修改。
相關文章
相關標籤/搜索