# (notdone)Redux document
###### tags: `Javascript, React`
[resource: redux 官網](https://redux.js.org/introduction/getting-started)
# Getting Started with Redux
> Redux is a predictable state container for JavaScript apps.
> Redux 是一個可預測的 state 容器對於 Javascript apps 來說
首先推薦插件安裝在你的瀏覽器上
[Redux DevTools](https://github.com/reduxjs/redux-devtools)
# Installation
## Create a React Redux App
兩個版本可以參考
```npm=
# Redux + Plain JS template
npx create-react-app my-app --template redux
# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript
```
## Redux Core
```npm=
# NPM
npm install redux
# Yarn
yarn add redux
```
# Basic Example
整個 global state 會儲存在一個物件樹裡面並在 store 檔案內,而唯一改變 state tree 的方式是創造 action ,action 會描述發生了什麼改變並且使用 dispatch 到 store 做出改變。
為了明確說明 state 是如何被 action 改變,必須寫入 pure reducer functions 基於舊的 state 來計算新的 state
```javascript=
import { createStore } from 'redux'
// 1. 這個基本範例是一個 reducer ,它會拿一個現在的 state 以及 action 物件來描述"發生了甚麼事情",然後 return 新的 state value
// 2. Redux state 應該只包含了 JS 物件、陣列以及純值
// 3. Root state value 通常會是物件型別,然而重要的是不要改變到 state 物件,而是 return 一個新的 state 物件如果有任何的改變
// 4. 可以使用任何判斷式在 reducer 中,在下面範例操作的是 switch
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case 'counter/incremented':
return { value: state.value + 1 }
case 'counter/decremented':
return { value: state.value - 1 }
default:
return state
}
}
// 創建一個 Redux store 讓它來包裹這個 app 的 state
// 它有數個 API 可以操作 { subscribe, dispatch, getState }.
let store = createStore(counterReducer)
// 你可以使用 subscribe() 回應 state 的改變來更新 UI
// 一般會使用 view binding 函式庫 (e.g. React Redux) 比較少直接使用 subscribe()
// 但一定也會有額外的應用狀況使用上 subscribe() 是有幫助的
store.subscribe(() => console.log(store.getState()))
// 要改變內部 state 的唯一方法只有 dispatch in action
// actions 可以被拆開分布、紀錄、儲存以及晚點再使用它
store.dispatch({ type: 'counter/incremented' })
// {value: 1}
store.dispatch({ type: 'counter/incremented' })
// {value: 2}
store.dispatch({ type: 'counter/decremented' })
// {value: 1}
```
* 你使用一個 plain object 指名你想要發生的改變就是 actions
* 使用特別的 function reducer 來決定每個 action 會如何改變整個 app 的 state
在一個典型的 Redux App 中,一開始只會有一個 store 以及一個 root reducing function ,然而隨著 app 規模越來越大,你會開始把 root reducer 拆分成更小的 reducers 並且個別的運行在不同的 state tree 上,這就像是 React 的 root component 拆分成小的 component 的概念一樣。
目前這樣的架構使用在這個加減的範例上確實看起來大材小用,但是放到大型專案你就明白它的好了。Redux 也能夠使用非常強力的開發者工具因為其可以被追蹤每一個改變步驟以及造成其改變的 actions ,這樣一來你就可以記錄使用者使用的 pattern 並且複製它們藉著重新執行 actions
# Should You Use Redux?
以下是一些可以使用的情境
* 你有數量可觀的資料會隨著時間改變
* 你需要你的 state 保持 single source
* 你發現把所以的 state 從 root 當作 props 發送給要使用的 component 非常沒有效率時
# Core Concepts
想像你的 state 會長的像這樣這物件
```jsx=
{
todos: [{
text: 'Eat food',
completed: true
}, {
text: 'Exercise',
completed: false
}],
visibilityFilter: 'SHOW_COMPLETED'
}
```
為了改變 state 內容則需要 dispatch actions ,而 action 也就只是 JS 物件並且解釋了它會做到什麼事情這邊是一些例子
```jsx=
// 前方的 type 就像是在解釋發生了什麼
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
```
為了把 state, action 綁在一起於是我們使用了 reducer, reducer 是一個函式把 state, action 當作參數使用並且返回下一個階段的 state,下方展示小部分的範例 :
可以看到下方兩個 function 帶入的兩個參數就是 state, action
```jsx=
function visibilityFilter(state = 'SHOW_ALL', action) {
if (action.type === 'SET_VISIBILITY_FILTER') {
return action.filter
} else {
return state
}
}
// 這個函式處理的是 todo 的 completed 狀態,針對對應的 todo 修改其 completed 狀態
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([{ text: action.text, completed: false }])
case 'TOGGLE_TODO':
return state.map((todo, index) =>
action.index === index
? { text: todo.text, completed: !todo.completed }
: todo
)
default:
return state
}
}
```
這部分會把所有的 reducer 整合起來使用,並且會針對對應的 key 做出反應(比方說點擊了特定的 todo 就只會執行 action 在那個 todo 上)
```jsx=
function todoApp(state = {}, action) {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
}
}
```
這邊基本上就是 redux 的運作了,基本上都是純 JS 還尚未使用到 redux API ,但是主要的重點就是描述你的 state 是怎麼隨著時間被 action 更新的
# Redux Essentials, Part 1: Redux Overview and Concepts
在此章節中你會學到:
1. Redux 是什麼/ 為什麼會想使用它
2. Redux 關鍵字以及其觀念
3. Redux 中的 data flow
## What is Redux?
Redux 使用 actions 來管理以及更新 state ,是一個中央化管理來自整個 app 的 state,並且遵守著規則 “state 只能被做出可被預測的改變”
## Why Should I Use Redux?
1. 當 app 有很大量的 state 被操作在各種不同的地方時
2. state 經常有變化
3. 更新 state 的邏輯相當複雜時
4. 當有多人協作或是程式碼中/大量時
## Redux Terms and Concepts
下方是個簡單的點擊按鈕 counter 就會加一的簡單程式
```jsx=
function Counter() {
// State: a counter value
const [counter, setCounter] = useState(0)
// Action: code that causes an update to the state when something happens
const increment = () => {
setCounter(prevCounter => prevCounter + 1)
}
// View: the UI definition
return (
<div>
Value: {counter} <button onClick={increment}>Increment</button>
</div>
)
}
```
它包含了三個部分:
1. state 驅動整個程式的來源
2. view 宣告性描述的 UI 基於現在的 state
3. actions 是 app 中目前發生的事件基於使用者 input 觸發 state 的更新

這邊是一個 one-way data flow 範例:
* State 描述了目前 app 的狀況
* UI 基於現狀 render 出畫面
* 當有改變發生時(如使用者點擊按鈕)state 就會根據情狀更新
* UI 也會 re-render 畫面
上面的範例是發生在比較簡單架構的 app ,當 app 有很多個 components 並且同時需要使用相同的 state 時,上面的範例舊不堪使用了,這時候就可以使用到 Redux
Redux 的核心概念就是中央化管理全部的 state ,並且在更新 state 時有特定的模式可以遵循讓程式碼是可預測的也才能方便維護
## Immutability
要談到 Immutability 可以先看看 mutable 也就是改變的範例
```jsx=
const obj = { a: 1, b: 2 }
// 外面的物件是一樣的但是內容已經改變了
obj.b = 3
const arr = ['a', 'b']
// 陣列也可以做到一樣的事情
arr.push('c')
arr[1] = 'd'
```
**然而,為了做到 Immutably ,你使用的程式碼必須複製現存的物件或是陣列,然候只修改複製的版本**
可以使用展開運算子 / 陣列方法達成(下方範例使用 concat, slice)
```jsx=
const obj = {
a: {
c: 3
},
b: 2
}
const obj2 = {
// 複製 obj
...obj,
// 改寫 a
a: {
// 複製 obj.a
...obj.a,
// 改寫 c
c: 42
}
}
const arr = ['a', 'b']
// concat 會創造一個包含 c 的新陣列
const arr2 = arr.concat('c')
// 直接使用 slice 創造一個複製的陣列
const arr3 = arr.slice()
// 並對複製的陣列做操作
arr3.push('c')
```
Redux 期待所有的 state 更新都必須 done immutably
## Terminology
### Actions
actions 指的是一個 JS 物件有著 type 區域,**你可以想像成 action 是一個事件它描述了發生在 app 內的事情**
* type 區域必須填入字串型態的內容給予 action 一個描述性的名字 like "todos/todoAdded"
* 斜線前方是這個 action 的特色或是分類,後方則是實際發生的事件
* 一個 action 可以有額外的區域填寫額外的資訊針對發生了什麼事一般會放在 payload
一個典型的 action
```jsx=
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
```
### Action Creators
action creator 會創造並且返回 action 物件,通常用來避免重複撰寫 action
```jsx=
const addTodo = text => {
return {
type: 'todos/todoAdded',
payload: text
}
}
```
### Reducers
reducer 會接受目前的 state 以及一個 action 物件並根據需求更新 state ,並且返回新的 state : (state, action) => newState
**你可以把 reducer 想成 JS 中的 event listener ,他會處理事件根據 action (event) 的類型**
reducer 必須遵循各種一些規則:
* 他們操作最新的 state 必須根據參數 state, action 來處理
* 不允許修改現存的 state ,取而代之的是必須讓 state immutable,藉著使用複製現在的 state 的方式並且只在複製的 state 上面做操作
* 不能使用非同步邏輯、計算隨機的值、或是任何造成 side effect 的行為
在 reducer 內的邏輯一般會遵守:
* 確認 reducer 是否要使用這個 action (如果是的話則複製 state 並進行操作)
* 如果確認失敗則返回原本的未修改的 state
```jsx=
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'counter/increment') {
// If so, make a copy of `state`
return {
...state,
// and update the copy with the new value
value: state.value + 1
}
}
// otherwise return the existing state unchanged
return state
}
```
在 reducer 內可以使用各種邏輯呈現 不管是 if ... else/ switch/ loops 等等
### Store
儲存目前 app state 的地方就是 store
store 的目的在於傳遞 reducer 並且他有一個方法 getState 會返回目前的 state 值
```jsx=
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
// {value: 0}
```
### Dispatch
Redux store 有一個方法 dispatch ,是唯一可以更新 state 的方式,呼叫 `store.dispatch()` 並且傳入 action 物件
這時後 reducer 就會跑 reducer function 並且儲存最新的 state 在裡面,可以藉著 getState 來取得最新的 state 值就可以知道已經被更新摟 !
```jsx=
store.dispatch({ type: 'counter/increment' })
console.log(store.getState())
// {value: 1}
```
可以把 dispatch 想成“正在被觸發的事件”,reducer 就像事件監聽器,所以當他們監聽到 action 是他們想要的時候就會更新 state 來回應
一般我們是會 call action creators 來 dispatch action
```jsx=
const increment = () => {
return {
type: 'counter/increment'
}
}
store.dispatch(increment())
console.log(store.getState())
// {value: 2}
```
### Selectors
seletors 是一個 funciton 用來擷取特定的資訊從 store state ,當 app 越來越龐大,seletors 可以避免在不同的 components 中重複撰寫邏輯來取得相同的資料
```jsx=
const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2
```
## Redux Application Data Flow
針對剛剛提到的 one-way data flow 在 Redux 中可以分的更細:
### Initial setup
* 首先使用 root reducer function 建立 Redux store
* 一開始 store 會呼叫 root reducer 一次並且儲存返回值當作初始 state
* 在 UI 第一次 render 之前,它會取得目前 store 內的 state 並由取得的內容決定 render 內容,並且會關注任何未來的更新這樣就可以知道是否 state 有無更新
### Updates
* 有事件發生了如使用者點擊按鈕
* 此時 app dispatch action 給 Redux store 像是`dispatch({type: 'counter/increment'})`
* store 會再次開始跑 reducer function 使用先前的 state 以及剛剛傳來的 action ,並且把返回值儲存為新的 state
* 這時 store 會通知所有的 UI state 已經更新
* 每個 UI 需要來自 store 的資料確認他們是否需要更新
* 每個 component 看到資料改變後,就會執行 re-render 就可即時更新最新資料上去畫面
[範例影片](https://redux.js.org/assets/images/ReduxDataFlowDiagram-49fa8c3968371d9ef6f2a1486bd40a26.gif)
## What You've Learned
* Redux 是一個管理 app 整體 state 的函式庫
* Redux 使用 one-way data flow 作為 app 架構
* Redux 使用不同種的 types
action 是一個 JS 物件有著 type 區域,並且描述著”發生了什麼“
Reducers 是一個函式會產出新的 state 基於先前的 state + action
Redux store 執行 root reducer 當 action 被 dispatch
# Redux Fundamentals, Part 3: State, Actions, and Reducers
你會學到
1. 如何定義包含你整個 app 資料的 state value
1. 如何定義用來形容在你 app 內發生什麼事的 action 物件
2. 如何撰寫會基於現存的 state 以及 action 更新的 reducer 函式
## Todo Example App
又來做 todo app 摟

UI 會有三個部分
* input bar 給使用者輸入新的 todo
* list of todos
* footer 顯示 幾個 todo 以及 filter 的選項
1. 每個 todo 需要有個 checkbox 來開關他們的 completed 狀態並且可以設定顏色來做 filter
1. footer 需要有個 counter 來計數還有多少個 todo 未完成
1. 需要有一個按鈕來直接全部完成,以及清除完成的按鈕
會有兩種 filter 方式
* filter by status all/ active/completed
* filter by color
## Designing the State Values
> UI 的呈現必須基於 state
> 所以設計 app 的第一件事情就是必須先思考所有的 state 如何描述 app 的運作
所以我們來開始設定 state,針對每一個 todo 物件會包含這些資訊
1. 使用者輸入的文字
2. boolean 值顯示的 todo completed or not
3. 獨特的 ID
4. 被選擇的顏色類別
針對 filter value 的部分
1. completed 的狀態 All Active Completed
1. Colors: 黃紅綠藍橘紫
* 這邊的 todo 可以被稱為 app state 也就是這個 app 最核心的資料
* 而 filter vaule 則是 UI state 主要描述 app 目前發生什麼事情
## Designing the State Structure
state 必須是單純的 JS 物件或是陣列,不會有 class instance, build-in JS function map/set/Date/promise 等等
所以我們來定義 todo state ,他會包含這些東西
* id
* text
* completed
* color
定義 filter value
* 目前的 completed 狀態
* 目前被選擇的 color 陣列內容
大致上我們的 state 會長這樣
```javascript=
const todoAppState = {
todos: [
{ id: 0, text: 'Learn React', completed: true },
{ id: 1, text: 'Learn Redux', completed: false, color: 'purple' },
{ id: 2, text: 'Build something fun!', completed: false, color: 'blue' }
],
filters: {
status: 'Active',
colors: ['red', 'blue']
}
}
```
這邊有一點提醒,在 Redux 外面存在有 state 也是沒問題的,有些 state 管理的東西比較小就不需要放進去 Redux 內,例如是否需要 dropdown 或是 form input 的 value
## Designing Actions
Actions 一樣是單純的 JS 物件包含有 type 這個區域,你可以把 Actions 想成一個事件,主要在描述 app 裡面發生什麼
在 app 裡面發生了什麼呢?
1. 基於使用者輸入的內容,新增新的 todo 進去 list
2. 開關 todo 的 completed 狀態
3. 為一個 todo 選擇顏色
4. 刪除 todo
5. 標記所有的 todo 完成
6. 選擇另一個 completed 的值 比方說 active, all 等等
7. 新增一個新的 color filter
8. 移除 color filter