iT邦幫忙

2022 iThome 鐵人賽

DAY 26
0
自我挑戰組

30天深入淺出Redux系列 第 26

Redux 深入淺出 - [ Day 26 ] React RTK Typescript 基本應用

  • 分享至 

  • xImage
  •  

接續前一篇的進度,我們已經設置好 store 了,接著我們來試做一個簡單的 counter 講解一下在 Typescript 的環境下如何處理我們的 slice 檔案吧!

https://upload.wikimedia.org/wikipedia/commons/4/4c/Typescript_logo_2020.svg

那麼我們按照一如既往的結構於 features 資料夾下新增 slices 資料夾,另起一個 counterSlice.ts 的檔案,記住是 .ts 副檔名,內容如下:

// src/features/slices/counterSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "../store";

// 這也是 typescript 的產物
interface CounterState {
  count: number;
}

const initialState: CounterState = {
  count: 0
}

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
		// 按一下-1
    minus: (state) => {
      state.count--
      return state
    },
		// 按一下+1
    plus: (state) => {
      state.count++
      return state
    },
		// 直接 setting
    setCount: (state, action: PayloadAction<number>) => {
      state.count = action.payload
      return state
    },
  }
})

export const { minus, plus, setCount } = counterSlice.actions
// 在 typescript 裏面型別被加強了,所以剛剛我才會在 store.ts 裏面處理 RootState
export const selectCounter = (state: RootState) => state.counter

export default counterSlice.reducer;

當你有設定 interface 時應該會發現提示字的部分變得很準確了,雖然 PropTypes 也有類似功能,但其 bug Tracking 遠遠不及 Typescript 的貼心程度,這也是為什麼我喜歡改用 Typescript 來開發,當然這部分牽扯到很多泛型的應用,版上也有相當豐富的資源我這裡就不再贅述了。

完成 counterSlice 之後,別忘了調整 store,作法與先前 React Redux 一樣,如下:

// src/features/stor.ts
import { configureStore } from "@reduxjs/toolkit";
import counterSlice from "./slices/counterSlice";

const store = configureStore({
  reducer: {
    counter: counterSlice,
  },
})
// RootState要記得下export來提供slices使用
export type RootState = ReturnType<typeof store.getState>

export default store;

在製作 component 之前,我們將之前專案部分的 CSS 轉移過來,讓它好看一點,如下:

* {
  box-sizing: border-box;
  margin: 0;
}

body {
  padding: 0;
}

.container {
  width: min(100%, 768px);
  padding: 0 2rem;
  margin: 0 auto;
}

.card {
  border-radius: 15px;
  box-shadow: 2px 2px 5px #888;
  background-color: #fafafa;
  padding: 1rem;
}

.f-b-c {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.f-c-c {
  display: flex;
  justify-content: center;
  align-items: center;
}

.d-none {
  display: none;
}

.btn {
  border: 0;
  border-radius: .5rem;
  cursor: pointer;
  outline: 0;
  display: inline-flex;
  position: relative;
  align-items: center;
  vertical-align: middle;
  justify-content: center;
  text-decoration: none;
  background-color: transparent;
  transition: all 0.2s ease-in-out;
}

.btn:hover {
  transition: all 0.2s ease-in-out;
  filter: brightness(1.5);
}

.btn:active {
  transition: all 0.2s ease-in-out;
  filter: brightness(0.5);
}

.btn:disabled {
  color: #aaa;
}

.filled {
  background-color: indigo;
  color: white;
}

有了這些 setting 後我們可以開始製作 Counter Component 了,良好的習慣要持續的養成,所以我們不要在 App 裏面接著做,我們改在 components 資料夾下新增 Counter.tsx 的檔案,然後如下:

// src/components/Counter.tsx
import { useRef } from "react"
import { useDispatch, useSelector } from "react-redux"
import { minus, plus, selectCounter, setCount } from "../features/slices/counterSlice"

const Counter = () => {
  const counter = useSelector(selectCounter)
  const dispatch = useDispatch()
  const seterRef = useRef<HTMLInputElement>(null)
	// 處理剛剛定義的三種功能
  const handleMinus = () => dispatch(minus())
  const handlePlus = () => dispatch(plus())
  const handleSetCount = () => {
    if(seterRef.current) {
      if(seterRef.current.value.length > 0) {
        const num = parseInt(seterRef.current.value)
        dispatch(setCount(num))
      } else {
        alert('請先輸入')
      }
    } 
  }
  return (
    <div className="card">
      <h4>Counter Demo</h4>
      <div className="f-c-c" style={{margin: '.5rem 0'}}>
        <button 
          className="btn filled" 
          onClick={handleMinus} 
          style={{margin: '0 .5rem'}}
        >
          -
        </button>
        <p>{counter.count}</p>
        <button 
          className="btn filled" 
          onClick={handlePlus} 
          style={{margin: '0 .5rem'}}
        >
          +
        </button>
      </div>
      <div className="f-c-c" style={{padding: '.25rem'}}>
        <input type="number" ref={seterRef} />
        <button
          className="btn filled"
          onClick={handleSetCount}
          style={{margin: '0 .5rem', padding: '.25rem .5rem'}}
        >
          設定
        </button>
      </div>
    </div>
  )
}

export default Counter

完成後於 App 引入剛剛完成的 component,如下:

// src/App.tsx
import Counter from "./components/Counter"

function App() {
  return (
    <div className="container">
      <h1>Typescript Demo</h1>
      <Counter/>
    </div>
  )
}

export default App

這時候畫面上應該就會有可以加減及輸入數字功能的卡片了,那麼今天的內容就到這邊,下一篇我們來講解 extraReducer 的部分應用。


上一篇
Redux 深入淺出 - [ Day 25 ] Redux Toolkit Query 簡介
下一篇
Redux 深入淺出 - [ Day 27 ] React RTK Typescript ExtraReducer
系列文
30天深入淺出Redux31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言