首页 文章 精选 留言 我的

精选列表

搜索[依赖管理],共10000篇文章
优秀的个人博客,低调大师

管理不断变化的 state,Redux + React

「写在前面:Redux 和 React 之间没有关系」 安装 npminstall--saveredux React 绑定库和开发者工具 npminstall--savereact-redux npminstall--save-devredux-devtools redux 三大原则 单一数据源 state 是只读的 使用纯函数执行修改 action创建函数 action 来描述“发生了什么” store 数据的唯一来源 actions.js exportconstSET_LOG_USER='SET_LOG_USER'; //用户登录,存储用户信息 exportconstloginUserTodo=(userData)=>({ type:SET_LOG_USER, userData, }); reducer reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。 使用 reducers 来根据 action 更新 state (previousState,action)=>newState loginReducers.js import{getAuth,getUserData}from'@/utils/localStroage'; import{SET_LOG_USER}from'../actionTypes'; constinitState={ auth:getAuth(),//登录信息储存到localstroage userData:getUserData(), }; exportconstloginReducer=(state=initState,action)=>{ switch(action.type){ caseSET_LOG_USER:{ setAuth(true); setUserData(action.state); return{...state,...action.state,auth:true}; } default: returnstate; } }; reducers.js import{combineReducers}from'redux'; import{loginReducer}from'./loginReducers'; constcombineAllReducers=combineReducers({ loginReducer, }); exportdefaultcombineAllReducers; Store Store 就是把reducer 和 action联系到一起的对象 维持应用的 state; 提供 getState() 方法获取 state; 提供 dispatch(action) 方法更新 state; 通过 subscribe(listener) 注册监听器; 通过 subscribe(listener) 返回的函数注销监听器。 Redux 应用只有一个单一的 store store.js import{createStore}from'redux'; importcombineAllReducersfrom'./reducers'; conststore=createStore(combineAllReducers); exportdefaultstore; 数据流 严格的单向数据流是 Redux 架构的设计核心。 Redux 应用中数据的生命周期遵循下面 4 个步骤: 调用 store.dispatch(action) Redux store 调用传入的 reducer 函数。 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。 Redux store 保存了根 reducer 返回的完整 state 树 搭配React 安装 react-redux Redux 默认并不包含 React 绑定库,需要单独安装 npminstall--savereact-redux 实现容器组件 容器组件就是使用 store.subscribe() 从 Redux state 树中读取部分数据,并通过 props 来把这些数据提供给要渲染的组件。你可以手工来开发容器组件,但建议使用 React Redux 库的 connect() 方法来生成,这个方法做了性能优化来避免很多不必要的重复渲染。 使用 connect() 前,需要先定义 mapStateToProps 这个函数来指定如何把当前 Redux store state 映射到展示组件的 props 中。例如,VisibleTodoList 需要计算传到 TodoList 中的 todos,所以定义了根据 state.visibilityFilter 来过滤 state.todos 的方法,并在 mapStateToProps 中使用。 import{connect}from'react-redux'; import{loginUserTodo}from'@/redux/actions'; importLoginfrom'./components'; constmapStateToProps=(state)=>({ auth:state.loginReducer.auth, }); constmapDispatchToProps=(dispatch)=>({ loginIn:(data)=>{ dispatch(loginUserTodo(data)); }, }); exportdefaultconnect(mapStateToProps,mapDispatchToProps)(Login); 传入 store 所有容器组件都可以访问 Redux store,所以可以手动监听它。一种方式是把它以 props 的形式传入到所有容器组件中。但这太麻烦了,因为必须要用 store 把展示组件包裹一层,仅仅是因为恰好在组件树中渲染了一个容器组件。 建议的方式是使用指定的 React Redux 组件 来 魔法般的 让所有容器组件都可以访问 store,而不必显示地传递它。只需要在渲染根组件时使用即可。 importReactfrom'react'; importReactDomfrom'react-dom'; import{Provider}from'react-redux'; importAppfrom'./pages/App'; importstorefrom'./redux/store'; ReactDom.render( <Providerstore={store}> <App/> </Provider>, document.getElementById('root'), ); module.hot.accept();

优秀的个人博客,低调大师

FlutterDojo设计之道—状态管理之路(七)

Provider在列表中使用 在前面的讲解中,我们大部分的场景都是在普通的Box布局中,相信大家对Provider的使用已经非常清楚了,下面来看下在List中的使用场景,相信对于很多App来说,列表应该是大部分页面的核心UI,所以,到底如何在列表的「下拉刷新」、「上拉加载更多」、「Item点击修改状态」这几种场景下来使用Provider呢?官方并没有给出很好的建议,官方的Demo也都是在静态的列表中做的演示,并不涉及到列表的修改,所以下面,我将和大家一起讨论下如何在列表中使用Provider。 当然,这只是我的探索,希望读者能给出更好的方案。 首先,先创建本例的Demo界面。 为了简化Demo,让读者专注于Provider的使用,这里并没有使用下拉刷新和上拉加载的框架,而是通过两个Button来模拟这两个操作,同时,每个Item都提供了一个CheckBox,用于演示单个Item的刷新。 为了尽可能还原场景,这里还提供了Mock数据的获取,代码如下所示。 classItemModel{Stringtitle;boolisCheck;intlikeCount;ItemModel(this.title,this.isCheck,this.likeCount);}classDataModel{List<ItemModel>dataList=List();Future<List<ItemModel>>getData(intpageIndex)async{List<ItemModel>items=awaitapi.getListDataOfIndex(pageIndex);dataList.addAll(items);returndataList;}}classAPI{Future<List<ItemModel>>getListDataOfIndex(intpageIndex)async{List<ItemModel>data=List();awaitFuture.delayed(Duration(seconds:3));List.generate(10,(index)=>(data.add(ItemModel('Title$index@Page$pageIndex',false,Random().nextInt(20)+1))),);returndata;}}varapi=API(); 其它的UI代码,大家可以参考Dojo的源码,如下所示。 flutter_dojo/category/backend/providerstate4widget.dart 使用Setstate 首先来看下最基本的方式。通过setState来更新数据,其原理就是在Future完成之后,使用setState刷新UI。核心代码如下所示。 获取数据。 data.getData(pageIndex).then((value){setState(()=>data.dataList=value);}); 刷新选中。 Checkbox(value:itemModel.isCheck,onChanged:(flag){setState((){varisCheck=itemModel.isCheck;if(isCheck){checkedCount--;}else{checkedCount++;}returnitemModel.isCheck=!isCheck;});}), 下拉刷新与上拉加载。 RaisedButton(onPressed:(){setState(()=>data.dataList.clear());data.getData(0).then((value){setState(()=>data.dataList=value);});},child:Text('Refresh'),),RaisedButton(onPressed:(){data.getData(++pageIndex).then((value){setState(()=>data.dataList=value);});},child:Text('LoadMore'),),Text('CheckedCount$checkedCount'), 这种方式并没有什么问题,特别是当List占据当整个UI界面时,这样其实最简单也不太会影响效率。只有当页面比较复杂的时候,才需要考虑采用Provider来降低刷新带来的效率问题。 下面我们来考虑下如何通过Selector来改造整个Demo,完成数据刷新、数据加载更多、显示Checked数量几个功能。 改造Model Model是Provider的数据处理对象,封装了数据模型和对数据的处理操作。这里的改造和前面讲解的使用Provider的Model的处理方式基本相同,代码如下所示。 classItemModel{Stringtitle;boolisCheck;intlikeCount;ItemModel(this.title,this.isCheck,this.likeCount);}classDataModelwithChangeNotifier{List<ItemModel>dataList=List();intcheckedCount=0;boolshouldListRebuild=true;getData(intpageIndex)async{List<ItemModel>items=awaitapi.getListDataOfIndex(pageIndex);shouldListRebuild=true;dataList.addAll(items);notifyListeners();}refreshData(){dataList.clear();checkedCount=0;shouldListRebuild=true;notifyListeners();}updateChecked(intindex,boolisChecked){shouldListRebuild=false;varitem=dataList[index];if(isChecked){checkedCount++;}else{checkedCount--;}dataList[index]=ItemModel(item.title,isChecked,item.likeCount);notifyListeners();}} 新增了几个函数,分别用于获取分页数据,刷新数据,更新Item的Checked状态。 改造ListItem选中的刷新逻辑 在之前的方案中,当我们点击一个Item做修改时,整个List都将Rebuild,通过Selector,可以根据属性筛选,过滤出需要刷新的Item。 当List内容固定时,不需要刷新整个List,只需要更新改变的Item。 在List的ItemBuilder中,我们做一个Selector筛选,筛选内容为dataList中的ItemModel,当在指定的Item中点击CheckBox后,model被更新,所以Selector的shouldRebuild被判断为true,所以这个Item就会被更新,而其它未点击的Item则因为没有改变所以不会被更新,这样就控制了List的刷新范围为被更新的Item,代码如下所示。 returnListView.builder(itemBuilder:(context,index){returnSelector<DataModel,ItemModel>(selector:(context,value)=>value.dataList[index],builder:(BuildContextcontext,data,Widgetchild){debugPrint(('Item$indexrebuild'));returnCard(child:Padding(padding:constEdgeInsets.all(8.0),child:Row(children:[Checkbox(value:data.isCheck,onChanged:(flag){dataModel.updateChecked(index,!data.isCheck);}),Text(data.title),Spacer(),Icon(Icons.favorite),ConstrainedBox(constraints:BoxConstraints(minWidth:30),child:Center(child:Text(data.likeCount.toString())),),],),),);},);},itemCount:dataModel.dataList.length,); Item的刷新控制其实比较简单,也是Selector的通用解决方案,但是Selector的使用场景是固定数据的List。如果List的数据会发生改变,则Selector的使用则会存在问题,举个例子,我们大部分APP的List使用场景都包含刷新数据、加载分页数据这样两个过程,所以List的数据源是一直在变化的,当首页数据加载时,还可能需要展示一个Loading界面,因此,这些场景下,整个List是一定需要Rebuild的,这时候,一个Selector就无能为力了,但是,我们可以再增加一个Selector,用于控制List是否需要刷新,这样就可以在不同的场景下,精细控制List的刷新范围。 当列表数据不固定时,刷新整个List 当列表数据固定时,只刷新更新的Item 有了这样的思路,就可以理解前面的Model中为什么需要一个shouldListRebuild变量了吧,剩下的代码如下所示。 Expanded(child:Selector<DataModel,DataModel>(shouldRebuild:(pre,next)=>pre.shouldListRebuild,selector:(context,dataModel)=>dataModel,builder:(BuildContextcontext,DataModeldataModel,Widgetchild){if(dataModel.dataList.length>0){returnListView.builder(itemBuilder:(context,index){returnSelector<DataModel,ItemModel>(selector:(context,value)=>value.dataList[index],builder:(BuildContextcontext,data,Widgetchild) 这里一个技巧是Selector<DataModel, DataModel>,这里只借助了Selector的shouldRebuild功能,所以并没有对数据做筛选,完整的代码大家请参考Dojo中的实现。 flutter_dojo/category/backend/providerstate4widget.dart 实际上的操作就是在刷新和加载分页数据这些操作的时候,让shouldRebuild为true,而当只修改Item数据的时候,让shouldRebuild为false,这样就完整的控制了List的刷新范围。 综上 当然,这样的处理只针对于对性能极致要求的场景,大部分情况下,并不太需要考虑的这么细,对List的Rebuild并不会产生多大的性能开销,开发者需要针对不同的场景采用不同的方案,没有必要太过严苛的控制刷新。 更多内容,请访问 https://xuyisheng.top 点击阅读原文,一键直达 本文分享自微信公众号 - Android群英传(android_heroes)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

FlutterDojo设计之道—状态管理之路(四)

在Flutter中,跨Widget的数据共享,可以如下图这样表示。 当Child Widget想要跨Widget拿到其它Widget的数据时,通常就需要使用构造函数,将数据一层层传递到Child Widget,这显然不是一个好的解决方案,不仅让Widget之间有了很大的耦合,也产生很多的冗余数据。 为了解决这个问题,Flutter SDK提供了InheritedWidget这个Widget,InheritedWidget是除了StatefulWidget和StatelessWidget之外的第三个常用的Widget。当把InheritedWidget作为Widget Tree的根节点时,这个Widget Tree就具有了一些新的功能,例如,Child Widget可以根据BuildContext找到最近的指定类型的InheritedWidget,而不是通过Widget Tree的构造函数一层层进行传递,如下图所示。 InheritedWidget的使用其实非常简单,即共享数据给Child。所以它的核心点,其实就是两个。 需要共享的数据 重新updateShouldNotify的条件 通过BuildContext的dependOnInheritedWidgetOfExactType函数,就可以直接获取父Widget中的InheritedWidget。所以在InheritedWidget内部,通常会有一个of函数,用过调用BuildContext的dependOnInheritedWidgetOfExactType函数来获取对应的父InheritedWidget。 只读的InheritedWidget InheritedWidget默认情况下都是只读的,即只能将某个数据共享给Child Widget,而不能让Child Widget对数据做更新。下面这个例子演示了一个最基本的InheritedWidget是如何共享数据的。 classInheritedWidgetReadOnlyWidgetextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnReadOnlyRoot(count:1008,child:ChildReadOnly(),);}}classChildReadOnlyextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){debugPrint('build');ReadOnlyRootroot=ReadOnlyRoot.of(context);returnColumn(children:<Widget>[SubtitleWidget('InheritedWidget本身不具有写数据的功能,需要结合State来获取数据修改的能力'),Text('show${root.count}',style:TextStyle(fontSize:20),),],);}}//仅支持读取属性classReadOnlyRootextendsInheritedWidget{staticReadOnlyRootof(BuildContextcontext)=>context.dependOnInheritedWidgetOfExactType<ReadOnlyRoot>();finalintcount;ReadOnlyRoot({Keykey,@requiredthis.count,@requiredWidgetchild,}):super(key:key,child:child);@overrideboolupdateShouldNotify(ReadOnlyRootoldWidget)=>count!=oldWidget.count;} 给InheritedWidget增加读写功能 数据的状态通常情况下都是保存在StatefulWidget的State中的,所以,InheritedWidget必须要结合StatefulWidget才能具有修改数据的能力,因此,思路就是在InheritedWidget中持有一个StatefulWidget的State实例,同时,使用一个StatefulWidget,将原本的Child Widget之上,插入这个InheritedWidget,这样就可以借助StatefulWidget来完成数据的修改能力,通过InheritedWidget来实现数据的共享能力。 classRootContainerextendsStatefulWidget{finalWidgetchild;RootContainer({Keykey,this.child,}):super(key:key);@override_RootContainerStatecreateState()=>_RootContainerState();static_RootContainerStateof(BuildContextcontext)=>context.dependOnInheritedWidgetOfExactType<Root>().state;}class_RootContainerStateextendsState<RootContainer>{intcount=0;voidincrementCounter()=>setState(()=>count++);@overrideWidgetbuild(BuildContextcontext){returnRoot(state:this,child:widget.child);}}//同时支持读取和写入classRootextendsInheritedWidget{final_RootContainerStatestate;Root({Keykey,@requiredthis.state,@requiredWidgetchild,}):super(key:key,child:child);//判断是否需要更新@overrideboolupdateShouldNotify(RootoldWidget)=>true;} 在这种写法中,InheritedWidget(Root)是在StatefulWidget(RootContainer)中初始化的,当使用StatefulWidget(RootContainer)的setState函数时,InheritedWidget(Root)重建了,但是其child并不会重建,因为它是widget.child,并不会因为State的重建而重建。 要注意的是,虽然这里的StatefulWidget通过setState来修改数据了,但其子Widget并不会全部重绘,因为InheritedWidget的存在,Child Widget会有选择性的进行重绘。 在这基础上,使用就比较简单了,代码如下所示。 classInheritedWidgetWidgetextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnRootContainer(child:Column(children:<Widget>[Widget1(),Widget2(),Widget3(),],),);}}classWidget1extendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){debugPrint('buildWidget1');returnSubtitleWidget('InheritedWidget本身不具有写数据的功能,需要结合State来获取数据修改的能力');}}classWidget2extendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){debugPrint('buildWidget2');returnText('show${RootContainer.of(context).count}',style:TextStyle(fontSize:20),);}}classWidget3extendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){debugPrint('buildWidget3');returnRaisedButton(onPressed:(){RootContainer.of(context).incrementCounter();},child:Text('Add'),);}} 相关代码 Flutter Dojo-Widgets-Async-InheritedWidget 在上面这个Demo中,Widget2、3分别获取和修改了InheritedWidget中的共享数据,实现了跨Widget的数据共享。 通过Log我们可以发现,初始化的时候,Widget1、2、3都执行了build,但点击的时候,只有Widget2、3重新build了,但是Widget1并不会重新build。 这是什么原因呢? 其实这就是RootContainer.of(context)导致的。 当我们执行RootContainer.of(context)这个函数的时候,实际上调用的是context.dependOnInheritedWidgetOfExactType函数,这个函数不仅仅会返回指定类型的InheritedWidget,同时也会将Context对应的Widget添加到订阅者列表中,也就是说,即使你调用这个函数,只是为了执行某个函数,并不是想刷新UI,但是系统依然认为你需要刷新,从而导致Widget2、3都会执行rebuild。而Widget1,由于没有调用过of函数,所以不会被添加到订阅者列表中,所以不会执行rebuild。 要想解决这个问题也非常简单,那就是在不需要监听的时候,使用findAncestorWidgetOfExactType即可,这个函数只会返回指定类型的Widget,而不会将监听加入订阅者列表中。 static_RootContainerStateofNoBuild(BuildContextcontext)=>context.findAncestorWidgetOfExactType<Root>().state; 点击按钮的函数,只需要调用上面的这个函数,在点击的时候,Widget3就不会执行rebuild了。 除了这种方式以外,还有一个方式,那就是通过const关键字,将一个Widget设置为常量Widget,即不会发生改变,这个时候rebuild的时候,系统会发现const Widget并没有发生改变,就不会rebuild了,这也是为什么在Flutter中,很多不需要改变的Padding、Margin、Theme、Size等参数需要尽可能设置为const的原因,这样可以在rebuild的时候,提高效率。 在Flutter中,Theme的实现,就是采用的这种方式。 Widget Tree的遍历 前面提到了两种方式来获取Widget Tree中的InheritedWidget,dependOnInheritedWidgetOfExactType和findAncestorWidgetOfExactType,从调用结果上来看,一种是会被加入订阅者名单,一种只是单纯的查找。 下面再来继续仔细的看看这两个函数的区别。 findAncestorWidgetOfExactType 首先来看下这个函数的注释。 从中我们可以提取几个关键信息。 不会触发rebuild O(n)复杂度 最好在didChangeDependencies中调用 所以findAncestorWidgetOfExactType有几个比较常用的使用场景。 在断言中判断父Widget的使用条件 获取父Widget对象,调用其方法 例如在一些Widget中,可以通过Assert来判断当前是否有使用该Widget的条件,例如Hero Widget。 dependOnInheritedWidgetOfExactType 首先也来看下这个函数的注释。 会触发rebuild O(1)复杂度 最好在didChangeDependencies中调用 可以发现,其实他跟findAncestorWidgetOfExactType是非常类似的,主要的区别还是在于是否会rebuild,另外,dependOnInheritedWidgetOfExactType的效率很高。 项目地址 Flutter Dojo 本文分享自微信公众号 - Android群英传(android_heroes)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册