如何使用Phoenix和React创建实时提要

最终产品图片
您将要创造的

在本教程中,我将向您展示如何使用ReactPhoenix的功能来创建一个供稿应用程序,当向数据库中添加新的供稿时,该应用程序将实时更新自身。

介绍

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 /上查看它的运行情况。

你可以参考我的承诺

添加必要的容器

在此步骤中,我们将向我们的应用添加两个新容器FeedsPageAddFeedPageFeedsPage容器将显示供稿列表,而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 。 它将包含四个功能: connectToSocketjoinChannel 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 saveFeedErrorupdateAttributes函数将更新新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.jssub-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 handleSubmithandleChange 每当我们添加一些文本时,函数负责更新我们的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值