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';
// constants
export enum ActionTypes {
BillChange = '[Bill] change',
PercentageChange = '[Percentage] change',
SplitIncrement = '[Split] increment',
SplitDecrement = '[Split] decrement',
Reset = '[Reset]',
}
// actions
export 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>
<input
value={bill}
onChange={(e) =>
dispatch({ type: ActionTypes.BillChange, payload: e.target.value })
}
/>
</div>
<div>
<span>Tip %: </span>
<input
value={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;