Redux从入门到跳楼

本文由 Michael Ciurus 公布于 raywenderlich ,原版的书文地址。

ReSwift 是用 斯威夫特 完成的好像 Redux 的库。翻译那篇作品是在投机全然根据文章学习了叁回后,在文章的逐个部分或然参与我自身的对于这几个设计格局的一对思想,如有不对的地点,请各位大神教导。蟹蟹

image.png

参照链接:

Flux, Reflux)和Redux)它们都以用于管理数据流转的。那么它们为什么会现出,各自又缓和了怎么难点啊?

图片 1Build a Memory Game App with ReSwift!.png

趁着 app 的升华, MVC 慢慢的满意不断业务的急需。大家都在商量形形色色的架构形式来适应这种场所,疑似MVVM、VIPE宝马7系、Riblets 等等。 他们皆某些的表征,可是都有同一个骨干: 通过多向数据流将代码根据单一职务标准来划分代码。在多向数据流中,数据在相继模块中传送。

  • Redux汉语文档
  • Redux 入门教程-阮一峰
  • 看漫画,学 Redux
  • 在react-native中使用redux
  • [React Native]Redux的骨干采纳方式
  • Redux管理复杂应用数据逻辑

Flux

倘使对Flux并不打听,能够先读书图解Flux噢。

一个Flux应用富含4个部分:

  • Action,驱动Dispatcher的JavaScript对象,告诉Store数据产生了扭转,以及何地变化了(payload)
  • Dispatcher,管理Action分发,维持Store之间的依赖关系
  • Store, 负担存款和储蓄数据和管理数据逻辑,就像是MVC中的Model同样
  • View,UI,担任显示客商分界面。在Store数据变动之后,会自上而下达成一回渲染(re-render)。

化解的标题
MVC(Model-View-Controller)框架存在已久,我们也意识了无数的难点。其一直难点是分化模块之间的借助关系目不暇接而且不可预测。对代码进行修改很轻易引进bug。
比方说您想像中的MVC是那样子的,

图片 2

当实际它是这样子的:

图片 3

故而为了消除这么些难题,Flux推翻了MVC框架,用二个新的思维来管理数据流转--单向数据流。

图片 4

Flux

Note: 那一个课程使用 Xcode 8 和 斯威夫特 3。

多向数据流并不一定是你想要的,反而,单向数据流才是大家更欣赏的多少传递格局。在那些ReSwift 的学科中,你会学到怎样利用 ReSwift来兑现单向数据流,并产生一个动静驱动的嬉戏——MemoryTunes

目录

  • 行使场景
  • 选拔的三尺度
    • 单纯数据源
    • 情景是只读的
    • 通过纯函数修改State
  • redux状态管理的流水生产线及相关概念
    • store
    • Action
    • Action 成立函数(Action Creator)
    • Reducer
  • redux怎么着与组件结合
    • 实际示例1
    • 切实示例2

Reflux

Reflux,是一个落成Flux设计形式的库,意在使全体应用架构变得更是简明

图片 5

Reflux

七个Reflux应用只含有3个部分:

  • Action,担任消息的散发,一般是由顾客作为触发
  • Store,肩负监听Action的接触,也承担文告View更新UI
  • View,肩负监听Store的数码变动,在Store数据变动现在,会自上而下跌成二回渲染(re-render)。

化解的难点
Flux中Actions和Stores(新闻订阅者)间存在着多对多的涉及,而作为Flux中唯一的Publisher(音讯发表者)Dispatcher,不得不在音信揭露时,通过在payload中加多actionType字段来区分音讯类型,且Store也就此只好在回调函数中用Switch语句举办判别actionType管理。

而在Reflux中,由于每二个Action都以贰个Publisher,且具有特定的意思(actionType),即四个Publisher对应于多个Subscriber(或称为Listener),Store就能够有目标性地接纳订阅想监听的Action,而不在监听全数的Action,再通过Switch语句进行筛选;别的,Action(新闻)的发表,也只会通报给前边有订阅过的Store,并不是有所Store,所以并不会形成任何能源浪费。

归咎,Reflux把Dispatcher的成效合併到Action中去,使得每三个Action都兼备了音信揭露的效率,能够一向被Store监听。

乘势 iOS 应用的轻重缓急不断拉长, MVC 渐渐的不再成为设计情势的首荐。

什么是 ReSwift

ReSwift 是贰个轻量级的框架,能够辅助您相当轻便的去创设一个 Redux 架构的app。当然它是用Swift 完结的。

LANDx斯威夫特 有以下多少个模块

  • Views: 响应 Store 的改换,並且把他们映今后页面上。views 发出 Actions
  • Actions:发起app 种状态的转移。Action 是有 Reducer 操作的。
  • Reducers: 直接退换程序的动静,那几个景况由 Store 来保存。
  • Store:保存当前的次第的图景。其余模块,举例说 Views 能够订阅那么些情景,何况响应状态的改动。

ReSwift 至少有以下这几个优势:

  • 很强的约束力:把一部分代码放在不合适的地点再三有着很强的诱惑性,即便这么写很有益。Re斯威夫特通过很强的约束力来制止这种情状。
  • 单向数据流:多向数据流的代码在阅读和debug上都大概成为一场祸殃。贰个改观大概会推动一雨后春笋的相干反应。而单向数据流就能够让程序的运作特别具备可预测性,也能够减弱阅读那几个代码的悲苦。
  • 轻便测量检验:大非常多的职业逻辑都在Reducer 中,那个都是纯的效果。
  • 复用性:Re斯威夫特 中的每一个组件—Store、Reducer、Action ,都是能在所有人家平台独立运行的,能够很自在的在iOS、macOS、恐怕tvOS 中复用那个模块。

利用场景

React设计意见之一为单向数据流,那从单向有助于了数量的管住。但是React本人只是view,并未提供完备的多少管理方案。随着应用的接踵而至 蜂拥而至复杂化,要是用react创设前端选取的话,将要应对纷纷复杂的数量通信和管理,js需求有限支撑越来越多的情景(state),这么些state恐怕包含用户音讯、缓存数据、全局设置情状、被激活的路由、被入选的标签、是还是不是加载动作效果或许分页器等等。

那时,Flux架构应时而生,Redux是其最优雅的落到实处,Redux是二个不依据任何库的框架,不过与react结合的最棒,当中react-redux等开源组件就是把react与redux组合起来进行调用开辟。

备注:

1.万一你不亮堂是还是不是须求 Redux,那正是没有要求它

2.唯有境遇 React 实在化解不了的主题素材,你才要求 Redux

Redux使用情状:

  • 有个别组件的事态,须要分享
  • 有个别状态须要在别的地方都得以获得
  • 二个组件必要更动全局状态
  • 一个零件须求改动另一个组件的图景

比如说,论坛应用中的晚间安装、回到最上端、userInfo全局分享等场景。redux最终目标正是让景况(state)变化变得可预测.

Redux

Redux是Flux设计情势的又一种达成形式。假若对Redux并不通晓,能够先读书图解Redux噢。

图片 6

Redux

Redux中的四个宗旨概念时Action,Store和Reducer,当然一个Redux应用只怕富含6局地:

  • Action,一个用来表示全部会挑起状态变化的表现的对象,一般都会含有八个字符串类型的type字段来表示将在实践的动作和内需传递给选择的其余数据消息(数据方式顾客可以自定义)
  • ActionCreator,三个函数,最后回到二个Action对象
  • Reducer,一个函数,该函数接受几个参数,三个旧的意况previousState和叁个Action对象,重临二个新的事态newState
  • Store, Redux中多少的统一存款和储蓄,维护着state的有所内容。Store通过dispatch(action)格局来接过不一致的Action,依据Action对象的type和数据新闻,Store对象足以经过Reducer函数来更新意况的从头到尾的经过
  • Middleware,首要功效是处理Action,Redux中的Action必得是三个plain object。可是为了落到实处异步的Action或别的职能,Action恐怕是四个函数,或然是二个promise对象。那是就需求Middleware帮忙来拍卖这种特有的Action。相当于说,Redux中的Middleware会对一定项目action做确定的调换,所以最终传给reducer的action一定是标准的plain object
  • View,UI,负担突显顾客分界面。在Store数据变动之后,会自上而下完结二次渲染(re-render)。

消除的难题

  1. 数据源不独一

在 Flux/Reflux中,数据遍布在五个Store中,很轻松造成数据冗余,当数码存在重叠时,数据或者不设有独一性,即在一致性方面会出标题。

消除方法
Redux提议单一Store存款和储蓄数据。但那有望面对另二个标题,数据结构嵌套太深,数据访谈变得繁琐。

为此Redux提议通过定义多少个reducer对数码开展拆除访谈照旧涂改,最后再通过combineReducers函数将零散的多寡拼装回去,将贰个个reducer自上而下顶级拔尖地联合起,最后收获八个rootReducer

  1. 热重载

在Flux中,store蕴涵两有的:

  • State改动的逻辑
  • 当前的State

将这两点放在同一个目的上则无法扩充热重载。当您重载store对象来看state更动后的职能,你会失掉store持有的数额。别的,你打乱了连年store到系统其余部分的事件订阅。

焚林而猎办法
拆分那三个方法。让store持有state,store将不会被另行加载。Reducer包涵全数state改造的逻辑,reducer能被再一次加载,因为它无需关注持有别的state。那样就足以热重载state变化的逻辑(reducer)而不遗弃store中所存款和储蓄的state音信。

  1. 任何action的触发都会重写state

在时间游览调节和测量检验中,你追踪每种版本的state状态。那样,你能够回溯到某一个较早的动静。

每一回state改动后,你必要将旧的state追加到多少个封存以前state的数组中。可是因为JavaScript的干活措施,轻便的增加三个变量到数组中并不干活。那不会创设那么些state对象的截图,只是成立了七个新的指针指向同一个对象。

为了让它工作,每一个版本都亟需一个全然分开的靶子,以防你相当的大心修改到事先的某部版本的state。

焚薮而田办法

当store中接收到三个action,不要通过改造状态来管理它。而是拷贝原本的state,然后更换那一个拷贝的state。

  1. 尚无让第三方插件能能很好出席的地方

当您在开采开辟工具时,你须要将它们写的更布满有个别。三个顾客能够只将工具加进去,而不用去修改他们自身的代码来让它职业。

为此,你需求扩充比相当多点或面,能够让您的code能够加上一些新的事物到它其中。

一个例子,打字与印刷日志。 当action被触发时,你需求打字与印刷每一个action,然后您还要求打字与印刷通过action退换的state。在Flux中,你只好监听dispatcher的创新,然后去立异各样store。不过那是自定义代码,实际不是通过第三方工具来归纳的完结那几个供给。

化解方法
卷入一部分类别到另一些对象中让它变得轻巧。那么些指标在本来对象的基础上加上它们那部分的成效。你能够因而类似enhancers、higher order对象或middleware看出那类扩张的点。

另外,用树状结构去结构化state改换的逻辑。在整整state树被拍卖以后,store只需求接触贰个平地风波就能够布告到全数state改造了的view。

Redux有三大骨干尺度:

  1. 唯一数据源:整个应用唯有贰个Store,全体的数据源都以其一Store上的事态。
  2. 保持状态只读:无法直接修改Store上的气象,要修改Store的气象,必得通过接触一个action来完毕,在更换状态时,不是去修改情形上的值,而是创设贰个新的情形再次回到给Redux,由Redux来完毕创设新的情况。
  3. 多少变动只好通过纯函数完毕:这里的纯函数即Reducer,Redux中,Reducer函数不只是经受action作为参数,还收受state,它只担负技巧意况,不担当存款和储蓄状态。

今昔iOS开垦者有比很多可扩充的设计方式可供选用,举例 MVVM, VIPELacrosse 和 Riblets(大哥第叁遍听到这些格局,哎,照旧太菜了)。他们看起来差距异常的大,可是她们都有一个联袂的目的:在多向数据流的情势下将代码分割到单一职务的地点。在多向流中,数据在相当多模块中以种种方向流动。

多向数据流 vs. 单向数据流

由此以下的多少个例子,我们来通晓一下怎样是数据流。一个基于 VIPE奥迪Q5架构完毕的顺序就允许数据在其组件中多向传递。

VIPETiguan 中的多向数据流

VIPERAV4 中的多向数据流

跟 Re斯威夫特 中的数据传递方向比较一下:

Re斯威夫特 中的单向数据流

能够看出来,数据是单向传递的,这么做,能够让程序中的数据传递特别清晰,也可以很自在的恒久到难题的各省。

运用的三条件

  • 纯净数据源

整套应用的state,存储在独一二个object中,同有时常间也独有三个store用于存款和储蓄那些object.

  • 场地是只读的

唯一能退换state的不二等秘书诀,即是触发action操作。action是用来说述正在发生的风云的二个指标

  • 因此纯函数修改State

纯函数的难点,也是出自于函数式编制程序观念,大家在中学时学的函数正是纯函数,对于同三个输入,必然有雷同的输出。那就确认保证了数码的可控性,这里的纯函数正是reducer

References

[译]图解Flux[A cartoon guide to Flux]
[译]图解Redux - A cartoon Intro to Redux

稍加时候,你并不想多向的数据流—你想要数据依照三个方向流动:那正是单向数据流。在这些ReSwift 教程中,你想要摆脱常规路径还须要学如何使用 ReSwift框架在二个状态复杂的 Memory Game app - MemoryTunes 中完结单向数据流。

开始

那是三个早已把全路框架大概搭建起来的模版项目,富含了有的骨架代码,和库。GitHub

率先供给做一些预备专门的学问,首先就是要安装这么些app最重视的一些:state

打开AppState.swift 文件,创设一个 AppState 的结构体:

import ReSwift

struct AppState : StateType{

}

这么些协会体定义了整套app的意况。

在创制包括全数的 AppState 的 Store 在此以前,还要创设一个主 Reducer

Reducer 是独一可以直接退换 StoreAppState 的值的地点。唯有Action 能够使得 Reducer 来改换近来前后相继的图景。而 Reducer 更换近日AppState 的值,又在于他承受到的 Action 类型。

瞩目,在程序中唯有贰个 Store, 他也唯有多少个主 Reducer

接下去在AppReducer.swift 中开创主 reducer:

import ReSwift

func appReducer(action: Action, state: AppState?) -> AppState {
  return AppState()
}

appReducer 是五个收下 Action 何况重临更动之后的 AppState 的函数。参数 state 是程序当前的 state。 那个函数能够遵照他收下的 Action 直接改换这么些 状态。以往就能够很轻松的创办多个 AppState 值了。

现行反革命理应创造 Store 来保存 state 了。

Store 包括了方方面面程序当前的地方:那是 AppState 的三个实例。张开AppDelegate.swift ,在 impore UIkit 上边增添如下代码:

import ReSwift

var store = Store<AppState>(reducer: appReducer, state: nil)

这段代码通过 appReducer 创设了一个大局的变量store,appReducer 是以此 Store 的主 Reducer,他带有了收纳到action的时候,store 应该怎么转移的平整。因为那是一对预备职业,所以只是传递了叁个 nil state 进去。

编写翻译运维,当然,什么都看不见。因为还没写啊!

redux状态处理的流程及相关概念

图片 7

image

  • store

Store 正是保存数据的地方,保存着本程序有所的redux管理的多寡,你可以把它看做三个器皿。整个应用只能有一个Store。(四个store是七个指标, reducer会改换store中的有些值)

Redux 提供createStore这一个函数,用来生成 Store。

import { createStore } from 'redux';
const store = createStore(fn);

地方代码中,createStore函数接受另叁个函数作为参数,重临新生成的 Store 对象。这一个fn正是reducer纯函数,日常我们在开拓中也会利用中间件,来优化架构,比如最常用的异步操作插件,redux-thunk,假如同盟redux-thunk来创建store的话,代码示例:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/rootReudcer';

let createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
let store = createStoreWithMiddleware(rootReducer);

redux-thunk的源码及其简单,如下:

// 判断action是否是函数,如果是,继续执行递归式的操作。所以在redux中的异步,只能出现在action中,而且还需要有中间件的支持。
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

同步action与异步action最大的分别是:

一道只回去二个普通action对象。而异步操作中途会重返三个promise函数。当然在promise函数管理达成后也会重临三个普通action对象。thunk中间件就是决断假如回到的是函数,则不传导给reducer,直到检验到是普通action对象,才交由reducer管理。


Store 有以下职务:

  • 提供 getState() 方法猎取 state;
  • 提供 dispatch(action) 方法革新 state;
  • 通过 subscribe(listener) 注册监听器;
  • 因而 subscribe(listener) 重回的函数注销监听器。

一般景观下,大家只要求getState()和dispatch()方法就能够,就可以以消除绝抢先五成标题。

小编们得以自定义中间件

诸如大家自定义三个足以打印出当下的触发的action以及出发后的state变化的中间件,代码改换如下:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/rootReudcer';

let logger = store => next => action => {
    if(typeof action === 'function') {
        console.log('dispatching a function')
    }else{
        console.log('dispatching', action);
    }

    let result = next(action);
    // getState() 可以拿到store的状态, 也就是所有数据
    console.log('next state', store.getState());
    return result;
}

let middleWares = {
    logger, 
    thunk
}
// ... 扩展运算符
let createStoreWithMiddleware = applyMiddleware(...middleWares)(createStore);

let store = createStoreWithMiddleware(rootReducer);

增加补充:大家自定义的中间件,也会有相应的开源插件,redux-logger,人家的更决心。

万一,app中涉及到登入难点的时候,可以应用redux-persist其三方插件,那一个第三方插件来将store对象存款和储蓄到地点,以及从地点复苏数据到store中,比如说保存登入音信,下一次开垦应用能够一向跳过登陆分界面,因为大家眼下的利用属于内嵌程序,不登入的话也进不来,所以并不是它。

  • Action

Action 是贰个对象,描述了接触的动作,仅此而已。大家约定,action 内必得运用三个字符串类型的 type 字段来表示将在试行的动作。平常它长一下这些样子:

{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

除了 type 字段外,action 对象的结构完全由你协调决定,来看一个复杂点的:

{
    type: 'SET_SCREEN_LAST_REFRESH_TIME',
    screenId,
    lastRefreshTime,
    objectId
}

一般性我们会增加多少个新的模块文件来囤积那么些actions types,举个例子大家新建贰个actionTypes.js来保存:

//主页actions
export const FETCH_HOME_LIST = 'FETCH_HOME_LIST';
export const RECEIVE_HOME_LIST = 'RECEIVE_HOME_LIST';
//分类页actions
export const FETCH_CLASS_LIST = 'FETCH_CLASS_LIST';
export const RECEIVE_CLASS_LIST = 'RECEIVE_CLASS_LIST';
//分类详细页actions
export const FETCH_CLASSDITAL_LIST = 'FETCH_CLASSDITAL_LIST';
export const RECEIVE_CLASSDITAL_LIST = 'RECEIVE_CLASSDITAL_LIST';
export const RESET_CLASSDITAL_STATE = 'RESET_CLASSDITAL_STATE'; 
// 设置页actions
export const CHANGE_SET_SWITCH = 'CHANGE_SET_SWITCH';
export const CHANGE_SET_TEXT = 'CHANGE_SET_TEXT';
// 用户信息
export const USER_INFO = 'USER_INFO';

援用的时候,能够:

import * as types from './actionTypes';
  • Action 创立函数(Action Creator)

Action 创制函数 便是生成 action 的方法。“action” 和 “action 创立函数” 那四个概念很轻松混在同步,使用时最佳注意区分。在 Redux 中的 action 创立函数只是轻松的回到一个 action。

import * as types from './actionTypes';
// 设置详情页内容文字主题
let changeText = (theme) => {
    return {
        type: types.CHANGE_SET_TEXT,
        theme
    }
}   

// 函数changeText就是一个简单的action creator。

完整的action文件(setAction.js)

import * as types from './actionTypes';

let setTitle = (value) => {
    return (dispatch, getState) => {
        dispatch(changeValue(value))
    }
}

let setText = (text) => {
    return dispatch => {
        dispatch(changeText(text))
    }
}

// 修改标题颜色主题
let changeValue = (titleTheme) => {
    return {
        type: types.CHANGE_SET_SWITCH,
        titleTheme
    }
}

// 设置详情页内容文字颜色
let changeText = (textColor) => {
    return {
        type: types.CHANGE_SET_TEXT,
        textColor
    }
}

export {
    setText,
    setTitle
};

能够看到上述setTitle、setText函数,再次回到的并非二个action对象,而是回到了多个函数,那一个暗许redux是迫于管理的,那就须要动用中间件管理了,redux-thunk中间件用于拍卖回来函数的函数,下面也介绍了redux-thunk的施用基本方法。

  • Reducer

Store 收到 Action 以往,必需交给三个新的 State,那样 View 才会爆发变化。这种 State 的总括进程就叫做 Reducer。
Reducer 是多个函数,它接受 Action 和当下 State 作为参数,重临三个新的 State。

函数签字:

(previousState, action) => newState

Reducer必得保持相对十足,恒久不要在 reducer 里做那个操作:

  • 修改传入参数;
  • 举办有副效率的操作,如 API 诉求和路由跳转;
  • 调用非纯函数,如 Date.now() 或 Math.random();

完整的Reducer方法,(setReducer.js):

import * as types from '../actions/actionTypes';

const initialState = {
    titleTheme: false,
    textColor: false
}
// 这里一个技巧是使用 ES6 参数默认值语法 来精简代码
let setReducer = (state = initialState, action) => {

    switch(action.type){
        case types.CHANGE_SET_SWITCH:
            return Object.assign({}, state, {
                titleTheme: action.titleTheme,
            })

        case types.CHANGE_SET_TEXT:
            return Object.assign({}, state, {
                textColor: action.textColor
            })

        default:
            return state;
    }
}

export default setReducer

注意:

  • 绝不涂改 state。 使用 Object.assign() 新建了贰个别本。不能这么使用 Object.assign(state, {
    titleTheme: action.titleTheme,
    }),因为它会改动第二个参数的值。你必得把第3个参数设置为空对象。你也足以开启对ES7提案对象开展运算符的补助, 从而使用 { ...state, ...newState } 到达同等的指标。
  • 在 default 情状下再次回到旧的 state。遭逢未知的 action 时,必需要回到旧的 state

有关拆分Reducer

这里只是比喻了二个归纳的职能的reducer,假使有两样的功能,要求统一盘算相当多reducer方法,注意每个reducer 只负担管理全局 state 中它担当的一片段。每种 reducer 的 state 参数都比不上,分别对应它管理的那部分 state 数据。

比如大家这几个项指标reducer文件结构:

图片 8

image.png

里头rootReducer.js就是贰个根reducer文件,使用了Redux 的 combineReducers() 工具类来拓宽打包整合。

/**
 * rootReducer.js
 * 根reducer
 */
import { combineReducers } from 'redux';
import Home from './homeReducer';
import Class from './classReducer';
import ClassDetial from './classDetialReducer';
import setReducer from './setReducer';
import userReducer from './userReducer';

export default rootReducer = combineReducers({
    Home,
    Class,
    ClassDetial,
    setReducer,
    userReducer,
})

与此相类似依照这一个根reducer,可以生成store,请看上文store的创导进度。

首先- ReSwift 是什么?

App Routing

当今能够制造第一个精神的 state了,然而使用 IB 的导航,恐怕是 routing。

App 路由在颇具的架构形式中都以四个挑衅,在 ReSwift 中也是。在 MemoryTunes 中校使用很简短的法子来做这件业务,首先须求经过 enum 定义全部的极限,然后让 AppState 持有当前的极限。AppRouter 就能够响应这么些值的变动,到达路由的指标。

AppRouter.swift 中加多底下的代码:

import ReSwift

enum RoutingDestination: String {
  case menu = "MenuTableViewController"
  case categories = "CategoriesTableViewController"
  case game = "GameViewController"
}

其一枚举代表了app 中的全数 ViewController。

到今日,终于有可以放在你程序状态中的数据了。在那几个例子里面,只有一个state 结构体(AppState), 你也足以在那个 state 里面通过子状态的措施,将境况进行分拣,那是八个很好的实行。

打开 RoutingState.swift 增添如下的子状态结构体:

import ReSwift

struct RoutingState: StateType {
  var navigationState: RoutingDestination

  init(navigationState: RoutingDestination = .menu) {
    self.navigationState = navigationState
  }
}

RoutingState 包罗了 navigationState, 那些事物,就是日前荧屏体现的分界面。

menu 是 navigationState 的私下认可值。若无制定的话,将它设置成这么些app的后期状态。

AppState.swift 中,增添如下代码:

let routingState: RoutingState

今昔 AppState 就有了 RoutingState 那么些子状态。编写翻译一下,会发觉二个谬误。

appReducer 编写翻译可是了!因为我们给 AppState 添加了 routingState,然而在早先化的时候并未把那个东西传进去。未来还索要叁个reducer 来创制 routingState

以后我们独有多个主 Reducer, 跟 state 类型,大家也得以通过 子Reducer 来将 Reducer 划分开来。

RoutingReducer.swift 中增添上边包车型大巴 Reducer

import ReSwift

func routingReducer(action: Action, state: RoutingState?) -> RoutingState {
  let state = state ?? RoutingState()
  return state
}

跟 主 Reducer 差不多, routionReducer 依照接收到的 Action 改变状态,然后将这么些情景重临。到现行反革命,还一直不成立 action 所以若无接收到 state 的话,就 new 三个 RoutingState,然后重返。

子 reducer 肩负创建他们相应的 子状态。

近年来归来 AppReducer.swift 去改动那几个编写翻译错误:

return AppState(routingState: routingReducer(action: action, 
                                         state: state?.routingState))

给 AppState 的初阶化方法中添加了对应的参数。其中的 action 和 state 都以由main reducer 传递步向的。

redux如何与组件结合

以上部分介绍了Redux 涉及的基本概念,上面介绍与组件交互的行事流程。

梳理一下Redux的干活流程:

图片 9

image

1.率先,用户发生 Action。

store.dispatch(action);

2.Store 活动调用 Reducer,而且传入八个参数:当前 State 和吸收接纳的 Action。 Reducer 会重回新的 State 。

let nextState = todoApp(previousState, action);

3.state假若有浮动,Store就能够调用监听函数,组件能够感知state的变动,更新View。

let newState = store.getState();
component.setState(newState);

现实示例1:

图片 10

fsdf.gif

安装页面有个switch按键,能够全局设置标题栏的大旨。

ReSwift 是二个微型的框架,协理您在 斯威夫特 中创制类似 Redux 的架构。ReSwift 有多少个入眼的零件:

订阅 subscribing

还记得 RoutingState 里面极度暗许的 state .menu 吗?他正是 app 暗中同意的场馆。只是你还从未订阅它。

任何的类都能够定于那个 store, 不仅是 View。当叁个类订阅了这么些Store 之后,每一回 state 的更改他都会博得照管。大家在 AppRouter 中订阅那些 Store, 然后收到文告之后,push 一个 Controller

打开 AppRouter.swift 然后再次写 AppRouter

final class AppRouter {

  let navigationController: UINavigationController

  init(window: UIWindow) {
    navigationController = UINavigationController()
    window.rootViewController = navigationController

    // 1
    store.subscribe(self) {
      $0.select {
        $0.routingState
      }
    }
  }

  // 2
  fileprivate func  pushViewController(identifier: String, animated: Bool) {
    let viewController = instantiateViewController(identifier: identifier)
    navigationController.pushViewController(viewController, animated: animated)
  }

  fileprivate func instantiateViewController(identifier: String) -> UIViewController {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    return storyboard.instantiateViewController(withIdentifier: identifier)
  }

}

// MARK: - StoreSubscriber
// 3
extension AppRouter :StoreSubscriber {
  func newState(state: RoutingState) {
    // 4
    let shouldsAnimate = navigationController.topViewController != nil
    pushViewController(identifier: state.navigationState.rawValue, animated: shouldsAnimate)
  }
}

在这段代码中,我们改了 AppRouter 这一个类,然后增添了二个extension。我们看看现实每一步都做了怎么啊!

  1. AppState 今后订阅了全局的 store, 在闭包里面, selct 注解正在订阅 routingState 的变动。
  2. pushViewController 用来开始化,况兼 push 这几个调节器。通过 identifier 加载的 StoryBoard 中的调节器。
  3. AppRouter 响应 StoreSubscriber, 当 routingState 改换的时候,将新的值重返回来。
  4. 根调节器是无需动画的,所以在这几个地方推断一下根调整器。
  5. 当 state 爆发更动,就足以去出 state.navigationState, push 出相应的 controller

AppRouter 以后就就起始化 menu 然后将 MenuTableViewController push 出来

编写翻译运行:

现在 app 中就是 MenuTableViewController 了, 以往当然仍然空的。毕竟大家还未曾起来学 view。

代码拆分:

1.设置开关所在组件:

// SetContainer.js

import React from 'react';
import {connect} from 'react-redux';
import SetPage from '../pages/SetPage';

class SetContainer extends React.Component {
    render() {
        return (
            <SetPage {...this.props} />
        )
    }
}

export default connect((state) => {

    const { setReducer } = state;
    return {
        setReducer
    }

})(SetContainer);

那是容器组件,将SetPage组件与redux结合起来,当中最要害的不二诀假如connect,这一个示例中是将setReducer作为品质传给SetPage组件,关于connect的详解,请移步到connect()。

2.SetPage组件

import React, {
    Component
} from 'react';
import {
    StyleSheet,
    Text,
    Image,
    ListView,
    TouchableOpacity,
    View,
    Switch,
    InteractionManager,
} from 'react-native';

import Common from '../common/common';
import Loading from '../common/Loading';
import HeaderView from '../common/HeaderView';

import {setText,setTitle} from '../actions/setAction';

export default class SetPage extends Component {
    constructor(props){
        super(props);
        this.state = {
            switchValue: false,
            textValue: false
        }

        this.onValueChange = this.onValueChange.bind(this);
        this.onTextChange = this.onTextChange.bind(this);
    }

    componentDidMount() {
        // console.log(this.props)
    }

    onValueChange(bool) {
        const { dispatch } = this.props;
        this.setState({
            switchValue: bool
        })
        dispatch(setTitle(bool));
    }

    onTextChange(bool) {
        const { dispatch } = this.props;

        this.setState({
            textValue: bool
        });

        dispatch(setText(bool));
    }

    render() {
        return (
            <View>
                <HeaderView
                  titleView= {'设置'}
                  />

                <View>
                    <View style={styles.itemContainer}>
                        <Text style={{fontSize: 16}}>全局设置标题主题</Text>
                        <Switch 
                            onValueChange={this.onValueChange}
                            value={this.state.switchValue}
                        />
                    </View>

                    <View style={styles.itemContainer}>
                        <Text style={{fontSize: 16}}>设置详情页文字主题</Text>
                        <Switch 
                            onValueChange={this.onTextChange}
                            value={this.state.textValue}
                        />
                    </View>
                </View>
            </View>
        )
    }
}

const styles = StyleSheet.create({
    itemContainer:{
        paddingLeft: 20,
        paddingRight: 20,
        height: 40,
        flexDirection: 'row',
        justifyContent: 'space-between',
        alignItems: 'center'
    }
})

能够只看全局设置标题核心这一个方法,设置实际情况页文字颜色和他同理。这里能够清楚的观察,顾客切换宗旨switch按键的时候,触发的措施:

dispatch(setTitle(bool));

3.我们查阅一下setTitle这一个action的源码:

// setAction.js
import * as types from './actionTypes';

let setTitle = (value) => {
    return (dispatch, getState) => {
        dispatch(changeValue(value))
    }
}

let setText = (text) => {
    return dispatch => {
        dispatch(changeText(text))
    }
}

// 修改标题主题
let changeValue = (titleTheme) => {
    return {
        type: types.CHANGE_SET_SWITCH,
        // 这里将titleTheme状态返回
        titleTheme
    }
}

// 设置详情页内容文字主题
let changeText = (textColor) => {
    return {
        type: types.CHANGE_SET_TEXT,
        textColor
    }
}

export {
    setText,
    setTitle
};

4.action只是承受发送事件,并不会回来贰个新的state供页面组件调用,它是在reducer中回到的:

// setReducer.js

import * as types from '../actions/actionTypes';

const initialState = {
    titleTheme: false,
    textColor: false
}

let setReducer = (state = initialState, action) => {

    switch(action.type){
        case types.CHANGE_SET_SWITCH:
            return Object.assign({}, state, {
                titleTheme: action.titleTheme,
            })

        case types.CHANGE_SET_TEXT:
            return Object.assign({}, state, {
                textColor: action.textColor
            })

        default:
            return state;
    }
}

export default setReducer

最简便易行的reducer,正是依附开端值和action对象,重临一个新的state,提须求store,那样,页面里能够从store中赢获得那么些全局的state,用于更新组件。

大家只是写了怎么样发送action和接收action发出newState的,上边来看这么些标题组件是何等和redux结合的。

5.HeaderView组件

/**
 * Created by ljunb on 16/5/8.
 * 导航栏标题
 */
import React from 'react';
import {
    StyleSheet,
    View,
    Text,
    Image,
    TouchableOpacity,
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import Common from '../common/common';
import {connect} from 'react-redux';

class HeaderView extends React.Component {

    constructor(props){
        super(props);

        this.state = {

        }
    }

    render() {
        // 这里,在这里
        const { titleTheme } = this.props.setReducer;
        let NavigationBar = [];

        // 左边图片按钮
        if (this.props.leftIcon != undefined) {
            NavigationBar.push(
                <TouchableOpacity
                    key={'leftIcon'}
                    activeOpacity={0.75}
                    style={styles.leftIcon}
                    onPress={this.props.leftIconAction}
                    >
                    <Icon color="black" size={30} name={this.props.leftIcon}/>
                </TouchableOpacity>
            )
        }

        // 标题
        if (this.props.title != undefined) {
            NavigationBar.push(
                <Text key={'title'} style={styles.title}>{this.props.title}</Text>
            )
        }

        // 自定义标题View
        if (this.props.titleView != undefined) {
            let Component = this.props.titleView;

            NavigationBar.push(
                <Text key={'titleView'} style={[styles.titleView, {color: titleTheme ? '#FFF' : '#000'}]}>{this.props.titleView}</Text>
            )
        }


        return (
            <View style={[styles.navigationBarContainer, {backgroundColor: titleTheme ? 'blue' : '#fff'}]}>
                {NavigationBar}
            </View>
        )
    }
}

const styles = StyleSheet.create({

    navigationBarContainer: {
        marginTop: 20,
        flexDirection: 'row',
        height: 44,
        justifyContent: 'center',
        alignItems: 'center',
        borderBottomColor: '#ccc',
        borderBottomWidth: 0.5,
        backgroundColor: 'white'
    },

    title: {
        fontSize: 15,
        marginLeft: 15,
    },
    titleView: {
        fontSize: 15,
    },
    leftIcon: {
       left: -Common.window.width/2 40,
    },
})


export default connect((state) => {

    const { setReducer } = state;
    return {
        setReducer
    }

})(HeaderView);

以此组件同样使用connect方法绑定了redux,形成了容器组件(container component)。

connect真的相当重大,请详细查看官方文书档案,上面有链接。

其余不相干的源委忽略,大旨代码是:

// 拿到全局的state 当有变化的时候,会马上修改
const { titleTheme } = this.props.setReducer;

具体示例2:

图片 11

image.png

动用redux来央浼数据、下拉刷新、上拉加载越多。

1.首先,封装action。

import * as types from './actionTypes';
import Util from '../common/utils'; 
// action创建函数,此处是渲染首页的各种图片
export let home = (tag, offest, limit, isLoadMore, isRefreshing, isLoading) => {
    let URL = 'http://api.huaban.com/fm/wallpaper/pins?limit=';
    if (limit) URL  = limit;
    offest ? URL  = '&max='   offest : URL  = '&max=';
    tag ? URL  = '&tag='   encode_utf8(tag) : URL  = '&tag='

    return dispatch => {
        // 分发事件  不修改状态   action是 store 数据的唯一来源。
        dispatch(feachHomeList(isLoadMore, isRefreshing, isLoading));
        return Util.get(URL, (response) => {
            // 请求数据成功后
            dispatch(receiveHomeList(response.pins))
        }, (error) => {
            // 请求失败
            dispatch(receiveHomeList([]));
        });

    }

}

function encode_utf8(s) {
    return encodeURIComponent(s);
}

// 我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。
let feachHomeList = (isLoadMore, isRefreshing, isLoading) => {
    return {
        type: types.FETCH_HOME_LIST,
        isLoadMore: isLoadMore,
        isRefreshing: isRefreshing,
        isLoading: isLoading,
    }
}

let receiveHomeList = (homeList) => {
    return {
        type: types.RECEIVE_HOME_LIST,
        homeList: homeList,
    }
}
  • feachHomeList表示正在呼吁数据的动作;
  • receiveHomeList表示乞求数据完后的动作;
  • dispatch(feachHomeList(isLoadMore, isRefreshing, isLoading));表示分发央求数据的动作;

2.封装reducer函数

import * as types from '../actions/actionTypes';
// 设置初始状态
const initialState = {
    HomeList: [],
    isLoading: true,
    isLoadMore: false,
    isRefreshing: false,
};

let homeReducer = (state = initialState, action) => {

    switch (action.type) {
        case types.FETCH_HOME_LIST:
            return Object.assign({}, state, {
                isLoadMore: action.isLoadMore,
                isRefreshing: action.isRefreshing,
                isLoading: action.isLoading
            })

        case types.RECEIVE_HOME_LIST:
            // 如果请求成功后,返回状态给组件更新数据
            return Object.assign({}, state, {
            // 如果是正在加载更多,那么合并数据
                HomeList: state.isLoadMore ? state.HomeList.concat(action.homeList) : action.homeList,
                isRefreshing: false,
                isLoading: false,
            })

        case types.RESET_STATE: // 清除数据
            return Object.assign({},state,{
                HomeList:[],
                isLoading:true,
            })
        default:
            return state;
    }
}

export default homeReducer;
  • 此地并不曾管理未有更加大多据的动静。

3.容器组件

import React from 'react';
import {connect} from 'react-redux';
import Home from '../pages/Home';

class HomeContainer extends React.Component {
    render() {
        return (
            <Home {...this.props} />
        )
    }
}

export default connect((state) => {
    const { Home } = state;
    return {
        Home
    }
})(HomeContainer);
  • 此地首若是使用connect函数将Home state绑定到Home组件中,并视作它的props;

4.UI组件

  • 组件挂载供给数据
...
let limit = 21;
let offest = '';
let tag = '';
let isLoadMore = false;
let isRefreshing = false;
let isLoading = true;
...
componentDidMount() {
    InteractionManager.runAfterInteractions(() => {
      const {dispatch} = this.props;
      // 触发action 请求数据
      dispatch(home(tag, offest, limit, isLoadMore, isRefreshing, isLoading));
    })
}
...
  • 下拉刷新
// 下拉刷新
  _onRefresh() {
    if (isLoadMore) {
      const {dispatch, Home} = this.props;
      isLoadMore = false;
      isRefreshing = true;
      dispatch(home('', '', limit, isLoadMore, isRefreshing, isLoading));
    }
  }
  • 上拉加载越多
// 上拉加载
  _onEndReach() {

    InteractionManager.runAfterInteractions(() => {
      const {dispatch, Home} = this.props;
      let homeList = Home.HomeList;
      isLoadMore = true;
      isLoading = false;
      isRefreshing = false;
      offest = homeList[homeList.length - 1].seq
      dispatch(home(tag, offest, limit, isLoadMore, isRefreshing, isLoading));
    })

  }
  • render方法
render() {
    // 这里可以拿到Home状态
    const { Home,rowDate } = this.props;
     tag = rowDate;

    let homeList = Home.HomeList;
    let titleName = '最新';
    return (
      <View>
        <HeaderView
          titleView= {titleName}
          leftIcon={tag ? 'angle-left' : null}
          />
        {Home.isLoading ? <Loading /> :
          <ListView
            dataSource={this.state.dataSource.cloneWithRows(homeList) }
            renderRow={this._renderRow}
            contentContainerStyle={styles.list}
            enableEmptySections={true}
            initialListSize= {10}
            onScroll={this._onScroll}
            onEndReached={this._onEndReach.bind(this) }
            onEndReachedThreshold={10}
            renderFooter={this._renderFooter.bind(this) }
            style={styles.listView}
            refreshControl={
              <RefreshControl
                refreshing={Home.isRefreshing}
                onRefresh={this._onRefresh.bind(this) }
                title="正在加载中……"
                color="#ccc"
                />
            }
            />
        }
      </View>

    );

  }

迄今,一个差不离的Reducer程序实现了,我们多少总括一下:

  • 一体应用独有三个store,用来保存所有的意况,视图无需自身维护状态。
  • 视图通过connect函数绑定到store,当store状态变化后,store会通告视图刷新。
  • 接触一个action之后,会透过也许N个reducers管理,最终根reducer会将持有reducers管理今后的场地合併,然后提交store,store再布告视图刷新。

正文的源码地址: 案例Demo

  • Views: 相应 store 的转移何况将她们来得在显示屏上。Views 发送 Actions
  • Actions: 在应用中 起初化三个 state 的退换。 一个 Action 被三个Reducer 持有。
  • Reducers: 直接退换使用的 state,何况被寄放在 Store 中。
  • Store: 存款和储蓄当前利用状态的值。别的零件像 Views 能够订阅和呼应它的变动。

View

别的东西都或许是叁个 StoreSubscriber, 不过相当多状态下都以 view 层在响应状态的改换。今后是让 MenuTableViewController 来呈现五个不一样的 menu 了。

MenuState.swift, 创造对应的 Reducer

import ReSwift

struct MenuState: StateType {
  var menuTitles: [String]

  init() {
    menuTitles = ["NewGame", "Choose Category"]
  }
}

MenuState 有一个 *menuTitles, 那脾性格正是 tableView 的 title

MenuReducer.swift 中,成立这些 state 对应的 Reducer:

import ReSwift

func menuReducer(action: Action, state: MenuState?) -> MenuState {
  return MenuState()
}

因为 MenuState 是静态的,所以无需去处理情状的浮动。所以那边只需求轻易的回来一个新的 MenuState

回到 AppState.swift 中, 添加

let meunState: MenuState

编写翻译又倒闭了,然后要求到 AppReducer.swift 中去修改那几个编写翻译错误。

return AppState(routingState: routingReducer(action: action,
                                  state: state?.routingState),
      meunState: menuReducer(action: action, state: state?.meunState))

近来有了 MenuState, 接下来正是要订阅它了。

先在开发 MenuTableViewController.swift, 然后将代码改成那样:

import ReSwift

final class MenuTableViewController: UITableViewController {
  // 1
  var tableDataSource: TableDataSource<UITableViewCell, String>?

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // 2
    store.subscribe(self) {
      $0.select {
        $0.menuState
      }
    }
  }

  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    // 3
    store.unsubscribe(self)
  }

}

// MARK: - StoreSubscriber
extension MenuTableViewController: StoreSubscriber {
  func newState(state: MenuState) {
    // 4
    tableDataSource = TableDataSource(cellIdentifier: "TitleCell", models: state.menuTitles) {
      $0.textLabel?.text = $1
      $0.textLabel?.textAlignment = .center
      return $0
    }

    tableView.dataSource = tableDataSource
    tableView.reloadData()
  }
}

今昔大家来拜访这段代码做了如何?

  1. TableDataSource 包涵了UITableView data source 相关的事物。
  2. 订阅了 menuState
  3. 注销订阅
  4. 这段代码便是兑现 UITableView 的代码,在此时能够很明朗的观察 state 是怎么成为 view 的。

或者曾经意识了,Re斯维夫特命全权大使用了许多值类型变量,并不是指标类型。况兼推荐应用表明式的 UI 代码。为啥吗?

StoreSubscriber 中定义的 newState 回调了事态的变动。你大概会通过那样的章程去接货那一个值

final class MenuTableViewController: UITableViewController {
  var currentMenuTitlesState: [String]
  ...

而是写声明式的 UI 代码,能够很显眼的敞亮 state 是怎么转变来 View 的。在这一个事例中的难点的 UITableView 并从未如此的API。那正是自个儿写 TableDataSource 来桥接的源委。假如你感兴趣的话能够去探视那几个 TableDataSource.swift

编写翻译运维,就能够看到了:

ReSwift 有了众多独到之处:

Action

搞活了 View 接下来就来写 Action 了。

Action 是 Store 中多少变动的缘由。贰个 Action 正是一个有成都百货上千变量结构体,那写变量也是这些 Action 的参数。 Reducer 管理一名目多数的 action, 然后转移 app 的景观。

咱俩前几天先创建一个 Action, 张开 RoutingAction.swift

import ReSwift

struct RoutingAction: Action {
  let destination: RoutingDestination
}

RoutingAction 改换方今的 routing 终点

后天,当 menu 的 cell 被点击的时候,派发一个 action。

MenuTableViewController.swift 中增多底下的代码:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    var routeDestination: RoutingDestination = .categories
    switch indexPath.row {
    case 0: routeDestination = .game
    case 1: routeDestination = .categories
    default:break
    }
    store.dispatch(RoutingAction(destination: routeDestination))
  }

这段代码,依据选拔的 cell 设置不一致的 routeDestination 然后用dispatch 方法派发出去。

以此 action 被派发出去了,不过,还一向不被另外的 reducer 给补助。未来去 RoutingReducer.swift 然后做一下相应的修改。

  var state = state ?? RoutingState()

  switch action {
  case let routingAction as RoutingAction:
    state.navigationState = routingAction.destination
  default: break
  }
  return state

switch 语句用来判别是或不是传入的 action 是 RoutingAction。借使是,就修改 state 为这么些 action 的 destination

编写翻译运转,今后点击 item , 就能够相应的 push 出 controller。

  • 残忍的限定: 大家很轻便贪图方便将一部分代码放在了不属于它的地方。ReSwift对于就要产生如何和在何地发生有严苛的界定。
  • 单向数据流: 那么些完毕了多向数据流的选择的代码易读性不高並且难以调节和测量试验。四个改动能够引致某个列的平地风波将数据发送到整个程序。单向流动进一步便于预测而且大大减弱了读替代码所须求的知识。
  • 轻松测量检验: 抢先八分之四逻辑都包涵在 Reducer 中,并且那是一个纯函数(纯函数的表征:应当要有八个输入值,並且一定会有重回值,只要输入值同样,它的再次来到值也必然是同样的,它的值不会受外围条件的熏陶)。
  • 阳台非亲非故: 全部 Re斯维夫特 的要素- Stores, Reducers, 和 Actions --都以阳台非亲非故的。你能够很轻便的在iOS, macOS, 只怕 tvOS中复用。

Updating the State

如此那般去实现导航可能是由短处的。当你点击 "New Game" 的时候,RoutingStatenavigationState 就会从menu 变成 game。 可是当您点击 controller 的回到开关的时候,navigationState 却从没改观。

在 Re斯威夫特 中,让情形跟 UI 同步是十分重大的,不过那又是最轻松搞忘的东西。非常是向下边那样,由 UIKit自控的东西。

我们能够在 MenutableViewController 出现的时候更新一下以此景况。

MenuTableViewController.swiftviewWillAppear: 方法中,添加:

store.dispatch(RoutingAction(destination: .menu))

如此这般就可见在上头的标题出现的时候消除这几个难点。

运营一下呢?呃... 完全乱了。也说不定会看到一个家徒四壁。

打开 AppRouter.swift, 你会看到每一遍收到到三个新的 navigationState 的时候,都会调用 pushViewController 方法。也正是说,每便响应就能够 push 八个 menu 出来!

由此大家还非得在 push 以前明确那一个 controller 是否正在显示器中。所以大家修改一下 pushViewController 这么些法子:

fileprivate func  pushViewController(identifier: String, animated: Bool) {
    let viewController = instantiateViewController(identifier: identifier)
    let newViewControllerType = type(of: viewController)
    if let currentVc = navigationController.topViewController {
      let currentViewControllerType = type(of: currentVc)
      if currentViewControllerType == newViewControllerType {
        return
      }
    }
    navigationController.pushViewController(viewController, animated: animated)
}

地点的不二等秘书诀中,通过 type(of:) 方法来防止当前的 topViewController 跟 要推出去的 Controller 实行自己检查自纠。假诺相等,就一贯 return

编译运转,那时候,又一切平日了。

当 UI 发生变化的时候更新当前的情景是相比复杂的作业。那是写 ReSwift的时候供给求消除的一件事情。幸亏他不是那么左近。

为了呈现自身对数据流的了然,查看下边包车型大巴板栗。多少个应用在 VIPE福睿斯补助下在它的组件间落成了多向数据流:

Category

后天,我们一而再来完结 CategoriesTableViewController 这一局地跟在此以前的一部分比起来更目迷五色一些。这些分界面须要允许顾客来抉择音乐的品类,首先,我们在CategoriesState.swift 中增添响应的图景。

import ReSwift

enum Category: String {
  case pop = "Pop"
  case electrinic = "Electronic"
  case rock = "Rock"
  case metal = "Metal"
  case rap = "Rap"
}

struct CategoriesState: StateType {
  let categories: [Category]
  var currentCategorySelected: Category

  init(currentCategory: Category) {
    categories = [.pop, .electrinic, .rock, .metal, .rap]
    currentCategorySelected = currentCategory
  }
}

这一个枚举定义了有个别音乐的等级次序。CategoriesState 满含了一个数组的档案的次序,以及当前选用的种类。

ChangeCategoryAction.swift 中增加这个代码:

import ReSwift

struct ChangeCategoryAction: Action {
  let categoryIndex: Int
}

此地定义了对应的 action, 使用 categoryIndex 来研究对应的音乐体系。

今后来贯彻 Reducer了。 那一个 reducer 需求承受 ChangeCategoryAction 然后将新的 state 保存起来。展开 CategoryReducer.swift

import ReSwift

private struct CategoriesReducerConstants {
  static let userDefaultCategoryKey = "currentCategoryKey"
}

private typealias C = CategoriesReducerConstants

func categoriesReducer(action: Action, state: CategoriesState?) -> CategoriesState {
  var currentCategory: Category = .pop
  // 1
  if let loadedCategory = getCurrentCategoryStateFromUserDefaults() {
    currentCategory = loadedCategory
  }
  var state = state ?? CategoriesState(currentCategory: currentCategory)

  switch action {
  case let changeCategoryAction as ChangeCategoryAction:
    // 2
    let newCategory = state.categories[changeCategoryAction.categoryIndex]
    state.currentCategorySelected = newCategory
    saveCurrentCategoryStateToUserdefaults(category: newCategory)
  default: break
  }
  return state
}

// 3
private func getCurrentCategoryStateFromUserDefaults() -> Category?
{
  let userDefaults = UserDefaults.standard
  let rawValue = userDefaults.string(forKey: C.userDefaultCategoryKey)
  if let rawValue = rawValue {
    return Category(rawValue: rawValue)
  } else {
    return nil
  }
}

// 4
private func saveCurrentCategoryStateToUserdefaults(category: Category) {
  let userDefaults = UserDefaults.standard
  userDefaults.set(category.rawValue, forKey: C.userDefaultCategoryKey)
  userDefaults.synchronize()
}

跟另外的 Reducer 一样,这几个措施实现了一晃比较复杂的景况的转移,并且将选拔之后的状态通过 Userdefault 持久化。

  1. 从 UserDefault 中获得 category, 然后赋值给 CategoriesState
  2. 在接受到 ChangeCategoryAction 的时候更新情况,然后保留下去
  3. 从 Userdefault 中获取state
  4. 将 state 保存在 UserDefault 中

3、4 中的五个方式都以法力很单纯的主意,並且是大局的。你也能够把他们放在三个类依然结构体中。

接下去很当然的,就能够供给在 AppState 中增添新的地方。展开 AppState.swift 然后增添对应的气象:

let categoriesState: CategoriesState

然后去 AppReducer.swift 中去修改对应的错误

  return AppState(routingState: routingReducer(action: action,
                                               state: state?.routingState),
                  meunState: menuReducer(action: action, state: state?.meunState),
        categoriesState: categoriesReducer(action: action, state: state?.categoriesState))

这两天还索要 View 了。未来内需在 CategoriesViewController 中去写这一部分的 View

import ReSwift

final class CategoriesTableViewController: UITableViewController {
  var tableDataSource: TableDataSource<UITableViewCell, Category>?

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    //1
    store.subscribe(self) {
      $0.select {
        $0.categoriesState
      }
    }
  }

  override func viewWillDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    store.unsubscribe(self)
  }

  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // 2
    store.dispatch(ChangeCategoryAction(categoryIndex: indexPath.row))
  }
}

extension CategoriesTableViewController: StoreSubscriber {
  func newState(state: CategoriesState) {
    tableDataSource = TableDataSource(cellIdentifier: "CategoryCell", models: state.categories) {
      $0.textLabel?.text = $1.rawValue
      // 3
      $0.accessoryType = (state.currentCategorySelected == $1) ? .checkmark : .none
      return $0
    }
    tableView.dataSource = tableDataSource
    tableView.reloadData()
  }
}

这有的的代码跟 MenuTableViewController 大概。注释中标识的内容分别是:

  1. viewWillAppear 中订阅 categoriesState 的改变,然后在 viewillDisappear 中打消订阅。
  2. 将事件派发出去
  3. 标识选用的情形

富有的事物都写好了,未来试一下!

图片 12VIPER – Multidirectional data flow.png

异步职务

怎么都跑不了这些话题,那在 ReSwift 也很有益于。

场景:从 iTunes的 API 中去获取照片。首先要求创立对应的 state, reducer 以及有关的 action.

打开 GameState.swift 添加

import ReSwift

struct GameState: StateType {
  var memoryCards: [MemoryCard]
  // 1
  var showLoading: Bool
  // 2
  var gameFinishied: Bool
}

这段代码定义了 Game 的图景。

  1. loading 的 黄华,是或不是存在
  2. 玩耍是还是不是终止

接下去是Reducer GameReducer.swift:

import ReSwift

func gameReducer(action: Action, state: GameState?) -> GameState {
  let state = state ?? GameState(memoryCards: [],
                                 showLoading: false,
                                 gameFinishied: false)
  return state
}

这段代码正是轻易的创始了一个 GameState, 稍后会再回去这一个地点的。

AppState.swift 中,加多对应的图景

let gameState: GameState

修改 AppReducer.swift 中出现的编写翻译错误

return AppState(routingState: routingReducer(action: action,
                                               state: state?.routingState),
                  meunState: menuReducer(action: action, state: state?.meunState),
                  categoriesState: categoriesReducer(action: action, state: state?.categoriesState),
                  gameState: gameReducer(action: action, state: state?.gameState))

意识了规律了啊,在每一遍写完 Action/Reducer/State之后应该做什么都以可见何况很轻易的。这种气象,得益于Re斯维夫特的单向数据特效和残忍的代码约束。独有 Reducer 可以转移 app 的 Store,独有 Action 能够接触这种响应。那样做力所能及让您通晓在上头地点找代码,在哪些地方做新功能。

明天上马定义 Action, 这几个 action 用来更新卡牌。在 SetCardsAction.swift

import ReSwift

struct SetCardsAction: Action {
  let cardImageUrls: [String]
}

其一 action 用来设置 GameState 中图纸的U奥迪Q5L

现今开端希图程序中率先个异步行为呢!在 FetchTumesAction.swift 中,增多底下的代码:

import ReSwift

func fetchTunes(state: AppState, store: Store<AppState>) -> FetchTunesAction {
  iTunesAPI.searchFor(category: state.categoriesState.currentCategorySelected.rawValue) {
    store.dispatch(SetCardsAction(cardImageUrls: $0))
  }
  return FetchTunesAction()
}


struct FetchTunesAction: Action{}

fetchTunes 通过 itunesAPI 获取了图片。然后在闭包中将结果派发出来。 Re斯维夫特 中的异步任务便是这样轻松。

fetchTunes 重回二个 FetchTunesAction 这么些 action 是用来证实恳求的。

打开 OpenReducer.swift 然后增加对那七个 action 的支撑。把 gameReducer 中的代码改成上面那样:

 var state = state ?? GameState(memoryCards: [],
                                 showLoading: false,
                                 gameFinishied: false)
  switch action {
  // 1
  case _ as FetchTunesAction:
    state = GameState(memoryCards: [],
                      showLoading: true,
                      gameFinishied: false)
  // 2
  case let setCardsAction as SetCardsAction:
    state.memoryCards = generateNewCards(with: setCardsAction.cardImageUrls)
    state.showLoading = false
  default:break
  }
  return state

这段代码,正是基于实际的 action 做不一致的作业。

  1. FetchTunesAction, 设置 showLoading 为 true
  2. Set卡德sAction, 打乱卡牌,然后将 showLoading 设置为 false。 generateNewCards 方法能够在 MemoryGameLogic.swift 中找到

于今初始写 View

CardCollectionViewCell.swift 中加多底下的方式:

  func configCell(with cardState: MemoryCard) {
    let url = URL(string: cardState.imageUrl)
    // 1
    cardImageView.kf.setImage(with: url)
    // 2
    cardImageView.alpha = cardState.isAlreadyGuessed || cardState.isFlipped ? 1 : 0
  }

configCell 那几个点子做了上边两件事情:

  1. 动用 Kingfisher 来缓存图片
  2. 决断是还是不是出示图片

下一步,实现 CollectionView。在 gameViewCotroller.swift 倒入 import ReSwift 然后在 showGameFinishedAlert 上边增多下边包车型客车代码:

 var collectionDataSource: CollectionDataSource<CardCollectionViewCell, MemoryCard>?
  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    store.subscribe(self) {
      $0.select {
        $0.gameState
      }
    }
  }

  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    store.unsubscribe(self)
  }

  override func viewDidLoad() {
    // 1
    store.dispatch(fetchTunes)
    collectionView.delegate = self
    loadingIndicator.hidesWhenStopped = true

    // 2
    collectionDataSource = CollectionDataSource.init(cellIdentifier: "CardCell", models: []) {
      $0.configCell(with: $1)
      return $0
    }
    collectionView.dataSource = collectionDataSource
  }

鉴于并未有写 StoreSubscriber ,所以这里会有一小点的编译错误。大家先借使已经写了。这段代码,首先是订阅了注销订阅 gameState 然后:

  1. 派发 fetchTunes 来收获图片
  2. 行使 CollectiondataSource 来布局 cell 相关消息。

未来我们来增添 StoreSubscriber :

extension GameViewController: StoreSubscriber {

  func newState(state: GameState) {
    collectionDataSource?.models = state.memoryCards
    collectionView.reloadData()
    // 1
    state.showLoading ? loadingIndicator.startAnimating() : loadingIndicator.stopAnimating()
       // 2
    if state.gameFinishied {
      showGameFinishedAlert()
      store.dispatch(fetchTunes)
    }
  }
}

这段代码实现了 state 改动的时候对应的改造。他会更新 dataSource

  1. 更新 loading indicator 的状态。
  2. 当娱乐结束时,弹窗

今昔,运维一下吗!

与之相比,在 Re斯维夫特 上构建利用的单向数据流:

Play

游戏的逻辑是: 让客户翻转两张卡牌的时候,假诺它们是一眼的,就让他们保持,假设差异样就翻回到。顾客的任务是在尽恐怕少的品味之后翻转全数的卡牌。

于今亟待三个扭曲的风浪。在 OpenCardAction.swift 中增添代码:

import ReSwift

struct FlipCardAction: Action{
  let cardIndexToFlip: Int
}

当卡牌翻转的时候: FlipCardAction 使用 cardIndexToFlip 来更新 gameState 中的状态。

下一步修改 gamereducer 来扶助那么些 action。张开 GameReducer.swift 增加下边临应的case

  case let flipCardAction as FlipCardAction:
    state.memoryCards = flipCard(index: flipCardAction.cardIndexToFlip,
                                 memoryCards: state.memoryCards)
    state.gameFinishied = hasFinishedGame(cards: state.memoryCards)

对 FlipCardAction 来说, flipCard 改动卡牌的景色。hasFinishedGame 会在戏耍甘休的时候调用。七个办法都足以在 MemoryGameLogic.swift 中找到。

最后三个难题是在点击的时候,把扭曲的 action 派发出去。

GameViewController.swift 中,找到 UICollectionViewDelegate 这个 extension。在 collectionView(_:didSelectItemAt:) 中添加:

store.dispatch(FlipCardAction(cardIndexToFlip: indexPath.row))

当卡牌被增选的时候,关联的indexPath.row 就能随着 FlipcardAction 被派发出去.

再运转一下,就能意识!

图片 13ReSwift– Unidirectional data flow.png因为数量只好依据一个侧向流动,那更加直观地跟随代码並且追踪你利用中的难题。

结束语

模版项目曾经全部项目都在 GitHub

Re斯威夫特 不唯有是大家前天提到的剧情。他还以相当多:

  • Middleware: 中间件。swift这段日子还未有很好的不二诀窍来做切面。不过Re斯威夫特 消除了那几个标题。能够利用Re斯维夫特 的 [Middleware] 脾性来化解这些题目。他能够令你轻易的断面(logging, 总结, 缓存)。
  • Routing: 在那么些 app 中曾经落到实处了温馨的 Routing, 还应该有个更通用的减轻方案ReSwift-Routing 单那在社区要么二个还一直不完全减轻的主题素材。说不定化解它的人正是您!
  • Testing: ReSwift 或者是最低价测量试验的框架了。 Reducer 富含了你必要测量检验的具备代码。他们都以纯的机能函数。这种函数在接受了同二个input 总是回到同叁个值,他们不回放重于程序当前的地方。
  • Debugging: Re斯维夫特的保有情形都在三个结构体中定义,而且是单向数据流的,debug 会非常的回顾,以至你还足以用 ReSwift-Recorder 来记录下导致 crash 的事态
  • Persistence: 因为具有的情景都在二个位置,拓宽和百折不挠都以很轻松的业务。缓存离线的数码也是一个相比麻烦的架构难题,不过Re斯维夫特 化解了那几个标题。
  • others: Redux 架构并非二个库,它是一种编制程序范式,你也能够团结完结一套,还应该有 Katana 或者 ReduxKit 也能够做那事

尽管您想学习越来越多关于 Re斯威夫特 的事物,能够看 Re斯维夫特 作者 Benjamin Encz 的发言录制

Christian Tietze’s blog 的博客上有非常多风趣的例证。

那篇小说翻译自Ray wenderlich ReSwift Tutorial: Memory Game App]

一齐始大家要下载二个最先的品类,那之中蕴涵了部分代码骨架和框架,富含Re斯维夫特,而且随着你的不断深刻将会学到非常多。

首先,你必要设置一下 Re斯维夫特的标准。以设置使用的中央:state 为初始。

打开 AppState.swift 并且创制叁个 AppState 结构体服从 StateType 协议:

import ReSwiftstruct AppState: StateType { }

这些结构体将概念了动用的万事 state。

在您创设一个包罗 AppStore 值的 Store 在此以前,你只好创制首要的 Reducer

图片 14reducer.pngReducers 是唯一的块能够一向更改近期 AppState 的值,并且被 Store 持有。只有 Actions能够初阶化二个 Reducer来改换近年来使用的事态。Reducer 依照它接受的 Action 的情景来退换近期的 AppStore 的值。

Note: 在使用中独有独一多个 Store,並且它唯有独一二个重中之重的 Reducer

AppReducer.swift 中开创应用的主要的 reducer

import ReSwiftfunc appReducer(action: Action, state: AppState?) -> { return AppState()}

AppReducer 是三个接受三个 Action 参数和再次来到改造了的 AppState 的函数。state 是当前使用的场合;那个艺术根据它接受的 action 的花色来改动 state。未来,那很轻巧就创办了二个新的 AppState 值---你将会将其归来一旦配置好了 Store。因为 reducer 有了七个 state 所以是时候创造了个 Store 了,该做些行动了。

图片 15store.pngStore 满含了你的全体应用的动静:那正是您的 AppState 结构体的值。打开 AppDelegate.swift 何况用一下的代码代替 import UIKit:

import ReSwift//TODOvar store = Store<AppState>(reducer: appReducer, state: nil)

那边通过传播 appReducer 来伊始化贰个大局的 Store 变量。appReducer 是 Stores 的首要的 Reducer,这里带有的证实证明了 store 是何许改动的当它接受二个Action。因为那是最早的创制,并非迭代的变动,所以你只需传入一个空的 state。营造何况运营来有限支撑应用得要编写翻译:

图片 16It’s not very exciting… But at least it works.png

是时候在利用中创制第三个实际的 state 了。你将从导航分界面恐怕路由伊始。应用中的路由对于各样架构都是三个挑战,不只是 ReSwift。你就要在 MemoryTune中动用一种特别轻便的章程,你要将持有的目标地定义在贰个枚举类型中,並且你的 AppState 将会持有当前的指标地的值。AppRouter 将会相应它的值的改变况且将准确的页面突显在荧屏上。张开 AppRouter.swift 並且用一下代码代替 import UIKit

import ReSwiftenum RoutingDestination: String { case menu = "MenuTableViewController" case categories = "CategoriesTableViewController" case game = "GameViewController"}

其一枚举类型代表了你的行使中的全体页面。

您到底有部分东西得以积攒在利用的 State中。整个应用中只有唯一三个主state结构(AppState 在这些事例中),不过你能够将动用的处境分成一些子气象並且被那几个主state所引用。

因为那是贰个很好的施行,所以你将要组织那些state变量放到子状态的构造总。打开RoutingState.swift 并且为路由增添一下子动静结构:

import ReSwiftstruct RoutingState: StateType { var navigationState: RoutingDestination init(navigationState: RoutingDestination = .menu) { self.navigationState = navigationState }}

RoutingState 包含了 navigationState---那象征了显示屏上的页面。

Note: navigationState 的暗中认可值是 menu。要是您未曾经在路由初叶化的时候钦点它的情景,那么隐式地使它变成应用的私下认可值。

AppState.swift 中,将弹指间代码加多到结构体中:

let routingState: RoutingState

编写翻译并运维,然则你会发觉多个主题材料:

图片 17missingInit.pngappReducer 不再被编写翻译!那是因为你将 routingState 添加到 AppState ,可是并从未在暗中同意的发轫化中传送任何值。为了创设 routingState, 你需求二个 reducer。独有一个主 Reducer 方法,可是就像是 state 同样, reducers 应该被分成五个子 reducers。图片 18Sub-State and Sub-Reducers.png为了导航加多一下 ReducerroutingReducer.swift :

import ReSwiftfunc routingReducer(action: Action, state: RoutingState?) -> RoutingState { let state = state ?? RoutingState() return state}

和主 Reducer 一样,routingReducer 改换 state 依赖它接受的 action,然后就重回它。将来您还从未别的 actions, 所现在后创立贰个新的 RoutingState 如果 state 是 nil 何况再次回到它。子reducers 负担初始化与它们相关的子 states 的值。回到 AppReducer.swift 来管理编写翻译警告。修改 appReducer 的内容来与下部的相契合:

return AppState(routingState: routingReducer(action: action, state: state?.routingState))

AppState 开首化的时候增添 routingState 参数。从主 reducer 传入 actionstateroutingReducer 来创建新的 state。熟知这一套操作,因为您将会在您创设的子 state 和子 reducer 中重复这一动作。

还记得 RoutingState 中的默许值 menu 吗?那个实在是你的施用的近来处境。你不会在别的地方订阅它。任何类都足以订阅 Store,不只是视图。当二个类订阅了 Store, 每当当前的 state 可能是 子 state 产生改换那些类都会接收公告。你将在在 AppRouter 做那个以便它在 UINavigationController 中退换这两天的荧屏当 routingState 退换的时候。

打开 AppRouter.swift 而且用一下的代码替代 AppRouter:

final class AppRouter { let navigationController: UINavigationController init(window: UIWindow) { navigationController = UINavigationController() window.rootViewController = navigationController //1 store.subscribe { $0.select { $0.routingState } } } //2 fileprivate func pushViewController(inentifier: String, animated: Bool) { let viewController = instantiateViewController(indetifier: indetifier) navigationController.pushViewController(viewController, animated: animated) } private func instantiateViewController(identifier: String) -> UIViewController { let storyboard = UIStoryboard(name: "Main", bundle: nil) return storybard.instantiateViewController(withIdentifier: indentifier) }}//MARK: - StoreSubscriber//3extension AppRouter: StoreSubscriber { func newState(state: RoutingState) { //4 let shouldAnimate = navigaionController.topViewController != nil //5 pushViewController(indetifier: state.navigationState.rawValue, animated: shouldAnimate) }}

如上的代码,你更新了 AppRouter 并且增多了一个扩充。让大家看看这一个是做怎么着的:

  1. AppState 现在订阅了大局的 store。在闭包中, select 代表了您特别订阅了 routingState 的改变。
  2. pushViewController 将被用来伊始化和将赋予的视图推到导航栈中。这里运用了instantiateViewController措施,那几个措施依据传入的identifier来加载视图。
  3. 使 AppRouter 遵循 StoreSubscriber 左券来收获newState回到当 routingState 产生转移。
  4. 若果当前的是根视图调节器那么你将不需求动画,所以所以检查当前的指标地是或不是是推向根视图。
  5. 当状态改变的时候,你将依赖 state.navigationStaterawValue---那是视图调整器的名字,将新的目标地视图推向 UINavigationController
  6. AppRouter 未来将要相应伊始值 menu并且将 MenuTableViewController 推到导航调控器中。

编写翻译运营并查阅:

图片 19Menu.png你的行使体现了空的 MenuTableViewController 。你将接纳按键来填充它,它将会导航到任何视图在下一章会呈报。图片 20View.png任何事物都能够用作 StoreSubscriber ,但经常来讲是视图来对号入座意况的变动。你的指标是使 MenuTableViewController 体现几种分裂的美食做法按键。是时候开始你的State/Reducer 操作了。展开 MenuState.swift 而且创建三个景色和下部的等同:

import ReSwiftstruct MenuState: StateType { var menuTitles: [String] init() { menuTitles = ["New Game", "Choose Category"] }}

MenuState 包含了 menuTitles,你将使用这些来初叶化展现在 table view 上的标题。展开 MenuReducer.swift , 用以下的代码为这些情状创设三个Reducer:

import ReSwiftfunc menuReducer(action: Action, state: MenuState?) -> MenuState { return MenuState()}

因为 MenuState 是静态的,你不要挂念状态的改换。所以就归纳的回到了三个新的场馆就好了。回到 AppState.swift,在 AppState 的尾部增添 MenuState

let menuState: MenuState

编写翻译器将会报错因为你又三回修改了默许的开始化操作。在AppReducer.swift 中,像上面同样修改 AppState 初始化:

return AppState( routingState: routingReducer(action: action, state: state?.routingState), menuState: menuReducer(action: action, state: state?.menuState))

现今你有了 MenuState,是时候订阅它了还要用它来渲染菜单视图了。张开 MenuTableViewController.swift再便是将原来的代码替换为上面包车型地铁:

import ReSwiftfinal class MenuTableViewController: UITableViewController { //1 var tableDataSource: TableDataSource<UITableViewCell, String?> override func viewWillAppear(_ animated: Bool) { super.viewWillAppear //2 store.subscribe { $0.select { $0.menuState } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear //3 store.unsubscribe }} // MARK: - StoreSubscriber extension MenuTableViewController: StoreSubscriber { func newState(state: MenuState) { tableDataSource = TableDataSource(cellIdentifier: "TitleCell", models: state.menuTitles) { cell, model in cell.textLable?.text = model cell.textLable?.textAlignment = .center return cell } tableView.dataSource = tableDataSource tableView.reloadData() } }

那一个调控器现在订阅了 MenuState的改造而且证明式的渲染了状态。

  1. TableDataSource 是被含有在了运转器中还要作为UITableView的叁个注解式的数据源。
  2. viewWillAppear 中订阅 menuState 。以往每当menuState 退换的时候 , 你将选用到 newState 的回调。
  3. 退订,要是须求的话。
  4. 那是声称的部分。那是您填充UITableView 的地点。你能够很了然地看来代码中状态是怎么样转化到视图中的。

Note: 你只怕曾经注意到了,ReSwift喜欢不可变性-重度使用结构体而不是目的。一样鼓劲你来写注明式的UI代码。为啥呢?

newState 回调--定义在StoreSubscriber 传递了状态的改造。你只怕会在性质中捕获状态的值,就像这么:

final class MenuTableViewController: >UITableViewController { var currentMenuTitlesState: [String]....}

只是编写申明式的UI代码清楚地体现了情状是何许转化成视图,这种办法更加的绝望。

净并且进一步便于模仿。这么些例子中的难题是 UITableView并从未申明式的API。那正是干吗本人创设了 TableDataSource.swift

编写翻译并运转,你将看到菜单选项:

图片 21MenuView.png图片 22Action.png未来您有菜单选项,倘使它们展开新的页面那么就能很棒。是时候写你的率先个事件了。事件改造了Store。一个事件是一个简便的结构用来积攒变量:事件的参数。一个Reducer 持有贰个被分发的时刻还要根据事件的等级次序和它的参数来改变使用的事态。在 RoutingAction.swift 中创建五个平地风波:

import ReSwiftstruct RoutingAction: Action { let destination: RoutingDestination}

RoutingAction 改变最近的路由destination

现行反革命您就要分发 RoutingAction 当贰个菜谱开关被点按。

打开 MenuTableViewController.swift 何况增进一下代码到 MenuTableViewController:

override func tableView(_ tableView: UITableView, disSelectRowAt indexPath: IndexPath) { var routeDestination: RoutingDestinationi = .categories switch(indexPath.row) { case 0: routeDestination = .game case 1: routeDestination = .categories default: break } //这里通过 store 分发了一个 actioin,这个 action 带有上面生成的 routeDestination store.dispatch(RoutingAction(destination: routeDestination))}

这依照选中的 row 设置 routeDestination。然后利用 dispatch 来向 Store 传递 routingAction

以那事件正在分发,不过它还一向不被其它 reducer 帮忙。张开RoutingReducer.swift 并且将 routingReducer 中的内容替换到一下的代码-更新状态:

var state = state ?? RoutingState()switch action { case let routingAction as RoutingAction: state.navigationState = routingAction.destination default: break}return state

这个 switch 检查传递来的 action 是不是是二个 RoutingAction。借使是,它就用那么些 destination 来改变 RoutingState,然后重返它。

编写翻译和平运动行。未来当您点击菜单按键,准确的视图调控器就能够被推到导航调控器的上方。

图片 23navigation.gif

您只怕发掘了当前导航器达成中的循环。当您点击 New Game 开关选项,RoutingStatanavigationStatemenu变成了game。不过当您利用导航调节器的回到箭头来回到到菜单分界面包车型客车时候, navigationState 未有生出任何退换。

在 ReSwift中,保持状态与当下的UI状态的同步是很首要的。我们很轻易将那点忘记当UI基特帮我们一同消除那个事,仿佛导航器重回开关只怕客户在 UITextField中打字。

每当 MenuTableViewController 出现就更新 navigationState,通过这种措施来修复这些bug。

store.dispatch(RoutingAction(destination: .menu))

只要导航器的回到箭头被利用那么就手动更新store。

运作应用还要又一次测量试验导航。。。啊啊。。。。今后导航已经完全坏了。未有页面能够被推入,并且你也许会看到使用奔溃。

图片 24stickers.gif打开 AppRouter.swift ;你应有会想到,每当收到贰个新的 navigatioinStatepushViewController都会被调用三次。

你只可以动态检查 MenuViewController在推送从前是还是不是业已可知。用一下代码代替 pushViewController的内容:

let viewController = instactiateViewController(indentifier: identifier)let newViewControllerType = type(of: viewController)if let currentVc = navigationController.topViewController { let currentViewControllerType = type(of: currentVc) if currentViewControllerType == newViewControllerType { return }}navigationController.pushViewController(viewController, animated: animated)

在时下的视图调整器中调用 type 並且和将要被推入的新的视图相比较。借使它们符合,就径直 return并不供给将再次的视图调控器推入。编写翻译和运行程序,导航器将和以后同等工作,当你退出当前栈时 menu 的图景也会趁机改动。

图片 25navigationnew.gif

UI事件来更新情况和动态检查当前气象是拾贰分复杂的。这是叁个你必须克制的搦战之一当您使用ReSwift。幸运的是,这不会不常发生。

这段时间您将更上一层楼来兑现二个进一步头眼昏花的页面:CategoriesTableViewController。你供给允许顾客挑选音乐的种类,所以当他们在玩游戏的时候能够欣赏她们最喜悦的乐队了。从增进气象到 CategoriesState.swift开始:

import ReSwiftenum Category: String { case pop = "Pop" case electronic = "Electronic" case rock = "Rock" case metal = "Metal" case rap = "Rap"}struct CategoriesState: StateType { let categories: [Categories] var currentCategorySelected: Category init(currentCategory: Category) { categories = [.pop, .electronic, .rock, .metal, .rap] currentCategorySelected = currentCategory }}

这个 enum 定义了三种音乐项目。CategoriesState 包蕴了一部分列可用的categories况且还或许有保存境况的currentCategoriesSelected

ChangeCategoriesAction.swift中加多一下代码:

import ReSwiftstruce ChangeCategoryAction: Action { let categoryIndex: Int}

其一创立了一个得以更改CategoriesState的事件,使用 categoryIndex来维系音乐类型。以后您供给贯彻贰个 Reduce来接受 ChageCategoryAction并且存款和储蓄更新后的状态。打开 CategoriesReducer.swift 而且增加一下代码:

import ReSwiftprivate struce CategoriesReducerConstants { static let userDefaultsCategoryKey = "currentCategoryKey"}private typelias C = CategoriesReducerConstantsfunc categoriesReducer(action: Action, state: CategoriesState?) -> CategoriesState { var currentCategory: Category = .pop //1 if let loadedCategory = getCurrentCategoryStateFromUserDefaults() { currentCategory = loadedCategory } var state = state ?? CategoriesState(currentCategory: currentCategory) switch action { case let changeCategoryAction as changeCategoryAction: //2 let newCategory = state.categories[changeCategoryAction.categoryIndex] state.currentCategorySelected = newCategory saveCurrentCategoryStateToUerDefaults(category: newCategory) default: break } return state}//3private func getCurrentCategoryStateFromUserDefaults() -> Category? { let userDefaults = UserDefault.standard let rawValue = userDefault.string(forKey: C.userDefaultsCategoryKey) if let rawValue = rawValue { retunr Category(rawValue: rawValue) } else { return nil }}//4private func saveCurrentCategoryStateToUserDefault(category: Category) { let userDefaults = UserDefaults.standard userDefaults.set(category.rawValue, forKey: C.userDefaultsCategoryKey) userDefaults.sychronize()}

就想别的的reducers同样,这贯彻了叁个主意来产生意况的换代。在这一个例子中,你也将已入选的连串存入 UserDefaults。简要的看一下那都做了些什么:

  1. 比如只怕的话从 UserDefaults 中加载当前的lei'bie类别,并且用它来实例化叁个 CategoriesState 若是不设有的话。
  2. 通过立异state 和保存新的花色到 UserDefault 来响应 ChangeCategoryAction
  3. getCurrentCategoryStateFromUserDefaults 是一个帮忙方法从 UserDefaults中加载种类。
  4. saveCurrentCategoryStatetoUserDefaults 是二个声援方法来将品种保存到 UserDefaults

那一个协理方法也一致是全局的纯函数。展开 AppState.swift 并且将弹指间的丰盛到 struct的末尾:

let categoriesState: CategoriesState

categoriesState 现在是 AppState 的一局地了。你就要管理这一部分!

打开 AppReducer.swift 而且修改再次回到值来合作一下的代码:

return AppState( routingState: routingReducer(action: action, state: state?.routingState), menuState: menuReducer(action: action, state: state?.menuState), categoriesState: categoriesReducer(action: action, state: state?.categoriesState))

您早已将 categoriesState 添加到 appReducer 传递了 actioncategoriesState

当今您需求创建项目页面,就想 MenuTableViewController一样。你就要使它订阅Store並且选用 TableDataSource

打开 CategoriesTableViewController.swift 并且用一下的代码取代:

import ReSwiftfinal class CategoriesTableViewController: UITableViewController { var tableDataSource: TableDataSource<UITableViewCell, Category>? override func viewWillAppear(_ animatedL: Bool) { //1 store.subscribe { $0.select { $0.categoriesState } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear store.unsubscribe } override func talbeView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { //2 store.dispatch(ChangeCategoryAction(categoryIndex: indexPath.row)) }}//MARK: - StoreSubscriberextension CategoriesTableViewController: StoreSubscriber { func newState(state: CategoriesState) { tableDataSource = TableDataSource(cellIdentifier: "CategoryCell", models: state.categories) { cell, model in cell.textLable?.text = model.rawValue //3 cell.accessoryType = (state.currentCategorySelected == model) ? .checkmark : .none return cell } self.tableView.dataSource = tableDataSource self.tableView.reloadData() }}

此处的代码和 MenuTalbeViewController 十二分相似。看一下独到之处的片段:

  1. viewWillAppear 中订阅 categoriesState 的变化而且在 viewWillDisappear 中撤除订阅。
  2. 当客商挑选一个cell的时候分发 ChangeCategoryAction
  3. newState中,当前被选中的cell加多标识。

装有的事情都搞好了。未来您能够选择一个品类了。编写翻译和运作程序,为您本身选取一个 Choose Category

图片 26ChooseCategory.png

异步操作很难? 嗯?对于Re斯维夫特来讲并非。

您将在从 iTunes API 下载图片给 Memory Cards。首先,你要创造一个游玩state, reducer和血脉相通的 action。

打开 GameState.swift 并且你将拜会到贰个 MemoryCard 结构体代表了三个游戏卡。他包蕴了浮今后卡上的图片的 imageUrlisFliped 代表了前一张牌是不是可知并且 isAlreadyGuessed dai'bi代表了卡片是还是不是合作。

您将在在文件中增多游戏状态。从导入 ReSwift 开头:

import ReSwift

以后足够一下代码到文件的底层:

struce GameState: StateType { var memoryCards: [MemoryCard] //1 var showLoading: Bool //2 var gameFinished: Bool}

这几个概念了游戏的情景。除了包含可用的卡牌的数组外,这里的属性代表了:

  1. 加载提醒器是不是可知
  2. 游戏是还是不是结束

GameReducer.swift 中添加贰个game Reducer:

import ReSwiftfunc gameReducer(action: Action, state: GameState?) -> GameState { let state = state ?? GameState(memoryCard: [], showLoding: false, gameFinished: false) return state}

当下单独是开创了三个新的 GameState。你等说话还有恐怕会回来。

AppReducer.swift 中,最后一遍创新开头化操作:

return AppState( routingState: routingReducer(action: action, state: state?.routingState), menuState: menuReducer(action: action, state: state?.menuState), categoriesState: categoriesReducer(action:action, state: state?.categoriesState), gameState: gameReducer(action: action, state: state?.gameState))

Note: 在做了四回 Action/Reducer/State 常规操作后您会静心到独具的事情都是可预测的、轻便和熟知。那编制程序友好的健康操作得益于 ReSwift的单向的本质况且在各样组件中严酷的范围了它。大概您早已学到了,只有Reducer 能够退换 app Store并且独有 Action 能够初叶化那些变化。你也立刻就通晓到哪里去看,和到哪里去增加新的代码。

最近定义多个平地风波来更新卡牌通过加多一下的代码到 SetCardsAction.swift :

import ReSwiftstruct SetCardsAction: Action { let cardImageUrls: [String]}

GameState 中那操作为卡牌设置了图片地址。

今昔您就要成立你的首先个异步事件。在 FetchTunesAction.swift 中,增加一下代码:

import ReSwiftfunc fetchTunes(state: AppState, store<AppState>) ->FetchTunesAction { iTunesAPI.searchFor(category: state.categoriesState.currentCategorySelected.rawValue) { imageUrls in store.dispatch(SetCardsAction(cardImageUrls: imageUrls)) } return FetchTunesAction()}struct FetchTunes: Action { }

fetchTunes 通过动用 iTunesAPI (包罗运营装置 来获得图片。在闭包中,你分发了二个 带有结果SetCardAction 事件。在 ReSwift中操作异步职分就是那么轻巧:当它产生时,及时分发二个平地风波。就是那般。

fetchTunes 返回了 FetchTunesAction ,被用来表示收获已经起首了。

打开 GameReducer.swift 并且为多少个新的风云加多援助。用一下的代码提到 gameRedcer 中的内容:

var state = state ?? GameState(memoryCards: [], showLoading: false, gameFinished: false)switch { //1 case _ as FetchTunesAction: state = GameState(memoryCards: [], showLoading: true, gameFinished: false) //2 case let setCardsAction as SetCardsAction: state.memoryCards = generateNewCards(with: setCardsAction.cardImageUrls) state.showLoading = false default: break}return state

你更动状态使其改为常量,然后再落实贰个 action,switch做了刹那间事情:

  1. 当action为 FetchTunesAction ,设置了 showLoadingtrue
  2. 当action为 setCardsAction, 随机化卡组何况安装 showLoadingfalsegenerateNewCards 可以在 MemoryGameLogic.swift 中被找到,这饱含了运营装置。

是时候在 GameViewController 中画出卡牌了。以那是cell初步。

打开 CardCollecionViewCell.swift 並且增加一下措施到 CardCollectionViewCell 的底部:

func configureCell(with cardState: MemoryCard) { let url = URL(string: cardState.imageUrl) //1 cardImageView.kf.setImage(with: url) //2 cardImageView.alpha = cardState.isAlreadyGuessed || cardState.isFlipped ? 1 : 0}

configure 做了一下政工:

  1. 行使很棒的 Kingfisher 库来储存图片。
  2. 当卡片已经被猜出只怕卡牌被扭转时突显图片。

接下去你将在实现 collection view 来展现卡牌。就疑似table views这样,对UICOllectionView有个申明式的卷入叫CollectionDataSource.swift ,那一个都被含有在运行器中还要你将会在今后所使用。

打开GameViewController,将瞬间代码增添到 showGameFinishedAlert 的上面:

var collectionDataSource: CollectionDataSource<CardCollectionViewCell, MemoryCard>?override func viewWillAppear(_ animated: Bool) { super.viewWillAppear store.subscribe { $0.select { $0.gameState } }}override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear store.unsubscribe}override func viewDidLoad() { // 1 store.dispatch(fetchTunes) collectionView.delegate = self loadingIndicator.hidesWhenStopped = true // 2 collectionDataSource = CollectionDataSource(cellIdentifier: "CardCell", models: [], configureCell: { (cell, model) -> CardCollectionViewCell in cell.configureCell(with: model) return cell }) collectionView.dataSource = collectionDataSource}

只顾这里只怕会导致编写翻译警告直至你兑现了 StoreSubscriber 协议。视图在 viewWillAppear 中订阅了 gameState 并且在 viewWillDisappear 中收回了订阅。在 viewDidLoad 中做了一下轩然大波:

  1. 当最早从 iTunes API 获取图片时分发 fetchTunes
  2. 因此运用 CollectionDataSource - 获取合适的 modelconfigureCell 来设置cells。

后天您要求丰硕一个扩充来遵守StoreSubscriber。在文件的平底增加一下代码:

// MARK: - StoreSubscriberextension GameViewController: StoreSubscriber { func newState(state: GameState) { collectionDataSource?.models = state.memoryCards collectionView.reloadData() // 1 state.showLoading ? loadingIndicator.startAnimating() : loadingIndicator.stopAnimating() // 2 if state.gameFinished { showGameFinishedAlert() store.dispatch(fetchTunes) } }}

这贯彻了 newState 来持有状态的改动。那改换了数据源同一时间:

  1. 基于事态来退换加载提醒器的景观。
  2. 再一次一场游戏和显示提醒当娱乐截至的时候。

编写翻译和运营游戏,选用 New Game ,况兼你今后得以观望 mrmory 卡片。

图片 27GameAlmost.png

二十日游的逻辑是客户点击两张卡牌,如若它们同样,它们保持正面;固然它们不相同样,它们将再一次掩盖。游戏用户的目的是用最少的步数使具有卡片都为方正。为了做到,你须求二个点击的风浪。展开FlipCardAction.swift 况且拉长一下代码:

import ReSwiftstruct FlipCardAction: Action { let cardIndexToFlip: Int}

FlipCardAction 将会动用 cardIndexToFlip 来更新 GameState 当一张卡片被点击了。改变gameReducer 来支持 FlipCardAction和娱乐的算法。展开 GameReducer.swift并且在default 从前增加一下的事例:

case let flipCardAction as FlipCardAction: state.memoryCards = flipCard(index: flipCardAction.cardIndexToFlip, memoryCards: state.memoryCards) state.gameFinished = hasFinishedGame(cards: state.memoryCards)

对此一个 FlipCardAction来说, flipCard 根据 cardIndexTiFlip和任何娱乐逻辑来更动 memory cards 的事态。 hasFinishedGame 被调用来分明游戏是或不是得了和呼应的更新情况。多个方法都得以在MemoryGameLogic.swift 中找到。

终极一块拼图是殡葬一个点击事件当卡牌被选中了。那会做到娱乐的逻辑並且使得相关的境况变化。

GameViewController.swift 中,找到 UICollectionViewDelegate 扩充。加多一下代码到collectionView(_: didSelectItemAt:) :

store.dispatch(FlipCardAction(cardIndexToFlip: indexPath.row))

当collection view 中的卡片被选中了,相关连的indexPath.row 将被发送到 FlipCardAction

运行游戏,未来你能够玩游戏了。当您玩的欢娱的时候记得回来。

图片 28GameFinished.png

你能够在此处 下载最后的 MemoryTunes。接下来任然有为数十分的多Re斯威夫特相关的内需上学。

  • ReSwift 介绍
  • The Right Way to Architect iOS App with Swift

本文由星彩网app下载发布于计算机编程,转载请注明出处:Redux从入门到跳楼

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。