简述

github: <https://github.com/private-fork-repos/staged-components>
about: Make React function component staged.
desc: react hooks只能放在顶层,这个库很短小的一个库,打破了这个规则。

工程化

项目非常小,所以没什么工程化可以研究,主要的就是用了gulp打包,@testing-library做测试。

Npm Scripts

{
   "scripts": {
    "build": "gulp prebuild && tsc",
    "test": "jest"
  },
}

build

可以看到主要是分两步,一个是gulp,一个是tsc。

gulp里面代码也很简单。

  • 清除产物目录lib。
  • copy package.json等文件到lib目录。

src里面的代码使用tsc转译成js。

package.json

没有什么值得说的。

源码部分

src目录

.
├── __tests__
|   └── main.test.tsx
└── index.tsx

index.tsx

所有的源码都在这一个文件里。

用法

import { useState, useEffect } from 'react';
import { staged } from '@/components/stage-components';

export default staged(() => {
  const [waiting, setWaiting] = useState(true);
  useEffect(() => {
    setTimeout(() => {
      setWaiting(false);
    }, 1000);
  }, []);

  if (waiting) return null;

  return () => {
    const [count, setCount] = useState(1);

    return (
      <div>
        <p>{count}</p>
        <button onClick={() => { setCount(count + 1); }}>Change</button>
      </div>
    );
  };
});

可以看到其实staged接收了一个函数,有会返回null,有可能返回真正的react组件。

根据代码可以猜测staged的做法就是当返回null的时候就把下面真正的组件给缓存(stage)起来不执行。下面看一下核心代码。

核心代码

import React, {FC, PropsWithChildren, ReactElement, Ref, RefForwardingComponent} from 'react'

type StageRender = () => StageRender | ReactElement | null
type StageRenderRoot<P> = (props: PropsWithChildren<P>) => StageRender | ReactElement | null
type StageRenderRootWithRef<P, R> = (props: PropsWithChildren<P>, ref: Ref<R>) => StageRender | ReactElement | null

function processNext(next: StageRender | ReactElement | null) {
  // 如果是函数 StageRender 或者 函数 就继续执行。否则直接返回。一直递归到ReactElement | null 就结束。
  if (typeof next === 'function') {
    return (
      <Stage stage={next} />
    )
  } else {
    return next
  }
}

function Stage<P>(props: {
  stage: StageRender
}) {
  const next = props.stage()
  return processNext(next)
}

// 导出文件,大部分都是类型定义,可以忽略。
export function staged<P = {}>(
  stage: StageRenderRoot<P>
): FC<P>
export function staged<P = {}, R = any>(
  stage: StageRenderRootWithRef<P, R>,
): RefForwardingComponent<R, P>
export function staged<P = {},  R = any>(
  // stage才是用户真正的函数组件
  stage: StageRenderRootWithRef<P, R>,
) {
  return function Staged(props, ref) {
    // 这里先把用户的组件给缓存起来
    // 这里拿到stage函数的返回值,根据上面的demo可以知道有可能是null或者真正的react组件也可能是stage函数。
    // 把结果传递给 processNext
    const next = stage(props, ref)

    return processNext(next)
  } as FC<P>
}