"待辦事項列表"這個例子是redux文檔推薦的,可是對於新人來講,官方文檔和網上博客的相關解釋都不是很好,沒有思路分析到代碼實現整個個過程,這令我在學習的時候很是頭疼。看着不分文件、不梳理數據流向、不解釋代碼思路的各類文章,我決定本身寫一篇redux入門實例——簡單的待辦事項列表。
githubjavascript
state
被儲存一個object tree
中,而且這個object tree
只存在於惟一的store
中。state
的方法是經過dispatch
觸發action
,action
是一個描述修改意圖的普通對象,例如:{ type: add, value }
。store
收到action
後,開始在reducer
中計算,最後返回一個新的state
。create-react-app redux-todolist
cnpm i redux react-redux -S
複製代碼
在根組件外用provider
包裹,可讓全部組件都能拿到state
。css
// 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;
複製代碼
實際應用中,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
複製代碼
新建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; 複製代碼
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"
// 添加事件
複製代碼
在容器組件addtodo
中觸發了dispatch
並傳遞一個新的action
,自動調用reducers
中的todolist
。react
// 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要在這裏引入
})
複製代碼
這裏將會使用UI組件ShowList
的容器組件showlist
,showlist
經過輸入邏輯mapStateToProps
獲取reducer
處理完成後的state
,並將其映射到UI組件ShowList
的props
中。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)
複製代碼
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
爲每條事項添加點擊事件,將點擊的事項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;
複製代碼
經過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'
複製代碼
一樣調用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
會從新渲染,相關辦法不改變。
添加三個按鈕的點擊事件,分別對應容器組件上的方法。
// 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;
複製代碼
經過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'
複製代碼
調用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
中經過接收到的NewList
和FilterType
,對list
進行篩選,返回給UI組件篩選完成後的新表。UI組件ShowList
從新渲染。由於在點擊篩選按鈕的過程當中沒有添加新的事項,因此state
中NewList
一直是最後一次添加完成後的內容。
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
進行修改。