用Hookstate简化React的状态管理

742 阅读11分钟

如果你用React构建了一个Web应用,你很可能在状态管理方面遇到过重大挑战。长期以来,我们依靠Redux进行状态管理,但由于其复杂性和过多的代码量,我们最终转向其他解决方案,如RxJS/React Hooks和React Context API。我们还看到Redux Toolkit解决了Redux的模板问题--我可以说,它是我刚才提到的所有工具中最简单的。

然后是React的useState Hook,用于本地状态管理。如果你以前用过它,那么你可能会想,为什么全局状态管理不能同样简单。为什么我们还需要这么多的模板来用Context API管理状态?如果我们不想受制于Redux Toolkit这样的舆论工具,或者被迫在我们的React应用中使用动作和还原器,怎么办?

这就是Hookstate出现的地方。Hookstate不只是另一个状态管理解决方案。除了功能丰富、快速和灵活之外,该库将简化React应用程序中的状态管理的想法提升到了一个全新的水平。

Hookstate的工作方式几乎与ReactuseState Hook完全一样。事实上,有了Hookstate,创建全局状态就像用useState Hook创建本地状态一样简单。除了它的简单性,Hookstate还用其他有用的功能扩展了我们创建的状态实例。

在这篇文章中,我们将通过建立一个演示的聊天应用程序来介绍Hookstate作为React应用程序的一个简单而有效的状态管理解决方案。你跟随这篇文章的唯一前提是要有React的知识。

Hookstate和它的功能

顾名思义,Hookstate是一个基于React状态钩的快速而灵活的状态管理工具。它是一个小型库,包含了全局和局部状态,以及部分状态更新和异步加载状态等功能。

我们这篇文章的重点是@hookstate/core ,但Hookstate有几个可选的插件,使我们能够扩展或定制我们的状态钩子--而且该库的文档写得很好,并有很多好的演示。下面是一些值得注意的插件。

  • [@hookstate/persistence](https://hookstate.js.org/docs/extensions-persistence)使我们能够将我们的状态持久化到浏览器的本地存储中,这对于离线应用程序或如果你希望用户在重新加载页面后保留他们的状态数据是很有用的
  • [@hookstate/validation](https://hookstate.js.org/docs/extensions-validation)对表单字段非常有用,因为它可以为一个状态提供验证和错误/警告信息
  • [@hookstate/broadcasted](https://hookstate.js.org/docs/extensions-broadcasted)如果你想让你的状态在不同的浏览器标签中同步,这是一个非常有用的工具。

让我们来探索Hookstate的一些核心功能,使其成为React应用程序中状态管理的一个好选择。我们将通过建立一个聊天应用程序来完成这个任务。以下是我们的应用程序在文章结束时应该有的样子。

Final chat app demo

我们的聊天应用程序将有两个组件,它们将能够通过从我们的全局Hookstate存储中发送和接收数据来相互作用。

用React和Hookstate构建我们的应用程序

让我们首先使用create-react-app 包来生成一个新的React应用程序。我们将在我们的终端运行以下命令。

npx create-react-app hookstate-chat

接下来,我们将cd 到我们新的hookstate-chat 目录并安装Hookstate。

cd hookstate-chat
npm install --save @hookstate/core

设置我们的聊天框组件

现在我们已经安装了Hookstate,让我们来安装 [react-custom-chat](https://www.npmjs.com/package/react-custom-chat).我为这篇文章创建了这个npm包,这样我们就可以专注于Hookstate,而不必为我们的聊天应用程序的设计而烦恼,但如果你喜欢,你可以使用或建立另一个。

npm install --save react-custom-chat

安装好react-custom-chat ,让我们去./src 目录,为我们的第一个聊天组件创建一个JSX文件。我们将它命名为FirstPerson

接下来,我们将在我们的./src/FirstPerson.jsx 文件中添加以下代码。

import ChatBox from 'react-custom-chat'

const FirstPerson = () => {
  return (
    <ChatBox
      messageList={[]}
      onSendMessage={newMessage => {}} // do something with newMessage
      settings={{
        position: 'left',
        navColor: 'green',
        navText: 'Mycroft'
      }}
    />
  )
}

export default FirstPerson

在我们的FirstPerson 组件中,我们首先从react-custom-chat 包中导入了ChatBoxChatBox 组件有一个messageList 道具,它将包含我们的消息对象的数组。每个消息对象将包含。

  • 一个类型为stringtext 属性,它代表消息文本
  • 一个类型为stringperson 属性,可以是 "主要 "或 "次要"。

person 属性帮助ChatBox 组件确定如何对每条消息进行样式设计。

onSendMessage 道具期待一个函数,这个函数将告诉它每当消息被发送时该做什么。

最后,我们添加了一个settings 道具来定义我们的聊天框的外观。在这种情况下,我们希望FirstPerson聊天框位于我们页面的左边。

让我们为我们的第二人称聊天框做同样的事情。我们将在我们的./src 目录中创建一个名为SecondPerson 的JSX文件,并在其中粘贴以下代码。

import ChatBox from 'react-custom-chat'

const SecondPerson = () => {
  return (
    <ChatBox
      messageList={[]}
      onSendMessage={() => {}}
      settings={{
        position: 'right',
        navColor: 'blue',
        navText: 'Cortana'
      }}
    />
  )
}

export default SecondPerson

注意我们是如何改变SecondPerson聊天框的位置、导航颜色和文本的。你可以在react-custom-chat 文档中找到ChatBox 组件的其他自定义设置。

将我们的聊天组件投入使用

随着我们的聊天组件成功创建,我们现在可以在我们的./src/App.js 文件中导入和使用它们。让我们用下面的代码替换当前的内容。

import FirstPerson from './FirstPerson'
import SecondPerson from './SecondPerson'

const App = () => {
  return (
    <>
      <FirstPerson />
      <SecondPerson />
    </>
  );
}

export default App

我们可以继续前进,在终端上运行npm start ,启动我们的应用程序。当我们在浏览器中打开我们的应用程序时,我们应该看到一个看起来像这样的页面。

Chat app start up page

点击聊天图标应该可以打开我们的聊天窗口。

Chat app with open chat windows

用Hookstate创建我们的全球商店

现在我们已经成功创建了我们的聊天组件,让我们使用Hookstate来设置和管理我们应用程序的消息功能。我们将首先在./src 目录中创建一个名为store.js 的新JavaScript文件,它将存放我们应用程序的全局状态。

Hookstate对你创建全局状态的地方没有任何限制,只要任何需要全局状态的组件能够成功导入它。

在我们的./src/store.js 文件中,我们将使用Hookstate的createState 方法来创建我们的状态。

import { createState } from '@hookstate/core'

const store = createState({
  firstPersonMessageList: [],
  secondPersonMessageList: []
})

export default store

另外,我们也可以单独创建我们的FirstPersonSecondPerson 状态。

...
const firstPersonMessageList = createState([])
const secondPersonMessageList = createState([])
...

无论哪种选择对我们的应用程序来说都是可行的,因为Hookstate使我们能够创建任意多的状态,并且还为我们提供了轻松更新嵌套状态属性的能力。为了我们教程的目的,我们将采用第一种方法。

我们还为createState 方法提供了一个初始对象状态,其中包含了属性firstPersonMessageListsecondPersonMessageList

随着我们的状态成功创建,我们可以继续使用Hookstate的useState Hook来访问它。由于这是一个React钩子,我们需要在我们的React组件中调用它。我们从useState Hook返回的状态将有。

  • 一个get() 方法,我们可以用它来获取状态数据
  • 一个set() 方法,用于为我们的状态设置一个新值
  • 一个merge() 方法,用于向我们的状态添加数据。

还有其他一些方法,如:attach()batch() ,但我们的演示应用程序不需要它们。

访问和更新我们的全局存储

让我们看看我们如何从各个组件中访问和更新我们的存储。我们将转到./src/FirstPerson.jsx 文件并从Hookstate导入useState Hook。我们也将从store.js 文件中导入我们的商店。

import { useState } from '@hookstate/core'
import store from './store'
...

现在我们可以使用useState Hook来访问我们的商店。由于useState 是一个React钩子,我们将需要在我们的FirstPerson 组件的主体内使用它。让我们创建一个名为globalState 的变量,并调用useState Hook,将我们导入的商店作为其值。

...
const FirstPerson = () => {
  const globalState = useState(store)
  ...
}

export default FirstPerson

globalState 变量应该包含我们提供给我们商店的初始状态。当使用useState 钩子访问我们的商店时,我们也可以直接对firstPersonMessageListsecondPersonMessageList 属性进行结构化。让我们把我们的globalState 变量声明改为下面这行代码。

const { firstPersonMessageList, secondPersonMessageList } = useState(store)

现在,我们可以把firstPersonMessageListsecondPersonMessageList 作为单独的状态使用。

这是一个强大的Hookstate优势,因为我们的嵌套属性也有我们的globalState 变量会有的状态方法。现在我们同样可以在我们的非结构化属性中使用get(),set(), 和merge() 方法。

处理用户的sendMessage 事件

接下来,让我们创建一个处理用户sendMessage 事件的函数。我们将它命名为handleSendMessage

...
const handleSendMessage = newMessage => {
  firstPersonMessageList.merge([{text: newMessage, person: 'primary'}])
  secondPersonMessageList.merge([{text: newMessage, person: 'secondary'}])
}

在上面的代码块中,我们已经创建了一个名为handleSendMessage 的函数,并为它提供了一个名为newMessage 的参数。我们的newMessage 参数代表我们的用户在聊天输入栏中输入的任何内容。对于我们添加到firstPersonMessageList 中的每一条新的主要信息,我们也会在secondPersonMessageList 中进行相应的二次添加。当我们到了第二人称组件时,我们会做相反的事情。

注意到用merge() 方法来更新我们的状态是多么容易。如果我们使用set() 方法或React内置的useState 钩子,我们的函数会看起来类似于这样。

const handleSendMessage = newMessage => {
  firstPersonMessageList.set([...firstPersonMessageList, {text: newMessage, person: 'primary'}])
  secondPersonMessageList.merge([...secondPersonMessageList, {text: newMessage, person: 'secondary'}])
}

我们的第二个函数肯定比第一个函数看起来更复杂。使用merge() 方法,如果当前的状态值和参数都是数组,Hookstate将完成连接当前值和参数值的工作并将其设置为状态。你可以在Hookstate文档中看到使用merge() 方法的其他方法。

为了美观起见,让我们把secondPersonMessageList 的状态更新延迟500毫秒。

...
const handleSendMessage = newMessage => {
  firstPersonMessageList.merge([{text: newMessage, person: 'primary'}])
  setTimeout(() => {
    secondPersonMessageList.merge([{text: newMessage, person: 'secondary'}])
  }, 500)
}
...

我们现在可以提供handleSendMessage 函数作为我们的ChatBox onSendMessage 的道具值。我们还将使用firstPersonMessageList 中的get() 方法来访问我们的状态,然后将其作为我们的ChatBox messageList 道具的值。

...
<ChatBox
  messageList={firstPersonMessageList.get()}
  onSendMessage={handleSendMessage}
  settings={{
    position: 'left',
    navColor: 'green',
    navText: 'Mycroft',
    isOpen: true
  }}
/>
...

我们的FirstPerson.jsx 文件现在应该是这样的。

import { useState } from '@hookstate/core'
import ChatBox from 'react-custom-chat'
import store from './store'

const FirstPerson = () => {
  const { firstPersonMessageList, secondPersonMessageList } = useState(store)

  const handleSendMessage = newMessage => {
    firstPersonMessageList.merge([{text: newMessage, person: 'primary'}])
    setTimeout(() => {
      secondPersonMessageList.merge([{text: newMessage, person: 'secondary'}])
    }, 500)
  }

  return (
    <ChatBox
      messageList={firstPersonMessageList.get()}
      onSendMessage={handleSendMessage}
      settings={{
        position: 'left',
        navColor: 'green',
        navText: 'Mycroft'
      }}
    />
  )
}

export default FirstPerson

让我们在我们的SecondPerson.jsx 文件中做同样的事情。由于我们已经详细解释了这些步骤,我们可以继续在我们的文件中粘贴以下代码。

import { useState } from '@hookstate/core'
import ChatBox from 'react-custom-chat'
import store from './store'

const SecondPerson = () => {
  const { firstPersonMessageList, secondPersonMessageList } = useState(store)

  const handleSendMessage = newMessage => {
    secondPersonMessageList.merge([{text: newMessage, person: 'primary'}])
    setTimeout(() => {
      firstPersonMessageList.merge([{text: newMessage, person: 'secondary'}])
    }, 500)
  }

  return (
    <ChatBox
      messageList={secondPersonMessageList.get()}
      onSendMessage={handleSendMessage}
      settings={{
        position: 'right',
        navColor: 'blue',
        navText: 'Cortana'
      }}
    />
  )
}

export default SecondPerson

在我们的SecondPerson 组件的handleMessage 函数中,我们做了与我们在FirstPerson 组件中所做的相反的事情:每当一个消息被发送时,它被作为主要部分加入到secondPersonMessageList ,作为次要部分加入到firstPersonMessageList

现在,当我们在浏览器中打开我们的应用程序时,我们应该能够通过Hookstate工具在我们的两个组件中发送消息。

Final chat app demo

总结

我们已经学会了如何在React应用程序中使用Hookstate进行状态管理。我们也看到了为什么Hookstate不仅仅是另一个状态管理解决方案,而是一个将React应用程序中的状态管理简化到一个全新水平的工具。

我还没有在一个大规模的应用中使用它,但到目前为止,它被证明是一个高效的库。这里有我们的react-custom-chat 包的代码库和我们的演示应用程序的链接。如果你想保持联系,可以考虑订阅我的YouTube频道并在GitHub上关注我。继续建设!

进一步阅读

The postSimplify React state management with Hookstateappeared first onLogRocket Blog.