今天聊一聊 react + redux 環境快速搭建,以及實戰一個 TodoList,多是有史以來最簡潔的方法哦,是否是很期待,當時橙子也是很吃驚這樣的搭建速度。html
本文適合有 react 和 redux 基礎的同窗看,固然你也能夠看完本文去學基礎。下面進入正題node
沒錯,看過前面博客的同窗猜到,我會選擇各類 cli 工具,固然你可能猜錯了,這裏用的不是 create-react-app 這個 star 最多的構建工具。react
這裏咱們用 react-redux-starter-kitwebpack
須要 node 4.5.0+ & npm 3.0.0+
便可,首先git
git clone https://github.com/davezuko/react-redux-starter-kit.git <my-project-name> cd <my-project-name> npm install npm start
沒錯你已經搭建環境完畢。好快的樣子!github
此時你能夠看到 localhost:3000
的首屏了,welcome 而後一個鴨子,奇怪的首屏,做者說 duck 是 redux 的諧音,好吧。。。web
此時可能你已經興奮的點完了,這個 cli 的所有內容。chrome
先不急看效果,既然是 redux,就要有對應的 chrome 插件,讓咱們直觀的看到 store 的變化。npm
推薦 redux-devtool,點開連接,添加至 chrome 便可。redux
此時開啓控制檯,找到 Redux 的 tab,會看到以下界面
功能強大,想深刻學習的能夠看文檔,這裏咱們只關心 Action 和 State 的變化
此時項目在 localhost:3000/counter
這個路由下有個累加器,Increment 每次同步加 1,Double(async) 每次異步乘 2。
異步處理速度慢?不是的看代碼會發現這樣一段
export const doubleAsync = () => { return (dispatch, getState) => { return new Promise((resolve) => { setTimeout(() => { dispatch({ type : COUNTER_DOUBLE_ASYNC, payload : getState().counter }) resolve() }, 200) }) } }
沒錯,是一個 200ms 的 setTimeout。
你們印象裏,官方文檔有說 action 是當即執行的同步函數,咱們能夠藉助中間件的形式實現異步,這裏用的是 redux-thunk
,這種方式的確解決了問題,以後的文章還會單獨介紹更好的辦法。
代碼簡單過一遍,沒什麼難點。接下來咱們就能夠根據這個項目漸進開發了。
好,咱們首先添加一個路由
src/compoments/Header/Header.js
在 div 標籤內最下面添加以下代碼
{' · '} <Link to='/todo' activeClassName='route--active'> Todo </Link>
src/routes/index.js
對應位置以下修改,添加一個子路由。
import CoreLayout from '../layouts/CoreLayout' import Home from './Home' import CounterRoute from './Counter' import TodoRoute from './Todo' export const createRoutes = (store) => ({ path : '/', component : CoreLayout, indexRoute : Home, childRoutes : [ CounterRoute(store), TodoRoute(store) ] })
咱們如今尚未 Todo 對應路由的組件,下面在 routes 下建立 Todo 文件夾,而後文件夾結構以下
Todo |-components | |-Todo.js | |-containers | |-TodoContainer.js | |-modules | |-todo.js | |-index.js
簡單解釋一下這個目錄結構,index.js 是 Todo 做爲路由頁面的入口文件,components 下放的是模塊的視圖部分,containers 做爲一個容器來綁定組件的 event、 state 和 prop,modules 主要是負責從 action->reducer->newState 的過程。
條例清晰,梳理完結構,看看每一個文件都怎麼完成的
index.js
import { injectReducer } from '../../store/reducers' export default (store) => ({ path : 'todo', getComponent (nextState, cb) { require.ensure([], (require) => { const Todo = require('./containers/TodoContainer').default const { itemReducer } = require('./modules/todo') const { todoListReducer } = require('./modules/todo') injectReducer(store, { key: 'itemData', reducer: itemReducer }) injectReducer(store, { key: 'todoList', reducer: todoListReducer }) cb(null, Todo) }, 'todo') } })
這裏將 store 和 reducer 綁定,將 store 注入到 /todo 的路由下的 props 中去
components/Todo.js
import React, { Component } from 'react' export default class Todo extends Component { render () { return ( <div> <input className='form-control' style={{ marginBottom: '10px' }} type='text' ref='input' value={this.props.itemData} onChange={(e) => this.handleChange(e)} /> <button className='btn btn-primary btn-lg btn-block' style={{ marginBottom: '10px' }} onClick={(e) => this.handleSubmit(e)} > 添加 </button> <ul className='list-group'> { this.props.todoList.map((item, index) => { return <li className='list-group-item' key={index} > {item} <button className='btn btn-default' data-index={index} onClick={(e) => this.handleDel(e)} > ❌ </button> </li> }) } </ul> </div> ) } handleChange (e) { const node = this.refs.input const text = node.value.trim() this.props.updateItem(text) } handleSubmit (e) { const node = this.refs.input const text = node.value.trim() this.props.addItem(text) this.props.updateItem('') } handleDel (e) { const index = e.target.getAttribute('data-index') this.props.delItem(Number(index)) } } Todo.propTypes = { addItem: React.PropTypes.func.isRequired, itemData: React.PropTypes.string.isRequired, updateItem: React.PropTypes.func.isRequired, delItem: React.PropTypes.func.isRequired, todoList: React.PropTypes.array.isRequired }
就是個簡單的 jsx,這裏後綴沒有寫成 .jsx 文件,由於 webpack 配置瞭解析規則,.js 文件也能夠正常解析,這裏的 onClick 事件寫成了 =>
箭頭函數的形式,目的是爲了鎖定 this 的做用域,在 handle 函數內能夠拿到 this。
containers/TodoContainer.js
import { connect } from 'react-redux' import { updateItem, addItem, delItem } from '../modules/todo' import Todo from '../components/Todo' const mapDispatchToProps = { updateItem, addItem, delItem } const mapStateToProps = (state) => ({ itemData: state.itemData, todoList: state.todoList }) export default connect(mapStateToProps, mapDispatchToProps)(Todo)
這裏經過一個 connect 方法,將組件的 props 進行綁定。
modules/todo.js
export const UPDATE_ITEM = 'UPDATE_ITEM' export const ADD_ITEM = 'ADD_ITEM' export const DELETE_ITEM = 'DELETE_ITEM' export function updateItem (item) { return { type: UPDATE_ITEM, payload: item } } export function addItem (item) { return { type: ADD_ITEM, payload: item } } export function delItem (index) { return { type: DELETE_ITEM, payload: index } } export const actions = { updateItem, addItem, delItem } const ITEM_ACTION_HANDLERS = { [UPDATE_ITEM]: (state, action) => action.payload } const LIST_ACTION_HANDLERS = { [ADD_ITEM]: (state, action) => state.concat(action.payload), [DELETE_ITEM]: (state, action) => state.filter((item, index) => { return index !== action.payload }) } export function itemReducer (state = '', action) { const handler = ITEM_ACTION_HANDLERS[action.type] return handler ? handler(state, action) : state } export function todoListReducer (state = [], action) { const handler = LIST_ACTION_HANDLERS[action.type] return handler ? handler(state, action) : state }
這裏 reducer 運用兩個數組方法 concat 和 filter,分別作拼接和刪除操做,這裏意遵循 redux 的官方文檔在不修改原 state,而是返回新的 state。在底部將兩個 reducer 導出,綁定到 index.js 中。完成一個閉環,數據更新完成。
到這裏咱們的 todoList 完成了。
一邊看界面一邊看控制檯 redux store 的變化吧!代碼沒有晦澀難懂的點,一個 todoList 也足以說明列表操做的流程,這也是各大框架都以 todoList 爲 demo 的緣由。
對於複雜的應用 redux 帶來的優點不贅述,本文的目的是講述如何快速開發 react + redux 應用,讓你們在配置環境上少踩坑,也經過一個簡單的例子,說明咱們應該怎麼更簡單的去使用 redux。動手寫起來吧,橙子的文章通常都看不出效果的,親手實現一遍會有意外收穫哦,上面代碼也是橙子本身踩過坑後寫出來的,各大神有什麼建議的地方儘管提出來!感謝閱讀。
文章出自 orange 的 我的博客 http://orangexc.xyz/