此实现主要基于有关此主题的其他 SO 帖子(请参阅相关参考资料)。代码假设 Rails 4.x 带有资产管道,HAML 用于视图,Bootstrap 用于样式,并使用 react-rails gem。
(更新:稍长的讨论可以在my blog找到)。
布局
这是app/views/layouts/application.html.haml 文件:
!!!
%html
%head
%title
Demo
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= csrf_meta_tags
%body
%div.container
%div#flash_messages
= yield
请注意,div 的 ID 为 #flash_messages。这个容器 div 的位置决定了 flash 消息出现的位置。
控制器
下面的私有例程只是将本机数组数组使用转换为标头中可解析的 JSON。
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
after_filter :flash_to_http_header
private
def flash_to_http_header
return unless request.xhr?
return if flash.empty?
response.headers['X-FlashMessages'] = flash.to_hash.to_json
flash.discard # don't want the flash to appear when you reload page
end
end
Javascript (ReactJS)
下面的 javascript 由一个 React 类、一个从响应头中处理 flash 消息的全局函数和一个用于完成 AJAX 调用的 JQuery 处理程序组成。它被放入一个单独的 JX 文件(我使用了app/assets/javascripts/react/flash_messages.js.jsx)以将相关的 JS 功能保持在一起。我将在下面详细讨论。
/** @jsx React.DOM */
var FlashMessages = React.createClass({
getInitialState: function() {
return {messages: this.props.messages};
},
messages: function (messageArray) {
this.replaceState({messages: messageArray});
},
render: function() {
return (
<div className='flash_messages_component'>
{this.state.messages.map(function(message, index) {
_level = message[0];
_text = message[1];
return (
<div key={index} className={this._flash_class(_level)}>
{_text}
</div>
);
}.bind(this))}
</div>
)
},
_flash_class: function(level) {
var _result = 'alert alert-error';
if (level === 'notice') {
_result = 'alert alert-info';
} else if (level === 'success') {
_result = 'alert alert-success';
} else if (level === 'error') {
_result = 'alert alert-error';
} else if (level === 'alert') {
_result = 'alert alert-error';
}
return _result;
}
});
function handleFlashMessagesHeader(node, xhr) {
var _message_array = new Array();
var _raw_messages = xhr.getResponseHeader("X-FlashMessages")
if (_raw_messages) {
var _json_messages = JSON.parse(_raw_messages);
count = 0
for (var key in _json_messages) {
_message_array[count] = new Array();
_message_array[count][0] = key;
_message_array[count][1] = _json_messages[key];
count += 1;
}
}
node.messages(_message_array);
}
$(document).ready(function() {
var dummy = new Array();
var flashDiv = React.render(<FlashMessages messages={dummy} />, $('#flash_messages')[0]);
$(document).ajaxComplete(function(event, xhr, settings) {
handleFlashMessagesHeader(flashDiv, xhr);
});
});
FlashMessages React 类做了一些事情。首先,它将道具移动到状态。通常这将是一种反模式,但这样做可以使非 React 代码在需要时触发更改。 messages 函数就是那个触发器,应该被外部 JS 代码调用。 render 的主要处理假设 Rails flash-native array-of-2-element-arrays 数据结构将处理保持在最低限度,并允许直接从视图中使用组件,而不仅仅是 AJAX 调用。最后,本地 _flash_class 方法支持 Bootstrap 样式(当然可以根据需要调整为其他样式)。
handleFlashMessagesHeader 是一个全局函数,用于将 JSON 转换回由 Rails 控制器过滤方法完成的数组数组。请注意,它从 Rails 应用程序视图/布局中获取带有 id 标记的 DOM 元素。
最后一部分是在页面加载时运行,因此取决于 JQuery 是否准备就绪。 React.render(正式名称为React.renderComponent)保存到一个全局变量中,以允许直接调用FlashMessages 对象的message 方法(使用的dummy 数组只是在第一次调用时播种一个空的flashes 数组——这可能也可以通过空测试在 React 类中处理)。每当触发ajaxComplete 时,就会调用handleFlashMessageHeader 函数并传递React 对象进行更新。
非 AJAX
因为 React 类假定原生数组数组,所以视图可以简单地称为
= react-component 'FlashMessages', flash, :div
而不是显示它,但是由于不支持动态响应,因此它当然用处不大。但是,无论以哪种方式调用它的能力都为某些用例提供了更大的灵活性。