与查克·诺里斯 (Chuck Norris) 进行 React Native 聊天

2025-06-10

与查克·诺里斯 (Chuck Norris) 进行 React Native 聊天

我是Stream的开发者布道师,负责 Feeds 和 Chat 的 API 开发。我有幸体验了我们各种工具、精美的 UI 套件和各种新产品!多年来,Stream 一直是 Feeds 即服务的领先提供商,为超过 5 亿终端用户提供 Feeds 服务。过去几个月,团队一直在努力开发一款新产品——Chat。

在本教程中,我将引导您了解如何使用 React Native、Gifted Chat(Stream 的 React Native Chat Components 目前处于测试阶段)、Serverless以及Chat by Stream构建一个简单的即时通讯应用。此外,我还将介绍一个后端 Lambda 函数,该函数将使用第三方 API 提供的 Chuck Norris 笑话和事实进行自动回复。

对于最繁重的工作,我们将使用Stream Chat JS,这是一个可以直接与 Stream API 通信的 JavaScript SDK。如果您有兴趣查看关于 Stream Chat 的精彩教程,请点击此处

我还应该指出,我将使用 macOS 和 iOS,因此如果您使用的是 Windows 或 Linux,本教程可能会略有偏差。

要继续阅读本文,请确保您已安装和/或设置以下内容:

正在寻找代码库?您可以在这里找到开源代码。想要快速演示一下吗?我也有!可以看看Appetize上的演示。

让我们玩得开心点!🏂

1. 按流配置聊天

前往https://getstream.io,点击网站右上角的“注册”按钮。然后按照提示操作。

溪流

跳转后,前往https://getstream.io/chat/#pricing并点击“开始试用”按钮——这将为您的帐户启用聊天功能(14 天)。试用期设置完成后,您就快完成了!

流定价

返回仪表盘并点击您的应用程序。仪表盘默认显示“动态”,因此请点击顶部的“聊天”按钮。

流仪表板

从这里向下滚动,您将看到新启用聊天功能的应用的各种设置。请确保在新标签页中保持打开状态——您将需要此页面上的KeySecret(底部)和App ID(顶部)。

如果您对 Stream Chat 的定价和产品比较感兴趣,请查看此处

2. 设置无服务器

当您只需要几个端点时(类似于此构建),无服务器通常是最明智的选择。启动服务器可能需要相当长的时间,而且成本可能相当高昂。我并不是说无服务器环境适合所有人和所有事物,但我要说的是,如果您要构建一个很小的 ​​API,那么无服务器环境绝对是最佳选择。

前往https://dashboard.serverless.com并创建一个新帐户。按照初始步骤操作(邮箱验证、用户名选择、应用创建等)。创建应用后,保存租户名称(例如 nick-chuck 用户名 = nickchuck 租户),并保存应用名称——我们在接下来的几个步骤中都需要这两个名称。

无服务器仪表板

3. 使用 Expo 创建 React Native 应用

Expo 通过使用 Expo API 简化了构建 React Native 应用的过程。从技术上讲,我们根本不需要它;但是,如果您想快速构建一些应用,并有可能在 iOS 或 Android 上发布,Expo 将是最快的方法。毕竟,您可以随时退出 Expo 项目。

要创建你的应用,请打开终端并转到你选择的目录(我将在 中~/Code)。一切准备就绪后,运行以下命令来搭建项目。

$ expo init react-native-chat-chucky
Enter fullscreen mode Exit fullscreen mode

按照 Expo CLI 的提示,在 tabs vs. blank 问题中选择“blank”。完成这些问题后,Expo 将生成目录并使用 yarn 或 npm 安装必要的依赖项。你的终端应该如下所示:

终端

一切就绪!👏

4. 将聊天 SDK 添加到 React Native Chat

接下来,让我们使用以下命令安装所有必需的依赖项。我将使用 yarn 来完成此操作,但如果您在初始设置时选择了 npm,请使用它来避免混淆锁文件。

$ yarn add axios md5 react-native-gifted-chat react-native-iphone-x-helper react-native-parsed-text react-router-native stream-chat
Enter fullscreen mode Exit fullscreen mode

5.添加默认消息

为了显示聊天界面,我们使用了react-native-gifted-chat,这是一个专为处理聊天应用程序而设计的出色 UI 库。有了 Gifted Chat,我们可以将 UI 放在一边,快速启动并运行!

要启动初始消息,我们需要创建一个新目录和一个 messages 文件。UI 已经连接到此文件,因此只需创建它并放入自定义消息即可。

$ mkdir data && touch messages.js
Enter fullscreen mode Exit fullscreen mode

完成该步骤后,将以下代码片段粘贴到文件中并保存。

module.exports = [
  {
    _id: Math.round(Math.random() * 1000000),
    text: "Say something... I'll tell you some fun facts! 🤣",
    createdAt: Date.now(),
    user: {
      _id: "chuck",
      name: "Chuck Norris"
    }
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text: "Chat with Chuck!",
    createdAt: Date.now(),
    system: true
  }
];
Enter fullscreen mode Exit fullscreen mode

一切准备就绪!🚀

6. 向 React Native 添加路由和屏幕

我们已经具备了所有必要的依赖关系,所以让我们继续前进并将一切联系在一起!

修改您的App.js文件以包含以下代码片段。

import React, { Component } from "react";
import { KeyboardAvoidingView, StyleSheet } from "react-native";
import { NativeRouter as Router, Route, Switch } from "react-router-native";

import Chat from "./screens/Chat";
import Login from "./screens/Login";

export default class App extends Component {
  render() {
    return (
      <KeyboardAvoidingView behavior="padding" enabled style={styles.root}>
        <Router>
          <Switch>
            <Route exact path="/chat" component={Chat} />
            <Route path="/" component={Login} />
          </Switch>
        </Router>
      </KeyboardAvoidingView>
    );
  }
}

const styles = StyleSheet.create({
  root: {
    flex: 1,
    backgroundColor: "white"
  }
});
Enter fullscreen mode Exit fullscreen mode

创建一个名为 screens 的目录,并在其中创建两个文件 — Chat.jsLogin.js

$ mkdir screens && cd screens && touch Chat.js && touch Login.js
Enter fullscreen mode Exit fullscreen mode

一旦这两个文件到位,我们就需要填充它们!将下面显示的代码放入相应的文件中。

Chat.js

import React, { Component } from "react";
import { Constants, LinearGradient } from "expo";
import {
  ActivityIndicator,
  Platform,
  SafeAreaView,
  StyleSheet,
  Text,
  View
} from "react-native";
import {
  GiftedChat,
  Bubble,
  InputToolbar,
  SystemMessage
} from "react-native-gifted-chat";
import { StreamChat } from "stream-chat";
import { isIphoneX, getBottomSpace } from "react-native-iphone-x-helper";
import axios from "axios";
import md5 from "md5";

const client = new StreamChat("<YOUR_STREAM_APP_ID>");

export default class Chat extends Component {
  constructor(props) {
    super(props);

    this.state = {
      messages: [],
      typingText: null,
      user: null,
      token: null,
      channel: null
    };

    this._isMounted = false;
    this._isAlright = null;
  }

  componentWillMount() {
    this._isMounted = true;

    this.setState({
      messages: require("../data/messages.js")
    });
  }

  async componentDidMount() {
    const { location } = this.props;
    const user = location.state.user;

    try {
      const init = await axios.post("<YOUR_SERVERLESS_INVOCATION_URL>", {
        name: user.name,
        email: user.email
      });

      await client.setUser(init.data.user, init.data.token);

      const channel = client.channel("messaging", md5(user.email), {
        name: "Chat with Chuck Norris",
        members: ["chuck", init.data.user.id]
      });

      await channel.create();
      await channel.watch();

      channel.on(event => this.incoming(event));

      this.setState({
        user: init.data.user,
        token: init.data.token,
        channel
      });
    } catch (error) {
      console.log(error);
    }
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  incoming(evt) {
    if (evt.type === "message.new" && evt.user.id !== this.state.user.id) {
      this.onReceive(evt);
    }
  }

  onSend = async (messages = []) => {
    try {
      await this.state.channel.sendMessage({
        text: messages[0].text
      });

      this.setState(previousState => {
        return {
          messages: GiftedChat.append(previousState.messages, messages),
          typingText: "Chuck Norris is typing..." // mock typing indicator
        };
      });
    } catch (error) {
      console.log(error);
    }
  };

  onReceive = data => {
    this.setState(previousState => {
      return {
        messages: GiftedChat.append(previousState.messages, {
          _id: data.message.id,
          text: data.message.text,
          createdAt: data.message.created_at,
          user: {
            _id: data.message.user.id,
            name: data.message.user.name
          }
        }),
        typingText: null
      };
    });
  };

  renderBubble = props => {
    return (
      <Bubble
        {...props}
        wrapperStyle={{
          left: {
            backgroundColor: "#f0f0f0"
          }
        }}
      />
    );
  };

  renderInputToolbar = props => {
    if (isIphoneX()) {
      return (
        <SafeAreaView>
          <InputToolbar {...props} />
        </SafeAreaView>
      );
    }
    return <InputToolbar {...props} />;
  };

  renderSystemMessage = props => {
    return (
      <SystemMessage
        {...props}
        containerStyle={{
          marginBottom: 15
        }}
        textStyle={{
          fontSize: 14
        }}
      />
    );
  };

  renderFooter = props => {
    if (this.state.typingText) {
      return (
        <View style={styles.footerContainer}>
          <Text style={styles.footerText}>{this.state.typingText}</Text>
        </View>
      );
    }

    return null;
  };

  render() {
    if (!this.state.user) {
      return (
        <View style={styles.loader}>
          <ActivityIndicator />
        </View>
      );
    }

    const { user } = this.state;

    return (
      <>
        <GiftedChat
          messages={this.state.messages}
          onSend={this.onSend}
          user={{
            _id: user.id // sent messages should have same user._id
          }}
          renderBubble={this.renderBubble}
          renderSystemMessage={this.renderSystemMessage}
          renderInputToolbar={this.renderInputToolbar}
          renderFooter={this.renderFooter}
          listViewProps={this._listViewProps}
        />
        <LinearGradient
          pointerEvents="none"
          colors={this._gradient}
          style={styles.header}
        />
      </>
    );
  }

  get _gradient() {
    return [
      "rgba(255, 255, 255, 1)",
      "rgba(255, 255, 255, 1)",
      "rgba(255, 255, 255, 0)"
    ];
  }

  get _listViewProps() {
    return {
      style: styles.listViewStyle,
      contentContainerStyle: styles.contentContainerStyle
    };
  }
}

const styles = StyleSheet.create({
  footerContainer: {
    marginTop: 5,
    marginLeft: 10,
    marginRight: 10,
    marginBottom: 10
  },
  footerText: {
    fontSize: 14,
    color: "#aaa"
  },
  header: {
    height: Constants.statusBarHeight + 64,
    position: "absolute",
    top: 0,
    left: 0,
    right: 0
  },
  listViewStyle: {
    flex: 1,
    marginBottom: isIphoneX() ? getBottomSpace() : 0
  },
  loader: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center"
  },
  contentContainerStyle: {
    paddingTop: 24
  }
});
Enter fullscreen mode Exit fullscreen mode

Login.js

import React, { Component } from "react";
import {
  Image,
  SafeAreaView,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  View
} from "react-native";
import { Link } from "react-router-native";

const hitSlop = { top: 24, right: 24, bottom: 24, left: 24 };

class Login extends Component {
  constructor(props) {
    super(props);

    this.state = {
      email: "",
      name: ""
    };
  }

  _handleChange = name => value => {
    this.setState({
      [name]: value
    });
  };

  _renderLink = props => (
    <TouchableOpacity disabled={!this._canLogin} hitSlop={hitSlop} {...props} />
  );

  render() {
    const { email, name } = this.state;
    return (
      <SafeAreaView style={styles.root}>
        <View style={styles.brand}>
          <Image style={styles.logo} source={require("../images/chuck.png")} />
          <Text style={styles.name}>Chuck Bot</Text>
        </View>
        <TextInput
          style={styles.input}
          placeholder="Name"
          onChangeText={this._handleChange("name")}
          value={name}
        />
        <TextInput
          autoCapitalize="none"
          style={styles.input}
          placeholder="Email"
          onChangeText={this._handleChange("email")}
          value={email}
        />
        <View style={styles.btnWrapper}>
          <Link to={this._to} component={this._renderLink}>
            <Text style={this._labelStyle}>Chat with Chuck</Text>
          </Link>
        </View>
      </SafeAreaView>
    );
  }

  get _to() {
    return {
      pathname: "/chat",
      state: {
        user: this.state
      }
    };
  }

  get _canLogin() {
    const { email, name } = this.state;
    return Boolean(name) && Boolean(email);
  }

  get _labelStyle() {
    return {
      ...styles.btnLabel,
      color: this._canLogin ? "rgb(0, 122, 255)" : "#eeeeee"
    };
  }
}

export default Login;

const styles = StyleSheet.create({
  btnLabel: {
    fontSize: 16,
    color: "rgb(0, 122, 255)"
  },
  btnWrapper: {
    alignItems: "center",
    justifyContent: "center",
    paddingVertical: 32
  },
  brand: {
    alignItems: "center",
    justifyContent: "center",
    marginBottom: 32
  },
  input: {
    flexDirection: "row",
    fontSize: 20,
    fontWeight: "600",
    marginVertical: 8,
    borderRadius: 8,
    borderColor: "#f9f9f9",
    borderWidth: 2,
    padding: 16,
    width: 343
  },
  logo: {
    width: 80,
    height: 112
  },
  name: {
    fontSize: 20,
    fontWeight: "800",
    marginTop: 16
  },
  root: {
    flex: 1,
    paddingHorizontal: 16,
    justifyContent: "center",
    alignItems: "center"
  }
});
Enter fullscreen mode Exit fullscreen mode

在第 22 行,从您的 Stream Dashboard 中输入 Stream App ID。在第 53 行,您需要添加由 AWS API Gateway 提供的 Lambda 端点——不用担心,您目前还没有这个端点,我们将在下一节中介绍。

哇!快到了!👨‍🚀

7. 添加无服务器目录和文件

现在您已经使用 Expo 成功搭建了 React Native 应用程序,请进入目录(例如 react-native-chat-c​​hucky)。

$ cd react-native-chat-chucky
Enter fullscreen mode Exit fullscreen mode

首先,让我们继续创建一个名为 serverless 的新目录,然后进入该目录,以便我们可以安装一些依赖项。

$ mkdir serverless && cd serverless
Enter fullscreen mode Exit fullscreen mode

创建一个新package.json文件。

$ touch package.json
Enter fullscreen mode Exit fullscreen mode

然后,将下面的示例代码片段的内容复制到您的 package.json 文件中。

{
  "name": "stream-react-chat",
  "version": "1.0.0",
  "description": "Facilitates communication between RN and Stream",
  "main": "handler.js",
  "license": "BSD-3-Clause",
  "private": false,
  "scripts": {
    "start": "sls offline",
    "deploy": "sls deploy"
  },
  "dependencies": {
    "@babel/runtime": "^7.3.1",
    "axios": "^0.18.0",
    "babel-runtime": "^6.26.0",
    "stream-chat": "^0.9.0",
    "uuid": "^3.3.2"
  },
  "devDependencies": {
    "babel-loader": "^8.0.5",
    "eslint": "^5.16.0",
    "eslint-plugin-import": "^2.17.2",
    "eslint-plugin-node": "^8.0.1",
    "eslint-plugin-promise": "^4.1.1",
    "prettier": "^1.17.0",
    "serverless-offline": "^4.9.4",
    "serverless-webpack": "^5.2.0",
    "webpack": "^4.30.0",
    "webpack-node-externals": "^1.7.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

添加 package.json 文件后,请确保在 serverless 目录中运行 yarn 以正确安装模块。

完成上述步骤后,您需要将以下文件内容复制/粘贴到无服务器目录中。

.eslintrc.json

{
  "plugins": ["babel"],
  "extends": ["eslint:recommended"],
  "rules": {
    "no-console": 0,
    "no-mixed-spaces-and-tabs": 1,
    "comma-dangle": 0,
    "no-unused-vars": 1,
    "eqeqeq": [2, "smart"],
    "no-useless-concat": 2,
    "default-case": 2,
    "no-self-compare": 2,
    "prefer-const": 2,
    "no-underscore-dangle": [2, { "allowAfterThis": true }],
    "object-shorthand": 1,
    "babel/no-invalid-this": 2,
    "array-callback-return": 2,
    "valid-typeof": 2,
    "arrow-body-style": 2,
    "require-await": 2,
    "react/prop-types": 0,
    "no-var": 2,
    "linebreak-style": [2, "unix"],
    "semi": [1, "always"]
  },
  "env": {
    "es6": true
  },
  "parser": "babel-eslint",
  "parserOptions": {
    "sourceType": "module",
    "ecmaVersion": 2018,
    "ecmaFeatures": {
      "modules": true
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

.prettierrc

{
  "trailingComma": "es5",
  "tabWidth": 4,
  "semi": true,
  "singleQuote": true
}
Enter fullscreen mode Exit fullscreen mode

webpack.config.js

const path = require("path");
const slsw = require("serverless-webpack");
const nodeExternals = require("webpack-node-externals");

module.exports = {
  entry: slsw.lib.entries,
  target: "node",
  devtool: "source-map",
  mode: "production",
  externals: [nodeExternals()],
  optimization: {
    minimize: false
  },
  performance: {
    hints: false
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "babel-loader"
          }
        ]
      }
    ]
  }
};
Enter fullscreen mode Exit fullscreen mode

serverless.yml

service: <YOUR_APP_NAME> # e.g. chuck-norris
app: <YOUR_APP_NAME> # e.g. chuck-norris
tenant: <YOUR_TENANT_NAME> # e.g. tenant

frameworkVersion: ">=1.32.0 <2.0.0"

provider:
  name: aws
  runtime: nodejs8.10
  stage: prod
  region: us-east-1
  memorySize: 3008
  timeout: 30
  environment:
    STREAM_KEY: "<YOUR_STREAM_KEY>"
    STREAM_SECRET: "<YOUR_STREAM_SECRET>"
    STREAM_APP_ID: "<YOUR_STREAM_APP_ID>"
functions:
  init:
    handler: handler.init
    events:
      - http:
          path: /init
          method: post
  reply:
    handler: handler.reply
    events:
      - http:
          path: /reply
          method: post

plugins:
  - serverless-webpack
  - serverless-offline

custom:
  webpack:
    packager: "yarn"
    webpackConfig: "webpack.config.js"
    includeModules:
      forceInclude:
        - "@babel/runtime"
  serverless-offline:
    port: 8000
Enter fullscreen mode Exit fullscreen mode

文件的顶部serverless.yml是定义租户名称和应用名称的地方,这些名称是从 Serverless Dashboard 保存的。在本例中,我使用nickchuck作为租户名称,使用chuck-norris作为应用名称。

干得好!👊

8. 设置AWS

网上有很多关于如何做到这一点的信息。我会提供必要的步骤,但我强烈建议你仔细阅读!

需要注意的一点是,无服务器需要几种不同的 AWS IAM 权限,例如 S3、CloudFormation、API Gateway 和 Lambda。有一些方法可以限定 IAM 权限的范围,使其仅满足必要的要求,具体方法可在此处找到。我发现,虽然这是最安全的方法,但肯定不是最快的。我建议,如果这是个人 AWS 账户,请节省一些时间,并从 IAM 授予您的账户“AdministratorAccess”权限。

设置您的 AWS 账户和 IAM 权限后,使用 aws-cli 在命令行中指定您的凭证(凭证可以在 IAM 下找到)。

$ aws configure
Enter fullscreen mode Exit fullscreen mode

aws-cli 会要求您输入访问密钥 ID 和秘密访问密钥。它还会询问您几个(可选)问题,以便 AWS CLI 能够正确配置您的配置文件。

AWS CLI

首次安装 aws-cli 需要注意什么?在 macOS 上超级简单。只需运行 brew install aws-cli,Homebrew 会帮你搞定剩下的事情!

干得好!🕺

9. 部署无服务器构建

配置好 AWS 并将 AWS 凭证安全地存储在您的计算机上后,就可以将无服务器构建部署到 Lambda 了!导航到 React Native 代码库中的无服务器目录,然后运行以下命令!

如果您尚未安装 Serverless CLI,可以使用 npm install -g serverless 进行安装。

$ sls deploy
Enter fullscreen mode Exit fullscreen mode

构建将开始,您应该开始看到实时日志!

SLS部署

遇到问题了吗?请在文章末尾的评论区留下详细信息,我会尽力帮助您!

轰!💥

10.启用 API 网关开始服务请求

无服务器架构帮你完成了一些繁重的工作,自动化了大部分构建工作。在无服务器架构出现之前,我们只能使用 Lambda,有时确实会有些不稳定和繁琐。使用纯 Lambda 的工作流程时,我们过去需要压缩代码库(包括 Node 模块),然后手动将压缩文件上传到 AWS。真讨厌!😫

要验证您的 Lambda 是否与 API Gateway 一起配置,请登录 AWS 并搜索 API Gateway。

AWS 仪表板 – API 网关

点击“API 网关”,您将被重定向到仪表板,其中包含您的 API 列表。我的帐户下有几个 API,因此显示的内容可能会有所不同。理想情况下,您应该查找名为 的 API prod-<YOUR_APP_NAME>。就我而言,它是prod-react-native-app。点击正确的 API 即可查看您的资源。

AWS 仪表板 – API 网关资源

选择顶级根资源后,单击“操作”下拉菜单并选择“部署 API”。

API 网关 – 部署

系统会显示一个模态框,供您指定“阶段”。如果您在下拉菜单中还没有“阶段”,请创建一个新的阶段,并随意命名。我选择使用“prod”这个名称,因为当我推送到 Lambda 时,它通常已经使用Serverless-Offline进行了测试,并且已准备好投入生产。

API 网关 – 部署

点击“部署”,您的 API 将被部署到 API 网关!现在,只需捕获调用 URL 并保存以备下一步使用!

API 网关 – 阶段编辑器

快到了!🏎

11. 在 Chat.js 中指定 Init 端点

/init保存调用 URL 后,初始化处理程序将通过附加到其末尾来调用。转到Chat.js第 53 行,并从 AWS API 网关插入您的调用 URL。这将处理流聊天所需的服务器端令牌的获取和生成。

有了正确的 URL,代码会/init在用户登录后(也就是你!)向服务器发送一个 POST 请求,请求中包含用户信息。POST 请求会返回一个序列化的对象,其中包含用户信息以及生成的用户 token。

Chat.js

您的终端节点目前未受 API 密钥保护。如果您想启用 API 密钥,可以在 API 网关内逐个路由地进行操作。

答对了!🎲

12.设置 Webhook 回复 URL

与上述步骤类似,该 URL 将是您的调用 URL,/reply并附加到末尾。在 Stream Chat 仪表板中,向下滚动到“聊天事件”部分,并通过滑动开关直至其变为绿色来启用 Webhook。然后将您的 URL 放入输入框中,然后点击“保存”。

流仪表板 – Webhooks

设置好 URL 并激活 Webhook 后,任何通过 UI 发送并发送到 Stream 的聊天事件都将通过 POST 转发到您的 Lambda。消息正文包含许多有用信息,包括聊天标识符 (CID)、发出请求的用户、消息正文等等。

如果你查看 serverless/handler.js 中的回复处理程序,你会注意到,只有当事件来自“chuck”(查克·诺里斯的预设用户)以外的用户时,我们才会返回聊天消息。这是一个相当简单的逻辑,应该不会太令人困惑。

Stream CLI 还提供了设置 Webhook URL 的功能——您可以在此处下载 Stream CLI 。请参阅此处的文档

再一步!🚶

13. 本地聊天火爆!

你已经取得了很大进展。到目前为止,我们已经使用 Expo 和 Gifted Chat 构建了一个自定义的 React Native 聊天 UI,绑定到 Stream,使用无服务器搭建了一个 AWS Lambda,并在 Stream 聊天仪表板上配置了一个 Webhook。哇,真是不少啊。

现在是时候和查克·诺里斯一起享受乐趣并聆听有关他一生的所有激动人心的故事了。

使用命令行启动 iOS 模拟器(从您的项目目录)。

$ expo start — ios
Enter fullscreen mode Exit fullscreen mode

或者

进入 React Native 的根目录并运行命令yarn start。Expo 将为您打开一个新的调试器窗口。接下来,请按照以下步骤操作。

  • 打开 Xcode
  • 导航到 Xcode 菜单栏,直到它下拉
  • 找到“打开开发者工具”,然后点击“模拟器”

iOS 模拟器将会启动并显示在窗口右上角。接下来,将焦点放在 Expo 打开的调试器窗口上。在左下角,点击“在 iOS 模拟器上运行”。

Expo 调试器

应用程序应该在 iOS 模拟器中加载,并且您应该看到登录屏幕!

iOS – 登录

输入您的姓名和电子邮件地址,然后点击底部的“与 Chuck 聊天”按钮。应用将向服务器发出请求,并检索您的应用在 Stream 上提供的有效用户令牌。从现在开始,一切尽在掌握!

iOS – 聊天

构建步骤卡住了?请在下方评论区留言,我很乐意帮你解决!

如果您希望进一步定制应用程序或将其转换为 APK(Android)或 IPA(iOS),我建议您查看以下链接:

好了!Stream Chat提供了从零开始构建聊天产品所需的后端基础架构,您只需完成前端即可!查看这些基于 React 构建的 Stream Chat 教程。如果您有兴趣使用其他语言/框架进行构建,Stream 提供了多个SDK以及一个iOS (Swift) SDK,适合那些喜欢在 iOS 上进行原生开发的用户。

鏂囩珷鏉ユ簮锛�https://dev.to/nickparsons/react-native-chat-with-chuck-norris-3h7m
PREV
教程:使用 Mux 和 Stream Chat 进行实时流式传输
NEXT
30分钟内构建功能齐全的消息平台