简体   繁体   English

使用异步状态设置从类转换为功能组件

[英]Converting from class to functional component with async state setting

I have a simple class-based component that I'm trying to convert to a function-based component, but am running into all kinds of dead ends. 我有一个简单的基于类的组件,我试图将其转换为基于函数的组件,但遇到了各种各样的死胡同。

My component is a straightforward adaptation of the boilerplate gifted-chat package, and uses Watson Assistant as a backend to provide responses. 我的组件是对样板gifted-chat程序包的直接改编,并使用Watson Assistant作为后端来提供响应。 There's nothing complex about the backend part, these are just thin wrappers on Watson Assistants's API: 后端部分没有什么复杂的,它们只是Watson Assistants API上的薄包装:

getSessionID = async (): Promise<string>

gets a session ID for use in communicating with the backend, and 获取用于与后端通信的会话ID,以及

sendReply = async (reply: string, sessionID: string): Promise<string>

returns Assistant's response to the string provided as a reply . 将Assistant的响应返回给作为reply的字符串。 These are not the source of the trouble I'm having (the bodies of both could be replaced with return await "some string" and I'd have the same issues): the class-based version (below) works perfectly. 这些不是我遇到的麻烦的根源(可以将它们的主体替换为return await "some string"而我会遇到相同的问题):基于类的版本(如下)可以正常工作。

But I'm at a loss to figure out how to convert this to a functional form, in particular: 但是我不知该如何将其转换为功能形式,特别是:

  1. I'm struggling to find a suitable replacement for componentWillMount . 我正在努力寻找合适的componentWillMount替代品。 Using useEffect with sessionID as state results in errors: getMessage gets called (even if I await ) before the required sessionID is set. 使用useEffectsessionID作为状态会导致错误:在设置所需的sessionID之前,将调用getMessage (即使我正在await )。

I can avoid this by not making sessionID state (which it arguably shouldn't be) and just making it a global (as in the functional attempt below). 我可以通过不设置sessionID状态(可以说不应设置),而只是将其设置为全局状态(如下面的功能尝试)来避免这种情况。 But even if I do this: 但是即使我这样做:

  1. After each user reply, and receipt of a response, the user reply is removed from the conversation, so that the entire conversation just consists of generated replies. 在每个用户回复并接收到响应之后,将从对话中删除该用户回复,以便整个对话仅由生成的回复组成。

Both of these problems are, I think, linked to the lack of callbacks in the hook-based state setting idiom, but the issue could also lie elsewhere. 我认为,这两个问题都与基于钩子的状态设置惯用法中缺少回调函数有关,但问题还可能出在其他地方。 In any case, I'm at a loss to know what to do. 无论如何,我不知所措。


Chatter.tsx (working class based version) Chatter.tsx (基于工作类的版本)

import React from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"



class Chatter extends React.Component {
    state = {
        messages: [],
        sessionID: null,
    }

    componentWillMount() {
        WatsonAssistant.getSessionID()
            .then((sID) => {    
                this.setState( {
                    sessionID: sID,
                } )    
            } )
            .then(() => this.getMessage(''))
            .catch((error) => {
                console.error(error)
            } )
    }

    onSend = (message = []): void => {
        this.setState((previousState) => ( {
            messages: GiftedChat.append(previousState.messages, message),
        } ), () => {
            this.getMessage(message[0].text.replace(/[\n\r]+/g, ' '))
        } )
    }

    getMessage = async (text: string): Promise<void> => {
        let response = await WatsonAssistant.sendReply(text, this.state.sessionID)
        let message = {
            _id: Math.round(Math.random() * 1000000).toString(),
            text: response,
            createdAt: new Date(),
            user: {
                _id: '2',
                name: 'Watson Assistant',
            },
        }
        this.setState((previousState) => ( {
            messages: GiftedChat.append(previousState.messages, message),
        } ))
    }

    render() {
        return (
            <GiftedChat
                messages={ this.state.messages }
                onSend={ messages => this.onSend(messages) }
                user={ {
                    _id: 1,
                } }
            />
        )
    }
}

export default Chatter

Chatter.tsx (failed function based attempt) Chatter.tsx (基于函数的失败尝试)

import React, {FC, ReactElement, useEffect, useState } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"

let sessionID: string

const Chatter: FC = (): ReactElement => {

    const [ messages, setMessages ] = useState([])

    useEffect(() => {
        const fetchData = async () => {
            WatsonAssistant.getSessionID()
                .then(sID => sessionID = sID )
                .then(() => getMessage(''))
                .catch((error) => {
                    console.error(error)
                } )
        }
        fetchData()
    }, [ ])

    const onSend = async (message = []) => {
        const newMessages = await GiftedChat.append(messages, message)
        await setMessages(newMessages)
        await getMessage(message[0].text.replace(/[\n\r]+/g, ' '))
    }

    const getMessage = async (text: string): Promise<void> => {
        let response = await WatsonAssistant.sendReply(text, sessionID)
        let message = {
            _id: Math.round(Math.random() * 1000000).toString(),
            text: response,
            createdAt: new Date(),
            user: {
                _id: '2',
                name: 'Watson Assistant',
            },
        }
        await setMessages(await GiftedChat.append(messages, message))
    }


    return (
        <GiftedChat
            messages={ messages }
            onSend={ messages => onSend(messages) }
            user={ {
                _id: 1,
            } }
        />
    )

}

export default Chatter

Chatter.tsx (working function based version) Chatter.tsx (基于工作功能的版本)

import React, {FC, ReactElement, useEffect, useState } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"

let sessionID: string

const Chatter: FC = (): ReactElement => {

    const [ messages, setMessages ] = useState([])

    useEffect(() => {
        const fetchData = async () => {
            WatsonAssistant.getSessionID()
                .then(sID => sessionID = sID )
                .then(() => getMessage('', []))
                .catch((error) => {
                    console.error(error)
                } )
        }
        fetchData()
    }, [ ])

    const onSend = async (message = []) => {
        const newMessages = await GiftedChat.append(messages, message)
        await setMessages(newMessages)   // Apparently, no waiting goes on here
        await getMessage(message[0].text.replace(/[\n\r]+/g, ' '), newMessages)
    }

    const getMessage = async (text: string, currentMessages): Promise<void> => {
        let response = await WatsonAssistant.sendReply(text, sessionID)
        let message = {
            _id: Math.round(Math.random() * 1000000).toString(),
            text: response,
            createdAt: new Date(),
            user: {
                _id: '2',
                name: 'Watson Assistant',
            },
        }
        await setMessages(await GiftedChat.append(currentMessages, message))
    }


    return (
        <GiftedChat
            messages={ messages }
            onSend={ messages => onSend(messages) }
            user={ {
                _id: 1,
            } }
        />
    )

}

export default Chatter

Ok, since I don't have your full code I'm not sure this will just work as-is (in particular without the types from your dependencies I'm not sure if/how much the compiler will complain), but should give you something you can adapt easily enough. 好的,因为我没有完整的代码,所以我不确定这是否可以按原样工作(特别是如果没有依赖项中的类型,我不确定编译器是否/会抱怨多少),但是应该给出您可以轻松地进行调整。

const reducer = ({ messages }, action) => {
  switch (action.type) {
    case 'add message':
      return {
        messages: GiftedChat.append(messages, action.message),
      };

    case 'add sent message':
      return {
        // Not sure if .append is variadic, may need to adapt
        messages: GiftedChat.append(messages, action.message, action.message[0].text.replace(/[\n\r]+/g, ' ')),
      }
  }
};

const Chatter = () => {
  const [sessionID, setSessionID] = useState(null);
  const [messages, dispatch] = useReducer(reducer, []);

  const getMessage = async (text: string, sessionID: number, type: string = 'add message'): Promise<void> => {
    const response = await WatsonAssistant.sendReply(text, sessionID);
    const message = {
      _id: Math.round(Math.random() * 1000000).toString(),
      text: response,
      createdAt: new Date(),
      user: {
        _id: '2',
        name: 'Watson Assistant',
      },
    };

    dispatch({
      type,
      message,
    });
  };

  useEffect(() => {
    const fetchData = async () => {
      WatsonAssistant.getSessionID()
        .then(sID => (setSessionID(sID), sID))
        .then(sID => getMessage('', sID))
        .catch((error) => {
          console.error(error)
        });
    }
    fetchData();
  }, []);

  return (
    <GiftedChat
      messages={messages}
      onSend={messages => getMessage(messages, sessionID, 'add sent message')}
      user={{
        _id: 1,
      }}
    />
  );
};

Main difference is useReducer . 主要区别是useReducer As far as I can tell in the original code you had two actions: append this message or append this message and then a copy of it with the text regex replaced. 据我在原始代码中所知道的,您有两个操作:追加此消息或追加此消息,然后用文本正则表达式替换它的副本。 I've used different dispatches to the reducer to handle the cases rather than the callback to setState . 我使用了不同的调度程序来处理化例,而不是使用setState的回调。 I've modified your attempt at useEffect , here I'm (ab)using the comma operator to return the ID returned from the service so that it can be fed directly to getMessage as a parameter rather than relying on state that hasn't been updated yet. 我已经修改了您对useEffect的尝试,在这里,我正在(ab)使用逗号运算符返回从服务返回的ID,以便可以将其直接作为参数传递给getMessage ,而不是依赖于尚未使用的状态更新了。

I'm still kinda skeptical in general about the hooks API, but assuming this works I actually think it simplifies the code here. 我对钩子API总体上还是持怀疑态度的,但是假设此方法有效,我实际上认为它可以简化此处的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 将带有状态的基于类的组件转换为带有钩子的功能组件 - converting class based component with state to functional component with hooks React js app - 使用 class 组件中 function 的返回值设置功能组件的 state (错误:未定义) - React js app - setting functional component's state with a returned value from function in class component (error: undefined) 反应:从功能组件内部在容器中设置状态 - React: setting state in container from within functional component 如何使用useContext从功能组件更新class组件的state - How to update state of class component from functional component using useContext 将类 setState 转换为功能组件 - converting class setState to functional component 将功能组件转换为 class 组件(ReactJS)时出现问题 - Problem in converting functional component to class component (ReactJS) 将类组件转换为功能组件的问题 - Problem converting class component to functional component 将功能组件转换为基于类的组件 - Converting functional component to class based component 将 React Class 组件转换为 React 函数式组件 - Converting React Class Component to a React Functional Component 将 Modal 类组件转换为带有钩子的功能组件 - Converting a Modal class component to a functional component with hooks
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM