有哪些ui方式可以表示ui元素状态伪类选择器的变化

IOS开发中UI状态保存与恢复[译]
原文地址:
转载请注明原作者和地址:
今年的《西藏-尼泊尔-阿里旅行计划》刚完,回到成都,这两天闲的蛋疼,没事看看博客,翻到猫哥的,想起了IOS6开始,苹果官方添加的新功能:UI状态的保存于恢复以及接口,既然APPLE帮开发者做了这些脏活累活,我们就应该用起来吧,其实早就知道,并且想用用,可惜一直都是太懒,关于UI状态都一直在手动搞,这下就活动活动脑子。但是网上找了一下关于IOS中UI状态的保存于恢复中文教程甚少,有的也是简单两句,基本上都比较老了,看的云里雾里的,跑也跑步起来,要如何带我装逼带我飞啊,无奈在overflow上找到了老外的教程,暂且粗矿翻一下,大神略过别见怪。
话说从iPhone的第一个SDK版本开始,苹果已经在鼓励开发者去思考应用程序的启动和切换,使其能有更好的用户体验。但是移动设备的资源是有限的,这意味着应用程序的终止是司空见惯的事情。所以返回到应用程序被终止之前用户所见的页面状态来替换每次启动初始化,那是再好不过的用户体验了。这种应用程序被暂停和恢复的能力被引入到iOS4有助于减少这个问题,但使应用程序终止状态返还给用户仍然需要开发人员的努力。
保存和恢复视图和视图控制器这种深层次嵌套需要一个很大的工作量。幸运的是,iOS6对UIKit直接支持UI状态的保存于恢复。这篇笔记,将慢慢带入如何进行UI状态的保存于恢复。
状态恢复流程
要了解有关UIKit如何实现状态的保存方式的一个关键点是,当应用程序进入后台的时候发生了什么。你要给UIKit指示,要给你想要保存的视图控制器(view
controllers)和视图(views)分配恢复标识符(restoration
identifier),UIKit然后会执行保存必要的数据到归档文件的底层工作。如果应用程序被系统终止
那么被保存的归档,将会在下次启动的时候用于恢复应用程序状态。
& & 状态恢复的过程是由UIKit所控制,应用程序委托和保存视图控制器(view
controllers)和视图(views)被总结为以下流程:
一个简单的示例
与往常一样通过一个简单的示例的方式很容易的来展示关键概念,所以这里只有一个非常简单并且包含少数UI元素的示例应用程序。主要的用户界面是一个标签栏控制器(tab-bar
controller),并且带有两个标签栏(tab-bar
item)。第一个标签是内嵌有表视图控制器(UITableViewController)的导航控制器(UINavigationController)。该表视图显示的是国家名字的列表,点击单元格(cell)将导航到到详细视图控制器显示有关该国的详细信息。第二个选项卡是包含一个简单开关(UISwitch)的设置视图。
接下来,在应用程序启动的时候,应用程序的状态将会被恢复:
在标签栏控制器(tab-bar controller)中被选中的标签(tab)将会被恢复
滚动位置和任何选择表格视图将会被恢复
导航器的导航层次结构(可见的表视图或详细视图)。
这里有一个好方法来区分的应用程序的数据模型和用户界面的状态。对于状态的保存和恢复,系统的目的是充分利用用户界面状态。正如我们很快就会看到的保存状态的内存数据都将被删除,如果用户强制退出应用程序,或者如果恢复过程失败。因此,你不应该试图用它来存储应用程序的数据模型。数据模型的最佳存储方式是持久性数据存储,比如通过将其写入文件,数据库或类似的核心数据(Core
Data)。当视图被恢复的时候,我们可以利用持久性数据模型的正确配置来恢复视图。
在这个简单的示例中,表示图的数据源从一个plist文件中加载,并且当前的设置状态将会被存储在用户默认配置中(NSUserDefault)。设置视图的加载和任何更改都将被保存来用于开关状态的初始化(UISwitch):
- (void)viewDidLoad
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
self.amazingSwitch.on = [defaults boolForKey:kUYLSettingsAmazingKey];
- (IBAction)amazingAction
BOOL amazingEnabled = self.amazingSwitch.isOn;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:amazingEnabled forKey:kUYLSettingsAmazingKey];
[defaults synchronize];
改变应用程序的启动进程
如果你回顾刚才的流程图,你会看到应用程序委托中有两个针对IOS6的可用方法,当应用程序初始化启动后:
application:willFinishLaunchingWithOptions:
application:didFinishLaunchingWithOptions:
&&这两种方法之间的主要区别在于,“willFinish”方法,该方法出现于IOS6,它是在状态恢复前执行的。“didFinish”方法是在任何状态恢复后被调用。作为通常的指引,在状态恢复发生前你将想要执行大多数应用程序初始化,如在视图控制器恢复时确保数据模型是可用的。
如果您有一个现有的应用程序,你可以把初始化代码从&application:didFinishLaunchingWithOptions:&方法都转移到&application:willFinishLaunchingWithOptions:&方法里。然而,如果你想向后兼容到iOS5或更早版本,“willFinish”的方法要小心,他将永远不会被调用。一种方法是将所以初始化代码都写到两个启动方法中。您可以进一步包装的初始化代码放在一个dispatch_once块,如果你需要确保初始化只执行一次:
- (void)commonFinishLaunchingWithOptions:(NSDictionary *)launchOptions
static dispatch_once_t onceT
dispatch_once(&onceToken, ^{
NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kUYLSettingsAmazingKey,
[[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
[self commonFinishLaunchingWithOptions:launchOptions];
return YES;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
[self commonFinishLaunchingWithOptions:launchOptions];
return YES;
&&如果你不关心iOS5的兼容性就可以避免这种额外的工作,直接把你的初始化代码放到相应的应用程序委托的方法中即可。
开始状态保存的基本步骤
以下开始状态保存和恢复的两个基本步骤:
应用程序委托必须选择加入
必须为每个视图控制器或视图加入一个保存/恢复标识符
除了这些强制性措施也有一些可选的,但很常见的步骤:
在应用程序启动的时候,如有未被创建的情况下,需要在整个恢复过程中为任何视图控制器(view controller)重新创建恢复类(restoration class)。&
要求在状态被保存和恢复的时候为每个视图或者视图控制器实现 encodeRestorableStateWithCoder: 和 decodeRestorableStateWithCoder:
这两个方法。&
为了知道哪些行是可见或者是被选中的,要给表视图和瀑布流视图数据源的对象实现UIDataSourceModelAssociation协议以用于保存和恢复。
应用程序代理入口(Application Delegate
状态存储和恢复是一项可选功能,所以你需要手动实现两两个代理方法保证其工作:
- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
return YES;
- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
return YES;
大多数时候,你只是想返回YES,来支持状态的存储和恢复。但是如果你发布一个应用升级,从以前的版本中还原是没有意义的,你可以从&application:shouldRestoreApplicationState:&这个方法里返回NO
比如这种情况,你是视图层次结构完全改变。
恢复标识符(Restoration
Identifier)
你需要给任何你想要保存和恢复的视图控制器或视图分配恢复标识字符串。任何具有恢复标识的视图控制器或者视图在保存的过程中,视图层次都会被存储到磁盘中。&
& & 该restorationIdentifier属性可以在视图控制器初始化的时候设置,但如果你要从nib文件或者故事板(storyboard)中加载视图控制器,你也可以在此设置。你可以选择任何字符串作为你想要的标识符,但你必须确保要恢复的每个对象恢复路径(根视图控制器下的恢复标识的顺序的视图层次)是唯一的。&
对于这个简单的示例项目,我们可以在故事板(storyboard)设置恢复标识符。对于视图控制器,你可以使用Interface Builder来设置一个Storyboard ID,并且通过重复使用相同的ID为恢复编号(Restoration ID):
视图控制器中都必须要设置恢复:
标签控制器(The Tab Bar Controller)
所有的导航控制器(Both Navigation
Controllers)
表示图控制器和相关的表示图(The Table View Controller
and the associated UITableView)
设置控制器(The Settings View
Controller)
一个容易犯的错误,当使用Interface
Builder在错误的对象上来设置恢复标识。举例来说,恢复标识符一定要设置在表视图本身,而不是在表视图单元格。下面故事板的截图显示我用的视图控制器的标识符:
恢复过程中查找或者创建视图控制器
在恢复过程中UIKit将尝试为每个正在恢复的视图控制器创建或找到一个引用。如果主用户界面从nib文件或故事板加载,他将在应用程序启动的时候被创建。在这种情况下的UIKit会找到一个已创建的视图控制器来代替新的实例。&
在这个简单的例子中,所有的视图控制器都将在应用程序启动的时候在故事板文件中被恢复。这意味着我们基本无事可做,UIKit会负责为我们重新创建视图控制器(我们仍然需要恢复的视图控制器的状态)。对于正常应用程序启动不能创建视图控制器的话,UIKit会尝试以下步骤:
如果视图控制器有一个恢复类(它实现了UIViewControllerRestoration协议),那么UIKit会调用viewControllerWithRestorationIdentiferPath:coder:
方法。通过实施该方法可以返回视图控制器的一个实例。&
如果视图控制器不具有恢复类,UIKit会在应用程序代理里面调用application:viewControllerWithRestorationIdentifierPath:coder:&
& & 我会把详细的讨论解析保存下来放到。
恢复视图控制器和他们的子视图
所有的视图控制器需要被我们创建创建,我们需要恢复任何状态,他们需要适当的重新创建用户界面和任何包含的相关的子视图。对于任何对象我们都设置了恢复标识,我们可以选择通过实现一下两个方法来保存和恢复状态信息:
encodeRestorableStateWithCoder:
decodeRestorableStateWithCoder:
在我们的示例应用程序,我们有一个视图控制器(UYLCountryViewController),在根表视图控制器中选择的国家之后用于显示与省会城市的视图。这个视图控制器的数据模型只有NSString属性这一个属性,它是当视图控制器被压入导航控制器的堆栈的时候被设置:
@property (nonatomic, copy) NSString *
当恢复该视图控制器,我们需要确保我们恢复这些数据。在这种情况下,是我们需要实现的编码和解码方法如下:
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
[coder encodeObject:self.capital forKey:UYLKeyCapital];
[super encodeRestorableStateWithCoder:coder];
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
self.capital = [coder decodeObjectForKey:UYLKeyCapital];
[super decodeRestorableStateWithCoder:coder];
请注意,在这两种情况下,你需要调用super确保父视图控制器得到一个机会来保存和恢复状态。
维护表视图状态
为了让表视图显示我们希望能够保存当前的选择的国家名单和滚动位置。由于数据源可以应用的推出之间改变,所以这会比保存第一个可见行和所选择的indexPath稍微复杂一点。如果当我们恢复了一些行已被删除或添加到表中,依托indexPath会给我们带来错误的结果。
& & 我们需要做的是映射indexPath和一个标识符,用它来明确定位数据项。要做到这一点,我们需要采用UIDataSourceModelAssociation协议:
@interface UYLTableViewController ()
这个协议有两个必要方法在表中一个索引路径的对象和NSString标识符之间进行映射:&
modelIdentifierForElementAtIndexPath:inView:
indexPathForElementWithModelIdentifier:inView:
在这个简单的例子中,我们可以使用国家名称来唯一地标识表中的一个对象。用字符串的表示形式来管理对象ID对于核心数据支持的数据模型是不错的选择。当我们保存状态的时候为一个字符串标识符映射索引路径,使用下面这个简单的方法:
- (NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view
NSString *identifier =
if (indexPath && view)
NSDictionary *country = [self.worldData objectAtIndex:indexPath.row];
identifier = [country valueForKey:@"capital"];
在恢复所使用的方法需要映射回索引路径用于所述标识符的对象。这是一个有点复杂,在这种情况下,我们需要寻找字典数组的匹配对象。幸运的是,我们已经有现成的教程():
- (NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view
NSIndexPath *indexPath =
if (identifier && view)
NSPredicate *capitalPred = [NSPredicate predicateWithFormat:@"capital == %@", identifier];
NSInteger row = [self.worldData indexOfObjectPassingTest:
^(id obj, NSUInteger idx, BOOL *stop)
return [capitalPred evaluateWithObject:obj];
if (row != NSNotFound)
indexPath = [NSIndexPath indexPathForRow:row inSection:0];
// Force a reload when table view is embedded in nav controller
// or scroll position is not restored. Uncomment following line
// to workaround bug.
[self.tableView reloadData];
return indexP
&&需要注意的是,我们无法找到对象,因为它已经被删除了导致返回nil的情况。另外要注意,因为在iOS的6中的一个bug,我们需要强制重载表视图或滚动位置才不被还原。请参阅相关教程()。
我们已经做了我们需要保存和恢复状态所有步骤。我们就可以利用模拟器或设备测试的结果,但你要记住我刚才提到的一个关键点。当应用程序被移动到后台,状态保存只能在你把应用置于后台并且强制退出应用程序。测试状态恢复使用模拟器,最简单的方法是使用下面的步骤:&
在模拟器中启动应用程序,并导航到创建所需的应用程序的状态(随便点,点了之后的页面不要和启动的时候一样就行了)。&
使用home键(⇧⌘H)让应用程序处于后台。&
使用Xcode停止应用程序。&
这里我们没有运行,但有一些后台保存状态的应用程序。要测试状态恢复,你可以直接在模拟器或从Xcode中再次运行它,重新启动该应用程序。一个常见的错误,我觉得测试时在应用后台之前应用程序就被停止了。
&&这是一个漫长的考虑后,我只涵盖了基础知识,但希望你已经跟着做成功了。
所需代码在github上,自己动手下哈()。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。&前端开发需要什么样的状态?
前端开发需要什么样的状态?
日09时49分来源:
题图来自Zoommy,这个服务今天挂了
本文由我和尹锋共同完成,尹锋完成了文章主体,我写了前面部分,并润色了全文。
尹锋,前端攻城师,目前就职微影时代,任前端基础平台建设部 Team Leader,主要负责前端基础平台建设以及微信演出票和电影票移动站的业务开发。
文章很长,但有价值。
总有读者来问,我该不该学习前端,我说该。我该不该从 Java 转到 JavaScript,我说转。前端有没有前途,我说有。其实很多人去提出一个问题的时候,已经有了预设的答案,他们只是需要一个肯定的答复而已。
做前端、后端和移动端,做好了,都是很好的方向。只是技术不同,场景不同,应用不同而已。互联网已经形成了一个巨大的反应场,好的技术,在这个互联网时代,都会有一席之地。
另外,常常有人抱怨前端的框架太多了,我想这样的人掌握的编程语言不会超过两门。由于浏览器引擎日益强大,平台趋势凸现,前端只是在后端走过的路罢了。模块化,组件化,状态化,异步,等等,不要抱怨框架太多,后端的每一门语言都有无数的框架。在一个技术突飞猛进的时候,更多的选择,往往比更少的选择要好。在大数据发展的初期,更大的数据量永远胜于优秀的算法。无论算法好坏,更多的数据总是能带来更好的效果,也是一个道理。
所以,投身前端领域的同学,要有不怕困难,热爱框架,勇于当炮灰的精神和状态。当然了,我也是站着说话不腰疼,因为我有个前端团队:)
今天的文章内容也和「状态」有关,上一篇讲了「React,一次学习,到处编码」,今天聊聊 Redux。
Redux 是什么?Redux 是 JavaScript 状态容器,提供可预测化的状态管理。Redux 可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供了非常棒的开发体验,比如这个时间旅行调试器,可以编辑后实时预览。
为什么要开发 Redux?Redux 的作者如是说:因为基于 JavaScript 的单页面应用越来越多了,现在不再是 HTML 的时代,而是 HTML App,JavaScript 需要管理比任何时候都要多的 State (状态)。这些 State 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。
管理不断变化的 State 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,然后,可能引起另一个 view 的变化,直到你把自己搞晕,State 如何变化似乎已经不受控制。不过,计算机领域就是这样,出现问题就会有解决问题的方案,于是 Redux 应运而生了,它与 React 的结合,很好的解决了这个问题。同时 Redux 并不强依赖 React,它可以配合其他任何框架或者类库一起使用。
但是话说回来,React 和 Redux 搭在一起真的很般配啊,是真爱!
先回顾一下 React。
一、对 React 的理解
1、理想的 UI 编程模型
React 大胆的跳出了 Web 的 DOM 实现约束,实现了高效的渲染机制,通过树形控制描述 UI、统一事件机制、单向数据流。
JSX 是 React 中一颗闪耀的新星,它一个非常好的工程化手段,JSX 允许我们将标签写入 JavaScript 文件中,其实这样做让程序更加聚合,具备良好的开发体验,我们能在编译快速清晰的看到错误信息,指定错误代码行号,方便调试。
说到 JSX,一般都会和模板进行比较,模板?套用一句功夫熊猫里面的台词,这真是太遗憾了,我想对模板的喜爱者说一个不幸的消息,JSX 天生具有模板无法触及的优势。
模板是把 “JavaScript” 放入 HTML 里面,而 JSX 是把 “HTML” 放入 JavaScript 里面。
JavaScript 远比 HTML 要强大,在 JSX 里面你可以使用 JavaScript 编程语言的能力和工具链来描述页面。因此,增强 JavaScript 使其支持HTML 要比增强 HTML 使其支持逻辑要合理的多。
咦,以前人们怎么没想到呢?因为技术和思想一直在进步啊!
当然了,JSX 也并非十全十美,有坑有陷阱,比如不能很好的处理 if-else 等,不过,这些可以克服的缺陷,不足以盖住 JSX 的光芒。
文章起始我对 Redux 做了简介,如果你已经很熟悉 Redux 了,可以略过这部分,直接跳到最后的State 数据结构的设计这一节,这部分是我们团队在开发了好几个大型项目后总结出来的。
1、Redux = reduce + Flux
我们看一下 JavaScript 中Array.prototype.reduce的用法,对数组中的所有元素调用指定的回调函数,该回调函数的返回值为累积结果,而且返回值在下一次调用回调函数的时候是作为参数提供的:
const initState = ''
const actions = ['Learn about actions, ','Learn about reducers, ', 'Learn about store']
const newState = actions.reduce((prevState, action)=& { return prevState + action }, initState)
这就是 Redux 的核心所在,State就是应用程序的数据,给定initState之后,随着action的值不断传入给计算函数,得到新的State。Array.prototype.reduce的第一个参数是一个函数:(prevState, actions)=& { return prevState + actions },这个计算函数被称之为Reducer。
能看到这的读者,大部分都是 Web 前端工程师,我们每天的工作就是构建很多网页,然后呈现给用户。那么,网页的本质是什么呢?
从 Web 前端工程师的角度看,网页的本质是将数据渲染出来,呈现给用户。这里有两个关键字,一是数据,二是渲染,当数据发生改变的时候,网页需要重新渲染。网页渲染和重新渲染,就是 React,数据和数据改变的逻辑就由我们的主角 — Redux 实现。
这里的数据和上面提到的State是同一个东西,因为State是 React 中的概念,所以后续会使用State这个概念。
Redux 约定:
整个应用的 State 被储存在一棵 Object Tree 中,并且这个 Object Tree 只存在于唯一一个 Store 中。
比如说,我们有这么一个 State:
todo: 'Learn about actions'
这个 State 只有一条记录,React 将他渲染成
&span& Learn about actions &/span&
我们把 State 修改以后
todo: 'Learn about actions'
todo: 'Learn about reducers'
那么渲染也会发生变化
&span& Learn about actions &/span&
&span& Learn about reducers &/span&
这就是 Redux 中最基本的概念:
页面中的所有状态,都应该以这种状态树的形式来描述,页面上的任何变化,都应该先去改变这个状态树,然后再渲染到页面上。
下面就可以解释 Redux 几个核心概念了。
Action 是把数据从应用传到 Store 的有效负载,它是 Store 数据的唯一来源。通俗的说,就是描述“发生了什么事情”。
在上面的例子中,如何来描述在 todo list “Learn about actions” 中新加入一条记录 “Learn about reducers” 呢?
export function addTodo(text) {
type: ‘ADD_TODO',
这个函数会返回一个 Action 对象,Action 本质上是 JavaScript 普通对象,这个对象描述“发生了什么事情”。Redux 约定,Action 内使用一个字符串类型的 type 字段来表示将要执行的动作,除此以外,还可以携带动作所需要的数据,随后这个对象会被传入到 Reducer 中。
3、Reducer
Action 只是描述了有事情发生了这一事实,并没有指明应用如何更新 State。而这正是 reducer 要做的事情。reducer 就是一个函数,接收旧的 State 和 Action,返回新的 State。
(prevState, action) =& newState
我们现在用这个公式来解决上面的 Action。
const initialState = [{
text: 'Learn about actions'
export default function reducer(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
id: state.reduce((maxId, todo) =& Math.max(todo.id, maxId), -1) + 1,
completed: false,
text: action.text
我们学会了使用 Action 来描述“发生了什么”,和使用 Reducers 来根据 Action 更新 State 的用法。
Store 就是把它们联系到一起的对象。Store 有以下职责:
维持应用的 State
提供 getState() 方法获取 State
提供 dispatch(action) 方法更新 State
通过 subscribe(listener) 注册监听器
再次强调一下 Redux 应用只有一个单一的 Store。
根据已有的 reducer 来创建 Store 是非常容易的。
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
5、发起 Actions
用一句话来描述 Redux:
将动作(Action)通过状态转换函数(Reducer),set 到一个统一的地方(Store),然后 UI 从 Store 中获取数据。
接下来,我们验证一下这个逻辑。
store.dispatch(actions.addTodo('Learn about actions')) // 添加一个 todo
store.dispatch(actions.addTodo('Learn about reducers')) // 添加一个 todo
store.dispatch(actions.addTodo('Learn about store')) // 添加一个 todo
store.pleteTodo(0)) // 完成第零个 todo
store.pleteTodo(1)) // 完成第一个 todo
store.dispatch(actions.clearCompleted()) // 清除已经完成的 todo
简直没有比这再清晰的了,你甚至都不用阅读源码,只需要看一下这个 Action 列表(每个 Action 日志中会打印出初始状态、执行动作和执行后的状态),就知道业务逻辑是怎样执行的,也不会出现 MVC 中一个 Model 更新了以后不知道哪些 View 会随之更新的情况了。
这正是 Redux 的一个很大的优点 — 可预测性。因此,我们清楚的知道发生了什么改变(Action),改变之后的数据是什么状态(State),以及发生了哪些改变(Action 记录)。
这段代码可以在所有 JavaScript 环境下执行,这意味我们可以进行业务逻辑的单元测试,也意味着这套业务逻辑可以用于 Web,用于 iOS、Android、tvOS…
Redux 与 React 没有直接联系,Redux 用于管理 State,与具体的 UI 框架无关,不过官方和社区提供了很多库,来绑定 Redux 和其他 UI 框架,比如 React、Angular、Vue 等等
三、Redux 搭配 React
为了在 React 中使用 Redux,React 官方提供了一个库 react-redux,用来结合 Redux 和 React 的模块。react-redux 提供了两个接口 Provider、connect。
首先,我们要获取 react-redux 提供的 Provider,并且在渲染之前将根组件包装进&Provider&。
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './web/containers/App'
import configureStore from 'app/store/configureStore'
const store = configureStore()
&Provider store={store}&
&/Provider&,
document.getElementById('root')
接下来,我们通过 react-redux 提供的connect()方法将包装好的组件连接到 Redux。
import React, { Component, PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as TodoActions from 'app/actions'
class App extends Component {
render() {}
export default connect(
(state) =& {
todos: state.todos
(dispatch) =& {
actions: bindActionCreators(TodoActions, dispatch)
四、State 数据结构的设计
第一次在项目中使用 Redux 的时候,对于 State 基本没有设计,后期全部推倒重来,当时,我们的内心是奔溃的。
第二个项目的时候,我们按照页面结构来组织 State,刚开始很好,但是到后来,就会出现,在某个页面需要到其他页面去获取 State,这样虽然可行,但不是好的实现方式,我对于这种做法一直耿耿于怀。
所以,对于 State,我一直在思考,如何回归到网页的本质。我们有数据,然后去渲染它。那应用包含哪些数据呢?
服务器响应
本地生成尚未持久化到服务器的数据
也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器
对数据进行一个分类处理,可以分为业务逻辑数据和页面 UI 数据。
举个例子,对于电影票应用而言,业务逻辑数据包含并不限于这些数据:正在热映列表、即将上映列表、电影详情、影院列表、影院详情、座位信息、支付信息、订单中心、用户中心等等,如果按照业务逻辑来组织 State,那么就可以设计如下的数据结构:
let state = {
hot: [], // 正在热映
coming: [], // 即将上映
detail: {}, // 当前电影详情
list: {}, // 影院列表
detail: {}, // 当前影院详情
scheds: [], // 影院排期
seats: {}, // 作为信息
locked: {}, // 已锁座位
payment: {},
detail: {},
routing: {},
这样,所有的数据结构一目了然,方便理解和记忆。数据存储好了以后,所有的 page 都从这个 State 里面获取数据,如果使用传统的 select 来计算,当页面比较大的时候,会存在性能问题,所以出现了 Reselect。
Reselect 库能够创建可记忆的、可组合的 selector 函数。Reselect selectors 可以用来高效地计算 Redux Store 里的衍生数据。
所以,我们在 state 和 page 中加了一层 selector。在 selector 里面在组装 page 需要的数据以及数据的计算,这个数据可能会来自多个业务 model。
比如说,在选座页会呈现出影片的信息、影院的信息、座位图的信息,就可以从三个业务 model 中获取数据(调用三次 action),然后计算,传递给选座页使用。
这只是一种方式。业务逻辑不同,数据格式和结构也会多种多样,找到适合自己的就好。
定义好 State,团队可以按照统一的 State 数据结构开发,各自相对独立,互不干扰。
五、不足之处
对从 OOP 开发转过来的程序猿来说,函数式编程的概念接受起来需要一点门槛。
JavaScript 对不变对象的支持并不是特别的友好,无论是引入 immutable.js 还是 ES6 的解构语法糖有时候都觉得 Reducer 里的代码读起来有些费力,特别是对刚接触 ES6 的同学来说。
以上源码在:
/ingf/caption
六、参考备注
thinking-in-react:
Angular 2 versus React: There Will Be Blood:
React’s JSX: The Other Side of the Coin:
大鲲,拉勾旗下新产品,专注于企业短期项目的人才对接,帮助企业1小时内找到专业人才,降低人力成本,提高完成质量。目前已有1000+专业人才入驻,覆盖开发、设计、市场运营、产品等方向。平台提供快速响应服务和全程监管,保障项目推进和资金安全。
点击阅读原文,发布项目或入驻成为专家。

我要回帖

更多关于 ui状态栏素材 的文章

 

随机推荐