React + Redux + TypeScript 最简上手教程

207 阅读3分钟

前言

众所周知,大型 React 项目需要有更系统的数据管理方案,Redux 便是最受欢迎的选择之一。笔者在公司使用的 React 数据管理工具也是基于 Redux 封装的一套,使用起来很方便,也想找机会学习一下原生的 Redux 使用方法和原理。

不过,一旦搜索「React + Redux + TypeScript + 教程」这组关键字时,得到的「教程」都非常繁琐。因此自己尝试摸索了一套最简的上手教程,目的是能学会如何在 React 项目里用上 Redux。

对,能用得上、跑得通就行,不需要很大很复杂。

image.png

背景

首先还是简单介绍一下 Redux ,作为一个单向的数据管理模型,主要有三个部分组成:

  • State:数据本身
  • Action:更新数据的最小单位,描述了如何更新数据
  • Reducer:执行数据更新,根据 state + action 得到一个新的 state

另外一个概念是 Dispatch ,即触发执行 Action 的方式。

React 则是一个 UI 框架。之所以要结合 Redux 来使用,(笔者认为)最首要的目的还是要解决跨组件之间数据传输的问题。这里跨组件指的是跨层级、或者同层级之间的数据传输。

上手

那就直接上手来实现一个「React + Redux + TypeScript」的最小项目吧。

完整项目可直接进入GitHub 仓库查阅.

启动

React 项目直接用 Vite 生成。这部分可参考 Vite官网

另外自行安装了两个依赖(参考React-Redux官方文档):

  • @reduxjs/toolkit
  • react-redux

把无关文件删掉之后,剩下的文件目录如下:

代码改动

代码部分主要关注上图红框的四个文件即可:

1. store.ts

这个文件用 Redux 定义了我们需要的数据模型。首先用到的两个 API 是:

  • configureStore
  • createSlice

如下,即可得到一个包含 root 子模块(reducer)的一个 Store,其中 root 模块的 state 有两个属性,一个计数用的 count 和一个字符串 input

另外定义了两个 Reducer,addCounteditInput(或者说这里定义的是两个 Action,之后 Controller.tsx 文件的用法可以看出)。

import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';

export const rootReducer = createSlice({
  initialState: {
    count: 1,
    input: '123123',
  },
  name: 'root',
  reducers: {
    addCount: (state) => {
      state.count += 1;
    },
    editInput: (state, action: PayloadAction<string>) => {
      state.input = action.payload;
    },
  }
})

const store = configureStore({
  reducer: {
    root: rootReducer.reducer,
  }
});

到这里,React 需要用的 Redux 模型其实已经完成了。接下来的问题就是,如何让 React 能用上这些数据,以及如何在 React 组件里执行这两个 Reducer。

1.5 还是 store.ts 文件

继续停留在这个文件里,为了能让 VSCode 里对 TypeScript 的代码补全功能发扬光大,这里还补充了几个类型声明:

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

这里用到的 API 主要有两个,都是 react hook

  • useSelector:读数据
  • useDispatch:写数据

通过类型转换后得到的 useAppDispatch 和 useAppSelector 就是与我们定义的 Store 类型一致的 hook 方法了。

2. App.tsx

这个组件展示了如何从 store 里使用类型安全的方式读到数据:

import React from 'react';
import './App.css'
import { useAppSelector } from './store';

function App() {
  const count = useAppSelector(state => state.root.count);
  const header = useAppSelector(state => state.root.input);

  return (
    <div>
      <div>{header}</div>
      <div>count: {count}</div>
    </div>    
  )
}

export default App

3. Controller.tsx

这个组件则是如何写数据:

import React from "react";
import { rootReducer, useAppDispatch } from "./store";

function Controller() {
  const dispatch = useAppDispatch();

  const handleClick = () => {
    dispatch(rootReducer.actions.addCount());
  }

  const handleChangeHeader = () => {
    dispatch(rootReducer.actions.editInput(`new header at ${new Date().toString()}`));
  }

  return (
    <div>
      <button onClick={handleClick}>ADD!!</button>
      <button onClick={handleChangeHeader}>CHANGE HEADER</button>
    </div>
  )
}

export default Controller;

这里直接调 rootReducer.actions.xxx(payload) 其实就能拿到之前在 rootReducer.reducers 定义的同名方法,生成一个 action 以供 dispatch 。

4. main.tsx

最后是最外层的容器。这里主要是要用 <Provider/> 把所有组件包装起来,并传入 store

import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import App from './App.jsx'
import store from './store.js'
import Controller from './Controller.js'


ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
      <Controller />
    </Provider>
  </React.StrictMode>,
)

成品

d50a3d5d-c988-4962-8b66-6e532360434d.gif

很简单的效果,但足够展示 React + Redux + TypeScript 的使用了。

后记

Redux 的其他 API,比如 MiddleWareCompose 等的最简使用,后续有机会再补上。

欢迎评论指正或点赞分享~