Redux
Redux
2020/11/23
** 感謝張晴惠同學提供本範例 **
簡介
Redux 可以讓開發者建立一個唯一的資料管理容器,用來集中管理資料,這個資料管理容器又稱為Store,主要由State、Action、Reducer組成。
React + Redux
因為React是單向資料流,所以不同子樹之間要交換資訊是很麻煩的,所以使用Redux之後,元件之間如果要傳送資訊,只需要透過發出action即可。
Redux官方也提供了另一套的React-Redux來整合React和Redux
React Redux : https://react-redux.js.org/introduction/quick-start
安裝
安裝redux 、 react redux
npm install redux
npm install react-redux
範例 (TodoApp)
範例為一個簡單的TodoList App,可以新增刪除待做事項,並顯示在畫面上,也可以點擊完成事項。
利用Redux統一將todoList這個陣列存在中央的State倉庫中。
Folder Structure ( 主要 )
- src/
--- App.js
--- components/
----- TodoList.js
--- store/
----- actions.js
----- reducer.js
App.js
建立Store : 透過createStore 將定義好的Reducer來建立Store。
Provider 是控制整個 state (store) 的 root component,在Provider下的元件(如:TodoList)都可以取得store的內容。(詳參: Provider )
import React from 'react';
import { createStore } from 'redux'
import { Provider } from 'react-redux';
import reducer from './src/store/reducer'
import TodoList from './src/components/TodoList'
const store = createStore(reducer)
export default function App() {
return (
// Provider管理store
<Provider store={store}>
<TodoList/>
</Provider>
)
}
每一個Action 用來描述事件類別(type),以及所乘載的資訊(payload)。要改變State的方法就是指派一個Action,但Action內不會直接修改State,而是交給Reducer,Reducer收到Action之後,根據Action裡的type及payload去修改State。
/src/store/actions.js
// 定義action type
export const ADD_TODOLIST = 'ADD_TODOLIST'
export const DELETE_TODOLIST = 'DELETE_TODOLIST'
export const FINISH_TODOLIST = 'FINISH_TODOLIST'
// 簡易的ID製造
function ID() {
return '_' + Math.random().toString(36).substr(2, 9)
}
// 新增的action
export const addTodoList = (todoDec) => {
return {
type: ADD_TODOLIST,
payload: {
id: ID(),
todoDec
},
};
}
// 刪除的action
export const deleteTodo = (todoIndex) => {
return {
type: DELETE_TODOLIST,
payload: todoIndex,
}
}
// 完成的action
export const finishTodo = (id) => {
return {
type: FINISH_TODOLIST,
payload: id,
}
}
reducer 就像是一個中央管理狀態(State)的地方,State裡有兩個陣列:todoList以及finishList,根據action 來改變state。dispatch將action傳進來,reducer根據這是甚麼類型的action(action.type)執行對應的動作,進而去改變(新增或刪除)中央管理的狀態(state)。
也就是reducer能夠取得當前的 State 和被指派的 Action,根據給定的 Action類型(action.type) 處理當前的State而回傳新的 State。
/src/store/reducer.js
import {
ADD_TODOLIST,
DELETE_TODOLIST,
FINISH_TODOLIST
} from './actions'
// 初始狀態
const initState = {
todoList: [],
finishList: [],
}
// reducer 就像是一個中央管理狀態的地方
// 只透過action 來改變state
// dispatch將action傳進來,告訴reducer,這是甚麼類型的action,
// reducer就會根據類型去做不一樣的事情,進而去改變(新增或刪除)中央的管理state
const reducer = (state = initState, action) => {
switch (action.type) {
case ADD_TODOLIST: {
const tempTodo = [...state.todoList];
tempTodo.push(action.payload);
return {
...state,
todoList: tempTodo,
};
}
case DELETE_TODOLIST: {
const tempTodo = [...state.todoList];
// 將傳進來的index位置刪除掉。
tempTodo.splice(action.payload, 1)
return {
...state,
todoList: tempTodo
}
}
case FINISH_TODOLIST : {
const TempFinish = [...state.finishList]
TempFinish.push(action.payload)
return {
...state,
finishList: TempFinish
}
}
default:
return state;
}
};
export default reducer
利用useSelector來抓取(select)reducer裡的state以及useDispatch可以幫將我們想要做的action傳遞到reducer (詳參: Hooks )
以新增為例,當使用者在介面中按下送出時,會呼叫handleAddTodo(),handleAddTodo()利用dispatch將todoDesc當作參數送出addToDoList。這時候addToDoList收到todoDesc,產生一個action物件,包括type(內容是:ADD_TODOLIST)、以及payload,payload裡包括id以及todoDesc。
接下來reducer就會收到這個action,發現type是ADD_TODOLIST,所以,就會將payload加到todoList的最後,並將結果回傳。然後,回到TodoList,將todoDesc清空。因為state更新了,使用者介面就會重整,看到最新的todoList及finishedList。
/src/components/ TodoList.app
import React, { useState } from 'react'
import {
View,
Text,
StyleSheet,
KeyboardAvoidingView,
Platform,
TouchableWithoutFeedback,
Keyboard,
Button
} from 'react-native'
import { Item, Input } from 'native-base'
import { useSelector, useDispatch } from 'react-redux'
import { addTodoList, deleteTodo, finishTodo } from '../store/actions'
function TodoList() {
const [ todoDec, setTodoDec ] = useState('')
// useSelector 來抓取(select)reducer裡的state
const todoList = useSelector(state => state.todoList)
const finishList = useSelector(state => state.finishList)
// useDispatch 可以幫將我們想要做的action傳遞到reducer
const dispatch = useDispatch()
// 判斷是否已經完成
function isFinish(id) {
return finishList.includes(id)
}
// 新增
function handleAddTodo() {
dispatch(addTodoList(todoDec))
setTodoDec('')
}
// 刪除
function handleDeleteTodo(todoIndex) {
dispatch(deleteTodo(todoIndex))
}
// 完成
function handleFinishTodo(id) {
dispatch(finishTodo(id))
}
return (
<>
{/* TouchableWithoutFeedback : 當在輸入時,按空白處可讓keyboard收起來 */}
<TouchableWithoutFeedback onPress={()=>Keyboard.dismiss()} >
<View style={styles.container}>
{todoList.map((todo, index)=>{
return (
<View key={`todo-${index}`} style={styles.todoItem}>
{/* 如果這條todo是完成的(回傳true),那就套用styles.finishText */}
<Text style={isFinish(todo.id) && styles.finishText}>{index + 1} / {todo.todoDec}</Text>
<View style={{ flexDirection: 'row' }}>
<Button onPress={()=>handleDeleteTodo(index)} title="DELETE" color="red" />
<Button onPress={()=>handleFinishTodo(todo.id)} title="FINISH" color="green" disabled={isFinish(todo.id)}/>
</View>
</View>
)
})}
</View>
</TouchableWithoutFeedback>
{/* KeyboardAvoidingView: 為了讓輸入框不要被keyboard遮住 */}
<KeyboardAvoidingView
behavior={Platform.OS == "ios" ? "padding" : "height"}
>
<View style={styles.footer}>
<Item regular>
<Input
placeholder="請輸入事項"
value={todoDec}
returnKeyType="send" //改變鍵盤右下角變為送出
onSubmitEditing={handleAddTodo} //按下鍵盤右下鍵 執行submit(新增)
onChangeText={(val)=>setTodoDec(val)}
/>
</Item>
</View>
</KeyboardAvoidingView>
</>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 48,
paddingHorizontal: 16,
},
todoItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 16,
borderBottomColor: '#ccc',
borderBottomWidth: 2
},
finishText: {
textDecorationLine: 'line-through',
textDecorationStyle: 'solid'
},
footer: {
marginBottom: 36,
paddingHorizontal: 16
}
})
export default TodoList
作業
將註冊、登入、登出改用redux。
Redux
How to integrate Redux into your application with React Native and Expo
Redux integration (with react navigation)