Redux

Redux

2020/11/23

** 感謝張晴惠同學提供本範例 **

簡介

Redux 可以讓開發者建立一個唯一的資料管理容器,用來集中管理資料,這個資料管理容器又稱為Store,主要由State、Action、Reducer組成。

React + Redux

因為React是單向資料流,所以不同子樹之間要交換資訊是很麻煩的,所以使用Redux之後,元件之間如果要傳送資訊,只需要透過發出action即可。

Redux官方也提供了另一套的React-Redux來整合React和Redux

安裝

安裝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。