接續前一篇的進度,我們已經設置好 store 了,接著我們來試做一個簡單的 counter 講解一下在 Typescript 的環境下如何處理我們的 slice 檔案吧!
那麼我們按照一如既往的結構於 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 的部分應用。