React 項(xiàng)目實(shí)踐——?jiǎng)?chuàng)建一個(gè)聊天機(jī)器人

我的理念很簡(jiǎn)單:如果你想要在某個(gè)方面精通,那么你需要持續(xù)實(shí)踐,實(shí)踐一次不會(huì)有多少效果,你必須反復(fù)實(shí)踐。
我在編程這件事上就是這么做的。
在這個(gè)過(guò)程中,我特別感受到:創(chuàng)建一些有意思的好東西是非常有趣的。你可以向朋友展示自己引以為傲的作品,坐下來(lái)敲代碼實(shí)現(xiàn)它的過(guò)程會(huì)讓你感覺(jué)歡喜。
比如說(shuō)我創(chuàng)建了一個(gè)聊天機(jī)器人(代碼)。
我們一起來(lái)創(chuàng)建吧!如果你想自己獨(dú)立完成這個(gè)挑戰(zhàn),可以直接參考這份文檔(其實(shí)是一個(gè)聊天機(jī)器人成品)。
好啦,我們開(kāi)始吧。我就假設(shè)你已經(jīng)安裝了 Node,可以運(yùn)行 npx 命令。如果沒(méi)有的話,訪問(wèn)這里。
初始設(shè)置
//?運(yùn)行以下代碼
npx?create-react-app?chatbot
cd?chatbot
yarn?add?react-chatbot-kit
yarn?start
安裝 npm 包,訪問(wèn) localhost:3000。
然后打開(kāi) App.js,修改如下:
import?Chatbot?from?'react-chatbot-kit'
function?App()?{
??return?(
????<div?className="App">
??????<header?className="App-header">
????????<Chatbot?/>
??????header>
????div>
??);
}
現(xiàn)在你的界面是這樣的:

聊天機(jī)器人要正常工作,需要接收三個(gè) props。首先,需要具有 initialMessages 屬性,包含聊天信息對(duì)象。然后,需要有 MessageParser 用于解析,還有 ActionProvider 基于解析結(jié)果執(zhí)行我們需要它執(zhí)行的動(dòng)作。
稍后我們進(jìn)一步講解這個(gè)?,F(xiàn)在,在這里獲取代碼。
把 MessageParser代碼放到MessageParser.js文件把 ActionProvider代碼放到ActionProvider.js文件把 config 代碼放到 config.js文件
現(xiàn)在返回到 App.js ?文件,添加以下代碼:
import?React?from?'react';
import?Chatbot?from?'react-chatbot-kit'
import?'./App.css';
import?ActionProvider?from?'./ActionProvider';
import?MessageParser?from?'./MessageParser';
import?config?from?'./config';
function?App()?{
??return?(
????<div?className="App">
??????<header?className="App-header">
????????<Chatbot?config={config}?actionProvider={ActionProvider}??????messageParser={MessageParser}?/>
??????header>
????div>
??);
}
localhost:3000 現(xiàn)在應(yīng)該是這樣顯示:

很棒!我們已經(jīng)初始化了聊天機(jī)器人,可以輸入和提交一些信息了。試試看!
理解聊天機(jī)器人是怎么工作的
我們暫停一下,看看 MessageParser 和 ActionProvider 是怎么配合讓聊天機(jī)器人執(zhí)行動(dòng)作的。
機(jī)器人初始化的時(shí)候,內(nèi)部 state 的 messages 屬性獲取 initialMessages 屬性值,將信息渲染到屏幕。
接著,當(dāng)我們?cè)诹奶炜蜉斎胄畔ⅲc(diǎn)擊?submit 提交時(shí),MessageParser(作為 props 傳遞給機(jī)器人調(diào)用 parse 方法。
我們進(jìn)一步看看 MessageParser 的代碼:
class?MessageParser?{
??constructor(actionProvider)?{
????this.actionProvider?=?actionProvider;
??}
??parse(message)?{
????...?parse?logic
??}
}
代碼中包含 actionProvider,這跟我們傳遞給聊天機(jī)器人的 props ActionProvider ?是一樣的。我們通過(guò)這個(gè)代碼解析信息,并告訴機(jī)器人執(zhí)行什么動(dòng)作。
比如,我們創(chuàng)建一個(gè)簡(jiǎn)單的響應(yīng)。首先,將 MessageParser ?改為:
class?MessageParser?{
??constructor(actionProvider)?{
????this.actionProvider?=?actionProvider;
??}
??parse(message)?{
????const?lowerCaseMessage?=?message.toLowerCase()
????if?(lowerCaseMessage.includes("hello"))?{
??????this.actionProvider.greet()
????}
??}
}
export?default?MessageParser
MessageParser ?接收到用戶的信息,檢查是否包含 “hello”。如果包含,則調(diào)用 actionProvider ?的 greet ?方法。
不過(guò)現(xiàn)在還行不通,因?yàn)槲覀冞€沒(méi)有執(zhí)行 greet ?方法。稍后再處理這個(gè)。先處理 ActionProvider.js ?文件如下:
class?ActionProvider?{
??constructor(createChatBotMessage,?setStateFunc)?{
????this.createChatBotMessage?=?createChatBotMessage;
????this.setState?=?setStateFunc;
??}
??greet()?{
????const?greetingMessage?=?this.createChatBotMessage("Hi,?friend.")
????this.updateChatbotState(greetingMessage)
??}
??updateChatbotState(message)?{
//?NOTE:?This?function?is?set?in?the?constructor,?and?is?passed?in??????//?from?the?top?level?Chatbot?component.?The?setState?function?here?????//?actually?manipulates?the?top?level?state?of?the?Chatbot,?so?it's?????//?important?that?we?make?sure?that?we?preserve?the?previous?state.
???this.setState(prevState?=>?({
?????...prevState,?messages:?[...prevState.messages,?message]
????}))
??}
}
export?default?ActionProvider
現(xiàn)在我們?cè)诹奶炜蜉斎?“hello”,可以看到:

很好!解析信息和響應(yīng)都沒(méi)有問(wèn)題了。我們?cè)僮鲆恍└鼜?fù)雜的東西,讓機(jī)器人提供我們想要的編程語(yǔ)言學(xué)習(xí)資料。
創(chuàng)建一個(gè)學(xué)習(xí)機(jī)器人
首先,回到 config.js 文件,稍作修改:
import?{?createChatBotMessage?}?from?'react-chatbot-kit';
const?config?=?{?
??botName:?"LearningBot",
??initialMessages:?[createChatBotMessage("Hi,?I'm?here?to?help.?What?do?you?want?to?learn?")],
??customStyles:?{
????botMessageBox:?{
??????backgroundColor:?"#376B7E",
????},
????chatButton:?{
??????backgroundColor:?"#376B7E",
????},
??},
}
export?default?config
我們?cè)黾恿艘恍傩?,修改了初始信息,特別是給機(jī)器人取了個(gè)名字,更改了 messagebox ?和 chatbutton ?組件的顏色。

好玩的部分來(lái)了。
我們不僅可以渲染信息和回復(fù)給用戶,還可以根據(jù)想要的信息來(lái)自定義 React 組件。比如,我們創(chuàng)建一個(gè)選擇組件,引導(dǎo)用戶做不同選擇。
首先,定義學(xué)習(xí)選項(xiàng)組件:
//?in?src/components/LearningOptions/LearningOptions.jsx
import?React?from?"react";
import?"./LearningOptions.css";
const?LearningOptions?=?(props)?=>?{
??const?options?=?[
????{?text:?"Javascript",?handler:?()?=>?{},?id:?1?},
????{?text:?"Data?visualization",?handler:?()?=>?{},?id:?2?},
????{?text:?"APIs",?handler:?()?=>?{},?id:?3?},
????{?text:?"Security",?handler:?()?=>?{},?id:?4?},
????{?text:?"Interview?prep",?handler:?()?=>?{},?id:?5?},
??];
??const?optionsMarkup?=?options.map((option)?=>?(
????<button
??????className="learning-option-button"
??????key={option.id}
??????onClick={option.handler}
????>
??????{option.text}
????button>
??));
??return?<div?className="learning-options-container">{optionsMarkup}div>;
};
export?default?LearningOptions;
//?in?src/components/LearningOptions/LearningOptions.css
.learning-options-container?{
??display:?flex;
??align-items:?flex-start;
??flex-wrap:?wrap;
}
.learning-option-button?{
??padding:?0.5rem;
??border-radius:?25px;
??background:?transparent;
??border:?1px?solid?green;
??margin:?3px;
}
然后在機(jī)器人代碼中使用組件。對(duì) config.js 文件作如下操作:
import?React?from?"react";
import?{?createChatBotMessage?}?from?"react-chatbot-kit";
import?LearningOptions?from?"./components/LearningOptions/LearningOptions";
const?config?=?{
initialMessages:?[
????createChatBotMessage("Hi,?I'm?here?to?help.?What?do?you?want?to???learn?",?{
??????widget:?"learningOptions",
????}),
??],
?...,
?widgets:?[
?????{
??????widgetName:?"learningOptions",
?????widgetFunc:?(props)?=>?<LearningOptions?{...props}?/>,
?????},
?],
}
理解 Widgets
小結(jié)一下:
我們創(chuàng)建了 LearningOptions?組件在 config 的 widgets?下使用組件給 createChatbotMessage?函數(shù)一個(gè)選項(xiàng)對(duì)象,說(shuō)明需要渲染哪個(gè) widget?和信息
結(jié)果:

很棒!但是,為什么要在 config 中以 widget?的形式引入組件呢?
通過(guò)將其設(shè)置為函數(shù),我們可以在調(diào)用時(shí)以聊天機(jī)器人的重要屬性來(lái)裝飾 widget。
我們定義的 widget?會(huì)接收到機(jī)器人的各種屬性:
actionProvider?- 將actionProvider?添加到?widget,以執(zhí)行動(dòng)作setState?- 將setState?添加到 widget,以操作 statescrollIntoView?- 滑動(dòng)到聊天框底部,在需要調(diào)整視圖時(shí)使用這個(gè)函數(shù)props?- 給 widget?定義的 props 將通過(guò)configProps傳遞給?widgetstate?- 通過(guò)mapStateToProps?屬性將自定義 state 傳遞給?widget
回頭想想,我們給 LearningOptions 組件設(shè)置了一些選項(xiàng):
const?options?=?[
????{?text:?"Javascript",?handler:?()?=>?{},?id:?1?},
????{?text:?"Data?visualization",?handler:?()?=>?{},?id:?2?},
????{?text:?"APIs",?handler:?()?=>?{},?id:?3?},
????{?text:?"Security",?handler:?()?=>?{},?id:?4?},
????{?text:?"Interview?prep",?handler:?()?=>?{},?id:?5?},
??];
暫時(shí)這些選項(xiàng)有一個(gè)空的 handler,我們想調(diào)用 actionProvider ?替換 handler。
那么,我們想在執(zhí)行這些函數(shù)的時(shí)候發(fā)生什么呢?理想狀況下,機(jī)器人已經(jīng)具有一些回復(fù)信息以及一個(gè) widget?顯示每個(gè)主題對(duì)應(yīng)的資源列表鏈接。我們看看怎么實(shí)現(xiàn)。
首先,創(chuàng)建一個(gè)鏈接列表組件:
//?in?src/components/LinkList/LinkList.jsx
import?React?from?"react";
import?"./LinkList.css";
const?LinkList?=?(props)?=>?{
??const?linkMarkup?=?props.options.map((link)?=>?(
????<li?key={link.id}?className="link-list-item">
??????<a
????????href={link.url}
????????target="_blank"
????????rel="noopener?noreferrer"
????????className="link-list-item-url"
??????>
????????{link.text}
??????a>
????li>
??));
??return?<ul?className="link-list">{linkMarkup}ul>;
};
export?default?LinkList;
//?in?src/components/LinkList/LinkList.css
.link-list?{
??padding:?0;
}
.link-list-item?{
??text-align:?left;
??font-size:?0.9rem;
}
.link-list-item-url?{
??text-decoration:?none;
??margin:?6px;
??display:?block;
??color:?#1d1d1d;
??background-color:?#f1f1f1;
??padding:?8px;
??border-radius:?3px;
??box-shadow:?2px?2px?4px?rgba(150,?149,?149,?0.4);
}
將這個(gè)組件添加到 widget?中:
import?React?from?"react";
import?{?createChatBotMessage?}?from?"react-chatbot-kit";
import?LearningOptions?from?"./components/LearningOptions/LearningOptions";
import?LinkList?from?"./components/LinkList/LinkList";
const?config?=?{
??...
??widgets:?[
????{
??????widgetName:?"learningOptions",
??????widgetFunc:?(props)?=>? ,
????},
????{
??????widgetName:?"javascriptLinks",
??????widgetFunc:?(props)?=>? ,
????},
??],
};
export?default?config;
如果我們想動(dòng)態(tài)給這個(gè)組件傳遞參數(shù),以便對(duì)其他選項(xiàng)復(fù)用,那就需要給 widget?添加另一個(gè)屬性:
import?React?from?"react";
import?{?createChatBotMessage?}?from?"react-chatbot-kit";
import?LearningOptions?from?"./components/LearningOptions/LearningOptions";
import?LinkList?from?"./components/LinkList/LinkList";
const?config?=?{
??...,
??widgets:?[
????...,
????{
??????widgetName:?"javascriptLinks",
??????widgetFunc:?(props)?=>?<LinkList?{...props}?/>,
??????props:?{
????????options:?[
??????????{
????????????text:?"Introduction?to?JS",
????????????url:
??????????????"https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/basic-javascript/",
????????????id:?1,
??????????},
??????????{
????????????text:?"Mozilla?JS?Guide",
????????????url:
??????????????"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide",
????????????id:?2,
??????????},
??????????{
????????????text:?"Frontend?Masters",
????????????url:?"https://frontendmasters.com",
????????????id:?3,
??????????},
????????],
??????},
????},
??],
};
export?default?config;
現(xiàn)在,這些 props 會(huì)作為參數(shù)傳遞給 LinkList ?組件。
我們?cè)僮鰞杉虑椤?/p>
給 actionProvider?添加一個(gè)方法
class?ActionProvider?{
??constructor(createChatBotMessage,?setStateFunc)?{
????this.createChatBotMessage?=?createChatBotMessage;
????this.setState?=?setStateFunc;
??}
??handleJavascriptList?=?()?=>?{
????const?message?=?this.createChatBotMessage(
??????"Fantastic,?I've?got?the?following?resources?for?you?on?Javascript:",
??????{
????????widget:?"javascriptLinks",
??????}
????);
????this.updateChatbotState(message);
??};
??updateChatbotState(message)?{
????//?NOTICE:?This?function?is?set?in?the?constructor,?and?is?passed?in?from?the?top?level?Chatbot?component.?The?setState?function?here?actually?manipulates?the?top?level?state?of?the?Chatbot,?so?it's?important?that?we?make?sure?that?we?preserve?the?previous?state.
????this.setState((prevState)?=>?({
??????...prevState,
??????messages:?[...prevState.messages,?message],
????}));
??}
}
export?default?ActionProvider;
把這個(gè)方法作為 LearningOptions組件 handler
import?React?from?"react";
import?"./LearningOptions.css";
const?LearningOptions?=?(props)?=>?{
??const?options?=?[
????{
??????text:?"Javascript",
??????handler:?props.actionProvider.handleJavascriptList,
??????id:?1,
????},
????{?text:?"Data?visualization",?handler:?()?=>?{},?id:?2?},
????{?text:?"APIs",?handler:?()?=>?{},?id:?3?},
????{?text:?"Security",?handler:?()?=>?{},?id:?4?},
????{?text:?"Interview?prep",?handler:?()?=>?{},?id:?5?},
??];
??const?optionsMarkup?=?options.map((option)?=>?(
????<button
??????className="learning-option-button"
??????key={option.id}
??????onClick={option.handler}
????>
??????{option.text}
????button>
??));
??return?<div?className="learning-options-container">{optionsMarkup}div>;
};
export?default?LearningOptions;
好啦,信息量比較大?,F(xiàn)在如果我們點(diǎn)擊聊天機(jī)器人的 JavaScript 選項(xiàng),會(huì)出現(xiàn):

完美!再進(jìn)一步,如果用戶輸入信息,機(jī)器人也應(yīng)該響應(yīng)。所以我們需要給 MessageParser ?創(chuàng)建新規(guī)則。
更新 MessageParser.js 文件:
class?MessageParser?{
??constructor(actionProvider)?{
????this.actionProvider?=?actionProvider;
??}
??parse(message)?{
????const?lowerCaseMessage?=?message.toLowerCase();
????if?(lowerCaseMessage.includes("hello"))?{
??????this.actionProvider.greet();
????}
????if?(lowerCaseMessage.includes("javascript"))?{
??????this.actionProvider.handleJavascriptList();
????}
??}
}
export?default?MessageParser;
在輸入框鍵入 “javaScript”,機(jī)器人會(huì)回復(fù)同樣的清單。完成啦!
歡迎在 GitHub 訪問(wèn)代碼和文檔。
結(jié)語(yǔ)
創(chuàng)建項(xiàng)目很有趣,也是一個(gè)幫助你拓展技能的很棒的方式。你完全可以動(dòng)動(dòng)腦筋,在這個(gè)項(xiàng)目基礎(chǔ)上再開(kāi)發(fā)別的,比如一個(gè)機(jī)器人通過(guò)一些簡(jiǎn)單的問(wèn)題找到網(wǎng)店里最適合的產(chǎn)品,或者是一個(gè)幫公司回復(fù)顧客常見(jiàn)問(wèn)題的機(jī)器人。
你可以盡量實(shí)踐你的新想法。也歡迎你 pull request,幫我完善這個(gè)項(xiàng)目。
我覺(jué)得持續(xù)創(chuàng)建項(xiàng)目真的是開(kāi)發(fā)者提升自己的唯一方式,建議你現(xiàn)在就動(dòng)起來(lái)!
原文鏈接:https://www.freecodecamp.org/news/how-to-build-a-chatbot-with-react/
?? 愛(ài)心三連擊
掃碼關(guān)注公眾號(hào),訂閱更多精彩內(nèi)容。

