redux好難懂,終於明白了一點,做了個小demo,記錄一下。
先看目錄結構:
src
|--index.js
|--actions
|--index.js
|--components
|--Additem.js
|--App.js
|--ItemList.js
|--reducers
|--index.js
最終效果如圖:
redux三大件:actions , reducers, store ,
Action 是把數據從應用(譯者注:這裏之所以不叫 view 是因爲這些數據有可能是服務器響應,用戶輸入或其它非 view 的數據 )傳到 store 的有效載荷。它是 store 數據的唯一來源。一般來說你會通過store.dispatch()
將 action 傳到 store。
這是官方的解釋,我個人的理解是 action就是定義一個事件,但不做任何邏輯上的操作,只是把你需要處理的數據和事件綁定到一起,然後等待store進行派發,也就是更新store。就好像定義了一個按鈕的點擊事件 ;
btn.addEventListener(MouseEvent.CLICK,onClickHandle);
這句代碼實際上什麼操作也沒有做,只是告訴系統有MouseEvent.CLICK消息的時候應該做什麼。
reducer Action 只是描述了有事情發生了這一事實,並沒有指明應用如何更新 state。而這正是 reducer 要做的事情。
這是官方解釋,我的理解是就像上面說的onClickHandle函數,就是在等待事件被觸發的時候要執行的代碼,當有action被派發出來的時候,store就會根據定義來找到這個函數去執行,然後改變store,從而引起react的重新渲染,最終使view發生改變。
Store 就是把action 和 reducer 聯繫到一起的對象。Store 有以下職責:
維持應用的 state;
提供
getState()
方法獲取 state;提供
dispatch(action)
方法更新 state;通過
subscribe(listener)
註冊監聽器。
Redux 應用只有一個單一的 store。當需要拆分處理數據的邏輯時,使用 reducer 組合 而不是創建多個 store。
我的理解是store就是一個事件監聽器加事件派發器加狀態記錄器。他會根據被觸發的action來找到對應的reducer。
我在寫這個demo的時候是先定義了action,也就是會有哪些操作,然後根據這些action去寫reducer,也就是每個事件應該怎麼處理。最後寫了界面,當然,也可以先寫界面,根據界面定義action。
下面看看代碼是怎麼寫的,很簡單並且很簡陋的一段代碼,
src/actions/index.js:
"use strict"; export const ADD_ITEM = 'add_item'; export const DEL_ITEM = 'delete_item'; export function addItem(text){ return { type:ADD_ITEM, text } } export function delItem(index){ return { type:DEL_ITEM, index } }
這裏定義了兩個事件,一個是添加記錄,一個是刪除記錄,每個方法都是返回一個對象,對象中必須有type屬性,這個type屬性實際上纔是真正的事件區分器,
然後additem中的text是要添加記錄的內容,index是要刪除記錄的所引。到這,action就完了。
reducers/index.js:
"use strict"; import { combineReducers } from 'redux'; import { ADD_ITEM, DEL_ITEM } from '../actions/index'; const initalState = { items:[] }; function aitems(state=[],action){ switch (action.type){ case ADD_ITEM: return state.concat([action.text]); case DEL_ITEM: state.splice(action.index,1); return [...state]; default: return state; } } let todos=combineReducers({ items:aitems }); export default todos;
這個文件裏的內容就是根據action做的操作,通過type來區分發生了什麼事,當應該添加一條記錄的時候,就把text內容保存到數組中並返回,
這裏有幾點需要說明:
1,initalState{} 初始state,裏面有一個屬性items,數組類型,當然可以叫別的名字。
2,aitems的第一個參數state=[],這裏爲什麼會讓state賦值一個數組類型呢?不應該是object麼?這是 因爲在給todos賦值那一句,items:aitems,我對這點的理解是當觸發事件的時候,redux會自動遍歷todos,然後把state和combineReducers中傳入的對象進行比較,遇到同名屬性,就把state中的同名屬性值傳給combineReducers中的同名屬性所對應的函數,有點繞!!!!
3,aitems的第二個參數action,你想的沒錯,就是剛纔我們定義的 src/actions/index.js中的方法所 返回的對象。
4,combineReducers函數是一個很有個性的函數,不多說了。
src/index.js:
"use strict"; import React from 'react'; import { render } from 'react-dom'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import App from './components/App'; import todos from './reducers/index'; let store = createStore(todos); let element = document.getElementById('app'); render( <Provider store={store}> <App /> </Provider>, element );
這個要說的不太多,因爲每個講react和redux有關的文章裏大部分會有這一段,主要是爲了把react 和redux 通過 react-redux連接起來。
下面看組件:
src/components/App.js:
"use strict"; import React from 'react'; import {connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as actions from '../actions/index'; import AddItem from './AddItem'; import ItemList from './ItemList'; class App extends React.Component{ render(){ console.log("app"); return <div> <AddItem onAddClick={(text) => this.props.actions.addItem(text)} /> <ItemList items={this.props.items} onDelClick={index=>this.props.actions.delItem(index)}/> </div> } } export default connect(({items})=>({items}),(dispatch) => ({actions:bindActionCreators(actions,dispatch)}))(App);
代碼很簡單,一看就能明白,唯一複雜一點的就是最後一句:
export default connect(({items})=>({items}),(dispatch) => ({actions:bindActionCreators(actions,dispatch)}))(App);
connect接收兩個對象,第一個對象是store中的state,也可以是一部分,會變成綁定view的props屬性,第二個對象是actions,也會變成當前view的props屬性,這樣就可以直接調用同名的actions,會自動派發對應的事件。connect函數執行過後會返回一個函數,再把當前的view傳進去,就可以把當前的view,state,action綁定到一起,這樣就能在當前的view中使用state的數據和派發事件了。
然後還有兩個組件:
src/components/Additem.js:
"use strict"; import React from 'react'; export default class AddItem extends React.Component{ static propTypes = { onAddClick:React.PropTypes.func }; _clickHandle(e){ console.log("additemclick"); this.props.onAddClick(this.refs.itemText.value.trim()); } render(){ console.log("additem"); return <div> <input type="text" ref="itemText"/><br/> <button onClick={this._clickHandle.bind(this)}>add</button> </div> } }
這就是普通的react組件了,從父級接收一些參數來使用,或者接收一些函數來實現在子組件中調用父組件的函數。
src/components/ItemList.js:
"use strict"; import React from 'react'; export default class ItemList extends React.Component{ static propTypes={ items:React.PropTypes.array.isRequired, onDelClick:React.PropTypes.func }; _onDelClick(index){ console.log("itemlistclick",index); this.props.onDelClick(index); } render(){ console.log("itemlist"); console.log(this.props.items); return <ul> { this.props.items.map((value,index) => <li>{index},{value}<button onClick={this._onDelClick.bind(this,index)}>del</button></li> )} </ul> } }
這個,同上,不再多做解釋。
恩。demo到這裏,主要代碼就完了,再來梳理一遍:
1,src/index.js把所有的包關聯起來。
2,定義actions,定義reducers。
3,把state和actions綁定到App.js上。
4,從App.js中把state和actions分別傳進子組件。
5,子組件中有操作,就會調用actions.
6,actions被觸發,store調用對應的reducers.
7,reducers被調用,修改state.
8,react檢測到state被修改,重新渲染組件,改變views。
恩。又跨近了一步。
參考:http://camsong.github.io/redux-in-chinese/docs/basics/ExampleTodoList.html