在本教程中,我将向您展示如何使用React和Phoenix的功能来创建一个供稿应用程序,当向数据库中添加新的供稿时,该应用程序将实时更新自身。
介绍
Elixir以其稳定性和实时功能而闻名,Phoenix利用Erlang VM的功能来处理数百万个连接,以及Elixir优美的语法和高效的工具。 这将帮助我们通过API生成数据的实时更新,React应用程序将使用这些API来在用户界面上显示数据。
入门
您应该安装了Elixir,Erlang和Phoenix。 有关更多信息,请参见Phoenix框架的网站 。 除此之外,由于维护良好且有据可查,我们将使用简陋的React样板 。
准备好API
在本部分中,我们将引导我们仅Phoenix API的应用程序,并添加通道以实时更新API。 我们将仅使用提要(它将包含标题和描述),并且一旦在数据库中更改了它的值,API就会将更新后的值发送到我们的前端应用程序。
引导应用
让我们首先引导Phoenix应用程序。
mix phoenix.new realtime_feed_api --no-html --no-brunch
这将在名为realtime_feed_api的文件夹中创建准系统Phoenix应用程序。 --no-html
选项不会创建所有静态文件(在创建仅API应用程序时很有用),而--no-brunch
选项不会包含Phoenix的静态捆绑器Brunch 。 请确保在提示时安装依赖项。
让我们进入该文件夹并创建数据库。
cd realtime_feed_api
我们将必须从config / dev.exs文件中删除用户名和密码字段,因为我们将在创建数据库时不使用任何用户名或密码。 这只是为了使本文简单。 对于您的应用程序,请确保首先使用用户名和密码创建数据库。
mix ecto.create
上面的命令将创建我们的数据库。 现在,我们可以运行Phoenix服务器并测试此时一切是否正常。
mix phoenix.server
上面的命令将启动我们的Phoenix服务器,我们可以转到http:// localhost:4000看到它正在运行。 目前,由于我们尚未创建任何路线,因此它将引发“ 找不到路线”错误!
随时通过我的提交来验证您的更改。
添加提要模型
在此步骤中,我们会将Feed模型添加到Phoenix应用程序中。 Feed模型将由标题和描述组成 。
mix phoenix.gen.json Feed feeds title:string description:string
上面的命令将生成我们的Feed模型和控制器。 它还将生成规范(为了简短起见,我们将不在本教程中进行修改)。
您需要在api作用域内的web / router.ex文件中添加/feeds
路由:
resources "/feeds", FeedController, except: [:new, :edit]
我们还需要运行迁移以创建 feeds表在我们的数据库中:
mix ecto.migrate
现在,如果我们转到http:// localhost:4000 / api / feeds ,我们将看到API正在向我们发送空白响应,因为feeds表中没有数据。
您可以检查我的提交以供参考。
添加提要频道
在这一步中,我们将Feed频道添加到Phoenix应用程序中。 通道提供了一种与来自Phoenix.PubSub
层的客户端进行双向通信的方式,以实现软实时功能。
mix phoenix.gen.channel feed
上面的命令将在web / channels文件夹内生成feed_channel.ex文件。 通过此文件,我们的React应用程序将使用套接字交换数据库中的更新数据。
我们需要将新频道添加到我们的web / channels / user_socket.ex文件中:
channel "feeds", RealtimeFeedApi.FeedChannel
由于我们不对此应用程序进行任何身份验证,因此可以修改我们的web / channels / feed_channel.ex文件。 我们需要一个 我们的React应用程序的join方法加入我们的feed频道 handle_out方法用于通过套接字连接推送有效负载,而一个broadcast_create方法将在数据库中创建新的提要时广播有效负载。
def join("feeds", payload, socket) do
{:ok, "Joined feeds", socket}
end
def handle_out(event, payload, socket) do
push socket, event, payload
{:noreply, socket}
end
def broadcast_create(feed) do
payload = %{
"id" => to_string(feed.id),
"title" => feed.title,
"description" => feed.description
}
RealtimeFeedApi.Endpoint.broadcast("feeds", "app/FeedsPage/HAS_NEW_FEEDS", payload)
end
上面定义了三种方法。 在broadcast_create方法中,我们将使用app/FeedsPage/HAS_NEW_FEEDS
因为我们会将其用作Redux状态容器的常量,该容器将使前端应用程序知道数据库中有新的feed。 我们将在构建前端应用程序时进行讨论。
最后,只要在create方法中插入了新数据,我们就只需要通过feed_controller.ex文件调用broadcast_change方法。 我们的创建方法将类似于:
def create(conn, %{"feed" => feed_params}) do
changeset = Feed.changeset(%Feed{}, feed_params)
case Repo.insert(changeset) do
{:ok, feed} ->
RealtimeFeedApi.FeedChannel.broadcast_create(feed)
conn
|> put_status(:created)
|> put_resp_header("location", feed_path(conn, :show, feed))
|> render("show.json", feed: feed)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(RealtimeFeedApi.ChangesetView, "error.json", changeset: changeset)
end
end
创建 方法负责在数据库中插入新数据。 您可以检查我的提交以供参考。
为API添加CORS支持
我们需要实现此支持,因为在本例中,该API是从http:// localhost:4000提供的,但是我们的前端应用程序将在http:// localhost:3000上运行 。 添加CORS支持很容易。 我们只需要将cors_plug添加到我们的mix.exs文件中:
defp deps do
[
...
{:cors_plug, "~> 1.3"}
]
end
现在,我们使用Control-C停止Phoenix服务器,并使用以下命令获取依赖项:
mix deps.get
我们需要将以下行添加到我们的lib / realtime_feed_api / endpoint.ex文件中:
plug CORSPlug
您可以检查我的提交 。 我们完成了所有后端更改。 现在让我们专注于前端应用程序。
实时更新前端数据
如前所述,我们将使用react-boilerplate开始我们的前端应用程序。 我们将使用Redux saga ,它将监听我们分派的操作,并在此基础上,用户界面将更新数据。
由于所有内容都已在样板中进行配置,因此我们无需对其进行配置。 但是,我们将使用样板中提供的命令来支持我们的应用程序。 让我们首先克隆存储库:
git clone
https://github.com/react-boilerplate/react-boilerplate.git
realtime_feed_ui
引导应用
现在,我们需要进入 realtime_feed_ui文件夹并安装依赖项。
cd realtime_feed_ui && npm run setup
这将使用此样板初始化一个新项目,删除react-boilerplate
git历史记录,安装依赖项,并初始化一个新的存储库。
现在,让我们删除样板提供的示例应用程序,并用开始编写我们的应用程序所需的最少样板代码替换它:
npm run clean
现在,我们可以使用npm run start
启动我们的应用程序,并在http:// localhost:3000 /上查看它的运行情况。
你可以参考我的承诺 。
添加必要的容器
在此步骤中,我们将向我们的应用添加两个新容器FeedsPage和AddFeedPage 。 FeedsPage容器将显示供稿列表,而AddFeedPage容器将允许我们向数据库中添加新的供稿。 我们将使用react-boilerplate生成器来创建我们的容器。
npm run generate container
上面的命令用于在我们的应用程序中搭建容器。 键入此命令后,它将询问组件的名称,在这种情况下,该名称将为FeedsPage ,我们将在下一步中使用Component选项。 我们不需要标题,但我们需要 动作/常量/选择器/归约器 以及我们异步流的sagas 。 我们不需要 适用于我们应用程序的i18n消息 。 我们还需要遵循类似的方法来创建AddFeedPage容器。
现在,我们有很多新文件可以使用。 这为我们节省了很多时间。 否则,我们将不得不自行创建和配置所有这些文件。 另外,生成器会创建测试文件,这非常有用,但是在本教程中我们不会编写测试。
让我们快速将容器添加到我们的route.js文件中:
{
path: '/feeds',
name: 'feedsPage',
getComponent(nextState, cb) {
const importModules = Promise.all([
import('containers/FeedsPage/reducer'),
import('containers/FeedsPage/sagas'),
import('containers/FeedsPage'),
]);
const renderRoute = loadModule(cb);
importModules.then(([reducer, sagas, component]) => {
injectReducer('feedsPage', reducer.default);
injectSagas(sagas.default);
renderRoute(component);
});
importModules.catch(errorLoading);
},
}
这会将我们的FeedsPage容器添加到我们的/feeds
路由中。 我们可以通过访问http:// localhost:3000 / feeds进行验证 。 目前,由于我们的容器中没有任何内容,因此它将完全空白,但是浏览器的控制台中不会出现任何错误。
我们将对AddFeedPage容器执行相同的操作 。
您可以参考我所做的所有更改。
构建提要列表页面
在此步骤中,我们将构建FeedsPage ,其中将列出我们的所有Feed。 为了使本教程小巧,在此我们将不添加任何样式,但是在我们的应用程序结尾,我将进行单独的提交,这将为我们的应用程序添加一些设计。
让我们开始在我们的app / containers / FeedsPage / constants.js文件中添加常量 :
export const FETCH_FEEDS_REQUEST = 'app/FeedsPage/FETCH_FEEDS_REQUEST';
export const FETCH_FEEDS_SUCCESS = 'app/FeedsPage/FETCH_FEEDS_SUCCESS';
export const FETCH_FEEDS_ERROR = 'app/FeedsPage/FETCH_FEEDS_ERROR';
export const HAS_NEW_FEEDS = 'app/FeedsPage/HAS_NEW_FEEDS';
我们将需要以下四个常量:
- FETCH_FEEDS_REQUEST常量将用于初始化获取请求。
- 当获取请求成功时,将使用FETCH_FEEDS_SUCCESS常量。
- 当获取请求失败时,将使用FETCH_FEEDS_ERROR常量。
- 当我们的数据库中有一个新的提要时,将使用HAS_NEW_FEEDS常量。
让我们在app / containers / FeedsPage / actions.js文件中添加操作:
export const fetchFeedsRequest = () => ({
type: FETCH_FEEDS_REQUEST,
});
export const fetchFeeds = (feeds) => ({
type: FETCH_FEEDS_SUCCESS,
feeds,
});
export const fetchFeedsError = (error) => ({
type: FETCH_FEEDS_ERROR,
error,
});
export const checkForNewFeeds = () => ({
type: HAS_NEW_FEEDS,
});
所有这些动作都是不言自明的。 现在,我们将构建应用程序的initialState结构,并在app / containers / FeedsPage / reducer.js文件中添加一个reducer:
const initialState = fromJS({
feeds: {
data: List(),
ui: {
loading: false,
error: false,
},
},
metadata: {
hasNewFeeds: false,
},
});
这将是我们应用程序的initialState(开始获取数据之前的状态)。 由于我们使用的是ImmutableJS ,因此可以使用其List数据结构来存储我们的不可变数据。 我们的减速器功能如下所示:
function addFeedPageReducer(state = initialState, action) {
switch (action.type) {
case FETCH_FEEDS_REQUEST:
return state
.setIn(['feeds', 'ui', 'loading'], true)
.setIn(['feeds', 'ui', 'error'], false);
case FETCH_FEEDS_SUCCESS:
return state
.setIn(['feeds', 'data'], action.feeds.data)
.setIn(['feeds', 'ui', 'loading'], false)
.setIn(['metadata', 'hasNewFeeds'], false);
case FETCH_FEEDS_ERROR:
return state
.setIn(['feeds', 'ui', 'error'], action.error)
.setIn(['feeds', 'ui', 'loading'], false);
case HAS_NEW_FEEDS:
return state
.setIn(['metadata', 'hasNewFeeds'], true);
default:
return state;
}
}
基本上,我们在这里所做的就是基于我们的行为常数来改变我们的状态。 我们可以很容易地以这种方式显示加载程序和错误消息。 当我们在用户界面中使用它时,它将更加清晰。
是时候使用reselect创建我们的选择器了,它是Redux的选择器库。 使用重新选择,我们可以很容易地提取复杂的状态值。 让我们将以下选择器添加到我们的app / containers / FeedsPage / selectors.js文件中:
const feeds = () => createSelector(
selectFeedsPageDomain(),
(titleState) => titleState.get('feeds').get('data')
);
const error = () => createSelector(
selectFeedsPageDomain(),
(errorState) => errorState.get('feeds').get('ui').get('error')
);
const isLoading = () => createSelector(
selectFeedsPageDomain(),
(loadingState) => loadingState.get('feeds').get('ui').get('loading')
);
const hasNewFeeds = () => createSelector(
selectFeedsPageDomain(),
(newFeedsState) => newFeedsState.get('metadata').get('hasNewFeeds')
);
如您在此处看到的,我们正在使用initialState的结构从状态中提取数据。 您只需要记住reselect的语法 。
是时候使用redux-saga添加我们的sagas了。 在这里,基本思想是我们需要创建一个函数来获取数据,并创建另一个函数来监视初始函数,以便每当分派任何特定操作时,我们都需要调用初始函数。 让我们在app / containers / FeedsPage / sagas.js文件中添加从后端应用程序获取供稿列表的函数:
function* getFeeds() {
const requestURL = 'http://localhost:4000/api/feeds';
try {
// Call our request helper (see 'utils/Request')
const feeds = yield call(request, requestURL);
yield put(fetchFeeds(feeds));
} catch (err) {
yield put(fetchFeedsError(err));
}
}
在这里, 请求只是一个util函数,它对后端进行API调用。 整个文件可从react-boilerplate获得 。 完成sagas.js文件后,我们将对其稍作更改。
我们还需要再创建一个函数来监视getFeeds函数:
export function* watchGetFeeds() {
const watcher = yield takeLatest(FETCH_FEEDS_REQUEST, getFeeds);
// Suspend execution until location changes
yield take(LOCATION_CHANGE);
yield cancel(watcher);
}
如我们在这里看到的,当我们分派包含FETCH_FEEDS_REQUEST常量的动作时,将调用getFeeds函数。
现在,让我们将react-boilerplate的request.js文件复制到app / utils文件夹内的应用程序中 ,然后修改request函数:
export default function request(url, method = 'GET', body) {
return fetch(url, {
headers: {
'Content-Type': 'application/json',
},
method,
body: JSON.stringify(body),
})
.then(checkStatus)
.then(parseJSON);
}
我刚刚添加了一些默认值,这些默认值将有助于我们稍后减少代码,因为我们不需要每次都传递方法和标头。 现在,我们需要在app / utils文件夹中创建另一个util文件。 我们将这个文件称为socketSagas.js 。 它将包含四个功能: connectToSocket , joinChannel , createSocketChannel和 handleUpdatedData 。
connectToSocket函数将负责连接到我们的后端API套接字。 我们将使用phoenix npm软件包。 因此,我们将必须安装它:
npm install phoenix --save
这将安装phoenix npm软件包并将其保存到我们的package.json文件中。 我们的connectToSocket函数将类似于以下内容:
export function* connectToSocket() {
const socket = new Socket('ws:localhost:4000/socket');
socket.connect();
return socket;
}
接下来,我们定义 joinChannel函数,它将负责从我们的后端加入特定的频道。 joinChannel函数将具有以下内容:
export function* joinChannel(socket, channelName) {
const channel = socket.channel(channelName, {});
channel.join()
.receive('ok', (resp) => {
console.log('Joined successfully', resp);
})
.receive('error', (resp) => {
console.log('Unable to join', resp);
});
return channel;
}
如果加入成功,我们将记录“加入成功”仅用于测试。 如果在加入阶段出现错误,我们还将记录该错误仅用于调试目的。
的 createSocketChannel将负责从给定的套接字创建事件通道。
export const createSocketChannel = (channel, constant, fn) =>
// `eventChannel` takes a subscriber function
// the subscriber function takes an `emit` argument to put messages onto the channel
eventChannel((emit) => {
const newDataHandler = (event) => {
console.log(event);
emit(fn(event));
};
channel.on(constant, newDataHandler);
const unsubscribe = () => {
channel.off(constant, newDataHandler);
};
return unsubscribe;
});
如果我们要退订特定频道,此功能也将很有用。
handleUpdatedData只会调用作为参数传递给它的操作。
export function* handleUpdatedData(action) {
yield put(action);
}
现在,让我们在app / containers / FeedsPage / sagas.js文件中添加其余的sagas。 我们将在这里再创建两个函数: connectWithFeedsSocketForNewFeeds和 watchConnectWithFeedsSocketForNewFeeds 。
的 connectWithFeedsSocketForNewFeeds函数将负责与后端套接字连接并检查新的提要。 如果有任何新的供稿,它将调用 utils / socketSagas.js文件中的createSocketChannel函数,它将为该给定的套接字创建一个事件通道。 我们的connectWithFeedsSocketForNewFeeds函数将包含以下内容:
function* connectWithFeedsSocketForNewFeeds() {
const socket = yield call(connectToSocket);
const channel = yield call(joinChannel, socket, 'feeds');
const socketChannel = yield call(createSocketChannel, channel, HAS_NEW_FEEDS, checkForNewFeeds);
while (true) {
const action = yield take(socketChannel);
yield fork(handleUpdatedData, action);
}
}
和 watchConnectWithFeedsSocketForNewFeeds将具有以下内容:
export function* watchConnectWithFeedsSocketForNewFeeds() {
const watcher = yield takeLatest(FETCH_FEEDS_SUCCESS, connectWithFeedsSocketForNewFeeds);
// Suspend execution until location changes
yield take(LOCATION_CHANGE);
yield cancel(watcher);
}
现在,我们将所有内容与我们的app / containers / FeedsPage / index.js文件绑定在一起 。 该文件将包含我们所有的用户界面元素。 让我们从调用道具开始,该道具将从我们的componentDidMount后端获取数据:
componentDidMount() {
this.props.fetchFeedsRequest();
}
这将获取所有提要。 现在,每当hasNewFeeds属性为true时,我们就需要再次调用fetchFeedsRequest 属性 (关于应用程序的结构,您可以参考reducer的initialState):
componentWillReceiveProps(nextProps) {
if (nextProps.hasNewFeeds) {
this.props.fetchFeedsRequest();
}
}
之后,我们只渲染 提供我们的渲染功能。 我们将创建一个具有以下内容的feedsNode函数:
feedsNode() {
return [...this.props.feeds].reverse().map((feed) => { // eslint-disable-line arrow-body-style
return (
<div
className="col-12"
key={feed.id}
>
<div
className="card"
style={{ margin: '15px 0' }}
>
<div className="card-block">
<h3 className="card-title">{ feed.title }</h3>
<p className="card-text">{ feed.description }</p>
</div>
</div>
</div>
);
});
}
然后,我们可以在render方法中调用此方法:
render() {
if (this.props.loading) {
return (
<div>Loading...</div>
);
}
return (
<div className="row">
{this.feedsNode()}
</div>
);
}
如果现在转到http:// localhost:3000 / feeds ,我们将在控制台中看到以下记录:
Joined successfully Joined feeds
这意味着我们的feeds API工作正常,并且我们已成功将前端与后端应用程序连接。 现在,我们只需要创建一个表单即可输入新的供稿。
构建表单以添加新的提要
在此步骤中,我们将创建一个表单,通过该表单可以向数据库中添加新的提要。
首先,将常量添加到我们的app / containers / AddFeedPage / constants.js文件中:
export const UPDATE_ATTRIBUTES = 'app/AddFeedPage/UPDATE_ATTRIBUTES';
export const SAVE_FEED_REQUEST = 'app/AddFeedPage/SAVE_FEED_REQUEST';
export const SAVE_FEED_SUCCESS = 'app/AddFeedPage/SAVE_FEED_SUCCESS';
export const SAVE_FEED_ERROR = 'app/AddFeedPage/SAVE_FEED_ERROR';
当我们在输入框中添加一些文本时,将使用UPDATE_ATTRIBUTES常量。 所有其他常量将用于将提要标题和描述保存到我们的数据库中。
AddFeedPage容器将使用四个操作: updateAttributes , saveFeedRequest , saveFeed和 saveFeedError 。 updateAttributes函数将更新新Feed的属性。 这意味着每当我们在提要标题和描述的输入框中键入内容时, updateAttributes函数都会更新Redux状态。 这四个动作如下所示:
export const updateAttributes = (attributes) => ({
type: UPDATE_ATTRIBUTES,
attributes,
});
export const saveFeedRequest = () => ({
type: SAVE_FEED_REQUEST,
});
export const saveFeed = () => ({
type: SAVE_FEED_SUCCESS,
});
export const saveFeedError = (error) => ({
type: SAVE_FEED_ERROR,
error,
});
接下来,让我们在app / containers / AddFeedPage / reducer.js文件中添加我们的reducer函数。 initialState将如下所示:
const initialState = fromJS({
feed: {
data: {
title: '',
description: '',
},
ui: {
saving: false,
error: null,
},
},
});
减速器功能如下所示:
function addFeedPageReducer(state = initialState, action) {
switch (action.type) {
case UPDATE_ATTRIBUTES:
return state
.setIn(['feed', 'data', 'title'], action.attributes.title)
.setIn(['feed', 'data', 'description'], action.attributes.description);
case SAVE_FEED_REQUEST:
return state
.setIn(['feed', 'ui', 'saving'], true)
.setIn(['feed', 'ui', 'error'], false);
case SAVE_FEED_SUCCESS:
return state
.setIn(['feed', 'data', 'title'], '')
.setIn(['feed', 'data', 'description'], '')
.setIn(['feed', 'ui', 'saving'], false);
case SAVE_FEED_ERROR:
return state
.setIn(['feed', 'ui', 'error'], action.error)
.setIn(['feed', 'ui', 'saving'], false);
default:
return state;
}
}
接下来,我们将配置app / containers / AddFeedPage / selectors.js文件。 它将具有四个选择器: 标题 说明 , 错误 ,以及 储蓄 。 顾名思义,这些选择器将从Redux状态中提取这些状态,并将其作为道具在我们的容器中使用。
这四个功能如下所示:
const title = () => createSelector(
selectAddFeedPageDomain(),
(titleState) => titleState.get('feed').get('data').get('title')
);
const description = () => createSelector(
selectAddFeedPageDomain(),
(titleState) => titleState.get('feed').get('data').get('description')
);
const error = () => createSelector(
selectAddFeedPageDomain(),
(errorState) => errorState.get('feed').get('ui').get('error')
);
const saving = () => createSelector(
selectAddFeedPageDomain(),
(savingState) => savingState.get('feed').get('ui').get('saving')
);
接下来,让我们为AddFeedPage容器配置我们的sagas。 它具有两个功能: saveFeed和 watchSaveFeed 。 的 saveFeed函数将负责对我们的API进行POST请求,并且具有以下功能:
export function* saveFeed() {
const title = yield select(feedTitle());
const description = yield select(feedDescription());
const requestURL = 'http://localhost:4000/api/feeds';
try {
// Call our request helper (see 'utils/Request')
yield put(saveFeedDispatch());
yield call(request, requestURL, 'POST',
{
feed: {
title,
description,
},
},
);
} catch (err) {
yield put(saveFeedError(err));
}
}
的 watchSaveFeed函数将类似于我们之前的watch函数:
export function* watchSaveFeed() {
const watcher = yield takeLatest(SAVE_FEED_REQUEST, saveFeed);
// Suspend execution until location changes
yield take(LOCATION_CHANGE);
yield cancel(watcher);
}
接下来,我们只需要在容器中呈现表单即可。 为了保持模块化,让我们为表单创建一个子组件。 在我们的app / containers / AddFeedPage / sub-components文件夹中创建一个新文件form.js ( sub-components文件夹是一个新文件夹,您必须创建)。 它将包含一个表单,其中一个输入框用于提要的标题,一个文本区域用于提要的描述。 render方法将包含以下内容:
render() {
return (
<form style={{ margin: '15px 0' }}>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="title"
placeholder="Enter title"
onChange={this.handleChange}
name="title"
value={this.state.title}
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<textarea
className="form-control"
id="description"
placeholder="Enter description"
onChange={this.handleChange}
name="description"
value={this.state.description}
/>
</div>
<button
type="button"
className="btn btn-primary"
onClick={this.handleSubmit}
disabled={this.props.saving || !this.state.title || !this.state.description }
>
{this.props.saving ? 'Saving...' : 'Save'}
</button>
</form>
);
}
我们将再创建两个函数: handleChange和 handleSubmit 。 handleChange 每当我们添加一些文本时,函数负责更新我们的Redux状态, handleSubmit函数调用我们的API以将数据保存为我们的Redux状态。
handleChange函数具有以下功能:
handleChange(e) {
this.setState({
[e.target.name]: e.target.value,
});
}
和handleSubmit 函数将包含以下内容:
handleSubmit() {
// doing this will make the component faster
// since it doesn't have to re-render on each state update
this.props.onChange({
title: this.state.title,
description: this.state.description,
});
this.props.onSave();
this.setState({
title: '',
description: '',
});
}
在这里,我们要保存数据,然后清除表单值。
现在,回到我们的app / containers / AddFeedPage / index.js文件,我们将只渲染刚刚创建的表单。
render() {
return (
<div>
<Form
onChange={(val) => this.props.updateAttributes(val)}
onSave={() => this.props.saveFeedRequest()}
saving={this.props.saving}
/>
</div>
);
}
现在,我们所有的编码已完成。 如有任何疑问,请随时检查我的承诺 。
定案
我们已经完成了构建应用程序。 现在,我们可以访问http:// localhost:3000 / feeds / new并添加将在http:// localhost:3000 / feeds上实时呈现的新feed 。 我们无需刷新页面即可查看新的供稿。 您还可以通过并排打开两个选项卡上的http:// localhost:3000 / feeds并进行测试来进行尝试!
结论
这只是一个示例应用程序,展示了将Phoenix与React结合在一起的真正功能。 我们现在在大多数地方都使用实时数据,这也许可以帮助您获得开发类似内容的感觉。 我希望您觉得本教程有用。
翻译自: https://code.tutsplus.com/tutorials/how-to-create-a-realtime-feed-using-phoenix-and-react--cms-29122