@adamdickinson/react-service 中文文档教程

发布于 3 年前 浏览 5 项目主页 更新于 2 年前

React Service

应用程序的内部 API。

React 服务是对以下问题的一种回答:

我应该如何管理我的应用程序中的状态? Redux 有很多样板文件并减慢了我们的速度,MobX 调试起来很棘手而且有点神奇,组件状态 + prop 钻孔很烦人并且增加了不必要的复杂性,React 上下文设置很烦人。 做什么?

Installation

npm install @adamdickinson/react-service

或者

yarn add @adamdickinson/react-service

API

只有一种方法是您真正需要了解的:

createService(hook) -> [ProviderComponent, UseServiceHook]

为新服务创建提供者和访问挂钩。

Parameters

hook

用于公开数据的 React 钩子函数。

Returns

[ProviderComponent, UseServiceHook]

很像React的useState方法,会返回两个值——定义 提供者组件和服务挂钩的定义。

提供者组件旨在在您的应用程序中呈现, 为所有子组件提供对已定义服务的访问。

服务挂钩旨在根据需要在子组件中使用 访问服务数据。

Usage

让我们停止讨论——现在是演示时间。 这里有一些方法可以 实现实际上是同一件事:

Basic Usage

作为一个人为的例子,假设我们想要提供随机数服务。

// 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

这是我们定义服务的方式。 我们从简明扼要地定义数据开始 和流程……

// 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 }

然后我们将它暴露给我们的应用程序……

// 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')
)

最后,在我们需要的地方开始使用它!

// 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

任何好的图书馆都会确定它失败的地方以及它想要的地方 提升。 为了使它也成为一个好的库,这里有:

Too many services spoil the broth

一次使用多个服务会带来一些有趣的挑战:

1. Nesting Nightmares

看看这个(如果你敢的话):

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

它只使用四个随机命名的服务,但它已经成为一个嵌套的服务 混乱。

2. Inter-service Relationships

假设我们有一个使用 API 服务进行通信的 Auth 服务 它的代表,但 API 服务需要一个授权令牌。 很快,我们发现 我们有一个循环依赖。 有办法处理这个 - 即 将令牌关联的任务委托给调用者而不是让 API 服务本身将其与呼叫相关联,但这增加了更多的复杂性。

3. Performance Risks

虽然在我们广泛的实验中没有出现性能问题 这个库,当它们根本不存在时,存在运行额外渲染的风险 需要,同样由于服务的嵌套性质,但是更多的研究是 此处需要在任何实际程度上确认或否认这一点。

There ain't much structure

Redux 在其操作和 reducers 方面设定了标准流程。 这一切都是明确而冗长的。 虽然这通常被视为一种诅咒,但它可以 也是一种祝福,因为它还定义了许多开发人员可以使用的结构 同时。

样板文件很少,因此显式结构很少 对服务的要求。 虽然这确实意味着事半功倍 更少的代码,它也使事情变得不那么冗长和结构化。

Got more?

提出一个问题!

Got a solution?

启动 PR! 标准很高,但反馈是任何标准的基石 好的 pull request 所以期待对任何贡献的热爱。

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.

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文