初窥 useState 以及 Flow 学习

137 阅读6分钟

初窥 useState 以及 Flow 学习

首先从 github 上 clone 一份React的源码。根据 Codebase OverView 所介绍的找到 React Core 所在的位置packages/react:

image.png

发现 React 截至到 v18.2.0 还是用 js 写的。我一直以为已经用 TypeScript 重构了。看源码里,React 还是用的flow来做 js 的静态类型检查。估计在学习 react 源码的中间也需要学习一下 flow。

让我们点到 index.js,可以看到它 export 了一堆的属性和方法。里面有我们平时比较常用的 API,比如 useStateuseEffect 等。

我准备从 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 存在的缺点是:

  1. React 需要被引入在你使用 JSX 的地方,因为它会被转换为React.createElement
  2. 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): 候选发布版本(尤指计算机软件的一个版本,在测试版之后发布,已开发完成,准备推出供用户使用)

问题记录

  1. run flow 报错 遇到一个问题,在使用 React 提供的 flow 类型检查命令后,报了两千多个 flow 类型错误。。不知道为啥。不知道是本事有这么多错,还是我配置不对。