1. 初始化配置
- yarn create react-app 项目名 —template=typescript
- yarn add redux react-redux prettier
- yarn add @types/react-redux
1.1 prettierrc 配置文件
在根目录下(和src同级)新建.prettierrc文件
.prettierrc 配置文件,用于统一格式化js代码
{"singleQuote": true,"jsxSingleQuote": true,"jsxBracketSameLine": true,"trailingComma": "all"}
在package.json中新增一条脚本命令
"scripts": {...+ "format": "prettier --write \"src/**/*.{ts,tsx}\""},
1.2 redux、redux devtools 配置
- 在src目录下新建store文件夹,在其中新建index.ts, reducer.ts
- 在App.tsx中导入store并使用
store/index.ts
import { createStore, compose } from 'redux';import { rootReducer } from './reducer';declare global {interface Window {__REDUX_DEVTOOLS_EXTENSION__?: Function;__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose;}}export interface StoreState {bill: number;percentage: number;split: number;}export const store = createStore(rootReducer,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),);
store/reducer.ts
import { Reducer, Action } from 'redux';import { StoreState } from '.';const initialState: StoreState = {bill: 0,percentage: 0,split: 1,};export const rootReducer: Reducer<StoreState, Action> = (state = initialState,action,) => state;
src/App.tsx
+ import { Provider } from 'react-redux';+ import { store } from './store';function App() {return (<Provider store={store}>...</Provider>);}
2. 编写actions, 完善reducer
- 在src/store目录下新建 actions.ts
src/store/actions.ts**
import { Action } from 'redux';// constantsexport enum ActionTypes {BillChange = '[Bill] change',PercentageChange = '[Percentage] change',SplitIncrement = '[Split] increment',SplitDecrement = '[Split] decrement',Reset = '[Reset]',}// actionsexport interface BillChangeAction extends Action {type: ActionTypes.BillChange;payload: string;}export interface PercentageChangeAction extends Action {type: ActionTypes.PercentageChange;payload: string;}export interface SplitIncrementAction extends Action {type: ActionTypes.SplitIncrement;}export interface SplitDecrementAction extends Action {type: ActionTypes.SplitDecrement;}export interface ResetAction extends Action {type: ActionTypes.Reset;}export type Actions =| BillChangeAction| PercentageChangeAction| SplitIncrementAction| SplitDecrementAction| ResetAction;
src/store/reducer.js 【其实感觉StoreState这个接口定义可以放在reducer.ts中,index.ts中没用到】
import { Reducer } from 'redux';import { StoreState } from '.'; // './index'import { Actions, ActionTypes } from './actions';const initialState: StoreState = {bill: 0,percentage: 0,split: 1,};export const rootReducer: Reducer<StoreState, Actions> = (state = initialState,action,) => {switch (action.type) {case ActionTypes.BillChange:return {...state,bill: Number(action.payload),};case ActionTypes.PercentageChange:return {...state,percentage: Number(action.payload),};case ActionTypes.SplitIncrement:return {...state,split: state.split + 1,};case ActionTypes.SplitDecrement:const split = state.split - 1;return {...state,split: split > 1 ? split : state.split,};case ActionTypes.Reset:return initialState;default:return state;}};
3. 使用useDispatch, useSelector
- 原本写法是直接 const bill = useSelector(state => state.bill) 这样,后来新建store/selectors.ts,把所有的select操作放在那边
src/components/TipCalculator.tsx
import React from 'react';import { useSelector, useDispatch } from 'react-redux';// import { StoreState } from '../store';import { ActionTypes } from '../store/actions';import {selectBill,selectPerPerson,selectSplit,selectTotal,selectTip,selectPercentage,} from '../store/selectors';export const TipCalculator = () => {// const bill = useSelector((state: StoreState) => state.bill);// const percentage = useSelector((state: StoreState) => state.percentage);// const split = useSelector((state: StoreState) => state.split);const bill = useSelector(selectBill);const percentage = useSelector(selectPercentage);const split = useSelector(selectSplit);const total = useSelector(selectTotal);const perPerson = useSelector(selectPerPerson);const tip = useSelector(selectTip);const dispatch = useDispatch();return (<div><div><span>Bill: </span><inputvalue={bill}onChange={(e) =>dispatch({ type: ActionTypes.BillChange, payload: e.target.value })}/></div><div><span>Tip %: </span><inputvalue={percentage}onChange={(e) =>dispatch({type: ActionTypes.PercentageChange,payload: e.target.value,})}/></div><div><span>Split</span><button onClick={() => dispatch({ type: ActionTypes.SplitIncrement })}>+</button><span>{split}</span><button onClick={() => dispatch({ type: ActionTypes.SplitDecrement })}>-</button></div><button onClick={() => dispatch({ type: ActionTypes.Reset })}>RESET</button><div>Bill Total: {total}</div><div>Tip: {tip}</div><div>Per Person: {perPerson}</div></div>);};
store/selectors.ts
import { StoreState } from '.';export type StoreSelector<T> = (state: StoreState) => T;export const selectBill: StoreSelector<number> = (state) => state.bill;export const selectPercentage: StoreSelector<number> = (state) =>state.percentage;export const selectSplit: StoreSelector<number> = (state) => state.split;export const selectTotal: StoreSelector<number> = (state) =>state.bill + state.bill * (state.percentage / 100);export const selectTip: StoreSelector<number> = (state) =>state.bill * (state.percentage / 100);export const selectPerPerson: StoreSelector<number> = (state) =>(state.bill + state.bill * (state.percentage / 100)) / state.split;
4. 使用tailwindcss
- 根目录(和src同级)下新建styles.css文件
- 运行 npx tailwind build styles.css -o src/index.css
- 可以开始直接使用了
styles.css
@tailwind base;@tailwind components;@tailwind utilities;
