npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-app-provider

v1.0.7

Published

React application root component generic provider.

Downloads

22

Readme

react-app-provider

version dw

基本介绍

React 应用程序根组件构造器,基于泛型抽象设计,理论上适用所有 React 应用环境,设计面向跨 Dom / Native / MiniProgram 多端适用。

  • [x] React 18
  • [x] React 17
  • [x] React Native
  • [x] Taro 小程序
  • [x] Remax 小程序

注:React DOM / React Native 可使用 App.onLaunch 阻塞,Taro 小程序(Remax),基于其机制,无法做到在 App.onLaunch 阻塞(请在 Page 中进行阻塞)

主要提供两大功能:

  1. createAppProvider 提供应用程序根组件。
  2. setupPage 提供应用基础页面组件,为你的所有 React Page 提供全局注入机制。

createAppProvider 使用说明

最基本的使用

index.tsx

import React from 'react';
import { render } from 'react-dom';
import { createAppProvider } from 'react-app-provider';

const [AppRoot, useAppContext] = createAppProvider();

render((
  <React.StrictMode>
    <AppRoot>
      {/* 你的组件加载入口 */}
    </AppRoot>
  </React.StrictMode>
), document.getElementById('root'));

加入应用程序类

我们将部分代码分离到 App.tsx

App.tsx

import { createAppProvider, AppInterface } from 'react-app-provider';

export class YourApp implements AppInterface {
  async onLaunch(): Promise<void> {
    await fetch('http://yourapi/app_launch').then(() => {
      // 程序启动前置
    });
  }
}

export const [AppRoot, useAppContext] = createAppProvider<YourApp>();

**注意:**这里建议指定 createAppProvider<YourApp> 这个泛型,这样 AppRoot 和 useAppContext 在整个应用环境都将能自动智能感知和提示 YourApp 的方法和属性。

如果觉得泛型的方式太啰嗦,也可以 createAppProvider(new YourApp) ,这样 AppRoot 就无需在指定 app 属性声明。

index.tsx

import React from 'react';
import { render } from 'react-dom';
import { AppRoot, YourApp } from './App.tsx';

render((
  <React.StrictMode>
    <AppRoot app={new YourApp()}>
      {/* 你的组件加载入口 */}
    </AppRoot>
  </React.StrictMode>
), document.getElementById('root'));

也可以是一个静态 Object

App.tsx

import { createAppProvider, AppInterface } from 'react-app-provider';

export const YourApp: AppInterface = {} as const;

export const [AppRoot, useAppContext] = createAppProvider<typeof YourApp>();

createAppProvider

createAppProvider 函数,根据你的应用环境需要,动态创建泛型的 AppContext 。他主要返回三个数据:


export const [
  AppRoot,       // 应用根节点组件
  useAppContext, // 获取 AppContext 的钩子
  AppConsumer,   // AppContext.Consumer 实际上不怎么用的上
] = createAppProvider<YourApp>();

AppRoot

其实你可以给他任何命名都可以,不一定非要叫 AppRoot

作为根节点组件,出于跨端考虑,没有默认绑定 React.StrictMode

AppRoot 内组件挂载,主要如下:

AppRoot
└─AppContext
   └─ErrorFallback
      └─LoaderFallback
         └─children => your code entry
  • AppRoot 为 PureComponent,只有两个 state { error: null, ready: false } ,主要职责
    • 等待 onLuanch 异步完成,更新 ready
    • 如果子组件出错,则捕获错误
  • AppContext 只持有三个属性:app 应用实例,appReady 应用是否准备好(onLaunch 异步完成),appError AppRoot 错误边界所捕获到的错误异常。
  • 如果存在 appError ,则 ErrorFallback 不会继续往下渲染,而是回退到 ErrorDisplay。
  • 如果 appReady 未预备,且指定了 AppRootProps.loader ,则回退到 Loader (应用程序准备中)

AppInterface 接口和 AppRoot 属性

/**
 * 应用程序接口类
 *
 * 该接口类声明了应用程序(管理)实例的接口定义,声明应用程序组件需要那些接口。
 *
 * 应用程序(管理)实例,可以是一个实现了 AppInterface 的类实例,也可以是一个 Object 结构。
 */
export interface AppInterface {

  /**
   * 应用程序启动接口,运行在 App 组件 componentDidMount
   */
  onLaunch?(): void | Promise<void>;

  /**
   * 当组件渲染接收到错误时的处理接口
   *
   * @param error - 错误实例
   */
  onError?(error: ErrorLike): void;

  /**
   * 当错误发生时,App 渲染是否切换至 ErrorFallback
   *
   * @param error - 错误实例
   */
  shouldErrorFallback?(error: ErrorLike): boolean;

  /**
   * 错误异常回退组件声明,不指定,则使用默认的 ErrorDisplay
   *
   * 可以是一个 Element 实例(自动注入 Error 实例),也可以是一个组件
   */
  readonly ErrorFallback?: ErrorFallbackComponent;

  /**
   * 应用预备中的加载器
   * 可以是一个 Element 实例(自动注入 ready),也可以是一个组件
   */
  readonly Loader?: LoaderComponent;

  // fallback
  [key: string]: unknown;
}

export type AppRootProps<App extends AppInterface> = {
  /**
   * 传入的应用程序实例
   */
  app?: App,
  /**
   * 应用启动接口
   */
  onLaunch?: () => void | Promise<void>,
  /**
   * 错误异常处理接口
   * @param error - 错误实例
   */
  onError?: (error?: ErrorLike) => void,
  /**
   * 错误异常回退组件声明
   * 可以是一个 Element 实例(自动注入 Error 实例),也可以是一个组件
   */
  errorFallback?: ErrorFallbackComponent,
  /**
   * 错误发生时,是否回退,默认值为 `true`
   */
  shouldErrorFallback?: boolean | ((error: ErrorLike) => boolean),
  /**
   * 应用程序准备中的加载器
   */
  loader?: LoaderComponent,
}

注意:AppInterfaceAppRootProps 某个属性或接口同时存在,如 onLaunch ,必优先 AppRootProps.onLaunch > AppInterface.onLaunch ,其他同理。

ErrorDisplay 和 setDefaultErrorDisplay

目前全部组件无任何具体的标签、样式渲染,唯独除了 ErrorDisplay ,所以额外提供了一个 setDefaultErrorDisplay 方法,允许因环境不同(如 MiniProgram 或 Native ),对默认的错误现实进行重载。

ErrorFallback

该组件根据传入的 error: ErrorLike 参数,决定是否回退(只有成功才现实 children)。

如果不指定 fallback 参数,则使用 ErrorDisplay 来显示错误。

该组件设计就是为了被复用的,其实常常需要用到这个组件。

LoaderFallback

该组件根据传入的 ready: boolean 参数,决定是否回退,但他和 ErrorFallback 不同的地方在于,必须同时指定 loader 属性。

即必须 ready === true && loader != null 时,才会回退显示加载中的界面。

该组件设计就是为了被复用的,其实常常需要用到这个组件。

小程序中使用(Taro)

src/services/AppService.ts

import { AppInterface, createAppProvider } from 'react-app-provider';

class AppService implements AppInterface {
  onLaunch(): void {
    // 小程序的 onLaunch 是无法被阻塞的
  }
}

export const [App, useAppContext] = createAppProvider(new AppService);

export const useApp = () => {
  const { app } = useAppContext();
  return app;
};

src/app.tsx

import { App } from './services/AppService';

export default App;

Taro 小程序启动,App 和 Page 两者是同步并发的,所以阻塞 App.onLaunch 是无意义的。App 层级的错误边界也是无用的,应该要在 Page 进行错误捕获。

React Native

import { createAppProvider } from 'react-app-provider';

const Loader: React.FC = () => {
  return (
    <SafeAreaView style={{ backgroundColor: Colors.darker }}>
      <View style={{ display: 'flex', height: '100%', justifyContent: 'center', alignItems: 'center' }}>
        <Text style={{ color: 'white' }}>App Loading...</Text>
      </View>
    </SafeAreaView>
  );
};

const [AppRoot, useAppContext] = createAppProvider({
  onLaunch(): Promise<void> {
    return new Promise<void>(resolve => {
      setTimeout(() => {
        resolve();
      }, 3000);
    });
  },
  Loader,
});

const App = () => {
  return (
    <AppRoot>
      <SafeAreaView>
        {/* ...... */}
      </SafeAreaView>
    </AppRoot>
  )
}

export default App;

setupPage 使用说明

setupPage 提供一个根据你的 React 应用环境,自行注入所需 props 到标准页面组件的注入机制,具体注入什么,你自己决定,基础页面布局,也由你自己决定。

import React, { ComponentType, createElement } from 'react';
import { isValidElementType } from 'react-is';
import { useLocation } from 'react-router-dom';
import { SetupPageProps, ErrorBoundary, ErrorFallback } from 'react-app-provider';
import { setupPage } from './setupPage';

// 要注入到页面的属性,
// 比如你的应用环境使用了 react-router-dom,你可能希望为每个页面注入 path, query 两个参数
type YourAppBasePageProps = {
  path: string,
  query: URLSearchParams,
}

// 你的页面初始化时,给定的一些额外的配置属性
type YourAppPageInitOptions = {
  
}

// 这里构建你的应用程序的基准页面,不一定非要用 class 模式,这里只是为了捕获页面错误
class YourAppBasePage extends ErrorBoundary<SetupPageProps<YourAppBasePageProps>> {
  state = {
    error: undefined,
  }

  render() {
    return (
      <ErrorFallback error={this.state.error}>
        {isValidElementType(render) ? createElement(render, props) : null}
      </ErrorFallback>
    );
  }
}

// 这里得到一个 page 函数,用来包装你既有的 Page 组件。
export const page = setupPage(
  (opts?: YourAppPageInitOptions): YourAppBasePageProps => {
    const { pathname, search } = useLocation();
    const query = React.useMemo(() => new URLSearchParams(search), [search]);
    return { path: pathname, query };
  },
  YourAppBasePage
);

比如你可能有一个 index.tsx

// 你原来的首页,可以不去改变他
const IndexPage = () => {
  return (
    <div>
      {/* 首页的代码 */}
    </div>
  );
};

// 页面初始化的配置,非必要
const pageOpts = {};

export default page(IndexPage, pageOpts);

实际应用中,我们往往会在 BasePage 加入一个 PageContext,以便于相关的页面内的所有组件,可以共享得到当前页面的上下文。

也不局限于一套页面机制,你可以定义多个,比如 userPage adminPage,并与之对应的 useUserPageuseAdminPage 等等。

setupPage 只为你提供最最基础的实现机制,具体要如何实现,完全取决于你的应用环境。

之所以这么设计,另一个重点在于为了同时兼顾 DOM / Native / MiniProgram 三端。因为这三端的严重差异性,几乎很难一言以概之,这样反而不如提供一种一样的可能性,各端再根据实际情况去定制底层页面,而应用层的页面声明,则可采用同样的方法。

安装

pnpm add react-app-provider

珍惜生命,爱惜电脑硬盘,请使用 pnpm

测试覆盖率

--------------------|---------|----------|---------|---------|-------------------
File                | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
--------------------|---------|----------|---------|---------|-------------------
All files           |     100 |       96 |     100 |     100 |                  
 AppRoot.tsx        |     100 |    94.73 |     100 |     100 | 135,138          
 ErrorBoundary.tsx  |     100 |      100 |     100 |     100 |                  
 ErrorFallback.tsx  |     100 |      100 |     100 |     100 |                  
 LoaderFallback.tsx |     100 |    88.88 |     100 |     100 | 19               
 setupPage.tsx      |     100 |      100 |     100 |     100 |                  
--------------------|---------|----------|---------|---------|-------------------

版本历史

1.0.7

  • 根据 react 18 ,增加相关组件的 children 属性声明

1.0.6

  • 增加 setupPage

1.0.5

  • 调整 rollup 配置,不提供 esm 版本

1.0.3

  • 兼容 React 18 ,测试代码 render 改为 createRoot
  • 拆分出 ErrorBoundary 组件

1.0.2

  • AppRoot 删除 async,省去生成的代码 __awaiter

1.0.1

2022/03/26

  • 修正 AppInterface 的属性声明,改为 [key: string]: any