@adamdickinson/react-service

中文文档
2.0.0发布于 2021/07/20MITTypeScriptCommonJS零依赖近 30 天下载 6.5万

Expose internal application APIs as a service.

原始使用文档
文档目录12 节
  1. Installation
  2. API
  3. Parameters
  4. Returns
  5. Usage
  6. Basic Usage
  7. Usage as an Auth Service
  8. Limitations
  9. Too many services spoil the broth
  10. There ain't much structure
  11. Got more?
  12. Got a solution?

React Service

Internal APIs for your application.

A React Service is one answer to the questions:

How should I manage state in my application? Redux has a lot of boilerplate and slows us down, MobX is tricky to debug and is kinda magic, component state + prop drilling is annoying and adds unnecessary complexity, React context is annoying to set up. What do?

Installation

npm install @adamdickinson/react-service

or

yarn add @adamdickinson/react-service

API

There's only one method you really need to know about:

createService(hook) -> [ProviderComponent, UseServiceHook]

Creates the provider and access hook for a new service.

Parameters

hook

A react hook function used to expose data.

Returns

[ProviderComponent, UseServiceHook]

Much like React's useState method, will return two values - the definition of a provider component, and the definition of a service hook.

The provider component is designed to be rendered in your application, providing access to the defined service to all child components.

The service hook is designed to be used within child components as needed to access service data.

Usage

Let's cut the discourse - it's demo time. Here are a handful of ways to implement what is effectively the same thing:

Basic Usage

As a contrived example, let's say we want to make a random number service.

// 1. We define the service
const [RandomProvider, useRandom] = createService(({ max }: { max: number }) => {
  const [number, setNumber] = useState<number>()
  return {
    update: () => setNumber(Math.floor(Math.random() * max)),
    number,
  }
})

// 2. We use the service with the `useRandom` hook in some components
const Displayer = () => {
  const random = useRandom()
  if (!random) return null
  return <p>The number is {random.number}</p>
}

const Changer = () => {
  const random = useRandom()
  if (!random) return null
  return <button onClick={random.update}>Randomize!</button>
}

// 3. We expose the service to those components that use it by rendering a parent `RandomProvider` component
ReactDOM.render(
  <RandomProvider max={100}>
    <Displayer />
    <Changer />
  </RandomProvider>,
  document.getElementById('root')
)

Usage as an Auth Service

Here's how we define a service. We start by simply and concisely defining data and processes...

// src/services/auth.ts
import createService from '@adamdickinson/react-service'
import store from 'store'
import { useEffect, useState } from 'react'

// Define our contract
interface User {
  id: string
  firstName: string
  lastName: string
  email: string
}

interface AuthAPI {
  logIn(username: string, password: string): Promise<User>
  logOut(): void
  user?: User
}

// Define our API as a hook so we can leverage React functionality
const useAuthAPI = ({ serverUrl }: { serverUrl: string }) => {
  const [user, setUser] = useState<User>()

  const onChangeUser = (newUser?: User) => {
    // Set local state, and persist change to storage
    store.set('user', newUser)
    setUser(newUser)
  }

  useEffect(() => {
    // Restore existing user from persistent storage into local state
    const existingUser = store.get('user')
    if (existingUser) {
      setUser(existingUser)
    }
  }, [])

  const api: AuthAPI = {
    logIn: async (username: string, password: string) => {
      const response = await fetch(`${serverUrl}/login`)
      if (!response.ok) {
        throw new Error(response.statusText)
      }

      onChangeUser(await response.json())
    },
    logOut: () => onChangeUser(undefined),
    user,
  }

  return api
}

const [AuthService, useAuth] = createService<AuthAPI>(useAuthAPI)

export { AuthService, useAuth }

... then we expose it to our application...

// src/index.tsx
import ReactDOM from 'react-dom'
import App from './App'
import { AuthService } from './services/auth'

ReactDOM.render(
  <AuthService serverUrl="http://api.myapp.com">
    <App />
  </AuthService>,
  document.getElementById('root')
)

... and finally, start using it wherever we need it!

// src/App.tsx
import { useAuth } from './services/auth'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'

export default () => {
  const auth = useAuth()
  return (
    <Router>
      <Switch>
        // Unauthed routes
        {!auth?.user && <Route path="/" component={LogIn} />}
        // Authed routes
        {!!auth?.user && <Route path="/" component={Welcome} />}
      </Switch>
    </Router>
  )
}
// src/containers/LogIn.tsx
import { useAuth } from './services/auth'
import { useState } from 'react'

export default () => {
  const auth = useAuth()
  const [loggingIn, setLoggingIn] = useState(false)
  const [error, setError] = useState('')

  const login = async () => {
    setLoggingIn(true)
    try {
      await auth.logIn('me', 'my-pass')
    } catch (error) {
      setError(error?.message || error)
      setLoggingIn(false)
    }
  }

  return (
    <>
      {error && <p>{error}</p>}
      <button onClick={login}>Log In</button>
    </>
  )
}
// src/containers/Welcome.tsx
import { useAuth } from './services/auth'

export default () => {
  const auth = useAuth()
  return `Welcome ${auth?.user?.firstName}!`
}

Limitations

Any good library will identify where it falls down and where it wants to improve. In an attempt to make this a good library too, here goes:

Too many services spoil the broth

Using multiple services at once poses some interesting challenges:

1. Nesting Nightmares

Look at this (if you dare):

const App = ({ children }) => (
  <AuthProvider>
    <APIProvider>
      <CommentsProvider>
        <PlaylistProvider>{children}</PlaylistProvider>
      </CommentsProvider>
    </APIProvider>
  </AuthProvider>
)

It only uses four randomly named services, but it's already becoming a nested mess.

2. Inter-service Relationships

Let's say we have an Auth service that uses an API service to communicate on its behalf, but the API service needs an auth token. Very quickly, we discover that we have a circular dependency. There are ways to handle this - namely delegating the task of token association to the caller rather than have the API service itself associate it to a call, but that's adding more complexity.

3. Performance Risks

While no performance issues have arisen in our extensive experimentation with this library, there is a risk of running extra renders when they are simply not required, again due to the nested nature of services, however more research is required here to confirm or deny this to any real extent.

There ain't much structure

Redux has set standard processes when it comes to its actions and reducers. It's all explicit and all verbose. While this is often seen as a curse, it can also be a blessing as it also defines a structure that many developers can work with simultaneously.

There is very little boilerplate, and therefore very few explicit structure requirements for services. While this does mean getting more done with much less code, it also makes things less verbose and less structured.

Got more?

Spin up an issue!

Got a solution?

Spin up a PR! Standards are pretty high, but feedback is a cornerstone of any good pull request so expect much love for any contributions.

版本与发布记录

@adamdickinson/react-service 共发布 7 个版本,首次发布于 2019/11/27;过去 12 个月发布了 0 个版本。

版本发布日期距今
2.0.0latest2021/07/205 年前
1.0.12021/05/315 年前
1.0.02021/05/285 年前
0.1.02020/08/266 年前
0.0.32020/07/166 年前
0.0.22020/07/086 年前

关于 @adamdickinson/react-service 的常见问题

@adamdickinson/react-service 是什么?

Expose internal application APIs as a service.

如何安装 @adamdickinson/react-service?

在项目目录运行 npm install @adamdickinson/react-service,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^16.13.1 || ^17.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@adamdickinson/react-service 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@adamdickinson/react-service 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@adamdickinson/react-service 可以商用吗?

@adamdickinson/react-service 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@adamdickinson/react-service 的下载量有多少?

根据 npm 官方下载统计,@adamdickinson/react-service 在 2026/10/05 – 2026/11/03 这 30 天内被下载 64,917 次,日均约 2,164 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@adamdickinson/react-service 还在维护吗?

最新版本 2.0.0 发布于 2021/07/20(5 年前);至今共发布 7 个版本,其中过去 12 个月发布了 0 个;首次发布于 2019/11/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。