• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

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

在本教程中,我将向您展示我们如何使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 来查看它的运行情况。目前,它会抛出 no route found错误,因为我们还没有创建任何路由!

随意使用我的提交来验证您的更改。

添加饲料模型

在这一步中,我们会将Feed模型添加到 Phoenix 应用程序中。Feeds 模型由标题和 描述组成。

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 正在向我们发送一个空白响应,因为我们的提要表中没有数据。

您可以查看我的提交 以供参考。

添加订阅频道

在这一步中,我们将把我们的 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 应用程序加入我们的 feed 通道的 join方法,一个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

上面定义了这三种方法。在 广播创建方法中,我们 app/FeedsPage/HAS_NEW_FEEDS将使用它作为 redux 状态容器的常量,它负责让前端应用程序知道数据库中有新的提要。我们将在构建前端应用程序时讨论这个问题。

最后,只要在create方法中插入新数据 ,我们只需通过 feed_controller.ex文件调用 broadcast_change方法。我们的 create方法看起来像:

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-boilerplategit 历史记录,安装依赖项,并初始化一个新存储库。

现在,让我们删除样板提供的示例应用程序,并用开始编写我们的应用程序所需的最少样板代码替换它:

npm run clean

我们现在可以使用http://localhost:3000/npm run start启动我们的应用程序并看到它运行 。

你可以参考我的提交。

添加必要的容器

在这一步中,我们将向我们的应用程序添加两个新容器 FeedsPage和 AddFeedPage。FeedsPage 容器将显示一个提要列表,而 AddFeedPage容器将允许我们将新提要添加到我们的数据库中。我们将使用 react-boilerplate 生成器来创建我们的容器。

npm run generate container

上面的命令用于在我们的应用程序中搭建一个容器。键入此命令后,它会询问组件的名称, 在本例中为FeedsPage,我们将 在下一步中使用Component选项。我们不需要标题,但我们需要 动作/常量/选择器/reducer以及 我们的异步流程的sagas。我们不需要 我们的应用程序的i18n 消息。我们还需要遵循类似的方法来创建我们的 AddFeedPage容器。

现在,我们有一堆新文件可以使用。这为我们节省了很多时间。否则,我们将不得不自己创建和配置所有这些文件。此外,生成器会创建非常有用的测试文件,但我们不会在本教程中编写测试。

让我们快速将我们的容器添加到我们的routes.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 。为了保持本教程的篇幅较小,我们不会在此处添加任何样式,但在我们的应用程序结束时,我将进行单独的提交,这将为我们的应用程序添加一些设计。

让我们首先在 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,

  },

});

这将是我们应用程序的初始状态(开始获取数据之前的状态)。由于我们使用的是 ImmutableJS,我们可以使用它的 List 数据结构来存储我们的不可变数据。我们的 reducer 函数将如下所示:

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));

  }

}

在这里,  request只是一个 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文件夹中的应用程序中,然后修改 请求函数:

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和 处理更新数据。 

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);

}

现在,让我们将其余的 sagas 添加到我们的 app/containers/FeedsPage/sagas.js文件中。我们将在这里创建另外两个函数: 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中从后端获取数据的 prop  :

componentDidMount() {

  this.props.fetchFeedsRequest();

}

这将获取所有提要。现在,只要 hasNewFeeds 属性为真,我们就需要再次调用 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

这意味着我们的提要 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 容器将使用四个操作: 更新属性, saveFeedRequest , saveFeed和 保存饲料错误。updateAttributes函数将 更新我们新提要的属性。这意味着每当我们在提要标题和描述的输入框中输入内容时,  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 函数。初始状态将如下所示:

const initialState = fromJS({

  feed: {

    data: {

      title: '',

      description: '',

    },

    ui: {

      saving: false,

      error: null,

    },

  },

});

reducer 函数看起来像:

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。这 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 (子组件文件夹是您必须创建的新文件夹)。它将包含一个表单,其中一个输入框用于提要的标题,一个文本区域用于提要的描述。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>

  );

}

我们将创建另外两个函数: 处理更改和 处理提交。句柄 变化每当我们添加一些文本时,函数负责更新我们的 Redux 状态,  handleSubmit函数调用我们的 API 以将数据保存在我们的 Redux 状态中。

handleChange 函数具有以下内容:

handleChange(e) {

  this.setState({

    [e.target.name]: e.target.value,

  });

}

和 句柄提交函数将包含以下内容:

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 上实时呈现 。我们无需刷新页面即可查看新提要。您也可以  通过并排打开两个选项卡上的http://localhost:3000/feeds来尝试此操作并进行测试!


文章目录
  • 介绍
  • 入门
  • 准备好 API
    • 引导应用程序
    • 添加饲料模型
    • 添加订阅频道
    • 为 API 添加 CORS 支持
  • 实时更新前端数据
    • 引导应用程序
    • 添加必要的容器
    • 构建供稿列表页面
    • 构建表单以添加新提要
  • 敲定