通过异步状态设置从类转换为功能组件

时间:2019-08-25 17:15:38

标签: javascript typescript react-native react-hooks asynccallback

我有一个简单的基于类的组件,试图将其转换为基于函数的组件,但遇到了各种各样的死胡同。

我的组件是样板gifted-chat包的直接改编,并使用Watson Assistant作为后端来提供响应。后端部分没有什么复杂的,它们只是Watson Assistants API上的薄包装:

getSessionID = async (): Promise<string>

获取用于与后端通信的会话ID,并且

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

返回助手对作为reply提供的字符串的响应。这些不是我遇到麻烦的根源(两者的主体都可以用return await "some string"替换,并且我会有相同的问题):基于类的版本(如下)可以正常工作。

但是我不知该如何将其转换为功能形式,

  1. 我正在努力寻找componentWillMount的合适替代品。将useEffectsessionID用作状态会导致错误:在设置所需的sessionID之前,会调用getMessage(即使我await也是如此)。

我可以通过不设置sessionID状态(它可能不应该这样),而只是使其成为全局状态(如下面的功能尝试)来避免这种情况。但是即使我这样做:

  1. 在每个用户回复并收到响应后,该用户回复将从会话中删除,因此整个会话仅由生成的回复组成。

我认为,这两个问题都与基于挂钩的状态设置习惯用法中缺少回调有关,但是问题还可能出在其他地方。无论如何,我不知所措。


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 (基于失败的功能尝试)

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 (基于工作功能的版本)

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

1 个答案:

答案 0 :(得分:3)

好吧,因为我没有完整的代码,所以我不确定这是否可以按原样工作(尤其是没有依赖项的类型时,我不确定编译器是否会抱怨多少),但应该给您一些可以轻松适应的东西。

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,
      }}
    />
  );
};

主要区别是useReducer。据我在原始代码中所知道的,您有两个操作:追加此消息或追加此消息,然后用文本正则表达式替换它的副本。我使用了不同的调度程序来处理化例,而不是处理setState的回调。我已经修改了您在useEffect的尝试,在这里,我(ab)使用逗号运算符返回从服务返回的ID,以便可以将其作为参数而不是参数直接馈送到getMessage依靠尚未更新的状态。

对于钩子API,我总体上还是持怀疑态度的,但是假设这样做有效,我实际上认为它可以简化此处的代码。