react 编程式路由_如何做React式编程。 第2部分:副作用

react 编程式路由

Despite the number, this is the third article about reactive programming. Today we are going to talk about how to handle side effects while using unidirectional approaches.

尽管数量众多,但这是有关React式编程的第三篇文章。 今天,我们将讨论如何使用单向方法处理副作用。

Before we start, I’d firstly highly recommend reading at least How to cook reactive programming. Part 1: Unidirectional architectures introduction.. However, if you’re not familiar with frameworks such as RxSwift or Combine, or reactive programming in general, I’d suggest reading this article as well.
在开始之前,我首先强烈建议您至少阅读 如何烹饪React式编程。 第1部分:单向体系结构介绍。 。 但是,如果您不熟悉 RxSwiftCombine或一般的React式编程等框架,建议您也阅读 本文
  1. What is Reactive Programming? iOS Edition

    什么是React式编程? iOS版

  2. How to cook reactive programming. Part 1: Unidirectional architectures introduction.

    如何做React式编程。 第1部分:单向体系结构介绍。

介绍 (Intro)

Before we will move to the talk about Side Effects I want to introduce you to the main subject of this article.

在我们开始讨论Side Effects我想向您介绍本文的主要主题。

This is a representation of the simplest State which you actually can find in nearly every application. Let me transform this image into the real code.

这是最简单State的表示,您几乎可以在几乎每个应用程序中找到它。 让我将此图像转换为真实代码。

enum State {
    case initial
    case loading
    case loaded(data: [String])
}

Much better! If you’re familiar with a state machine theory from the computer science class, this picture will be very recognisable to you. This is a simple state machine with 3 states. The Initial state can go to the Loading state. The Loading state to the Loaded state. And the Loaded state can go back to the Loading state. Remember I was talking about State data consistency. In this particular case it's really hard to make the state inconsistent. Each state of the system is represented as an enum case.

好多了! 如果您熟悉计算机科学课程中的状态机理论,那么您将很容易理解这张照片。 这是具有3个状态的简单状态机。 Initial状态可以进入Loading状态。 Loading状态变为Loaded状态。 并且Loaded状态可以返回到Loading状态。 记得我在说State数据一致性。 在这种特殊情况下,很难使状态不一致。 系统的每个状态都表示为枚举情况。

Don't worry, I'm not going to bother you with any computer science concepts here. It was mostly a representation of the ideal State which could be achieved. In the real world it's really hard to create only an enum state. In most cases it would be a structure. However, for the purposes of this article we will use this State for the experiments. And now let's move to the main topic.

不用担心,这里我不会打扰您任何计算机科学的概念。 它主要是可以实现的理想State的代表。 在现实世界中,很难仅创建一个枚举状态。 在大多数情况下,这将是一个结构。 但是,出于本文的目的,我们将使用此State进行实验。 现在让我们转到主要主题。

什么是副作用? (What are Side Effects?)

According to Wikipedia

根据维基百科

In computer science, an operation, function or expression is said to have a side effect if it modifies some state variable value(s) outside its local environment, that is to say has an observable effect besides returning a value (the main effect) to the invoker of the operation. State data updated "outside" of the operation may be maintained "inside" a stateful object or a wider stateful system within which the operation is performed. Example side effects include modifying a non-local variable, modifying a static local variable, modifying a mutable argument passed by reference, performing I/O or calling other side-effect functions. In the presence of side effects, a program's behaviour may depend on history; that is, the order of evaluation matters. Understanding and debugging a function with side effects requires knowledge about the context and its possible histories.
在计算机科学中,如果操作,函数或表达式在其局部环境之外修改了某些状态变量值,则称其具有副作用,也就是说,除了将值(主要效果)返回给变量之外,还具有可观察的效果。操作的调用者。 可以在操作的“外部”或内部的状态对象或更广泛的有状态的系统“内部”维护更新的状态数据。 副作用示例包括修改非局部变量,修改静态局部变量,修改通过引用传递的可变参数,执行I / O或调用其他副作用函数。 在存在副作用的情况下,程序的行为可能取决于历史记录。 也就是说,评估的顺序很重要。 要了解和调试具有副作用的功能,需要了解上下文及其可能的历史。

However, here we’re not talking about the strict definition of the side effects. Let's remember where we ended up the last time.

但是,这里我们不是在谈论副作用的严格定义。 让我们记得上次结束的地方。

struct State {
    var value: Int?

    static func reduce(state: State, event: Event) -> State {
        var state = state
        switch event {
        case .changeValue(let newValue):
            state.value = newValue
        }
        return state
    }
}

class Store {
    var state: State

    func accept(event: Event) {
        state = reduce(state: state, event: event)
    }
}

Generally, nearly all unidirectional architectures look like this. We have a State on which the rest application relies as only one source of truth. A reducer which alongside Event is the only way to mutate or update State. However, there should be something else. We don't live in a synchronous world, where every update for State can be done only with a synchronous reduce function. Every application needs to go to the network or database, for the new cat images. Every developer moves even hard computations on the background thread. So, how will all of this work with the existing code? The answer is side effects. In our case Side Effects are something asynchronous, which could mutate State and this "something" works on the side of the reducer. Imagine your beloved network service which somehow needs to be connected to the rest of the system. But first let's talk about why this architecture is called ‘unidirectional’.

通常,几乎所有的单向体系结构都是这样。 我们有一个State ,其余的申请仅以此为依据。 与Event一起使用的reducer是更改或更新State的唯一方法。 但是,应该还有其他东西。 我们不是生活在一个同步的世界中,在这个同步世界中,只有使用同步的reduce函数才能对State进行每次更新。 每个应用程序都需要转到网络或数据库,以获取新的cat图像。 每个开发人员甚至在后台线程上进行艰苦的计算。 那么,所有这些如何与现有代码一起使用? 答案是副作用。 在我们的情况下, Side Effects是异步的,它可以使State并且这种“东西”在reducer方面起作用。 想象一下您钟爱的网络服务,该服务需要以某种方式连接到系统的其余部分。 但是首先让我们谈谈为什么这种架构被称为“单向”。

One remark: Event in different implementations of unidirectional architectures could be called a Mutation or Action or Message, or maybe something different, for our purposes however naming is not so important.

备注:对于我们的目的,单向体系结构的不同实现中的Event可以称为MutationActionMessage ,也可以称为其他名称,但是命名并不是那么重要。

为什么将该架构称为单向? (Why is the architecture called unidirectional?)

Unidirectional architecture is also known as one-way data flow. This means that data has one, and only one way to be transferred to other parts of the application. In essence, this means child components are not able to update the data that is coming from the parent component. The main benefit of this approach is that data flows throughout your app in a single direction, giving you better control over it.

单向体系结构也称为单向数据流。 这意味着数据只有一种并且只有一种方式可以传输到应用程序的其他部分。 从本质上讲,这意味着子组件无法更新来自父组件的数据。 这种方法的主要好处是,数据可以在一个方向上流经您的应用程序,从而使您可以更好地控制它。

I think it should be quite easy to understand with theState reduce approach from the beginning. We can change or mutate State only with a strict described Event. As a result we've got a one-way (unidirectional) data flow. However, what should we do with Side Effects?

我认为从一开始就采用State reduce法应该很容易理解。 我们只能通过严格描述的Event来更改或变异State 。 结果,我们得到了单向(单向)数据流。 但是,我们该如何应对Side Effects

Imagine that for the State we have a service which as a result returns a list of the news titles.

想象一下,对于State我们有一项服务,结果返回了新闻标题列表。

func loadNewsTitles(completionHandler: ([String]) -> ()) {
    completionHandler(["title1", "title2"])
}

We know that reducer takes Event as an input not a closure… How can we connect this service to the reducer? The answer is quite simple. Let’s have a Side Effect, which will return Event, not just requested data.

我们知道reducerEvent作为输入而不是闭包……我们如何将该服务连接到reducer ? 答案很简单。 让我们有一个Side Effect ,它将返回Event ,而不仅仅是请求的数据。

The resulting system will look like this:

结果系统将如下所示:

enum State {
    case initial
    case loading
    case loaded(data: [String])
}

enum Event {
    case dataLoaded(data: [String])
    case loadData
}

func loadNewsTitles(completionHandler: (Event) -> ()) {
    loadNewsTitles { data in
        completionHandler(.dataLoaded(data: data))
    }
}

extension State {
    static func reduce(state: State, event: Event) -> State {
        var state = state
        switch event {
        case .dataLoaded(let data):
            state = .loaded(data: data)
        case .loadData:
            state = .loading
        }
        return state
    }
}

As you can see for now loadNewsTitles returns an Event, which could mutate the state. Our system works only in a unidirectional way. And there's an answer to why the architecture is called Unidirectional. After I’d answered one question, I've subsequently produced another one. How can we connect Side Effects and the rest of the system? This question actually is the most complicated so far. I'll try to answer it in the next section.

如您现在loadNewsTitles返回一个Event ,它可以改变状态。 我们的系统仅以单向方式工作。 关于该架构为何被称为Unidirectional的答案。 在回答了一个问题之后,我又提出了另一个问题。 我们如何连接Side Effects和系统的其余部分? 到目前为止,这个问题实际上是最复杂的。 我将在下一节中尝试回答。

存在哪些类型的副作用? (Which types of side effects exist?)

In nearly every unidirectional architecture you'll see a collaboration of State and some function for reducing this State according to the input Event. With Side Effects it's much more complicated. Almost every framework does this in a different way. Let me try to make you familiar with the most popular of them.

在几乎每个unidirectional architecture您都会看到State的协作以及根据输入Event reducingState一些功能。 使用Side Effects它要复杂得多。 几乎每个框架都以不同的方式执行此操作。 让我尝试让您熟悉其中最受欢迎的。

中间件 (Middleware)

Let's start with the Middleware approach. Middleware provides a third-party extension point between dispatching an Event, and the moment it reaches the reducer. In simple terms Middleware, sits in the middle between you performing or dispatching an Event and mutating your State inside the reducer.

让我们从Middleware方法开始。 Middleware提供了在调度EventEvent到达化简器之间的第三方扩展点。 简单来说Middleware ,坐在你执行或传递的中间Event和变异的State里面reducer

Let me provide you with a code example, which I found in one well-known framework for Redux implementation for Swift.

让我为您提供一个代码示例,该示例在一个著名的Swift的Redux实现框架中找到。

let middleware: Middleware = { store, getState in
    return { next in
        return { event in
            // perform middleware logic
            switch event {
            case .loadData:
                loadNewsTitles { event in
                    store.accept(event)
                }
            case .dataLoaded:
                break
            }

            // call next middleware
            return next(event)
        }
    }
}

As you can see a Middleware could be treated as an asynchronous preReducer. It catches all Events, carries out some manipulations over it — in our case, loading news titles -, and performs a new Event for the system if it's necessary. So, if the Event is loadData, listed Middleware will load news titles and in the closure send another Event to the Store. The next dataLoaded Event will just be ignored by this Middleware. One of the pros of this method is the possibility to chain Middlewares quite easily.

如您所见, Middleware可以被视为异步pre Reducer 。 它捕获所有Events ,对其进行一些操作(在本例中为加载新闻标题),并在必要时为系统执行新的Event 。 因此,如果EventloadData ,则列出的Middleware将加载新闻标题,并在结束时将另一个Event发送给Store 。 此Middleware将只忽略下一个dataLoaded Event 。 这种方法的优点之一是可以很容易地链接Middlewares

Also, if you want to read more about this approach, I’d highly recommend taking a look at ReSwift framework. This framework is an implementation of a unidirectional architecture, which is called Redux for Swift language. For those, who still refuse reactive frameworks, ReSwift could be a good start, because ReSwift doesn't use any.

另外,如果您想了解有关此方法的更多信息,强烈建议您看一下ReSwift framework 。 这个框架是单向架构的一种实现,称为Swift语言的Redux 。 对于仍然拒绝React式框架的人来说, ReSwift可能是一个好的开始,因为ReSwift不使用任何框架。

特效 (Effects)

The next approach I want to talk about is the Effects approach. The main idea is almost the same as the Middleware, but all actions are going on inside Reducer itself.

我要谈的下一个方法是“ Effects方法。 主要思想与Middleware几乎相同,但是所有动作都在Reducer本身内部进行。

In this approach Reducer has a little bit of a different shape, that I showed you before. It has a shape, which you can see below.

在这种方法中, Reducer具有一些不同的形状,这是我之前向您展示的。 它具有一个形状,您可以在下面看到。

func reducer(state: inout State, event: Event, environment: Environment) -> Effect

Nearly everything should be familiar. State is a type that holds the current state of the application. Event is a type that holds all possible events that cause the state of the application to change. However, there are two new characters: Environment and Effect. Environment is a type that holds all dependencies needed in order to produce Effect(s), such as API clients, analytics clients, random number generators, and so on.

几乎所有事物都应该熟悉。 State是保存应用程序当前状态的一种类型。 Event是一种类型,其中包含导致应用程序状态更改的所有可能的事件。 但是,有两个新角色: EnvironmentEffectEnvironment是一种类型,其中包含产生Effect(s)所需的所有依赖关系,例如API客户端,分析客户端,随机数生成器等。

So, how does it work for our example?

那么,它对我们的示例如何起作用?

struct Environment {
    let loadNewsTitles: (Event) -> ()
}

struct Effect {
    init(work: ((State) -> ())? = nil)
    func performWorkItem() -> Event
}

extension Effect {
    /// An effect that does nothing and completes immediately.
    static let none = Effect()
}

extension Effect {
    static func loadNewTitlesEffect(loadNewsTitles: (Event) -> ()) -> Effect
}

func reducer(state: inout State, event: Event, environment: Environment) -> Effect {
    switch event {
    case .dataLoaded(data: let data):
        state = .loaded(data: data)
        return .none
    case .loadData:
        return Effect.loadNewTitlesEffect(loadNewsTitles: environment.loadNewsTitles)
    }
}

How does this approach work? For every call of Reducer you provide all necessary dependencies via Environment to the Reducer itself, and afterward your Store will perform every workItem from the Effect itself. And then every Effect will return another Event to the Reducer. Unidirectional data flow works with all power here.

这种方法如何运作? 对于Reducer每次调用,您都需要通过EnvironmentReducer本身提供所有必要的依赖关系,然后您的Store将执行Effect本身中的每个workItem 。 然后每个Effect都会将另一个Event返回给Reducer 。 单向数据流在这里可以发挥所有功能。

You may ask, but what about the pure Reducer over there? You told us that Reducer is a pure function, and now you put Side Effects directly inside this function. Moreover, we mutate State inside this function, not just creating a new value. So, I can definitely explain that this variation of the Reducer is the most complicated one which we've seen so far. It has the Environment inside and it mutates State. However, let’s take a closer look. If we provide one implementation for the loadNewTitles service, our Reducer will perform the same and our State in the end will be the same. Yeah, in the real world, our server can answer with the different replies or different news titles, but it still has the same output — Effect as a return value, with the same network client in it. I hope you’ve got the idea. What about State mutation? Since all real mutations are always going on inside the Store, the main situation around changing State hasn't changed itself. Moreover, mutating State against creating new values for every reduce saves some performance for us. We don't need to allocate new memory each time.

您可能会问,但是那边的纯Reducer呢? 您告诉我们Reducer是一个纯函数,现在您将Side Effects直接放在此函数中。 此外,我们在此函数内对State突变,而不仅仅是创建一个新值。 因此,我可以肯定地解释说, Reducer这一变体是迄今为止我们所见过的最复杂的变体。 它内部具有Environment ,并且会改变State 。 但是,让我们仔细看看。 如果我们为loadNewTitles服务提供一个实现,那么我们的Reducer将执行相同的操作,而最终的State将相同。 是啊,在现实世界中,我们的服务器可以用不同的答复或不同的新闻标题回答,但它仍然有相同的输出- Effect作为返回值,与它相同的网络客户端。 我希望你有主意。 State突变呢? 由于所有真正的变异总是在Store内部进行,因此更改State的主要情况并未改变。 而且,改变State防止每次reduce都产生新的价值为我们节省了一些性能。 我们不需要每次都分配新的内存。

I don't want to provide a working example of this approach as well. My job is to make you familiar with it and explain the basics. However, I highly recommend taking a look at The Composable Architecture TCA from pointfree.co. In my personal opinion this framework is the most promising for now. It has its own cons such as the minimum iOS 13 version. They also have a website with a lot of useful videos available on it. It's not free, but I have a promocode for you. I'm sorry I couldn't miss this chance...

我也不想提供这种方法的有效示例。 我的工作是使您熟悉它并解释基本知识。 但是,我强烈建议您查看pointfree.co的The Composable Architecture TCA 。 我个人认为,此框架是目前最有前途的。 它有自己的缺点,例如最低的iOS 13版本。 他们也有一个网站,上面有很多有用的视频。 它不是免费的,但是我为您准备促销代码 。 对不起,我不能错过这次机会...

查询意见 (Query Feedback)

Let's move forward or downstairs. In contrast with Middleware or Effect approaches from the previous sections, there's a Query approach. The Query Feedback approach reacts to new changes from the different side of the Reducer compared to Middleware.

让我们前进或下楼。 与上一部分中的“ MiddlewareEffect方法相反,有一种“ Query方法。 与Middleware相比, Query Feedback方法对Reducer不同方面的新更改reactsMiddleware

Did you notice? We’ve moved the whole way through Side Effects approaches? Middleware was before Reducer, Effects approach was inside Reducer and now Query Feedback is after reducer. Quite a journey, huh?

你注意到了吗? 我们是否已通过Side Effects方法进行了全面改进? Middleware是前ReducerEffects的方法是内Reducer ,现在Query Feedback是减速后。 相当的旅程,对吧?

However, how does it work? We need to take a small piece of the State and start to Observe every change of this state. For the previous example it will look like:

但是,它如何工作? 我们需要占据一小部分State并开始Observe该状态的每一次变化。 对于前面的示例,它将如下所示:

extension State {
    var loadQuery: Void? {
        guard case .loading = self else { return nil }
        return ()
    }
}

In other words, there's some kind of Observer, which follows every change of this query and performs some actions over it. The optional Void type for my taste is the best representation when you need to understand whether you need to do any work or not.

换句话说,有某种Observer ,它跟随此查询的每次更改并对其执行一些操作。 当您需要了解是否需要执行任何工作时,适合我的口味的可选Void类型是最好的表示。

I think you’ve likely become bored with non-working examples in this article. So, let's try at least to implement this one. I'll use the Combine framework for this implementation. Whoah, this is the third article about reactive programming, and only now I'll start to use a reactive framework. Also, afterward, I'll explain why I prefer to use Combine over vanilla Swift. Technically Combine is already vanilla as well, but you’ve got the point.

我认为您可能对本文中的无效示例感到无聊。 因此,让我们至少尝试实现这一点。 我将在此实现中使用Combine框架。 哇,这是有关React式编程的第三篇文章,直到现在,我才开始使用React式框架。 另外,之后,我将解释为什么我更喜欢使用Combine不是Vanilla Swift。 从技术上讲, Combine也已经很普通了,但是您已经明白了。

Here is a small table of concepts for those who are new in Combine.

这是为Combine新手准备的一些概念表。

From now a little bit of tutorial started. Everything that I'll write below you can copy to your project and play with it afterward.

从现在开始一些教程。 我在下面写的所有内容都可以复制到您的项目中,然后再使用。

Let's introduce our old characters: State, Reducer, Query and Event:

让我们介绍一下我们的旧字符: StateReducerQueryEvent

enum State: Equatable {
    case initial
    case loading
    case loaded(data: [String])
}

enum Event {
    case dataLoaded(data: [String])
    case loadData
}

extension State {
    var loadQuery: Bool {
        guard case .loading = self else { return false }
        return true
    }
}

extension State {
    static func reduce(state: State, event: Event) -> State {
        var state = state
        switch event {
        case .dataLoaded(let data):
            state = .loaded(data: data)
        case .loadData:
            state = .loading
        }
        return state
    }
}

Nothing new so far — only a Query which I've shown to you recently. Now let's remove the callback from loadNewsTitles service and rewrite it in Combine fashion.

到目前为止,没有什么新鲜的-仅是我最近向您显示的Query 。 现在,让我们从loadNewsTitles服务中删除回调,并以Combine方式重写它。

func loadNewsTitles() -> AnyPublisher<[String], Never> {
    ["title1", "title2"]
        .publisher
        .delay(for: .microseconds(500), scheduler: DispatchQueue.main)
        .collect()
        .eraseToAnyPublisher()
}

Mostly it's just a pre-prepared mock with a small delay, which should simulate a real network environment. And now there's a new character in this play. Let's call it SideEffects. Obviously it’s not me who invented this name, but let's imagine it for the bigger narrative of the story.

通常,它只是一个预先准备好的模拟,延迟很小,应该可以模拟真实的网络环境。 现在这部戏有了一个新角色。 我们称它为SideEffects 。 显然不是我发明了这个名称,但让我们想象一下这个故事的更大叙述。

struct SideEffects {
    let loadNewTitles: () -> AnyPublisher<[String], Never>

    func downloadNewTitles() -> AnyPublisher<Event, Never> {
        loadNewTitles()
            .map(Event.dataLoaded)
            .eraseToAnyPublisher()
    }
}

As far as you can see, SideEffects for the Query Feedback approach is almost the same thing, as Environment for the Effect approach. I prefer to keep it as simple as possible, and most of the time it just converts the output from the services into Event which could be consumed by the Reducer.

据您SideEffects ,“ Query Feedback方法的SideEffects与“ Effect Environment方法几乎是同一回事。 我倾向于使其尽可能简单,并且大多数情况下,它只是将服务的输出转换为Event ,可以由Reducer

And for now, there’s only one question left- how do we connect SideEffects with the rest of the system? The answer isn’t so complicated, and Combine helps with it very much. Let's build our boss Store entity in which we'll connect every piece of our system.

现在,只剩下一个问题了:我们如何将SideEffects与系统的其余部分连接SideEffects ? 答案并不那么复杂,而Combine可以提供很多帮助。 让我们建立我们的老板Store实体,在其中我们将连接系统的每一部分。

typealias Reducer = (State, Event) -> State

class Store {
    @Published private(set) var state: State
    private let reducer: Reducer
    private let sideEffects: SideEffects

    init(
        initialState: State,
        reducer: @escaping Reducer,
        sideEffects: SideEffects
    ) {
        self.state = initialState
        self.reducer = reducer
        self.sideEffects = sideEffects
    }

    func accept(event: Event) {
        state = reducer(state, event)
    }

    func start() -> AnyCancellable {
        $state
            .map(\.loadQuery)
            .removeDuplicates()
            .filter { $0 == true }
            .map { _ in () }
            .flatMap(sideEffects.downloadNewTitles)
            .sink(receiveValue: accept(event:))
    }
}

The most interesting part of the code listing above is the start function. As far as you can see, I made our SideEffects react to the change of the piece of the State loadQuery. And for every time when our system will be in the loading State, our SideEffects will go to the network service, download new newsTitles and notify our system that new titles have been downloaded. Do you see it? Everything in the cycle, all data flow works in the one direction.

上面代码清单中最有趣的部分是start功能。 据您SideEffects ,我使SideEffectsState loadQuery的更改做出了React。 而且,每当我们的系统进入loading StateSideEffects都会访问网络服务,下载新的newsTitles并通知我们的系统新标题已经下载。 你看到了吗? 循环中的所有内容,所有数据流都沿一个方向工作。

Let's test what I've done so far.

让我们测试一下我到目前为止所做的。

let store = Store(
    initialState: .initial,
    reducer: State.reduce(state:event:),
    sideEffects: SideEffects(
        loadNewTitles: loadNewsTitles
    )
)
var cancellables: [AnyCancellable] = []

store
    .start()
    .store(in: &cancellables)

store
    .$state
    .removeDuplicates()
    .sink { state in print(state) }
    .store(in: &cancellables)

store.accept(event: .loadData)

// Console output

// initial
// loading
// loaded(data: ["title1", "title2"])

And it works, as expected! The system started from initial state then it went to the loading state after the new Event was sent, and ended up in loaded state. If you want to play with it some more, I've prepared a gist.

并按预期工作! 从开始系统initial state然后去了loading state的新后Event被发送,并在结束了loaded state 。 如果您想再玩一点,我已经准备了要点

I know that two Cancelables could look a little bit clumsy here, but I didn't want to make this example too complicated. There's another framework called RxFeedback where all these problems were solved. I think that you've already got it, that this framework uses RxSwift from the title, right? However, there's a constructor of Observable — it's a Publisher from the Combine — which creates the whole unidirectional system for you.

我知道两个Cancelables在这里看起来有点笨拙,但是我不想让这个例子过于复杂。 还有another framework called RxFeedbackanother framework called RxFeedback ,可以解决所有这些问题。 我认为您已经知道了,该框架使用标题中的RxSwift ,对吗? 但是,有一个Observable的构造函数—它是CombinePublisher —它为您创建了整个unidirectional系统。

typealias Feedback<State, Event> = (Observable<State>) -> Observable<Event>

extension Observable {
    public static func system<State, Event>(
        initialState: State,
        reduce: @escaping (State, Event) -> State,
        feedback: Feedback<State, Event>...
    ) -> Observable<State>
}

Typealias Feedback is a SideEffect itself. It takes changes in the State as an input and provides a sequence of Events as output.

Typealias Feedback本身就是SideEffect 。 它发生在的变化State作为输入,并提供一序列Events as output

In my personal opinion, this approach is the most hardcore one. As an advantage, you can take that your State always reflects what's going on in the system. The previous two rely on Event while doing any Side Effects, but this one only relies on the State itself. If you want to read a little bit more about the pros and cons of the Effect and Query approaches, you could read my discussion with TCA creators.

我个人认为,这种方法是最严格的方法。 作为优势,您可以认为您的State总是能反映出系统中正在发生的事情。 前两个在执行任何Side Effects都依赖于Event ,但此两个仅依赖于State本身。 如果您想了解有关EffectQuery方法的优缺点的更多信息,可以阅读我与TCA创作者的讨论

为什么我们为此需要一个被动的框架? (Why do we need a reactive framework for this?)

There are a lot of people who don't want to accept any reactive frameworks and don't understand why they are even needed. If you’re still reading this, and you are one of them, crash the like or clap button. This section is mostly for those who’ve been intrigued by the Unidirectional approach, but for some reason don't want to use reactive frameworks. Firstly I want to say, that you've already seen in my articles, that there’s nothing to be scared by inreactive frameworks and reactive programming in general. Most of you already use some techniques from it. I can say that it's much more handy to handle your data like a sequence or array than work with enormous closures. Use some functions, like filter, map or reduce etc. It really makes your code more clean and understandable. It's really hard to make a lot of mistakes from the start if you don't know how to cook it. That's why I write these articles for you.

很多人不想接受任何React式框架,甚至不理解为什么需要它们。 如果您仍然在阅读本文,并且您是其中之一,请按“赞”或“拍手”按钮崩溃。 本节主要针对那些对Unidirectional方法感兴趣的人,但由于某些原因,不想使用reactive框架。 首先,我想说的是,您已经在我的文章中看到了,在reactive框架和reactive编程中,没有什么可吓到的。 你们大多数人已经使用了其中的一些技术。 我可以说,像处理sequence或数组一样处理数据要比处理大量闭包要方便得多。 使用一些函数,例如filtermapreduce等。它确实使您的代码更清晰易懂。 如果您不知道如何烹饪,从一开始就很难犯很多错误。 这就是为什么我为您写这些文章的原因。

Let me show you another advantage in a reactive framework usage. Do you remember that I relied on ReSwift framework while showing a Middleware approach? This is a great framework, which was written by brilliant people. However, if you try to understand how it works under the hood, or even try to work with it you will end up with structures like this.

让我向您展示使用React式框架的另一个优势。 您还记得在展示Middleware方法时我依靠ReSwift框架吗? 这是一个很棒的框架,由才华横溢的人编写。 但是,如果您试图了解它在引擎盖下的工作原理,或者甚至尝试使用它,您将最终得到这样的结构。

/// Creates a middleware function using SimpleMiddleware to create a ReSwift Middleware function.
    func createMiddleware<State: StateType>(_ middleware: @escaping SimpleMiddleware<State>) -> Middleware<State> {

        return { dispatch, getState in
            return { next in
                return { action in

                    let context = MiddlewareContext(dispatch: dispatch, getState: getState, next: next)
                    if let newAction = middleware(action, context) {
                        next(newAction)
                    }
                }
            }
        }
    }

There are three closures inside each other! Moreover, I've used a helper to make it more simple. Of course, you could separate all of this somehow and avoid all the callback hell. However, if you take a look at my previous Query example you will see how everything was simple and straightforward. I wanted to say elegant as well, but for elegance it has to be refactored a little bit. If you want to have a closer look at ReSwift in action, I did a small test project some time ago.

彼此内部有三个闭包! 而且,我使用了一个助手来使其变得更简单。 当然,您可以以某种方式分离所有这些内容,并避免所有回调地狱。 但是,如果您看一下我以前的Query示例,您将看到一切如何简单明了。 我也想说优雅,但是为了优雅,必须对其进行一点重构。 如果您想更深入地了解ReSwift的实际应用, ReSwift我做了一个小型测试项目

奥托罗 (Outro)

There's no silver bullet on how to handle Side Effects. You can decide for yourself what to use. However, I think that most of you and myself will choose some pre-prepared solution like RxFeedback or TCA or ReSwift or something else.

没有关于如何处理Side Effects灵丹妙药。 您可以自己决定使用什么。 但是,我认为大多数人和我自己都会选择一些预先准备好的解决方案,例如RxFeedbackTCAReSwift或其他东西。

Finally, I have a chance to show you this gif. I took it from the ReSwift repo. This gif in full form represents the whole power of Unidirectional approaches. Technically you can store the whole history of State mutations and replay them at any moment you want.

最后,我有机会向您展示此gif。 我从ReSwift库中取出了它。 完整的gif表示Unidirectional approaches的全部功能。 从技术上讲,您可以存储State突变的全部历史记录,并在任何时候重播它们。

So far, you've become familiar with how to work and even how to build your own reactive framework and unidirectional architecture. However, we live in a world where applications are not one button flashlight apps anymore. We have teams of more than ten people. And if you noticed, the main idea of Unidirectional architecture is to keep all data inside one struct. I bet, if you start with this approach you will end up with a huge State and Reducer if not at the end of the week, then by at the end of the month. You may wonder how it's possible to separate the Unidirectional approach on different modules when the main idea is to keep everything in one place. What to do in this situation and what are the ways of app modularization I will show you in the next article. Let's keep in touch!

到目前为止,您已经熟悉如何工作,甚至如何构建自己的React框架和单向体系结构。 但是,我们生活在一个应用程序不再是一键式手电筒应用程序的世界。 我们有超过十人的团队。 而且,如果您注意到, Unidirectional architecture的主要思想是将所有数据保留在一个结构中。 我敢打赌,如果您从这种方法开始,那么如果不是在本周结束时,那么到月底,您将获得巨大的StateReducer 。 您可能想知道,当主要思想是将所有内容都放在一个地方时,如何在不同模块上分离Unidirectional方法。 在这种情况下该做什么以及应用程序模块化的方式是什么,我将在下一篇文章中向您展示。 让我们保持联系!

If you don't want to lose any new articles subscribe to my twitter account))

如果您不想丢失任何新文章,请subscribe我的Twitter帐户 ))

Twitter(.atimca)
.subscribe(onNext: { newArcticle in
    you.read(newArticle)
})

翻译自: https://habr.com/en/post/507290/

react 编程式路由

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值