初窥 useState 以及 Flow 学习
首先从 github 上 clone 一份React的源码。根据 Codebase OverView 所介绍的找到 React Core 所在的位置packages/react:
发现 React 截至到 v18.2.0 还是用 js 写的。我一直以为已经用 TypeScript 重构了。看源码里,React 还是用的flow来做 js 的静态类型检查。估计在学习 react 源码的中间也需要学习一下 flow。
让我们点到 index.js,可以看到它 export 了一堆的属性和方法。里面有我们平时比较常用的 API,比如 useState、 useEffect 等。
我准备从 useState 开始学习。
useState
export function useState<S>(
initialState: (() => S) | S
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
首先看到的就是<S>,(() => S) | S等这些类型定义。总体来说还比较好理解,和 TS 非常像。需要的话请阅读flow 官方文档。
这里我觉得需要注意的就是S的用法,S(不一定非要用S表示)是泛型(Generic Types),泛型(有时也被成为多态类型-Polymorphic Types)是抽象类型的方法。具体的可以去官网查看~
Flow 相关知点简单记录
记录一下遇到的 flow 相关的知识点。我并没有打算总体性的去过一遍 flow 的文档,我的思路是,先大致瞄一眼,看到感兴趣的看一下,需要记录的记录一下。后续遇到不懂的学习一下,这样感觉效率高一些。
1. flow 的类型检查并不是全局性的
flow 的后台程序监视着所有 flow 文件。那么问题来了,如何知道哪些是 要检查的 flow 文件呢?它并不是通过文件后缀来判断的,而是通过@flow注释。
当你在需要检查的文件写入了@flow注释,那么这个文件就会被检查。
// @flow
//或者
/* @flow */
2. 基础类型
(1234: number);
("hi": string);
(true: boolean);
([1, 2]: Array<number>);
({ prop: "value" }: Object);
(function method() {}: Function);
3. Subsets & Subtypes(子集和子类型)
我简单理解下来,在 flow 里,子集就是子类型。对于复杂类型(比如,对象、函数等)可能有点区别,不过先不深究。
简单记录几个例子:
-
基础类型
// TyepA是TypeB的子类型,TypeB不是TypeA的子类型。 type TypeA = 1 | 2 | 3; type TypeB = 1 | 2 | 3 | 4 | 5; -
复杂类型之对象
如果 B 对象包含了 A 对象所有的 key,且类型相同。那么 B 对象就是 A 对象的子类型。
// @flow type ObjectA = { foo: string }; type ObjectB = { foo: string, bar: number }; let objectB: ObjectB = { foo: "test", bar: 42 }; let objectA: ObjectA = objectB; // 正确! // @flow type ObjectA = { foo: string }; type ObjectB = { foo: number, bar: number }; let objectB: ObjectB = { foo: 1, bar: 2 }; let objectA: ObjectA = objectB; // 错误! -
复杂类型之函数 感觉可能用不太到?暂时没仔细看。后面遇到了再查文档。
4. Type Variance(类型变异)
涉及到 class 中的类型检查逻辑,比较长,可以移步官网 Type Variance查看。
5. Nominal & Structural Typing(名义类型和结构类型)
每一个类型系统都有一个重要的概念:他们是 Nominal 还是 Structural,或者两种混合使用。
所有类型比如 string,boolean,对象或 class 等,它们既有名称也有结构。
对于基本类型,它们的结构非常简单,所以只能使用名称来做类型检查(have a very simple structure and only go by one name)。
而对于更为复杂的类型,比如对象,则有更为复杂的结构,同时也有名称。所以用什么来做类型判断需要看各个系统的设计。
根据名称检查类型就是 Nominal Typing,根据结构检查类型就是 Structural Typing。
绝大部分语言都会混合使用它们,Flow 也是如此。Flow 对方法和对象使用 Structural Typing,对 class 使用 Nominal Typing。如果想对 class 也使用 Structral Typing 的话,请使用 interface。
Functions are structrally typed
// @flow
type FuncType = (input: string) => void;
function func(input: string) {
/*...*/
}
let test: FuncType = func; //正确!
Objects are structrally typed
type ObjType = { property: string };
let obj = { property: "value" };
let test: ObjType = obj; //正确!
Classes are nominal typed
// @flow
class Foo {
method(input: string) {
/* ... */
}
}
class Bar {
method(input: string) {
/* ... */
}
}
let test: Foo = new Bar(); // 错误!
使用 interface 来实现 structural typed classes。
interface Interface {
method(value: string): void;
}
class Foo {
method(input: string) {
/* ... */
}
}
class Bar {
method(input: string) {
/* ... */
}
}
let test1: Interface = new Foo(); //正确!
let test2: Interface = new Bar(); //正确!
衍生知识点
1. React Runtimes
在学习如何在 React 中设置 Flow 的教程中了解到了 React Runtimes 的概念,之前没有怎么关注这个概念。
我理解的 React Runtimes 是:因为浏览器只能执行 JS 代码,像 JSX 的语法是无法直接在浏览器中执行的,所以需要使用 babel 等转换为 JS。
@babel/plugin-transform-react-jsx
这是 React Automatic Runtime(React 自动运行时)。Automatic Runtime 是在v7.9.0添加的功能,当这个功能开启时,用来编译 JSX 的函数会被自动导入。
In
const profile = (
<div>
<img src="avatar.png" className="profile" />
<h3>{[user.firstName, user.lastName].join(" ")}</h3>
</div>
);
Out
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
const profile = _jsxs("div", {
children: [
_jsx("img", {
src: "avatar.png",
className: "profile",
}),
_jsx("h3", {
children: [user.firstName, user.lastName].join(" "),
}),
],
});
2. JSX Transform
看到上面这个@babel/plugin-transform-react-jsx自动引入的依赖react/jsx-runtime,我又感到了疑惑,如果 babel 是用来转换 JSX 的,那这个react/jsx-runtime又是啥?
于是我又去查了一下,发现这个是 React 和 Babel 合作开发的新的 JSX Transform(JSX 转换),是跟随着 React 17 RC 一起发布的。
旧 React Transfrom
旧的 JSX Transform 会把 JSX 转化为React.createElement()调用。
In
import React from "react";
function App() {
return <h1>Hello World</h1>;
}
Out
import React from "react";
function App() {
return React.createElement("h1", null, "Hello world");
}
旧的 Transfrom 存在的缺点是:
- React 需要被引入在你使用 JSX 的地方,因为它会被转换为
React.createElement React.createElement并不支持有些性能优化以及简化
新 React Transfrom
为了解决这个问题,React 17 在 React 中引入了两个提供给像 Babel 和 Typescript 的编译器(compiler)的入口(entry points)。
相对于将 JSX 转化为React.createElement,新的 JSX Transfrom 会从新的入口引入特殊的方法并使用它们来处理 JSX。
In
function App() {
return <h1>Hello World</h1>;
}
Out
// Inserted by a compiler (don't import it yourself!)
// 编译器会自动插入(不要手动引入哦)
import { jsx as _jsx } from "react/jsx-runtime";
function App() {
return _jsx("h1", { children: "Hello world" });
}
现在,在使用 JSX 的时候就不用 import React 了。当然,在使用 hooks 的时候还是需要。
- React 17 RC(Release Candidate): 候选发布版本(尤指计算机软件的一个版本,在测试版之后发布,已开发完成,准备推出供用户使用)
问题记录
- run flow 报错 遇到一个问题,在使用 React 提供的 flow 类型检查命令后,报了两千多个 flow 类型错误。。不知道为啥。不知道是本事有这么多错,还是我配置不对。