说三道四技术文摘-感悟人生的经典句子
说三道四 > 文档快照

详解React Flux架构工作方式

HTML文档下载 WORD文档下载 PDF文档下载
Flux是Facebook内部用来构建React应用的一套架构,该架构的最大特点是其倡导的单向数据流方案。本文将重点讨论Facebook Flux架构的工作方式,以及开发者应该如何在项目中使用它。

【编者按】作者Ken Wheeler在《 Getting To Know Flux, the React.js Architecture》一文中 讨论Facebook的Flux架构的工作方式,以及开发者应该如何在项目中使用它。为了让前端开发者更好地掌握本文内容,景庄对该文章进行了翻译,内容如下:

什么是Flux

Flux是Facebook内部用来构建React应用的一套架构。它本身并不是一个框架或库。它仅仅是一个用于完善React应用开发的一种新的应用程序架构,Flux架构最大的特点是其倡导的单向数据流方案。

Facebook还提供了Dispatcher的开源库。你可以将Dispatcher认为是一种全局pub/sub系统的事件处理器,用于向所注册的回调函数广播payloads。通常情况下,如果你使用Flux架构,你可以借助于Facebook提供的这个Dispatcher实现,并且可以借助NodeJS中的EventEmitter模块来配置事件系统, 从而帮助管理应用程序的状态。

组成结构

Flux主要包括下面四个独立的组件,下面简单地解释下:

  • Actions:帮助向Dispatcher传递数据的辅助方法;
  • Dispatcher:接收action,并且向注册的回调函数广播payloads;
  • Stores:应用程序状态的容器&并且含有注册到Dispatcher的回调函数;
  • Controller Views:React组件,从Store获取状态,并将其逐级向下传递给子组件。

让我们通过一幅图来简单看一下这四个部分的关系:


如何处理外部API

但是,很多场景下不仅仅包括应用程序内部的状态数据,还可能包括来自外部的数据,也就是如果我们需要消费RESTful服务,那么我们该如何将这些外部数据引入到Flux架构的单向数据流中呢?经过实践,我们发现,在Action中引入外部数据流是个不错的实践, 数据然后再被送到Store中。

在继续阅读之前,我强烈建议你阅读下Facebook提供的Flux官方示例程序的 源代码。在你简单阅读了这个例子的源代码后,我们再来详细的探讨Flux中这几个组件的作用:

Dispatcher

我们首先来看看Dispatcher的作用:你可以将Dispatcher看成是Flux架构中整个数据流程的管理者。或者你可以将它看成你整个应用的中央交换机。Dispatcher接收actions作为payloads,并且将payloads分发给所注册的回调函数。

那么它就是一个pub/sub吗?

不完全是。Dispatcher会将payload广播给所有向它注册的回调函数,并且包括了允许你以指定次序调用这些回掉函数的执行逻辑,例如在处理前等待数据更新。在Flux架构中只有一个Dispatcher,它是你整个应用的中央Hub。我们来创建一个简单的Dispatcher:

var Dispatcher = require('flux').Dispatcher;var AppDispatcher = new Dispatcher();AppDispatcher.handleViewAction = function(action) {  this.dispatch({    source: 'VIEW_ACTION',    action: action  });}module.exports = AppDispatcher;

在上面的代码中,我们创建了一个Dispatcher的实例,它包括了一个叫做handleViewAction的方法。创建这个方法的目的是为了让你能够轻松的区分, 是视图层触发的action,还是服务器/API触发的action。

在handleViewAction方法中调用了dispatch方法(该方法来自Dispatcher实现),它会将payloads广播给所有注册到它的回调函数 (注意,这里所说的回调是在store中注册给Dispatcher的)。然后,action会触发Store中相应事件,并最终体现在state的更新上。

可以用下面这张图来表示这个过程:


依赖

Facebook所提供的Dispatcher模块还有一个非常赞的功能,那就是它能够定义依赖,并在Store中向Dispatcher注册回调函数。 因此,如果你的应用的某个部分依赖于其他部分的数据更新,为了能够进行合适的渲染,Dispatcher中的waitFor方法将会非常有用。

为了能够利用这一特性,我们需要在Store中存储注册给Dispatcher的回调函数的返回值,这里,我们命名为dispatcherIndex:

ShoeStore.dispatcherIndex = AppDispatcher.register(function(payload) {});

然后在Store中处理每一个被分发的action时,我们可以使用Dispatcher的waitFor方法来确保我们的ShoeStore已经被更新了,示例代码如下:

case 'BUY_SHOES':  AppDispatcher.waitFor([    ShoeStore.dispatcherIndex  ], function() {    CheckoutStore.purchaseShoes(ShoeStore.getSelectedShoes());  });  break;

Stores

在Flux中,可以用Store来分领域的管理应用程序的状态,例如ShoeStore、AppStore等。从一个更高层角度来看,可以简单地认为应用的每一个部分可以对应于一个Store,Store可以用来管理数据,定义数据检索方法,此外,Store中还包括了注册给Dispatcher的回调函数。

下面我们来看一个简单的Store的例子:

var AppDispatcher = require('../dispatcher/AppDispatcher');var ShoeConstants = require('../constants/ShoeConstants');var EventEmitter = require('events').EventEmitter;var merge = require('react/lib/merge');// Internal object of shoesvar _shoes = {};// Method to load shoes from action datafunction loadShoes(data) {  _shoes = data.shoes;}// Merge our store with Node's Event Emittervar ShoeStore = merge(EventEmitter.prototype, {  // Returns all shoes  getShoes: function() {    return _shoes;  },  emitChange: function() {    this.emit('change');  },  addChangeListener: function(callback) {    this.on('change', callback);  },  removeChangeListener: function(callback) {    this.removeListener('change', callback);  }});// Register dispatcher callbackAppDispatcher.register(function(payload) {  var action = payload.action;  var text;  // Define what to do for certain actions  switch(action.actionType) {    case ShoeConstants.LOAD_SHOES:      // Call internal method based upon dispatched action      loadShoes(action.data);      break;    default:      return true;  }    // If action was acted upon, emit change event  ShoeStore.emitChange();  return true;});module.exports = ShoeStore;

以上代码可能做的最重要的一件事就是:我们使用NodeJS中的EventEmitter模块来扩展我们的Store。这使得我们的Store能够监听和广播事件。这也使得我们的视图/组件能够基于这些事件来更新。 这是因为我们的控制器视图(Controller View)会监听我们的Store, 并利用这一点通过发出事件方式通知控制器视图应用程序的状态发生了改变, 从而进行视图层的重新渲染。

在Store中还有一个值得注意的事,那就是我们使用Dispatcher的regiter方法来注册回调函数。 这意味着,我们创建的Store会监听来自AppDispatcher的广播,这里我们使用switch语句来 区分广播内容所对应的action。如果一个相关的action发生了,那么就触发一个change事件,并且监听 此事件的视图会根据新的状态(state)进行重新渲染。这个过程如下图所示。


在上面的代码中,ShoeStore中还包括一个公共方法getShoes(),它可以在控制器视图中被调用, 用于检索所有在_shoes对象中的数据,并且使用这个数据作为组件的状态数据。因为这是个非常简单 的例子,在实际应用开发中你可以使用更加复杂的数据处理逻辑。

Action创建器和Actions

行为创建器(Action Creators)是一组会在视图中被调用的方法(也可以在其他地方使用), 它们用于向Dispatcher发送actions。也就是说,我们使用Dispatcher分发的payloads,实际上就是actions。

在Facebook提供的例子中,action的类型常数被用于定义这些action所被应用的场景,并且是伴随着action一起被传递的。现在,在所注册的回调函数内部,这些actions可以根据他们的action类型来处理。

我们可以看一个简单的类型常数的定义:

var keyMirror = require('react/lib/keyMirror');module.exports = keyMirror({  LOAD_SHOES: null});

在上面的代码中,我们用了React的keyMirror库来生成我们的类型常数的Key/Value对。如果仅仅是看这个文件,我们大致可以猜到我们的应用会加载鞋子(shoes)。借助于类型常数,可以使得应用 中涉及到的事物变得更加地可组织,可以让开发者能通过这样的高层抽象来组织应用程序。

现在,让我们来大致的看一个对应的行为创建器(action creator)的定义:

var AppDispatcher = require('../dispatcher/AppDispatcher');var ShoeStoreConstants = require('../constants/ShoeStoreConstants');var ShoeStoreActions = {  loadShoes: function(data) {    AppDispatcher.handleAction({      actionType: ShoeStoreConstants.LOAD_SHOES,      data: data    })  }};module.exports = ShoeStoreActions;

在上面的代码中,我们在定义了ShoeStoreActions对象,并在其中定义了loadShoes()方法, 该方法内调用了来自AppDispatcher的handleViewAction方法,并且将相应的行为类型与 我们提供的数据相关联。现在,我们可以在我们的视图代码或API中引入这个action文件。我们可以调用ShoeStoreActions.loadShoes(ourData)方法来向Dispatcher发送payload,随后Dispatcher会将它广播出去。ShoeStore则会监听来自Dispatcher的广播,并且触发相应的事件来加载相应的鞋子。

控制器视图 Controller View

你可以简单的将控制器视图理解为React组件,这些组件会监听change事件,如果事件发生了, 就从Stores中获取应用程序新的状态数据。随后,可以将数据通过props逐级向子组件传递。这个过程大致如下图所示:


让我们来看看代码长啥样:

/** @jsx React.DOM */var React = require('react');var ShoesStore = require('../stores/ShoeStore');// Method to retrieve application state from storefunction getAppState() {  return {    shoes: ShoeStore.getShoes()  };}// Create our component classvar ShoeStoreApp = React.createClass({  // Use getAppState method to set initial state  getInitialState: function() {    return getAppState();  },    // Listen for changes  componentDidMount: function() {    ShoeStore.addChangeListener(this._onChange);  },  // Unbind change listener  componentWillUnmount: function() {    ShoesStore.removeChangeListener(this._onChange);  },  render: function() {    return (      <ShoeStore shoes={this.state.shoes} />    );  },    // Update view state when change event is received  _onChange: function() {    this.setState(getAppState());  }});module.exports = ShoeStoreApp;

在上面的代码中,我们使用addChangeListener来监听change事件,并且在接收到事件后更新应用程序的状态数据。

我们的应用程序状态数据存储在Store中,因此我们可以用Store中的公共方法来获取数据,然后将其设置为应用程序的状态。

完整的数据流程

前面我们依次解读了Flux架构中的每一个独立部分,我们有了对Flux架构中的每一个组件的一个大致的认识, 并且能大致的了解了Flux架构的工作流。在这之前,我们已经通过了几张图片描述了数据在组件之间的流向, 现在让我们将上面的子流程组合起来,以更好的理解Flux的数据流:


小结

在读完这篇文章后,如果你之前并不了解Facebook的Flux架构,那么希望你现在能够说理解了。 但另一方面,如果不在React的实际编程开发中使用这一架构,你是很难真正的体会到Flux架构的 特点的。

当你使用Flux后,你会发现没有Flux的React应用开发就相当于是没有jQuery的DOM遍历操作。也就是说, 没有Flux你可以照常进行React应用开发,但是你在代码组织和数据流程上会缺乏优雅的解决方案。

另一个方面,如果你想使用FLux架构,但你并不想使用React,那么你可以参考 Delorean,它是一个Flux框架,并且可以让你使用Ractive.js或者Flight。另一个值得参考的库是 Fluxxor,它实现了在紧耦合的Flux组件中包含一个中央Flux实例。

最后,再次说明,如果要真正的理解Flux,你就得在实际开发中体验它。

原文链接:Getting To Know Flux, the React.js Architecture

译者简介:景庄,前端工程师,关注Node.js、前端工程化。个人博客: http://wwsun.github.com。


欢迎加入CSDN前端交流群2:465281214,进行前端技术交流。  

也可参加CSDN前端大讲堂(微信公开课),享受高含金量在线公开课,与专家讲师在线切磋交流。

如何加入CSDN前端大讲堂?由于该群目前已超过人数限制,所以您首先不得不 扫描下面二维码,加CSDN编辑陈秋歌为好友,然后请她邀请您加入CSDN前端大讲堂微信群。加好友时,请务必注明“申请加入CSDN前端大讲堂”。


腾讯安全出杀手锏 搭建全国最大Wi-Fi开放平台帮助网友免费蹭网 云与数据安全实践尽在ISC 2014(免费门票) 开源的对决,MapR将Apache Drill引入企业应用 令程序员费解的10个语言特性 走进雅虎北京全球研发中心:五年光阴缔造雅虎全球创新引擎 超强集成游戏编辑器!开源跨平台引擎Wave 求别再侵犯儿童隐私!FTC狠罚Yelp和TinyCo 华为应用市场助力开发者 软硬结合造就强大生态系统 【CTO俱乐部看板研修班北京站现场速递】看板方法:渐进变革的过程 《近匠》不背单词,用“沉浸”征服英语学习 ETpl——强复用、灵活、高性能的JavaScript模板引擎 MDCC 2014大会日程概览发布 最新嘉宾议题揭秘 终于来了,微信企业号正式开启公测! 【问底】王帅:深入PHP内核(一)——弱类型变量原理探究 谷歌Kubernetes专访:未来BigTable开发只是课后习题 首届“最具价值CTO”评选来啦!我们在找你! 【CTO俱乐部走进顺丰总部】活动图文直播进行中 中国第一个云主机评测发布:天翼云性价比第一、青云质量第一 Ruby 2.1.3发布,降低内存消耗、修复众多Bug! OpenHW2014开源硬件总决赛鏖战西安 AMD力推异构计算 《坦克世界:闪电战》:PC转手游,不止免费! 【讲师】道里云毛文波:网络虚拟化与SDN实现Docker连通 性能测试:SequoiaDB vs. MongoDB vs. Cassandra vs. HBase 华云数据推运营型PaaS Plus平台,联合Tmax走出“去IOE”的云化之路 聚焦移动新势力 MDCC 2014免费展位团队名单公布 详解Google Authenticator工作原理 云计算的乐高玩具,Docker从根本上改变应用程序开发 【先锋】eSage缔造异构虚拟化管理技术,做数据中心核心思想执行者 Uplinq2014:亲身体验Vuforia与智能眼镜设备 MDCC 2014 移动开发者大会议程全面揭晓! 顺丰模式:物流行业“互联网化”的科技之路 请教一个服务端和客户端交互的问题? 高手帮忙 请问谁又对第三方控件imgscan.ocx操作的例子,发给我bit_hj@163.com。小弟不胜感激 肯请各位帮忙! 应该是简单问题,求高手帮忙,100分相送,绝不食言,一下午都没弄出来,太灰心了..... linux下,mysql数据库密码怎么找回?(在线等) 新手求救:关于8086和80386的中断类型号上的冲突? sleep函数 网络难题,跨网段传输 恢复数据库 有谁的信誉分比我低阿!!我送给他50分阿!! 谁能提供一个切实可用的“防止刷新页面重复提交表单”的方法 Web安装项目中的managed.msm和stdole.dll是何东东,何用?? java中字符串操作函数在哪?java中分解字串的函数(如asp中的:split)在哪个类中? 为何ACCESS出现“#已删除的”的记录? 欲交Java良师益友,愿伸援助之手请进。 关于复选框的初始问题(100份在线4:00-8:00 怎样在asp中把一个文件变成临时文件?? 求解奇怪问题,非常奇怪,分不够再加! 高分求IPMI开发资料 如何用Jtree将一个xml文件显示出来? 招聘系统分析员(北京) 深圳兼职电脑维护,哪个有兴趣? 求购 产品进销存源代码 VB or c# +SQL SERVER @@@@@@ 不送分而送现金!!!!! 谁能教我配置PHP服务器啊? 帮我看一下, 紧急求救!!!jsp的出错信息! 如何合并两个Image对象为一个Image对象 还有一个问题,帮帮我嘛! 高分请教高手!简单,看了你也是高手 代码编辑器的问题。 计论:大家都用什么工具进行数据库的比较? 我高中的班主任 请大家评评理,特别是JAVA版版主! 这样的SQL语句怎么写啊 form切换!VB.net里面如何在语句里调出别的FORM 如何用ADO连接EXCEL数据? 什么是上溯造型? 如何把console.write 输出很多内容保存到文件?thread.join()如何使用? 我想实现像网络蚂蚁那样在IE上点右键,把这个IE中的信息传送给我主程序,现在遇到两个问题! 控件的布局问题? 实例化算不算继承? 急!!!!关于SQL数据堵塞 请大家推荐一下哪个汇编集成环境比较好用?DOS下用什么?WIN下用什么? 向各位请教两个tomcat的问题:(谢谢) 高手请问 拉格朗日算法VB=>BCB 求ASP的字符串比较函数. 找C++BUILDER高手!! 我在做演示光盘,用camtasia录制的屏幕图象,可是在客户的机器上播放不出来,在线等待,解决问题马上揭贴! fastreport如何不进行预览而直接打印! 不使用下拉数据窗口,如何实现输入产品代码即可显示产品名称 几道不难的生物题,羊群中有没有最占优势者?它有哪些行为表现?最占优势者是公羊还是母羊?在平时和繁殖期的表现都有哪些特点? 一个周长为64厘米的长方形,是由3个完全一样的正方形平成的,求每个正方形的周长 谁能帮我写篇英语作文?作文要求如下:(Direction:Write a short piece of around 150 words about the advantages of bicycling to work.In your writing be sure to include the following points)Bicycling to Work1.lighten the burden on public tra 合力与分力?有一个刚体两个互成角度的力作用在两个端点带动物体运动 可以用平行四边形则求到合力的大小与方向但是不知道合理的作用点呀 (虽然不存在合理 可是合力与分力作用效果要 用32个一平方厘米的正方形平成长方形,有几种不同的拼法?他们的周长各是多少?列一个表格. 求写英语作文!高奖赏!求写八十字英语作文(初一学生)1.I love shopping2.My favourite international city3.It's fun to fly kites4.Our Life in the future5.We should save electricity6.An Enjoyable School Life 以"大漠孤烟直,长河落日圆"为题画一幅画要上颜色,可以的话直接画出来,也可以说出构思 伤仲永-受之天和受之人怎么理解 蜗牛每分钟爬行七分之三米,二分之一小时可以爬行多远?注意单位(分钟和小时) 设 cher a[20]={"switch"},*p=a;,则 printf(”%c\n“,*(p+2));的结果是 《伤仲永》中的“受于天”指什么?“受于人”又指什么,两者有何关系? 什么动物绕家三圈 分力与合力两个共点力F1F2的合力大小是100N已知F1F2的夹角是30°,则分力F2的最小值为 ,在这种情况下,分力F1的大小为 (伤仲永)受之天也的也是什么意思? 如何应用数学课程标准解决生活中的数学问题? 恰好有2位数字相同的三位数有多少……办法呢? 阿里巴巴和四十大盗中文+英文(简单一些) 到处为家的动物有什么 初三正方形几何题在正方形ABCD中,E,F分别是BC CD上的点,且EF=BE+DF,求证:∠EAF=45° 关于合力与分力合力可能小于它的任一分力合力大小可能等于某一分力的大小这两种说法对不哦? 麻烦看下高中数学小论文要从什么方面写哟? 会的人说下嘛,非常谢谢了耗7 正方形ABCD边长为8,点E F 分别在AB、BC上,AE=3,CF=1,P是对角线AC上在动点,求PE+PFD 的最小值 VB中Switch()和Choose()语句使用的多吗? 麻烦大家看下高中数学小论文要从什么方面写哟?会的人说下嘛, 已知二次函数y=ax2+bx+c的图象与x轴交于A(1,0),B(3,0)两点,与y轴交于点C(0,3)则二次函数的解析式是?请用十字相乘发解, 有谁提供一点阿里巴巴和四十大盗英文原文,几个片段也行 伤仲永里 受之天也 受的意思 请以“ABB”形式回答:蓝( )( )!快!回答啊! 关于合力与分力的假若质量为m的物体只受力F,F可分解为任意的F1、F2(1)则物体在F方向的加速度a=F/m(2)物体在F1方向的加速度a1=F1/m(3)物体在F2方向的加速度a2=F2/m以上说法正确吗 勤家有余指什么动物 天空蓝什么什么(ABB)?描写天空的 合力和分力1:合力既然能分解成两个力,分力又能合成合力,那还有什么意义叻?反正怎么分怎么合都是一样的?2:拱桥能把向下的力分解成两个斜向下的力,可是即使这样在结构上有什么好处呢 Can you finish your homework?No,I cna't.It's so d_____ .Can you help me?.填入d字開頭的單詞 好像是藏文谁知的什么意思 合力分力 同时存在合力分力是否能同时存在与物体上? 伤仲永中的受于天和受与人各指什么? 这个是藏文吗?是啥意思?ཨོཾ་མ་ཎི་པདྨེ་ཧཱུཾ། 家中长辈刚出院 说什么问候的话比较好 本人不大善于表达所以请教一下 真的很急了,本人先在此谢谢给位朋友了7n 初三的,几何问题,请说明过程 详细如图,正方形ABCD的边长是2,边BC在x轴上,边AB在y轴上,将一把三角尺如图放置,其中M为AD的中点,逆时针旋转三角尺.(1)当三角尺的一边经过C点时,此时三角尺的 伤仲永第三段受于天指什么 受于人又指什么 双手自残家难和 指一 个什么动物 自行车轮胎的详细尺寸解释,想配我27公路赛车轮胎, 伤仲永 怎样认识受之天与受之人的关系 伤仲永中受之天和受之人分贝什么意思? 请问一下ip2=ip2->next;fclose(fp1);intstatus;pData[i].x=pData[i].x PVx;person[N]="li",18," 四字词成语,第二同第四个字是一样的.例如:将心比心 特别简单的映射个数问题A集合有m个元素,B集合有n个元素,从A到B的映射个数为什么是n^m个.比如A:{a,b,c} B:{1,2} A中元素到B中元素不是有两种选择吗?那一共应该有2+2+2=6种选择,为什么是8种?求大家 chartemp;ip1=ip1->next;fclose(fp1);printf("\npleaseinputanumber:\n");s.push(root->LeftChild);v 问字~一个王字旁,草字头下面一个宝盖在下班一个玉,读什么? 映射数量问题假设集合A中有m个元素,集合B中有n个元素构造A到B的映射求A、B之间映射的数量和一一映射的数量 请问一下for(i=0;i 会的人说下嘛,非常感受大伙了 关于映射个数的题函数f:{1,2,3}→{1,2,3}满足f[f(x)]=f(x),则这样的函数个数共有几个? 几道生物题(高手请进)1.心壁主要由____组成,心壁最厚的是______.2.血液流入小肠壁前与流出小肠壁后相比,氧____(增加,减少)了,养料_____(增加,减少)了.3.正常情况下,红细胞能主要运输下列物质中 会的人说下嘛,非常谢谢了8i 【急】怎么求一一映射个数?如题,设集合A={1,2,3},集合B={a,b,c}那么从集合A到集合B的一一映射个数为( B)A.3 B.6 C.9 D.18 如何理解“大漠孤烟直,长河落日圆”一句中蕴含的图画美 麻烦大家看下高中数学小论文要从什么方面写哟?会的人说下嘛,非常谢谢了5i 形容头脑不清辨不清方向的成语是什么 美国电视荧屏常可看到公开对中国人侮辱俄“质子”火箭搭载美国通讯卫星升空美国承认窃听事件对美国与伙伴国关系造中国周边外交分量加重 升级、提速、加西方盟友会不会因“监听”散伙?美国监听丑闻变“质”环保部:上半年新疆青海等省区氨氮排放人社部:正研究办法治理公职人员“吃空亚冠决赛今夜首战 恒大优势大或破\"媒体称广州公务员年收入超17万 不少女子超市门口产下龙凤胎续:其亲人已经杭州一头藏獒蹿出工地咬伤小区保洁员欧洲盟友讨伐美国的“监听战争”俄“质子”火箭搭载美国通讯卫星升空诺奖得主:转基因之争需要一场高质量的地球变暖事实确凿 人类活动是主因美国对中国等七国钢铁产品发起贸易救济杭州国内经济合作洽谈会引资超155亿十对新人昨天在杭州运河边富义仓拜天地上海浦东一居民被物业劝离后死亡 官方教育部专家谈高考改革:自主招生或将进习近平会见美总统特别代表我国离婚率真的“爆表”了吗加拿大加中协会代表团访沪上海中富拍卖有限公司 拍卖公告三中全会学习材料出版“东方之光”上海将加快发展再制造产业中国电信 通告体育彩票开奖公告中华人民共和国上海海事局航行通告 沪学习贯彻三中全会精神用全会精神指导各项工作三狮军1年不败纪录终结 伪强队表现世“中国十年来最富雄心的改革蓝图”上海拍卖行有限责任公司 拍卖公告上海自贸区法庭受理第一案韩为何也对TPP“动心”中华人民共和国上海海事局航行通告 沪中美非传统安全合作 从“桌面”向实兵中美双方进行混合拔河友谊赛上海市区今明天气预报
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘