🍣

Redux 的 API

2023/08/29に公開

前言與介紹什麼是 Redux API ?

文章內容為 codecademy 的學習筆記。

Redux 的應用程式是建立在 one-way flow 的資料模型之上,並由 store 管理。這邊會介紹一些內部功能:

  • State 是可以描述應用程式的資料集合,它用來渲染 UI。
  • 當使用者與 UI 互動的時候會發送 action 到 store。Action 是一個會把預計要做的事情傳送到 state 的一個物件。
  • Store 會利用 reducer function,reducer function 會接收 action 以及 state 當作輸入 ,來產生下一個 state。
  • 最後 UI 會基於新 store 的 state 來重新渲染,整個過程會再重新開始。

這邊會專注在利用 Redux API 的 createStore() 以及相關的 store 來建立基礎的 Redux 應用程式。

  • store.getState()
  • store.dispatch(action)
  • store.subscribe(listener)

store.replaceReducer(nextReducer) 是一個比較進階的方法,之後有機會再介紹。

安裝 Redux Library

使用方式:

  1. 在終端機使用 npm 來安裝 redux 。
npm install redux
  1. 並在檔案中 import createStore 來使用。
import { createStore } from 'redux';

建立 Redux Store

每個 Redux application 都會使用 reducer function 來說明要用哪些 action 去對 state 做更新,以及 action 會怎麼到下一個 state。

可以看看下面舉例的使用方式:

const initialState = 'on';
const lightSwitchReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'toggle':
      return state === 'on' ? 'off' : 'on';
    default:
      return state;
  }
}

let state = 'on';
state = lightSwitchReducer(state, { type: 'toggle' });

console.log(state); // Prints 'off'

createStore()

而 Redux 有一個很好用的 helper function 叫做 createStore() ,它可以用來創建 store object,且只接收一個 reducer function 做為參數。

比如以下例子:

import { createStore } from 'redux'
 
const initialState = 'on';
const lightSwitchReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'toggle':
      return state === 'on' ? 'off' : 'on';
    default:
      return state;
  }
}
 
const store = createStore(lightSwitchReducer);

Dispatch Actions to the Store

這裡介紹一些利用 createStore() 所回傳的 store object 延伸的使用方法,可以用來與它的 state 以及 reducer function 做互動。

store.dispatch()

這是最常被使用的方法,它用來把 action 發送到 store object,表示說你希望去更新此 state。而它唯一個參數是一個 action object,這個 object 是 type 屬性,用來描述 state 的變化。

const action = { type: 'actionDescriptor' }; 
store.dispatch(action);

action.type 被 reducer 確認後,state 才會被更新與回傳。

以下來看看使用方式:

import { createStore } from 'redux';
 
const initialState = 'on';
const lightSwitchReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'toggle':
      return state === 'on' ? 'off' : 'on';
    default:
      return state;
  }
}
 
const store = createStore(lightSwitchReducer);
 
console.log(store.getState()); // Prints 'on'

store.dispatch({ type: 'toggle' }); 
console.log(store.getState()); // Prints 'off'
 
store.dispatch({ type: 'toggle' });
console.log(store.getState()); // Prints 'on'

當 store 執行 reducer function 時,內部的 state 其實是 store.getState()

Action Creators

若需要很多次的 store.dispatch(),因為需要一直重複,可能比較容易造成錯誤。這時可以透過 action creator 來回傳 type 屬性,使得程式可以簡化。

const toggle = () => {
  return { type: "toggle" };
}
store.dispatch(toggle()); // Toggles the light to 'off'
store.dispatch(toggle()); // Toggles the light back to 'on'
store.dispatch(toggle()); // Toggles the light back to 'off'

Respond to State Changes

若要觸發 DOM events 來與使用者互動,可以使用 event listener ,這邊當然也可以。

store.subscribe()

也就是說 action 發送給 store 的動作可以使用 store.subscribe() 的方法來監聽跟觸發。它只接收一個 listener function 參數,用來做改變 store’s state 的動作。

const reactToChange = () => console.log('change detected!');
store.subscribe(reactToChange);

在這個例子中,每當 action 發送給 store 時,state 就會發生變化,而 reactToChange() 這個 listener 就會被執行。而有時候它會用來阻止 listener 響應 store,因此 store.subscribe() 也可以返回一個 unsubscribe function 來停止相關動作。

一樣舉個例子來看看:

// lightSwitchReducer(), toggle(), and store omitted...
 
const reactToChange = () => {
  console.log(`The light was switched ${store.getState()}!`);
}
const unsubscribe = store.subscribe(reactToChange);
 
store.dispatch(toggle());
// reactToChange() is called, printing:
// 'The light was switched off!'
 
store.dispatch(toggle());
// reactToChange() is called, printing:
// 'The light was switched on!'
 
unsubscribe(); 
// reactToChange() is now unsubscribed
 
store.dispatch(toggle());
// no print statement!
 
console.log(store.getState()); // Prints 'off'

在前兩個 action 發送後,藉由呼叫 unsubscribe 來讓 reactToChange() 取消對 store 做響應。

Connect the Redux Store to a UI

要將 Redux store 串接到 UI 需要以下幾個步驟:

  1. 創建 Redux store。
  2. 渲染最初的 state。
  3. Subscribe to updates,在 the subscription callback 裡:
    • 取得 the current store state。
    • 選擇 UI 所需要的 data。
    • 用 data 去更新 UI。
  4. 藉由 Redux actions 去響應 UI events。

以下舉個例子,創建 index.html 跟 store.js 檔案來做範例:

<p id='counter'>Waiting for current state.</p>
<button id='incrementer'>+</button>
<button id='decrementer'>-</button>

大概說明一下 store.js 裡面的 function:

  • counterElement, incrementer, and decrementer : 用來取得 DOM 元素。
  • render : 用於響應 store state 做監聽。
  • incrementerClicked and decrementerClicked: DOM event handlers 為使用者與按鈕的互動做響應。
import { createStore } from 'redux'

const { createStore } = require('redux');

// Action Creators
function increment() { 
  return {type: 'increment'}
}

function decrement() { 
  return {type: 'decrement'}
}

// Reducer / Store
const initialState = 0;
const countReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'increment':
      return state + 1; 
    case 'decrement':
      return state - 1; 
    default:
      return state;
  }
};  
const store = createStore(countReducer);

// HTML Elements
const counterElement = document.getElementById('counter');
const incrementer = document.getElementById('incrementer');
const decrementer = document.getElementById('decrementer');

// Store State Change Listener
const render = () => {
  counterElement.innerHTML = store.getState();
}

render();
store.subscribe(render);

// DOM Event Handlers
const incrementerClicked = () => {
  store.dispatch(increment())
}
incrementer.addEventListener('click', incrementerClicked);
 
const decrementerClicked = () => {
  store.dispatch(decrement())
}
decrementer.addEventListener('click', decrementerClicked);

React and Redux

這邊更具體的說明一下 Redux 與 React 串接的常見步驟。

  • render() function 會被同意去渲染 top-level React component 。
  • 而 React component 會接收 store.getState() 目前的 value 當作 prop,並利用這筆資料來渲染 UI。
  • 附加在 React component 上的 event listeners 會發送 actions 到 store去。

這邊舉個 light switch 應用的例子做說明:

  • render() function 會被註冊到 store。
  • store.getState() 會作為 prop 當成 state 傳送到 <LightSwitch /> component。
  • 這個 LightSwitch component 會顯示 the current store state,可能是 'on' 或是 'off' ,而去相應地改變顏色。
  • LightSwitch component 會宣告一個 click 監聽來發送 toggle() action 到 store 中。
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';

// REDUX CODE
///////////////////////////////////

const toggle = () => {
  return {type: 'toggle'} 
}
 
const initialState = 'off';
const lightSwitchReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'toggle':
      return state === 'on' ? 'off' : 'on';
    default:
      return state; 
  }
} 
 
const store = createStore(lightSwitchReducer);

// REACT CODE
///////////////////////////////////
 
// Pass the store's current state as a prop to the LightSwitch component.
const render = () => {
  ReactDOM.render(
    <LightSwitch 
      state={store.getState()}
    />,
    document.getElementById('root')
  )
}
 
render(); // Execute once to render with the initial state.
store.subscribe(render); // Re-render in response to state changes.

// Receive the store's state as a prop.
function LightSwitch(props) {
  const state = props.state; 

  // Adjust the UI based on the store's current state.
  const bgColor = state === 'on' ? 'white' : 'black';
  const textColor = state === 'on' ? 'black' : 'white';  
 
  // The click handler dispatches an action to the store.
  const handleLightSwitchClick = () => {
    store.dispatch(toggle());
  }
 
  return (  
    <div style={{background : bgColor, color: textColor}}>
      <button onClick={handleLightSwitchClick}>
        {state}
      </button>
    </div>
  )
}

文章參考

Learn Redux

Discussion