iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 28
0
Modern Web

先你一步的菜鳥 - 從 0 開始的前端網頁設計系列 第 28

Day-28 使用 hook 打造專屬 blog(13) - Redux 實作(搭配 connect provider)

嗨大家好,我是 Chris,今天將來說說如何在 React 中實現 Redux。

首先我們需要知道在 React 中,Redux 把 component 切割得非常清楚,分為專門處理 UI 和專門處理 State 的 component,也就是Presentational ComponentContainer Component

這樣就能更好的將 state logic 和一些需要重複使用的組件分得很清楚,如果有跟過這一系列的專案的話,我一直以來都盡量把 state 往頂層放( 希望你有注意到QQ ) 且用 props 一層層的傳下來,這樣一來就可以輕易地重複取用 component (像是 title、infoboard ),也能輕鬆的自訂自己需要的內容。

可就算是這樣還是有一些 State 參雜在 UI 介面之中,不說那個 Pure Component 自帶的效能 buff,有時候明明是差不多的套件卻會需要浪費時間在寫一次。

所以這樣的切割搭配 Redux 的 api component 將能大大的提升撰寫專案的效率。

註:當你看完以上的解說,或許會想到這跟 hook 有點像其實是沒有錯的,因為 hook api 就是解決了這些狀態管理之間的問題,詳細的資訊我曾經在 day-10 說過,歡迎去看看。

Presentational Component

也就是純 UI 介面,不參雜任何 web request、state 處理,有的只是藉由 props 取用資料顯示畫面。

import React from "react";
import Title from "./Title";
export default function FeaturedBoard({ children, title,className }) {
  return (
    <div className="featuredboard">
      <div className="coverboard">
      <Title title={title} className={className}/>
      {children}
      </div>
    </div>
  );
}

像是這種可以重複使用的 component 就是一個很標準的 Presentational Component

如果有跟過前面的系列專案的話,絕大多數的 component 都是 Presentational Component。

只有少部分的 component ( Home、 Notes、NotesDetail等等只要有資料的都算,雖然說資料不應該在那啦。)

沒有跟過的話可以從 這裡 取得之前的進度。

註:我們在專案中有使用過幾個 package,所以下載下來一定要 npm install 安裝所有的 package,才能啟動。

Container Component

只要有任何處理資料的 component,像是上面的 Home、 Notes、NotesDetail等等,就算只是呼叫 data 的也算, Reducer 、 Action Creator 這些處理 state 的當然也是。

我們通常會在這類 Component 裡藉由 subscribe、getState、dispatch 等 api function 處理 state。

// NotesDetail.js 

  // 接收 param
  let { param } = useParams({});
  // 設定 state
  const [note, setNote] = useState([]);
  //使用 filter 找到要的內容
  useEffect(() => {
    setNote(...Notes.filter((item) => item.title === { param }.param));
  }, []);
  return (
    <div>
      <FeaturedBoard title="Note Title" className="title">
        <Banner article={article} />
      </FeaturedBoard>
      <InfoBoard pic={null}>
        <section className="note">
          <Title title={note.title} className="subtitle" />
          <div className="note-datetime">by Chris {note.date}</div>
          <div className="note-area">
            <p className="note-chapter">chapter 1</p>
            <article className="note-article">{note.article}</article>
            <article className="note-article">{note.article}</article>
            <p className="note-bold">粗體</p>
            <p className="note-slash">斜體</p>
          </div>
        </section>
      </InfoBoard>
    </div>
  );
}

像是 NotesDetail.js 這種本身自己擁有 state,且又呼叫 param 的 component 就是很明確的Container Component。

Connect

當你不可避免的讓你的 ui component 擁有且處理 state( 這是一定會發生的 ),這時我們就必須使用 Connect() 這個 React-Redux 特有的 function。

大家應該還記得我們昨天提到的 Action 吧,由於在 Redux 中傳遞的 state 都是 Action 這種普通 component 無法辨別的 object,所以需要 Connect() 讓 component 能夠讀取 Redux 的 state。

Connect() 接收兩個參數,mapStateToPropsmapDispatchToProps(),如果我們想要讓這個 component 可以接收和處理 state,就會像這樣:

import { connect } from 'react-redux'

function Presentational(){
  ...
}

const Container = connect(
  mapStateToProps,
  mapDispatchToProps
)(Presentational)

export default Container

Presentational 就是我們原本的 UI component,Container 就是我們藉由 connect() 轉變為處理 state 的 Component 所定義的名稱。

如果想單獨把 UI Component 轉變為 Container Component 也可以只使用這樣:

const Container = connect()(Presentational)

雖然我不知道這可以幹嘛 XD,不過還是提一下,知道的大神們希望也不吝賜教。

mapStateToProps

簡單的講,就是將一開始存在 state tree 裡的初始資料讀出來。

有關 initial state tree 的來源,這邊可以簡單的說一下,從 Store 的第2個參數 preloadedState 接收預設的 state 或是 middleware 套一些 api 讓我們拿得到 web state。

還有最後一種最常見的,就是在設定 reducer 時的初始資料,在昨天說到 reducer 時有提到:

initialState:{
List:[]
}

我覺得這個是一個痛點,因為我一開始在搞懂 store 的概念時花了很多時間,想說我翻遍了整個 demo 文件都沒有看到定義 data 的地方 (國王的data),後來才發現,每一個 reducer 所定義的 initial state 集結而成就是 store 的 state tree,現在分享給大家,讓你們少走冤枉路QQ

好的回到正題,mapStateToProps 可以把放在 store 的 state 放進 component 可以讀取的 state,大概會像這樣:

const [article, setNote] = useState();
const [chpater, setNote] = useState();

const mapStateToProps = (state) => {
  return {
    article : state.note.article,
    chpater : state.note.chpater,
    ...
  }
}

這樣就可以使用我們熟悉的 state 了。

mapDispatchToProps

既然有了輸入端,也會有輸出端,當我們有希望透過 reducer 去改變存在 store 的 state 時,我們就可以在這裡使用 dispatch 去達成想做的事。

使用起來像是這樣:

const mapDispatchToProps = dispatch => {
      return {
          getNotes: () => dispatch(fetchNoteList()),
          addNotes: (note) => dispatch(addNotes(note)),
          deleteNotes: (index) => dispatch(NotesList.update(index))
      };
  };

這樣就可以像普通 function 一樣傳遞 props 和透過 onclick 或 handlechange 去觸發,當然,最後的資料結構還是 action ,傳進去的 props 也是要符合你所建立 action。

Provider

雖然我們建立了整個 redux 系統,那我們該把這層架構放在哪呢?

記不記得我在開頭說過的,把所有的 state 放在最頂層的架構,這將可以讓我們能夠最大限度的使用 Redux。

我們在 app.js 管理 router、在 app下面的各個 page 建構了 UI 架構,所以看起來最好的位置就是放在 index 裡。

這時就可以使用 Provider() 這個 React-Redux 提供的 function,幫助我們實作,看起來會像這樣:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import Provider from "react-redux";
import store from "./store/index";
import { BrowserRouter as Router } from "react-router-dom";

ReactDOM.render(
  <React.StrictMode>
  <Provider store={store}>
    <Router>
      <App />
    </Router>
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);
    

這樣我們就可以在 React 裡使用 Redux 管理 state了。

以上就是 Redux 在 React 的作用方式,明天將更改我們的專案讓 Redux 接管資料流。


上一篇
Day-27 使用 hook 打造專屬 blog(12) - 搭配 Redux 整理資料流
下一篇
Day-29 使用 hook 打造專屬 blog(14) - 在個人 BLOG 創建 Redux 架構
系列文
先你一步的菜鳥 - 從 0 開始的前端網頁設計31

尚未有邦友留言

立即登入留言