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代码

  1. {
  2. "singleQuote": true,
  3. "jsxSingleQuote": true,
  4. "jsxBracketSameLine": true,
  5. "trailingComma": "all"
  6. }

在package.json中新增一条脚本命令

  1. "scripts": {
  2. ...
  3. + "format": "prettier --write \"src/**/*.{ts,tsx}\""
  4. },

1.2 redux、redux devtools 配置

  • 在src目录下新建store文件夹,在其中新建index.ts, reducer.ts
  • 在App.tsx中导入store并使用

store/index.ts

  1. import { createStore, compose } from 'redux';
  2. import { rootReducer } from './reducer';
  3. declare global {
  4. interface Window {
  5. __REDUX_DEVTOOLS_EXTENSION__?: Function;
  6. __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose;
  7. }
  8. }
  9. export interface StoreState {
  10. bill: number;
  11. percentage: number;
  12. split: number;
  13. }
  14. export const store = createStore(
  15. rootReducer,
  16. window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
  17. );

store/reducer.ts

  1. import { Reducer, Action } from 'redux';
  2. import { StoreState } from '.';
  3. const initialState: StoreState = {
  4. bill: 0,
  5. percentage: 0,
  6. split: 1,
  7. };
  8. export const rootReducer: Reducer<StoreState, Action> = (
  9. state = initialState,
  10. action,
  11. ) => state;

src/App.tsx

  1. + import { Provider } from 'react-redux';
  2. + import { store } from './store';
  3. function App() {
  4. return (
  5. <Provider store={store}>
  6. ...
  7. </Provider>
  8. );
  9. }

2. 编写actions, 完善reducer

  • 在src/store目录下新建 actions.ts


src/store/actions.ts**

  1. import { Action } from 'redux';
  2. // constants
  3. export enum ActionTypes {
  4. BillChange = '[Bill] change',
  5. PercentageChange = '[Percentage] change',
  6. SplitIncrement = '[Split] increment',
  7. SplitDecrement = '[Split] decrement',
  8. Reset = '[Reset]',
  9. }
  10. // actions
  11. export interface BillChangeAction extends Action {
  12. type: ActionTypes.BillChange;
  13. payload: string;
  14. }
  15. export interface PercentageChangeAction extends Action {
  16. type: ActionTypes.PercentageChange;
  17. payload: string;
  18. }
  19. export interface SplitIncrementAction extends Action {
  20. type: ActionTypes.SplitIncrement;
  21. }
  22. export interface SplitDecrementAction extends Action {
  23. type: ActionTypes.SplitDecrement;
  24. }
  25. export interface ResetAction extends Action {
  26. type: ActionTypes.Reset;
  27. }
  28. export type Actions =
  29. | BillChangeAction
  30. | PercentageChangeAction
  31. | SplitIncrementAction
  32. | SplitDecrementAction
  33. | ResetAction;

src/store/reducer.js 【其实感觉StoreState这个接口定义可以放在reducer.ts中,index.ts中没用到】

  1. import { Reducer } from 'redux';
  2. import { StoreState } from '.'; // './index'
  3. import { Actions, ActionTypes } from './actions';
  4. const initialState: StoreState = {
  5. bill: 0,
  6. percentage: 0,
  7. split: 1,
  8. };
  9. export const rootReducer: Reducer<StoreState, Actions> = (
  10. state = initialState,
  11. action,
  12. ) => {
  13. switch (action.type) {
  14. case ActionTypes.BillChange:
  15. return {
  16. ...state,
  17. bill: Number(action.payload),
  18. };
  19. case ActionTypes.PercentageChange:
  20. return {
  21. ...state,
  22. percentage: Number(action.payload),
  23. };
  24. case ActionTypes.SplitIncrement:
  25. return {
  26. ...state,
  27. split: state.split + 1,
  28. };
  29. case ActionTypes.SplitDecrement:
  30. const split = state.split - 1;
  31. return {
  32. ...state,
  33. split: split > 1 ? split : state.split,
  34. };
  35. case ActionTypes.Reset:
  36. return initialState;
  37. default:
  38. return state;
  39. }
  40. };

3. 使用useDispatch, useSelector

  • 原本写法是直接 const bill = useSelector(state => state.bill) 这样,后来新建store/selectors.ts,把所有的select操作放在那边

src/components/TipCalculator.tsx

  1. import React from 'react';
  2. import { useSelector, useDispatch } from 'react-redux';
  3. // import { StoreState } from '../store';
  4. import { ActionTypes } from '../store/actions';
  5. import {
  6. selectBill,
  7. selectPerPerson,
  8. selectSplit,
  9. selectTotal,
  10. selectTip,
  11. selectPercentage,
  12. } from '../store/selectors';
  13. export const TipCalculator = () => {
  14. // const bill = useSelector((state: StoreState) => state.bill);
  15. // const percentage = useSelector((state: StoreState) => state.percentage);
  16. // const split = useSelector((state: StoreState) => state.split);
  17. const bill = useSelector(selectBill);
  18. const percentage = useSelector(selectPercentage);
  19. const split = useSelector(selectSplit);
  20. const total = useSelector(selectTotal);
  21. const perPerson = useSelector(selectPerPerson);
  22. const tip = useSelector(selectTip);
  23. const dispatch = useDispatch();
  24. return (
  25. <div>
  26. <div>
  27. <span>Bill: </span>
  28. <input
  29. value={bill}
  30. onChange={(e) =>
  31. dispatch({ type: ActionTypes.BillChange, payload: e.target.value })
  32. }
  33. />
  34. </div>
  35. <div>
  36. <span>Tip %: </span>
  37. <input
  38. value={percentage}
  39. onChange={(e) =>
  40. dispatch({
  41. type: ActionTypes.PercentageChange,
  42. payload: e.target.value,
  43. })
  44. }
  45. />
  46. </div>
  47. <div>
  48. <span>Split</span>
  49. <button onClick={() => dispatch({ type: ActionTypes.SplitIncrement })}>
  50. +
  51. </button>
  52. <span>{split}</span>
  53. <button onClick={() => dispatch({ type: ActionTypes.SplitDecrement })}>
  54. -
  55. </button>
  56. </div>
  57. <button onClick={() => dispatch({ type: ActionTypes.Reset })}>
  58. RESET
  59. </button>
  60. <div>Bill Total: {total}</div>
  61. <div>Tip: {tip}</div>
  62. <div>Per Person: {perPerson}</div>
  63. </div>
  64. );
  65. };

store/selectors.ts

  1. import { StoreState } from '.';
  2. export type StoreSelector<T> = (state: StoreState) => T;
  3. export const selectBill: StoreSelector<number> = (state) => state.bill;
  4. export const selectPercentage: StoreSelector<number> = (state) =>
  5. state.percentage;
  6. export const selectSplit: StoreSelector<number> = (state) => state.split;
  7. export const selectTotal: StoreSelector<number> = (state) =>
  8. state.bill + state.bill * (state.percentage / 100);
  9. export const selectTip: StoreSelector<number> = (state) =>
  10. state.bill * (state.percentage / 100);
  11. export const selectPerPerson: StoreSelector<number> = (state) =>
  12. (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

  1. @tailwind base;
  2. @tailwind components;
  3. @tailwind utilities;