前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React Native最佳实践指北

React Native最佳实践指北

原创
作者头像
brzhang
发布2024-01-28 17:21:23
3052
发布2024-01-28 17:21:23
举报
文章被收录于专栏:玩转全栈玩转全栈

对于这个题目,我是很抗拒的,想了怎么写之后,大概有一个思路,准备使用React Natvie做一个与AI 大模型对话的App,为什么是React Native,因为我对Flutter 太过于熟悉了,以至于我觉得使用 flutter来写一个实在没有什么挑战,而我又对ReactNative基本没有怎么使用过,不来点挑战点的,似乎不能体现出我装逼的潜质,也恰好算作最佳实践指北吧。

废话不多说,直接上手开干,我们要做的一个App是和ChatGPT这样的大模型对话,不仅可以进行文本对话,还应该可以让他给我们生成图片,而且为了通用,我们不仅需要与ChatGPT对还,还要求可以和Gemini对话,甚至其他的大模型。可能说起来有点复杂,其实我们只需要对接一套api,多亏了这个库:https://github.com/songquanpeng/one-api,他可以将其他大模型的API统一为ChatGPT方式来访问。

如图所示,你只需要管理渠道即可:

见下图所示,我因为有Gemini 的API,所以我给添加进来了,注意这里是需要一点点黑科技的,one-api 最好部署在某些不可描述的“万恶”的区域,这样以便他可以顺利和一些很激动人心的大模型进行对话,当然你说我就想使用 混元大模型,那就直接在腾讯云上买一台服务器使用 Docker 部署就好了。

当然,以上都不是重点,但是么有以上,我们客户端的代码将会比较麻烦,会需要去适配多个模型,但是本质上,不都是一样吗,这也就应了那句话,你以为的岁月静好,不过是有人在替你负重前行罢了,所以,如果你认为这个项目帮助你了,你可以表示一下对吧。

开始整客户端

首先我们分析一下功能

应用功能:

  • 与 ChatGPT 对话,可能返回是文本,可能是图片
  • 对话可以传递附件📎,Gemini Pro Vision 模型实际上可以支持识别你上传的图片的,免费老够用了。
  • 在本地保存对话记录,和管理对话,当然就是简单的增删改查了。
  • 个人设置中心,包括配置 OpenAI 的 API 密钥、模型参数等。
  • 主题设置功能,最基本的是dark/light模式的切换了。

技术栈选择

当然,我们选择React Native,用于跨平台移动应用开发,这样一套代码可以搞定android和ios,后端one-api直接按照文档,使用docker 进行安装即可,没有什么难度。本地数据存储,我们使用 async-storage ,网络请求框架,我们这次晚点有意思的,使用 anstack.com/query

UI的选择

为什么UI的选择单独拿出来呢,因为颜值即正义,对吗,所以,我选择 reactnativeelements

他提供的demo可以直接看下,另外,因为他配置了 expo 的模板方式给我们初始化一个项目,那是相当的滋润啊。

代码语言:javascript
复制
npx create-expo-app --template @rneui/template

上述方式,直接给你生成一个项目,如下:这是我生成的项目:

而且是一个typescript版本的,也是省了很多配置的事情。

ok,我们动手写第一个页面,第一个页面普遍都是一个对话框页面,首先之后大概张这个样子:

这里当然就给得比较简单了,对话没有区分谁是谁说的,甚至markdown展示,以及复制/删除/暂停对话的能力我们统统没有实现,这目前还不是主要的,但是我们做了插入附件的功能,其代码如下:

代码语言:javascript
复制
import React, { useState } from "react";
import { View, FlatList } from "react-native";
import { ListItem, makeStyles } from "@rneui/themed";
import InputPanel from "../components/InputPanel"; // 确保正确导入 InputPanel 组件

const ChatScreen = () => {
  const styles = useStyles();
  const [messages, setMessages] = useState([
    // 测试数据,实际开发中应从后端获取
    { id: "1", text: "Hello there!" },
    { id: "11", text: "give me more message" },
  ]);

  type ItemType = (typeof messages)[0];
  const renderItem = ({ item }: { item: ItemType }) => (
    // Code inside the function

    <ListItem bottomDivider>
      <ListItem.Content>
        <ListItem.Title>{item.text}</ListItem.Title>
      </ListItem.Content>
    </ListItem>
  );

  return (
    <View style={styles.container}>
      <FlatList
        data={messages}
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        style={styles.list}
      />
      <InputPanel />
    </View>
  );
};

const useStyles = makeStyles((theme) => ({
  container: {
    flex: 1,
    backgroundColor: theme.colors.background,
  },
  list: {
    flex: 1,
  },
}));

export default ChatScreen;

其中,InputPanel的代码如下:

代码语言:javascript
复制
import React, { useState } from "react";
import { View } from "react-native";
import { Input, Button, Icon, makeStyles, Text } from "@rneui/themed";
import Attachment from "./Attachment";

const InputPanel = () => {
  const [text, setText] = useState("");

  type AttachmentType = {
    uri: string;
    type: string;
  };

  // 构造几个 Attachment 数据
  const [attachments, setAttachments] = useState<AttachmentType[]>([
    {
      uri: "<https://picsum.photos/200/400>",
      type: "image",
    },
    {
      uri: "<https://picsum.photos/200/500>",
      type: "file",
    },
  ]);

  const styles = useStyles();

  const handleSend = () => {
    // 发送文本逻辑
    console.log("Sending Text: ", text);
    setText("");
  };

  const handleAttach = () => {
    // 附件添加逻辑
    console.log("Attach file");
  };

  return (
    <View style={styles.container}>
      <View style={styles.attachments}>
        {attachments.map((item) => (
          <Attachment
            key={item.uri}
            uri={item.uri}
            type={item.type}
            onRemove={(uri) =>
              setAttachments(attachments.filter((item) => item.uri !== uri))
            }
          />
        ))}
      </View>
      <Input
        placeholder="Type a message..."
        value={text}
        multiline
        onChangeText={setText}
        inputContainerStyle={{
          borderBottomWidth: 0,
        }}
        errorStyle={{
          display: "none",
        }}
        leftIcon={
          <Icon name="attachment" type="material" onPress={handleAttach} />
        }
        rightIcon={
          <Icon
            name="send"
            type="material"
            iconStyle={{ alignSelf: "baseline" }}
            onPress={handleSend}
          />
        }
      />
    </View>
  );
};

const useStyles = makeStyles((theme) => ({
  container: {
    flexDirection: "column",
    alignItems: "center",
    backgroundColor: theme.colors.background,
    borderWidth: 1,
    borderColor: theme.colors.greyOutline,
    borderRadius: 5,
    margin: theme.spacing.md,
  },
  attachments: {
    flexDirection: "row",
    width: "100%",
    justifyContent: "flex-start",
    flexWrap: "wrap",
  },
}));

export default InputPanel;

以上就是本次对于react native 实现一个与ChatGPT这种大模型对话应用的UI部分,接下来就是逻辑部分了。

逻辑部分

思考一按,我恩要在对话框中问一个问题,然后请求模型得到响应,我们可能需要写一个模型请求的封装:

代码语言:javascript
复制
import useSettingsStore from "../store/settingsStore";

export const fetchOpenAiCompletion = async (message: string) => {
  const baseURL = useSettingsStore.getState().baseURL;
  const apiKey = useSettingsStore.getState().apiKey;
  console.log("fetchOpenAiCompletion", message);
  console.log("baseURL", baseURL, "apiKey", apiKey);
  const response = await fetch(`${baseURL}/v1/chat/completions`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      model: "gpt-3.5-turbo",
      messages: [{ role: "user", content: message }],
      temperature: 0.7,
    }),
  });

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  return await response.json();
};

这里,我们请求模型是需要配置一下 apikey 和 baseUrl 的,因此我们最好搞一个设置页面来配置一下这些参数,然后配合全局状态缓存,来存储这些设置。

代码语言:javascript
复制
import AsyncStorage from "@react-native-async-storage/async-storage";
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

const useSettingsStore = create(persist(
    (set) => ({
        apiKey: '',
        baseURL: '',
        themeMode: '',
        setApiKey: (apiKey) => set({ apiKey }),
        setBaseURL: (baseURL) => set({ baseURL }),
        setThemeMode: (themeMode) => set({ themeMode }),
    }),
    {
    name: 'settings-storage', // unique name
       storage: createJSONStorage(() => AsyncStorage),
    }
));

export default useSettingsStore;

ok,一切实际上准备的七七八八了,当然还有会话列表的功能,我们可以这么处理:

代码语言:javascript
复制
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
import { Session, Message } from "./sessionTypes";
import AsyncStorage from "@react-native-async-storage/async-storage";

interface SessionState {
  sessions: Session[];
  createSession: (session: Session) => void;
  addMessageToSession: (sessionId: string, message: Message) => void;
}

const useSessionStore = create<SessionState, any>(
  persist(
    (set) => ({
      sessions: [],

      createSession: (session: Session) =>
        set((state) => ({ sessions: [...state.sessions, session] })),

      addMessageToSession: (sessionId: string, message: Message) =>
        set((state) => ({
          sessions: state.sessions.map((session) =>
            session.id === sessionId
              ? { ...session, messages: [...session.messages, message] }
              : session
          ),
        })),
    }),
    {
      name: "session-storage",
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

export default useSessionStore;

而且,还实现了缓存。

测试一下,我们的模型是否打通,ok,看起来问题不大。所以,整个 react-native 的初步阶段就算是完结了,当然这个App 还需要大量的打磨,才可以拿出来用,如果有需要,可以私信我加入,一起搞事。

总结

本文探索了一下 react-native 的开发,使用 expo 直接进行开发,这样,我们不需要太多的环境配置就可以上手,注意最新的 expo ,即 50 版本可以直接文件路由的方式,这意味着熟悉 next.js 这套的人可以轻松上手 react-native ,几乎无任何门槛。

在 UI 方面,我选择了 react-native-element ,这个让我们不用担心界面太丑

在全局状态上,我们选择了 zustand,他相对 redux 会简单很多,配合中间件,结合 async storeage,可以非常方便实现一个代缓存的全局状态。

在与服务端数据通讯方面,我们使用 tanstack query ,未我们省下了相当多的麻烦状态维护的麻烦。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开始整客户端
    • 应用功能:
      • 技术栈选择
        • UI的选择
          • 逻辑部分
          • 总结
          相关产品与服务
          消息队列 TDMQ
          消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档