react dispatch_记 react 项目在 TypeScript 化中的一个坑,以及相应的类型改动
最近向 @types/react 提交了一个变动,改动了 useReducer 的定义,相信各位读者如果要 TypeScript 化,或者已经 TS 化的话,有可能会收到影响。
通过安装 @types/react@16.9.17 可以使用新的类型定义,这里简单的介绍一下这是怎样的一个能力。
我们首先看一下 react 官方 useReducer 文档中的 case,参见:
const initialState = {count: 0};function reducer(state, action) {switch (action.type) {case 'increment':return {count: state.count + 1};case 'decrement':return {count: state.count - 1};default:throw new Error();}
}function Counter() {const [state, dispatch] = useReducer(reducer, initialState);return (<>Count: {state.count}<button onClick={() => dispatch({type: 'decrement'})}>-</button><button onClick={() => dispatch({type: 'increment'})}>+</button></>);
}
@types/react 对这种 case 的支持是很充分的。然而,我们可能会写一个不需要 action 的 reducer,这种情况下,其实可以认为这个 reducer 只接受唯一可能的 action,如:
const [count, increase] = useReducer(v => v + 1, 0);
有些人会说,为什么不用 useState 来开发这种需求,如果用 useState:
const [count, setCount] = useState(0);
const increase = setCount(v => v + 1);
然而,increase 会在每次 render 的时候进行初始化,意味着每一次它是不一样的,如果不希望对子组件产生影响,就需要写成:
const [count, setCount] = useState(0);
const increase = useCallback(() => setCount(v => v + 1),[]
);
这样的代码我是不太能忍受的,react 在内部用类似 reducer = (state, action) => action
的方式实现 setState,结果开发者又要用 setState 重新实现 reducer。。。体会一下 code smell。。。
然而,@types/react 并没有考虑这个情况,这样的写法会得到一个 TS 报错:
const [count, increase] = useReducer(v => v + 1, 0);// TS2554: Expected 1 arguments, but got 0.
increase();
在考虑 reducer 永远接受 action 的情况下,这是正确的:
type Reducer<S, A> = (state: S, action: A) => S;type Dispatch<A> = (action: A) => void;// 伪代码,这里有 R 满足 R extends Reducer<S, A>
type UseReducer<R, S, A> = (reducer: R, initialArg: S) => [S, Dispatch<A>];
Dispatch<A> 会检查 dispatch(action) 中 action 的类型永远为 Reducer<S, A> 中的 A。
这里涉及到 TS 里的 类型推导功能,当我写:
function identity<T>(arg: T): T {return arg;
}const result = identity('myString');
TS 会识别 'myString' 的 类型为 string,并且推断出 result 的类型也为 string。
所以,回到上面,当我们 dispatch 传入空值的时候,TS 会报错并期望一个符合 action: A 的值。
然而,我们不能简单的通过 (action?: A) => void 来解决这个问题,当 Reducer 接受 action 参数的时候,我们希望 dispatch 一个确实符合的 action。而当 Reducer 不接受 action 参数的时候,我们才希望 dispatch 可以不传入一个 action,即:
const [, dispatch] = useReducer((sum, n) => sum + n);// Pass
dispatch(1);// ExpectError
dispatch();const [, dispatchWithoutAction] = useReducer(sum => sum + 1);// Pass
dispatchWithoutAction();
我们要使用 TS 的另一个特性 overload(重载)来实现这个需求,可以简化为:
type FuncBlank = () => void;
type Func<T> = (value: T) => void;function createFunc(): FuncBlank;
function createFunc<T>(value: T): Func<T>;
function createFunc<T>(value?: T): FuncBlank | Func<T> {// return ...
}
这样,调用 createFunc() 会得到一个 FuncBlank,调用 createFunc('myString') 会得到一个 Func<string>
在 useReducer 的重载上也是一样,我们需要判断 reducer 是一个带 action 的 reducer,或者是一个不带 action 的 reducer,来决定我们返回的 dispatch 的类型是否接受参数。
具体的实现可以参见 PR
大概就是这样,在此非常感谢 @LeuisKen @Gavin @NullMDR 的帮助。
react dispatch_记 react 项目在 TypeScript 化中的一个坑,以及相应的类型改动相关推荐
- 记前端状态管理库Akita中的一个坑
记状态管理库Akita中的一个坑 Akita是什么 Akita是一种基于RxJS的状态管理模式,它采用Flux中的多个数据存储和Redux中的不可变更新的思想,以及流数据的概念,来创建可观察的数据存储 ...
- idea 启动php项目路径,关于idea中Java Web项目的访问路径问题
说明 这里只以 servlet 为例,没有涉及到框架,但其实路径的基本原理和框架的关系不大,所以学了框架的同学如果对路径有疑惑的也可以阅读此文 项目结构 在 idea 中新建一个 Java Web 项 ...
- React.js 小书 Lesson1-2 - 前端组件化(一):从一个简单的例子讲起
React.js 小书 Lesson1-2 - 前端组件化(一):从一个简单的例子讲起 本文作者:胡子大哈 本文原文:http://huziketang.com/books/react/lesson2 ...
- React 折腾记 - (1) React Router V4 和antd侧边栏的正确关联及动态title的实现
前言 一如既往,实战出真理. 有兴趣的可以瞧瞧,没兴趣的大佬请止步于此. 免得浪费您的时间 效果图 基于antd的sidebar组件封装 折腾记的技术栈选型 Mobx & mobx-react ...
- 谈谈我对前端组件化中“组件”的理解,顺带写个Vue与React的demo
谈谈我对前端组件化中"组件"的理解,顺带写个Vue与React的demo 前言 前端已经过了单兵作战的时代了,现在一个稍微复杂一点的项目都需要几个人协同开发,一个战略级别的APP的 ...
- React——脚手架创建、基本操作、Typescript
React官方文档: https://react.docschina.org/ 1. 全局安装 cnpm i -g create-react-app 2. 创建项目 /*js*/ create-rea ...
- 使用react实现后台管理系统项目
一.开发React必须依赖三个库 1.react:包含react所必须的核心代码 2.react-dom:react渲染在不同平台所需要的核心代码 3.babel:将jsx转换成React代码的工具 ...
- [react] 你有在项目中使用过Yeoman脚手架吗?
[react] 你有在项目中使用过Yeoman脚手架吗? 前端工程化脚手架部分必备的依赖,用于初始化生成规范的项目目录 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, 但坚持一定很酷. ...
- fs react 使用 保存文件_入门TypeScript编写React
使用 create-react-app 开启 TypeScript Create React App 是一个官方支持的创建 React 单页应用程序的CLI,它提供了一个零配置的现代构建设置.当你使用 ...
最新文章
- SmartGit安装及使用
- [其他]JAVA与C#的Socket通信
- 第17节 业务流程管理和重组
- 用js自动把url加入ubb代码的函数
- Android之编译jni出错解决办法
- Matrix Computations 1
- Expected one result (or null) to be returned by selectOne(), but found: 2
- 基于JAVA+SpringMVC+Mybatis+MYSQL的学生健康信息管理系统
- Gcc 完全参考手册,参数说明,操作指南-Gcc Complete referene
- iphone开发我的新浪微博客户端-用户登录账号删除篇(1.6)
- Linux基础命令---accept打印机控制
- spring boot RESTFul API拦截 以及Filter和interceptor 、Aspect区别
- php 支付宝支付回调与查询订单
- windows server2003的邮箱服务器安装详细步骤
- 选择目录或选择文件(PyQt或Qt for python)
- 【Android】移动端接入Cronet实践
- CFS任务放置代码详解
- Android 侧边栏快速索引(点击索引、滑动索引),通讯录样式
- 小米笔记本锁屏睡眠无法唤醒修复方法
- E22 LoRa模块透传 定点传输 WOR模式测试与MicroPython应用
热门文章
- php与mysql关系大揭秘_【慕课笔记】PHP与MySQL关系大揭秘
- 如何安装html网站模板,网站模板安装说明
- linux mint 设置分辨率,Vmware中装Linux Mint 15 添加 1600x900分辨率
- 转载:(C++)浅谈多态基类析构函数声明为虚函数
- 关于SpringMVC中text/plain的编码导致的乱码问题解决方法
- html中hr的各种样式使用
- 覆写Activity的finish()方法
- 1.4 Arithmetic Progressions
- GridView控件日期格式化
- ‘cross-env‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。