前言
众所周知,大型 React 项目需要有更系统的数据管理方案,Redux 便是最受欢迎的选择之一。笔者在公司使用的 React 数据管理工具也是基于 Redux 封装的一套,使用起来很方便,也想找机会学习一下原生的 Redux 使用方法和原理。
不过,一旦搜索「React + Redux + TypeScript + 教程」这组关键字时,得到的「教程」都非常繁琐。因此自己尝试摸索了一套最简的上手教程,目的是能学会如何在 React 项目里用上 Redux。
对,能用得上、跑得通就行,不需要很大很复杂。
背景
首先还是简单介绍一下 Redux ,作为一个单向的数据管理模型,主要有三个部分组成:
- State:数据本身
- Action:更新数据的最小单位,描述了如何更新数据
- Reducer:执行数据更新,根据 state + action 得到一个新的 state
另外一个概念是 Dispatch ,即触发执行 Action 的方式。
React 则是一个 UI 框架。之所以要结合 Redux 来使用,(笔者认为)最首要的目的还是要解决跨组件之间数据传输的问题。这里跨组件指的是跨层级、或者同层级之间的数据传输。
上手
那就直接上手来实现一个「React + Redux + TypeScript」的最小项目吧。
完整项目可直接进入GitHub 仓库查阅.
启动
React 项目直接用 Vite 生成。这部分可参考 Vite官网。
另外自行安装了两个依赖(参考React-Redux官方文档):
@reduxjs/toolkitreact-redux
把无关文件删掉之后,剩下的文件目录如下:
代码改动
代码部分主要关注上图红框的四个文件即可:
1. store.ts
这个文件用 Redux 定义了我们需要的数据模型。首先用到的两个 API 是:
configureStorecreateSlice
如下,即可得到一个包含 root 子模块(reducer)的一个 Store,其中 root 模块的 state 有两个属性,一个计数用的 count 和一个字符串 input。
另外定义了两个 Reducer,addCount 和 editInput(或者说这里定义的是两个 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>,
)
成品
很简单的效果,但足够展示 React + Redux + TypeScript 的使用了。
后记
Redux 的其他 API,比如 MiddleWare、Compose 等的最简使用,后续有机会再补上。
欢迎评论指正或点赞分享~