# Diving into Redux - React compelte guide
###### tags: `Javascript, React`
# Module Inro
* 會介紹 Redux 這個 lib 是什麼
* 為何他這麼重要
* 以及如何使用它

# Another Look At State In React Apps
> Redux 是一個管理 state 的系統,主要應用在跨元件或是 app-wide 類型的 state

來看看這三種 state 的差異
1. local state
主要 state 的發生以及改變在一個檔案中,例如監聽 user input 或是 toggle 按鈕讓文案開啟\ 關閉,主要在單一個元件中操作 useState, useReducer
2. cross-component state
跨元件使用 state ,舉例 overlay modal 這種,根據數個不同的元件來決定是否 modal 來執行,就會透過各個不同元件的 props 傳遞來達成
3. app-wide state
跟上面很像,但是更廣,舉例 authentication state 會根據登入狀態修改全站 UI ,一樣可以透過傳遞 props 來達成

基於 2, 3 點要到處傳遞 props 相當麻煩,所以衍生出了 React Context API 以及現在要介紹的 Redux ,不過既然都有 Context API 為何 Redux 還會出現呢?
# Redux vs React Context
> 會有 Redux 的出現自然是因為 Context 有其缺點
## 注意!
**context API 跟 Redux 是可以同時出現的,Redux 控管整體的程式 state ,部分幾個 component 可以使用 context API 各別控制 state**
## 缺點
使用 context API 會因為有各種不同的 state 要傳遞,因此需要建立複數的 provider 來供應這樣的需求,就會變成這種情況,尤其是當 app 相當大的時候

不過這時候你會想,這樣把全部的 state 全部寫在同一個 provider 就好啦?
但會導致扣又肥又大,非常不好維護

一篇來自 React 開發者對於 context API 的描述在於,對於 state 更新率高過一般的的情況下,其效能會比較差

# How Redux Works
> central data 不會直接被 component 改動,必須透過 action 告訴 Reducer component 想做什麼, Reducer 內處理好後在覆蓋掉 central data ,然後 central 就會告訴訂閱的 components 需要改動的 UI

# Exploring The Core Redux Concepts
> 這邊會簡單操作一個 app 每次使用 action 後會使 state +1 的程式,並且操作 node 來執行
## 注意!
reducer function 一定會是 pure function

pure function 代表: 放入一樣的 input ,輸出的的結果每一次都會是一樣的 output ,並且不會有 side effect
1. 建立 store 操作 createStore 並且帶入參數 reducer
2. 建立 reducer function,他會吃兩個參數: state, action 並且返回 state 來更新 store
3. 建立 counterSubscriber 這邊是在模擬 UI component 它會收到 store 的 state 來更新 UI
4. 將設置好的 subscriber 跟 store 連結使用 store 內的函式 subscribe
5. 定義 action 名稱並且使用 store 內的函式 dispatch 來帶入
```javascript=
const redux = require('redux'); // 範例使用 node 所以這樣來引入 redux
const countReducer = (state = { counter: 0}, action) => { // 這邊 counter 要給預設值,設置為 0
return { counter:state.counter +1, }; // return +1,因此初始值為 1
};
const store = redux.createStore(countReducer);// 裡面的參數會是 reducer func
const counterSubscriber = () => {
const latestState = store.getState(); // 是 createStore 內建的函式,用途在於取得最新的 state
console.log(latestState); // 這邊是 node 執行的內容
};
store.subscribe(counterSubscriber);
store.dispatch({type: 'increment'}); // 這邊使用 dispatch 來執行特定 type 的動作
```
這邊印出直接 increment 後的內容也就是 2 !(畫面已經更新)

# More Redux Basics
> 一般 reducer function 中都會處理複數情況的 action
其實也不複雜就是加上判斷式針對 type 去做辨別即可,如果都沒有符合的 action 則 return 原本的 state
```javascript=
const countReducer = (state = { counter: 0}, action) => {
if (action.type ==='increment'){
return { counter:state.counter +1, };
} else if( action.type === 'decrement') {
return { counter:state.counter -1, };
} else {
return state;
}
};
```
# Preparing a new Project
> 這邊要把上面的加減的 app 搬上去 react 操作
要特別注意的是在安裝 redux 時,如果是操作在 react 上要記得加上 react-redux
`npm install redux react-redux`
# Creating a Redux Store for React
* 下方的扣可以看出跟 node 明顯的不同在於, import redux 的方式以及因為要在其他元件使用 store 所以必須 export 出去
* 另一點是不需要使用 subscribe 以及 dispatch 函式(因為牽涉到其他元件,不會只在這邊操作)
這樣子 store 的內容就建立好了
```javascript=
import { createStore } from 'redux';
const countReducer = (state = { counter: 0}, action) => {
if (action.type ==='increment'){
return { counter:state.counter +1, };
} else if( action.type === 'decrement') {
return { counter:state.counter -1, };
} else {
return state;
}
};
const store = createStore(countReducer);
export default store;
```
# Providing the Store
> 這邊的動作跟 context API 非常像,使用 provider 包裹最外層的 app 來傳遞所有的 props 以及操作 action 並且要記得綁定 store 當作 props
最外層的 index.js
```javascript=
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./store/index";
import "./index.css";
import App from "./App";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
```
# Using Redux Data in React Components
> 這邊要做的事情就是解釋 subscribe 在 react 中的行為
要把 store 中的內容呈現到 UI 剛剛解釋說得使用 subscribe ,但是使用 react 則是這樣操作:
* import useSelector 用來選取一小部分需要的 state
* 並且指派給變數後就可以操作那個 state 進去 jsx 中
如此一來這個 Counter.js 就跟 store subscribe 好,並且可以把 state 的數字呈現到頁面上摟

```javascript=
import classes from './Counter.module.css';
import { useSelector } from 'react-redux';
const Counter = () => {
const toggleCounterHandler = () => {};
const counterValue = useSelector( state => state.counter);
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
<div className={classes.value}>{counterValue}</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
```
# Dispatching Actions From Inside Components
> 這邊實際來操作 dispatch function
* dispatch 並不會攜帶內容進來元件,但它需要執行並且指派給變數做操作
* 接下來就是帶入 onClick 指向的函式,並且輸入 type 名稱即可
* 最後寫進去 jsx 中的 onClick 即可,畢竟 UI 的部分就是點擊可以做到加減的動作
```javascript=
const dispatch = useDispatch();
const incrementHandler = () => {
dispatch({type: 'increment'});
};
const decrementHandler = () => {
dispatch({type: 'decrement'});
};
```
# Redux with Class-based Components
> 直接把上面的 FC 改寫成 class components
* 首先引入 component 來使用 extends 創建 class Counter
* render 的部分操作 JSX 跟 FC 部分是一樣的
* 使用到的 methods 操作方式如下
最重要的部分是:
* connect 如何操作
connect 會帶入兩個參數並且他會再返回一個函式,它會直接執行,其參數為 class component,connect 的兩參數為:一是取得 store 的函式,另一為 dispatch 的函式
connect 也一樣會以 subscribe 的作用
* 取得 store 內的 props 的方法
基本上跟 FC 一樣寫入 state ,並且給予 key name 後帶入對應的 state 中的值
* 以及 dispatch 的寫法
dispatch 的部分則是先給予 key name 後,執行匿名函式在其中執行 dispatch
**要特別注意 JSX 中的值都修改成使用 this 以及 props 的方式帶入, onClick 執行函式的部分則是要記得 bind this 上去確保其 this 的指向正確**
```javascript=
import classes from "./Counter.module.css";
import { connect } from "react-redux";
import { Component } from 'react';
class Counter extends Component {
incrementHandler () {
this.props.increment();
}
decrementHandler () {
this.props.decrement();
}
toggleCounterHandler () {}
render () {
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
<div className={classes.value}>{this.props.counter}</div>
<div>
<button onClick={this.incrementHandler.bind(this)}>Increment</button>
<button onClick={this.decrementHandler.bind(this)}>Decrement</button>
</div>
<button onClick={this.toggleCounterHandler}>Toggle Counter</button>
</main>
);
}
}
const mapStateToProps = state => {
return {
counter: state.counter
};
}
const mapDispatchToProps = dispatch => {
return {
increment: () => dispatch({type: 'increment'}), // 這邊的寫法也要注意
decrement: () => dispatch({type: 'decrement'}),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter); // 這邊是最特別的地方
```
# Attaching Payloads to Actions
> 這邊要解釋如何使用 payloads ,像是 action 的參數的概念
在 dispatch type 的後方加入 payload ,名字是可以自定義的,雖然這邊 payload 是寫死的,不過一般情況下,可以透過 useState, useRef 直接輸入就可動態的帶入使用者的 payload 摟!
```javascript=
const incrementHandler = () => {
dispatch({type: 'increment', amount: 10});
};
```
並且在 redux 中操作的時候,使用即可相當簡易
```javascript=
if (action.type ==='increment'){
return { counter: state.counter + action.amount};
}
```

# Working with Multiple State Properties
> 這邊會多操作一種 state 來演示複數的 state 的情況下,如何在 redux 中操作
> 操作 toggle counter 讓按鈕點擊後,隱藏數字,雖然這個 state 相當單純甚至可以使用 local state 操作就好,不過基於 demo 需求,所以把它操作成 global state 來作用在 redux 內
這邊演示 store.js 內的操作,當新的 state showCounter 出現的時候,因為 Reducer 操作的方式是將 action 的操作覆蓋掉原本的 state ,因此內部的操作都必須加上 showCounter 儘管該 action 沒有關聯
```javascript=
if (action.type === 'decrement') {
return {
counter: state.counter - 1,
showCounter: state.showCounter // 儘管沒有關聯但是因為複數的 state ,這邊也需要加上
};
}
if (action.type === 'toggle') {
return {
showCounter: !state.showCounter, // 儘管沒有關聯但是因為複數的 state ,這邊也需要加上
counter: state.counter
};
}
```
在 Counter.js 操作第二個 state 一樣必須使用 useSelector 來抓取 state 後,dispatch 操作即可
```javascript=
const show = useSelector((state) => state.showCounter
const toggleCounterHandler = () => {
dispatch({ type: 'toggle' });
};
// 記得 JSX 的部分必須這樣條件操作來開關數字
return (
{show && <div className={classes.value}>{counter}</div>}
)
```
就可以 toggle 數字摟!

# How To Work With Redux State Correctly
> 影片開頭提到一個重點 redux 的 Reducer 最後一定會回傳一個 state 來取代目前最新的 state ,但是要非常注意,**它並不會 merge 原本的 state 它做的是取代**!!非常重要
第一個重點:
**redux 的 Reducer 回傳的 state 它並不會 merge 原本的 state 它做的是取代!!**
舉個例子:
假設 Reducer 中的 increment 我們忘記加上去了 showCounter 會發生什麼事呢?
```javascript=
if (action.type === 'increment') {
return {
counter: state.counter + 1,
};
}
```
它會覆蓋掉原來的 state ,變成 showCounter 永遠消失,因此 show 永遠都是 undefined 所以這樣的 side effect 就會讓數字從此不再出現
第二個重點:
**千萬不要 mutate Reducer 內的 state**
因為 物件或是陣列是傳址的特性,容易發生問題而找不到地方解決!
傳址 (referrence type) 很容易造成,當你修改其屬性,因為地址相同導致,你改了內容其他地方的內容也跟著修改
例子:
```javascript=
// 錯誤範例
if (action.type === 'increment') {
state.counter ++
return state
}
// 錯誤範例
if (action.type === 'increment') {
state.counter ++
return {
counter: state.counter,
showCounter: state.showCounter
};
}
// 正確做法 操作在 return 的物件內,讓原本的 state 保持原樣
if (action.type === 'increment') {
return {
counter: state.counter + 1,
showCounter: state.showCounter
};
}
```
[影片參考 - Reference vs Primitive Values](https://academind.com/tutorials/reference-vs-primitive-values)
# Redux Challenges & Introducing Redux Toolkit
> 接下來會介紹到 Redux Toolkit 來解決一些問題,不過會先介紹單純使用 Redux 的情況下並且程式使用的 state 用來越多,會遇到的挑戰
1. type 名稱不能打錯,不然絕對報錯
當然程式規模小不成問題,但是當多人協作,且規模擴大,這種情況就很可能發生
他是可以使用 JS 設定變數的方式來帶入,並且輸出到其他地方,不過下面會介紹更好的方式!
2. 剛剛有提到複數的 state ,每增加一個 steta 就必須所有的 type 都增加進去,程式才不會壞掉
這個情況當 state 越來越多,每一個 return 的 state 只會越來越長,似乎跟 Context API 一樣?不過其實他是有解的!待會再說
3. 還記得 Reducer 內的 state 不能 mutate 嗎?
但是當 state 愈來越多且複雜,有時候不是我們不想,是情況難以控制呀!
這邊 Redux toolkit 來救場啦!
他其實是 React redux 同一個團隊製造的工具,在下個章節中會開始介紹它!
# Adding State Slices
`npm i @reduxjs/toolkit` 先載個 toolkit ~
引入 createSlice 後來操作
1. 首先他會需要一個 name
1. 再來使用初始值
1. 接下來輸入 reducers
```javascript=
const counterSlice = createSlice({
name:'counter',
initialState,
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increase (state, action) {
state.counter = state.counter + action.amount;
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
}
}
})
```
跟 redux 最大的差異在於:
* 不需要根據 type 去寫 if/else
* 可以 mutate state ,但是其實在底層有做處理實際上 state 還是沒有被 mutate
* 可以分多個資料夾去處理不同類別的 state
# Connecting Redux Toolkit State
> 這邊我們會操作 configureStore 來取代 createStore
它的功能在於一樣會創造一個 store 之外,並且結合多個不同的 slice 的 reducers 變成一個 reducer 會負責 global state
```javascript=
const store = configureStore({
reducer: {counter: counterSlice.reducer} // 這邊如果有複數的 reducers 就可以在這個物件內新增下去,在底層的機制還是會把內部的 reducers 合併成一個輸出出去
});
```
# Migrating Everything To Redux Toolkit
來看看整個 refactor 過後的扣,簡潔很多
action 的部分會輸出到需要使用的資料夾即可使用
```javascript=
import { createSlice, configureStore } from '@reduxjs/toolkit';
const initialState = { counter: 0, showCounter: true };
const counterSlice = createSlice({
name:'counter',
initialState,
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increase (state, action) {
state.counter = state.counter + action.payload; // 這邊的 payload 會是其預設值,不能向 redux 一樣可以自訂
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
}
}
})
const store = configureStore({
reducer: counterSlice.reducer // 這邊要注意他的 reducer 不加 s
});
export const counterAction = counterSlice.actions;
export default store;
```
使用 actions 的方式,就直接取用 counterAction 底下的 methods 即可,帶入的參數會以 payload 的方式被帶入 reducer 內使用
```javascript=
const increaseHandler = () => {
dispatch(counterAction.increase(10));
};
```
# Working with Multiple Slices
> 剛剛學到的都是只有單一條 slice 且 state 很少的情況下,那如果有多條 slices 呢?
這邊會用一個登入狀態來解釋 global 的 state 如何呈現:
* 比方說那個 log out 按鈕只有在登入狀態下才會出現

其實步驟目前看起來跟只有 counterSlice 差不多
重點差異在於:
configureStore 內的因為多了一個 slice 它的寫法不太一樣了,變成需要有一個辨別 slice 的 identifier 在前面辨別,這邊要特別注意,**所以在 useSelector 的地方 state 的抓取會多一層**
這邊以 Counter 為例子,可以看到有兩層才能擷取到 state

```javascript=
const initialAuthState = {
isAuthenticated: false
};
const authSlice = createSlice({
name: 'authentication',
initialState: initialAuthState,
reducers: {
login(state) {
state.isAuthenticated = true;
},
logout(state) {
state.isAuthenticated = false;
}
}
})
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
auth: authSlice.reducer
}
});
export const counterAction = counterSlice.actions;
export const authAction = authSlice.actions;
```
# Reading & Dispatching From A New Slice
> 這邊會根據這個 login 的 state 去做到條件篩選 UI
以 App.js 舉例
* 首先抓出 isAuthenticated 來辦別登入與否
* 接下來在 JSX 中條件篩選 UI 來呈現
未登入

已登入

App.js
```javascript=
function App() {
const isAuth = useSelector(state => state.auth.isAuthenticated);
return (
<>
<Header/>
{!isAuth && <Auth/>} // 如果沒登入就顯示輸入帳號欄位,登入就讓其消失
{isAuth && <UserProfile/>} // 如果登入就顯示 user 帳號內容,沒有則消失
<Counter />
</>
);
}
```
dispatch 的部分則是操作跟之前的一樣,引入 authAction 之後就可以使用它內部寫好的方法摟!
Header.js
```javascript=
const Header = () => {
const isAuth = useSelector((state) => state.auth.isAuthenticated);
const dispatch = useDispatch();
const logOutHandler = () => {
dispatch(authAction.logout());
}
return (...省略)
}
```
# Splitting Our Code
> 隨著 slices 越來越多 store.js 檔案越來越大,因此我們來拆分他們
原本都放在 store/index.js 內,這邊把兩種 slices 拆分成 auth, counter

拆完之後的扣非常乾淨!
store/index.js
```javascript=
import {
configureStore
} from '@reduxjs/toolkit';
import counterReducer from './counter';
import authReducer from './auth';
const store = configureStore({
reducer: {
counter: counterReducer,
auth: authReducer
}
});
export default store;
```
slices 的部分使用 auth.js 做範例
* 基本上拆出 initialState
* 以及整個 slice 的身體
* 記得操作 actions
* 最後輸出 reducer 的部分(如果有使用到其他的部分也可以整個 authSlice 輸出
```javascript=
import {
createSlice
} from '@reduxjs/toolkit';
const initialAuthState = {
isAuthenticated: false
};
const authSlice = createSlice({
name: 'authentication',
initialState: initialAuthState,
reducers: {
login(state) {
state.isAuthenticated = true;
},
logout(state) {
state.isAuthenticated = false;
}
}
});
export const authAction = authSlice.actions;
export default authSlice.reducer;
```