前接REACT – 06 範例1 STATE 二訪,透過React本身的State機制,可以很方便改變顯示的原件,不過是有點笨重的感覺,例如deleteTotoItem()
class App extends React.Component {
deleteTotoItem(item) {
...
this.setState({todos:newTodos});
}
render() {
...
<TodoList todos={this.state.todos} deleteTotoItem={this.deleteTotoItem}/>
}
}
class TodoList extends React.Component {
...
listItem(item) {
return(
<TodoListItem ... deleteTotoItem={this.props.deleteTotoItem}/>
);
}
...
}
class TodoListItem extends React.Component {
...
onDeleteClick(event) {
...
this.props.deleteTotoItem(item);
}
...
}
在<App>
裡定義的deleteTotoItem()
,被傳了兩層才真的在TodoListItem
中用到,State
也是經過多層的傳遞才拿到,如果覺得這樣是個問題,可以準備使用redux了,如果覺得這不是問題,那接下來這兩篇就是用不到的部份。
簡介
Redux主要由三個部份組成。
- Store:用來將所有的State集中存放
- Reducer:用來進行State內容的初始化,並真正改變State的資料
- Action:這是通知Store目前要做的改變,Store會依Action裡的keyword,找到合適的Reducer來處理
其實我覺得Action
比較像是trigger,Reducer
這詞單看字面也不太能體會用法。
不過還好,都很簡單…
Action
Action是一個單純的JavaScript Object,裡面只有兩個property,一個是用來充當keyword的type
,另一個則是可以隨意命名,用來改變State內容的資料,大概就像下列這樣。
var sample_action = {
type:'DO_SOMETHING',
data:''
}
type
的值在同一元件內不可重覆,而且為了方便管理,所以最好用const
定義出來,像下列這樣
const DO_SOMETHING = 'DO_SOMETHING';
var sample_action = {
type:DO_SOMETHING,
date:''
}
Reducer
Reducer是個function
,只收兩個變數(currentState, action)
,然後根據action.type
來進行state的改變,例如等會要看到的例子
const defaultTodos = [
{
id:'abc',
name:'Homework'
},
{
id:'def',
name:'House keeping'
}
];
function todosReducer(todos = defaultTodos, action) {
console.log(`invoke todosReducer with action - ${action.type}`);
switch (action.type) {
case ADD_TODOITEM:
return [ ...todos, {id:Date.now(), name:action.name}];
case DELETE_TODOITEM:
return todos.filter((it) => {return (it.id != action.id);});
default:
return todos;
}
}
todos = defaultTodos
這樣是個方便的做法,當一開始State
裡的todos
還是null的時候,就用defaultTodos
來當預設值。再依action.type
來處理內容的更動,若遇到不認識的type,就直接回傳收到的todos
,實際上在產生Store
時,Redux也會故意用INIT
當做type,讓state裡的資料能取得預設值完成初始化。
Store
Store基本上就是Component外的State,利用Redux.combineReducers({reducers})來產生,Property的名稱要跟原來的State相同,但Property的值要放reducer。所以如果原來的state是這樣的話
this.state = {
todos : [
{
id:'abc',
name:'Homework'
},
{
id:'def',
name:'House keeping'
}
],
name : ``
}
就要改成
const store = Redux.createStore({
todos:todosReducer,
name:nameReducer
});
記得Reducer通常還要負責做預設值初始化的工作,Redux會故意丟一個@@redux/INIT
的Action type到reducer裡,強迫每個reducer至少執行一次以取得State的預設值。
透過store.subscribe()
來加入listener,當State有所變化時就會執listerner,以便通知Component。
store.dispatch(action)
則是依據傳入的action來觸發reducer完成State內容的更動。
store.getState()
則可以取得State現在的內容。
實際修改
由於要使用redux
,所以要外加兩個javascript library。
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.4/react-redux.js"></script>
再來是把所有的State集合取來,並為其產生對應的Action,Reducer
Action
//定義Action會用到的keyword
const ADD_TODOITEM = 'ADD_TODOITME';
const DELETE_TODOITEM = 'DELETE_TODOITEM';
const UPDATE_NAME = 'UPDATE_NAME';
//定義產生Action的function
function createAddTodoItemAction(name) {
return {
type : ADD_TODOITEM,
name : name
}
}
function createDeleteTodoItemAction(id) {
return {
type : DELETE_TODOITEM,
id : id
}
}
function createUpdateNameAction(name) {
return {
type : UPDATE_NAME,
name : name
}
}
三個createXXXAction
沒有定義也沒關係,寫出來只是如果會有重覆用到時比較不會混淆,但如果只用一次的話,直接在呼叫dispatch
裡寫出Action Object也是不錯的方式。
Reducer
const defaultTodos = [
{
id:'abc',
name:'Homework'
},
{
id:'def',
name:'House keeping'
}
];
//加上console.log可以方便觀察redux做了什麼事
function todosReducer (todos = defaultTodos, action) {
console.log(`todosReducer invoked with action[${action.type}]`);
switch (action.type) {
case ADD_TODOITEM:
return [ ...todos, {id:Date.now(), name:action.name}];
case DELETE_TODOITEM:
return todos.filter((it) => {return (it.id != action.id);});
default:
return todos;
}
}
function nameReducer(name = '', action) {
console.log(`nameReducer invoked with action[${action.type}]`);
switch (action.type) {
case UPDATE_NAME:
return action.name;
default:
return name;
}
}
重點在於初始化及最後預設回傳值。
Store
const reducers = Redux.combineReducers({
todos:todosReducer,
name:nameReducer
});
const store = Redux.createStore(reducers);
依原先State的樣子產生Object參數,只是Property裡的預設值由原來data或object改為Reducer。
Provider
為了方便原來的Component能拿到Store,所以Redux提供了一個ComponentProvider
,只要在原本的外加上去,各Component就都能拿到Store了。
//由於目前是透過include javascript的方式,所以需要為ReactRedux.Provider另外命名。
const Provider = ReactRedux.Provider;
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('Main'));
<Provider store={store}><App /></Provider>
就是重點囉。
其他Component的修改
App
class App extends React.Component {
componentWillMount() {
store.subscribe(()=>{this.setState(store.getState())});
}
render() {
return (
<div>
<TodoFrom />
<TodoList />
</div>
);
}
}
這裡利用componentWillMount()
這個life cycle method來加入listener,store.subscribe(()=>{this.setState(store.getState())});
當Store中的值改變時,就回存到State裡來。畢竟Store是外部的東西,要回歸到React裡來還是要依React的規則,使用this.setState()
才可以。
其他
其他Component的修改就很容易了,我們不用再把State的值與改變State的function一層層的傳下去,只要直接使用store.getState()
及store.dispatch()
就好。
不過這有點效率上的問題,再進一步會更好。
原始程式可以參考05_Redux_02.html,與先前程式04_ComponentLayout_03.html比較更容易看出差異。
2017/04/24 :
新增兩張圖,說明如何思考將State
轉換為Store
,Reducer
及Action
2 thoughts to “React – 07 Redux (1)”