目录
1. Redux 基本使用
目标:能够掌握如何在 TS 项目中初始化 redux
内容:
-
安装依赖包:
yarn add redux react-redux redux-devtools-extension redux-thunk
-
新建文件 store/index.ts( 后缀为
.ts
)
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import reducer from './reducers';
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));
export default store;
- 新建文件 store/reducers/index.ts
import { combineReducers } from 'redux';
import { todos } from './todos';
const rootReducer = combineReducers({
todos,
});
export default rootReducer;
- 新建文件 store/reducers/todos.ts
type TodoList = {
id: number;
text: string;
done: boolean;
};
const initialState: TodoList = [
{
id: 1,
text: '吃饭',
done: false,
},
{
id: 2,
text: '睡觉',
done: true,
},
{
id: 3,
text: '打豆豆',
done: false,
},
];
export const todos = (state = initialState, action: any) => {
return state;
};
- index.tsx 中
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
2. useSelector 的使用
目标:能够掌握 useSelector 在 TS 中的使用
内容:
-
useSelector
hook 是一个泛型函数,接收两个类型变量,分别来指定:-
第一个类型变量:指定 Redux 仓库 state 的类型
-
第二个类型变量:指定要获取状态的类型
-
// useSelector 类型,源码如下:
// TState = DefaultRootState 是 泛型参数 的默认值,设置默认值后,将来在调用该函数时,可以省略该泛型参数不写
export function useSelector<TState = DefaultRootState, TSelected = unknown>(
// 第一个参数:回调函数,用来获取 redux 状态的回调函数,通过回调函数的返回值来指定要获取的状态
selector: (state: TState) => TSelected,
// 第二个参数:可以拿到更新前后的两次状态,通过返回的布尔值就可以来知道状态是否发生变化
equalityFn?: (left: TSelected, right: TSelected) => boolean
): TSelected;
useSelector 的两种使用方式:
1.指定泛型类型:
// 比如,可以这样调用:
type RootState = { count: number };
const count = useSelector<RootState, number>((state) => state.count);
2.不指定泛型类型,只指定回调函数参数 state 的类型
type RootState = { count: number };
const count = useSelector((state: RootState) => state.count);
3. 获取 Redux 仓库的状态类型
目标:能够获取 Redux 仓库的状态类型
内容:
- 获取 Redux 仓库状态类型
- 思路:
store.getState()
可以用来获取 Redux 应用的状态,所以只需要获取其类型即可
// store/index.ts 中:
// 获取 Redux 整个仓库的状态类型:
export type RootState = ReturnType<typeof store.getState>;
ReturnType
是一个泛型工具类型,用来获取函数的返回值类型
function add(n1: number, n2: number): number {
return n1 + n2;
}
// 获取函数 add 的类型
type AddFn = typeof add;
// 获取函数 add 的返回值类型
type AddFnReturnType = ReturnType<AddFn>;
// 直接获取 add 函数的返回值类型
type AddFnReturnType = ReturnType<typeof add>;
使用 useSelector
hook 获取 Redux 状态:
import { RootState } from '../store';
// 获取todos数据
const todos = useSelector((state: RootState) => state.todos);
4. reducer 函数的类型
目标:能够掌握 reducers 在 TS 中的写法
内容:
action 的类型有两种实现方式:1 自己创建 action 的类型 2 根据 action creator 来得到(扩展)
1.手动创建 action 的类型
- 先创建 action 类型,然后,用 action 类型来约束 action creator
type AddTodo = {
type: 'todos/add';
payload: string;
};
type DelTodo = {
type: 'todos/del';
payload: number;
};
export type TodoAction = AddTodo | DelTodo;
// 添加任务
export const addTodo = (name: string): AddTodo => ({
type: 'todos/add',
payload: name,
});
// 删除任务
export const delTodo = (id: number): DelTodo => ({
type: 'todos/del',
payload: id,
});
2.根据 action creator 来得到(扩展)
- 先创建 action creator,然后,根据 action creator 得到 action 类型
// 1 添加任务
export const addTodo = (text: string) => ({
// as const 常量断言,会让当前类型固定为 字面量本身,而不再被扩大类型
// 比如,string 就是 字面量类型 'todos/add' 的扩大类型
// 如果不加 as const,type 被推断为: string
// 如果加了 as const,type 被推断为: 'todos/add'
type: 'todos/add' as const,
payload: text,
});
// 2 删除任务
export const delTodo = (id: number) => ({
type: 'todos/del' as const,
payload: id,
});
// 3 切换任务完成状态
export const toggleTodo = (id: number) => ({
type: 'todos/toggle' as const,
payload: id,
});
// 思路2:根据 action creator 来得到 action 的类型
type AddTodo = ReturnType<typeof addTodo>;
type DelTodo = ReturnType<typeof delTodo>;
type ToggleTodo = ReturnType<typeof toggleTodo>;
export type TodoAction = AddTodo | DelTodo | ToggleTodo;
- 指定 reducer 的 action 参数和返回值的类型
- 约定:明确为 reducer 指定返回值类型,来约束 return 的内容必须满足返回值类型的要求,防止返回错误的数据
import { TodoAction } from '../actions/todos';
export const todos = (state = initValue, action: TodoAction): TodoList => {
switch (action.type) {
// 此时,就会有明确的 action 类型提示了
case 'todos/add':
const id = state.length === 0 ? 1 : state[state.length - 1].id + 1;
return [
...state,
{
id,
text: action.payload,
done: false,
},
];
case 'todos/del':
return state.filter((item) => item.id !== action.payload);
default:
return state;
}
};
5. TS 类型-常量断言
目标:能够理解 TS 中的常量断言 as const
内容:
- as const 文档
as const
也是一种类型断言,主要用于字面量类型,来得到字面量类型而不是其扩大后的类型,规则如下:- 简单类型的字面量,类型变为字面量本身,比如:不会再将字符串值
"hello"
推断为string
类型 - 对象字面量的属性变为
readonly
- 数组字面量变为
readonly
元组
- 简单类型的字面量,类型变为字面量本身,比如:不会再将字符串值
// x 的类型是: string
let x = 'hello';
// 使用 const 断言后,x 的类型是: "hello" 字面量类型
let x = 'hello' as const;
// y 的类型是: { readonly text: "hello" }
let y = { text: 'hello' } as const;
// z 的类型是: readonly [10, 20]
let z = [10, 20] as const;
使用场景:使用 as const
来让 action creator 省略返回值类型
export const addTodo = (text: string) =>
({
type: 'todos/add' as const,
payload: text,
} as const);
// 或:
export const addTodo = (name: string) => ({
type: 'ADD_TODO' as const,
payload: name,
});
6. useDispatch 的使用
目标:能够掌握 useDispatch 在 TS 中的使用
内容:
- react-redux 指定 useDispatch 的类型
useDispatch
hook 是一个泛型函数,接收一个类型变量用于指定 Action 的类型,该泛型类型可以直接省略
const dispatch = useDispatch()
<button onClick={() => dispatch(delTodo(item.id))}>x</button>
7. React 事件对象的类型
目标:能够掌握如何在 TS 中为事件对象指定类型
内容:
为 JSX 标签绑定事件时,可能需要指定事件对象的类型,分两种情况:
1.直接在 JSX 标签上写事件处理程序,此时,不需要手动指定事件对象的类型
- 技巧:在 JSX 标签上先把事件处理程序写好,然后,鼠标移动到事件对象上面,来查看事件对象的类型
2.如果将事件处理程序抽离出来,需要手动指定函数参数(事件对象)的类型
const add = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.code === 'Enter') {
dispatch(addTodo(name));
setName('');
}
};
8. redux-thunk 的使用
目标:能够掌握 redux-thunk 在 TS 中的使用
内容:
thunk action 的类型处理:
1.创建 thunk action 的类型:RootThunkAction
import { ThunkAction } from 'redux-thunk';
// 第一个类型参数:thunk action 返回值类型
// 第二个类型参数:Redux 状态的类型
// 第三个类型参数:thunk action 额外参数的类型
// 第四个类型参数:Redux 中所有 action 的类型
export type RootThunkAction = ThunkAction<void, RootState, unknown, TodoAction>;
// 第1、3个类型参数,参照上述文档来指定即可
2.使用 thunk action 类型:将该类型作为 thunk action 的返回值类型
// 将删除任务的 action 修改为 thunk action
// 注意:返回的函数,才是 thunk action
// delTodo 是 action creator
export const delTodo = (id: number): RootThunkAction => {
return (dispatch, getState) => {
setTimeout(() => {
dispatch({
type: 'todos/del',
payload: id,
});
}, 1000);
};
};
9. redux-thunk 新版本特性
注意:redux-thunk@2.4.0 新版使用 TS 重写了框架源码,并且相关的 TS 类型也做了一些调整,变化如下:
1.在 redux-thunk@2.3.0 版本中,在 thunk action 中分发对象 action 时有明确的类型提示
export const delTodo = (id: number): RootThunkAction => {
return (dispatch) => {
setTimeout(() => {
// 此处,在写 对象action 时,输入 type 属性会有明确的代码提示
dispatch({
type: 'todos/del',
payload: id,
});
}, 1000);
};
};
2.在 redux-thunk@2.4.0 新版本中,上述操作没有了明确的类型提示
- 参考该 issue:https://github.com/reduxjs/redux-thunk/issues/326
- 大概的意思是:使用 action creator 就不需要此处的类型提示了,或者有更好的 TS 类型实现来替换 redux-thunk 中现有的类型
所以,如果想要在 thunk action 中 diapatch 对象 action 时有类型提示,可以安装 2.3.0 版本:yarn add redux-thunk@2.3.0