react 单向响应_如何做React式编程。 第1部分:单向体系结构简介

react 单向响应

Recently I wrote an article What is Reactive Programming? iOS Edition where in a simple way I described how to build your own Reactive Framework, and helped you to understand that no-one should be scared by the reactive approach. The previous article could now be named How to cook reactive programming. Part 0., since this is a continuation. I would recommend reading the previous article if you are not familiar with the reactive programming concepts.

最近我写了一篇文章什么是React式编程? 在iOS版中,我以一种简单的方式描述了如何构建自己的Reactive Framework,并帮助您了解React式方法不会吓到任何人。 上一篇文章现在可以命名为“ 如何烹饪React式编程”。 第0部分 ,因为这是续篇。 如果您不熟悉React式编程概念,我建议您阅读上一篇文章。

This article can be read not just by iOS developers. I used only the basic concepts of Swift. You will understand it if you have knowledge of any modern programming language.

不仅iOS开发人员可以阅读本文。 我只使用了Swift的基本概念。 如果您具有任何现代编程语言的知识,您就会理解它。

Today we are going to talk more about the practical aspects of reactive programming. I've already mentioned it's too easy to make a lot of errors with this approach after you’ve begun using any reactive framework, so I want to show you how to avoid any problems.

今天,我们将更多地讨论React式编程的实际方面。 我已经提到过,在开始使用任何React式框架后,使用这种方法很容易出错,所以我想向您展示如何避免任何问题。

问题所在 (The problems)

To start, I want to describe some common problems with reactive programming and with the most common approaches nowadays.

首先,我想描述一下React式编程和当今最常见方法的一些常见问题。

Let's begin with reactive problems. The most common fear about reactive programming is that when you start to use it, you can reach a point where there are hundreds of uncontrolled data sequences in your code base and you as a developer no longer have any power over it.

让我们从React性问题开始。 对React式编程的最普遍的担心是,当您开始使用它时,您可能会达到这样的地步:您的代码库中有数百个不受控制的数据序列,而您作为开发人员将不再对其具有任何控制权。

意外的突变 (Unpredicted mutations)

The first and the biggest problem of what a reactive way could do for you is unpredicted mutations. Let me make an example:

React性方法可以为您做什么的第一个也是最大的问题是无法预测的突变。 让我举一个例子:

class Foo {
    var val: Int?

    init(val: Int?) {
        self.val = val
    }
}

var array: [Foo] = [Foo(val: 1), Foo(val: 2), Foo(val: 3)]

array = array.map { element in
    array[0].val = element.val
    return element
}

// array == 3, 2, 3

For the simplest of reasons, I've replaced the reactive sequence with an array. The example itself is an oversimplified way of how you can make a mistake, doing any reactive things. Nobody can predict where and when you changed data, we have so many ways to do it and there are so many of them hidden under reactive framework implementations.

出于最简单的原因,我已将React性序列替换为数组。 该示例本身是一种过分简化的方式,该方式说明了如何做出错误,做出任何React性的事情。 没有人能预测何时何地更改数据,我们有很多方法可以做到,而且在React式框架实现中隐藏了许多方法。

However, how can we handle this, how can we protect ourselves? First of all, we should move from reference types to the value ones.

但是,我们该如何处理,如何保护自己? 首先,我们应该从reference类型转到value类型。

struct Foo {
    var val: Int?
}

var array: [Foo] = [Foo(val: 1), Foo(val: 2), Foo(val: 3)]

array = array.map { element in
    array[0].val = element.val
    return element
}

// array == 1, 2, 3

Just with this simple change, I protect my array from an unpredicted mutation. As far as you know structs are value types, and they copy themselves all the time (there's a copy on write mechanism, but you’ve got the idea).

只需进行此简单更改,我就可以保护我的阵列免受意外的突变。 据您所知, structs是值类型,它们一直在复制自身(写机制上有一个副本,但是您已经有了主意)。

However, let’s wait for a moment, and let's try to move one step forward and try to make one radical movement. Let's make variable val a constant.

但是,让我们等待片刻,然后尝试向前迈出一步,并尝试进行一次激进的动作。 让我们将变量val常量。

struct Foo {
    let val: Int?
}

var array: [Foo] = [Foo(val: 1), Foo(val: 2), Foo(val: 3)]

array = array.map { element in
    array[0].val = element.val // Cannot assign to property: 'val' is a 'let' constant
    return element
}

Yes! That's the protection I'm talking about. Now we don't have any chance to mutate our data on the way through the data Sequence.

是! 那就是我在说的保护。 现在我们没有机会在数据Sequence对我们的数据进行变异。

What are we going to do with all this? A mobile application is not a painting, it is not static, we need to mutate data to react on user behavior or any other external changes such as network data or timers. An app is a living thing. So we have two problems to solve. We need to keep our value entities in the floating sequences, and we need somehow to mutate data to show changes to the user. That sounds like a challenge but wait for it, let's talk a little bit about modern architectures. This is an article about architecture after all.

这一切我们要怎么办? 移动应用程序不是绘画,也不是静态的,我们需要对数据进行更改以对用户行为或任何其他外部更改(例如网络数据或计时器) react 。 应用是有生命的东西。 因此,我们有两个问题要解决。 我们需要将价值实体保持在浮动序列中,并且需要以某种方式对数据进行变异以向用户显示更改。 这听起来像是一个挑战,但请耐心等待,让我们来谈谈现代架构。 毕竟这是一篇有关体系结构的文章。

MV家族 (MV family)

The problem above is not only about the reactive approach. You can face it everywhere, no matter the paradigm you use. Let's take a small step back and talk a little bit about modern app architectures.

上面的问题不仅与React性方法有关。 无论使用哪种范例,您都可以在任何地方面对它。 让我们退后一步,谈谈现代应用程序体系结构。

mvc

I can bet you’ve seen this guy before. Actually, in my personal opinion, most modern architectures look like this. They could be called the MVsomething family. MVP, VIPER, MVVM, the same but different. I don't want to go deep inside to try to understand what all the differences are between them, you already know this. Let's talk about common things. They are all small pieces, separated by screens, single views, or just a piece of business logic. Whoever is familiar with the topic will understand in advance what I'm getting at. With all these architectures it’s easy to bump into an inconsistent state of the app. Let me provide an example — imagine you have a home page in your app, and this home page depends on the logged-in user. You have a quite complicated app already with a possibility to sign in or sign out the user from different parts of the app. Already understand what I'm talking about? Imagine you decided to sign out from one part of the app and your home page should react on this change. Let me just show a gif as an example.

我敢打赌,您以前见过这个家伙。 实际上,以我个人的观点,大多数现代建筑都是这样。 他们可以称为MVsomething家族。 MVP,VIPER,MVVM相同但不同。 我不想深入了解它们之间的所有区别,您已经知道了。 让我们谈谈常见的事情。 它们都是小块,由屏幕,单个视图隔开,或者只是一部分业务逻辑。 熟悉该主题的人将事先了解我的意思。 使用所有这些架构,很容易陷入应用程序的不一致状态。 让我提供一个示例-假设您的应用程序中有一个主页,并且该主页取决于登录用户。 您已经有一个非常复杂的应用程序,可以从应用程序的不同部分登录或注销用户。 已经了解我在说什么? 想象一下,您决定退出该应用程序的一部分,而您的主页应对此更改react 。 让我以gif为例。

You can use some sort of user service, which will notify all subscribers to this service about any changes to a user. This approach has two problems. First problem: I think that services should be stateless; they shouldn't hold any information, just perform actions. Second problem: imagine that we have more than one situation where we need to share information between several modules. In this case, we'd have a bunch of dirty services and we'd simply return to the starting point with a lot of floating mutations.

您可以使用某种用户服务,该服务将向用户通知该服务的所有订户。 这种方法有两个问题。 第一个问题:我认为服务应该是无状态的; 他们不应该保留任何信息,而只是执行操作。 第二个问题:假设我们有多个情况需要在多个模块之间共享信息。 在这种情况下,我们会有很多肮脏的服务,而我们只是回到带有很多浮动突变的起点。

What to do in this situation? I'll give you an answer shortly.

在这种情况下该怎么办? 我会尽快给您答复。

一个环统治他们全部 (One ring to rule them all)

frodo

As I said above, an application is a living, changing system. For now, we have two problems, which should be solved. The first problem is that we need to keep data in the sequences immutable. The second one is that we need to stop producing sources of truth inside the codebase. How can we achieve this? What if I say, we can have a single source of truth for the entire application? It sounds ridiculous, but let's try to build this kind of system.

如前所述,应用程序是一个不断变化的系统。 目前,我们有两个问题需要解决。 第一个问题是我们需要保持序列中的数据不可变。 第二个问题是我们需要停止在代码库内部生成真相来源。 我们怎样才能做到这一点? 如果我说,对于整个应用程序,我们可以有一个真实的来源吗? 听起来很荒谬,但是让我们尝试构建这种系统。

As I said before only one state object is needed. Let's create it.

正如我之前所说的,只需要一个状态对象。 让我们来创建它。

struct State {
    var value: Int?
}

Quite easy, yes? The system should be as simple as possible. There is only one source of truth, and every other participant in the codebase observes all changes within this source. Let's try to implement this as well.

很简单,是吗? 该系统应尽可能简单。 只有一个事实来源,而代码库中的所有其他参与者都可以observes该来源中的所有更改。 我们也尝试实现这一点。

protocol Observer {
    func stateWasChanged(with newState: State)
}

struct State {
    var value: Int?
}

class Store {
    var state: State = State() {
        didSet {
            observers.forEach { observer in
                observer.stateWasChanged(with: state)
            }
        }
    }

    var observers: [Observer] = []
}

struct Foo: Observer {
    func stateWasChanged(with newState: State) {
        print("It's newState from Foo: ", newState.value)
    }
}

struct Boo: Observer {
    func stateWasChanged(with newState: State) {
        print("It's newState from Boo: ", newState.value)
    }
}

let foo = Foo()
let boo = Boo()

let store = Store()
store.observers = [foo, boo]
store.state.value = 10

// It's newState from Foo:  10
// It's newState from Boo:  10

What have I done? There's a state object which holds data from the whole application. Store is an entity, which holds everything. Two Observer objects, which subscribed on every state change. And mostly that's it. By the way, do you remember the user example before? The listed solution perfectly handles this situation. However, there's one big problem. The state was changed directly via store.state.value = 10. This approach could lead to the same problems, which I'm trying to remove right now. I'm going to fix it with a solid сoncept of Event.

我做了什么? 有一个状态对象,用于保存整个应用程序中的数据。 Store是一个实体,它拥有一切。 两个Observer对象,它们在每个状态更改时都进行subscribed 。 大部分就是这样。 顺便说一句,您还记得以前的用户示例吗? 列出的解决方案可以很好地处理这种情况。 但是,有一个大问题。 通过store.state.value = 10直接更改状态。 这种方法可能会导致同样的问题,我现在正在尝试解决。 我将用Event的可靠概念来修复它。

One remark. I know that observers are held with a strong reference in Store, and it should be avoided in the real project. You can find out how to achieve this in the previous article. Here I want to save some time. Back to the Event.

一句话。 我知道在Storeobservers拥有很强的参考力,在实际项目中应避免使用它。 您可以在上一篇文章中找到如何实现此目的。 在这里我想节省一些时间。 回到Event

enum Event {
    case changeValue(newValue: Int?)
}

class Store {
    private var state: State

    func accept(event: Event) {
        switch event {
        case .changeValue(let newValue):
            state.value = newValue
        }
    }
}

For now, no-one can mutate the state in an unpredictable way. The way of mutation is clear, structured and encapsulated under the Event concept. If you have noticed, for now, you cannot reach State just like a property of the store. I’ve done this on purpose, because reactive programming is about reaction on system changes, not taking data whenever you want, as I described in my previous article.

目前,没有人能够以一种无法预测的方式改变状态。 在Event概念下,突变的方式是清晰,结构化和封装的。 如果您已经注意到,就目前而言,您无法像商店的财产一样到达State 。 我这样做是有目的的,因为响应式编程是对系统更改的reaction ,而不是像我在上一篇文章中所述的那样随时获取数据。

Wait one moment… Take a look at this signature of standard reduce function in Swift:

等一下...看一下Swift中标准reduce函数的这一特征:

func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> Result

It looks almost the same as our accept function. There's an intialResult as the previous State, and some function to mutate State according to the Event. Let's refactor existing code a little bit.

它看起来与我们的accept函数几乎相同。 有一个intialResult作为先前的State ,还有一些根据Event改变State函数。 让我们重构一下现有代码。

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

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

That’s much better. However, take a look at this reduce function… It looks like it's completely independent of the Store and mostly any other objects. Why is this so? A reduce function is a pure function — it has its input parameters and one output. It also means that no matter what is going on in Store itself, reduce will work in the same way. So, maybe we should extract it from the store and make it a high order function? It sounds like a great idea, doesn't it? Again, let’s wait one moment. Seems like reduce is closer to State than to Store, so let’s put it under the State namespace.

那好多了。 但是,请看一下reduce函数……看起来它完全独立于Store和几乎所有其他对象。 为什么会这样呢? 约reduce函数是纯函数-它具有其输入参数和一个输出。 这也意味着无论Store本身发生什么, reduce都将以相同的方式工作。 因此,也许我们应该从商店中提取它并使其具有高级功能? 听起来像个好主意,不是吗? 再次,让我们等一下。 好像reduce相比于State更接近于State不是Store ,因此让我们将其放在State命名空间下。

Here's the final solution for our DIY architecture.

这是我们DIY架构的最终解决方案。

protocol Observer {
    func stateWasChanged(with newState: State)
}

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
    }
}

enum Event {
    case changeValue(newValue: Int?)
}

class Store {
    var state: State = State() {
        didSet {
            observers.forEach { observer in
                observer.stateWasChanged(with: state)
            }
        }
    }

    var observers: [Observer] = []

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

As a result, we've got a simple unidirectional architecture. However, there are so many questions remaining. We don’t live in the synchronous world, where it's possible to put every mutation through the reduce function synchronously. Indeed internet requests or timers couldn’t be added like this to the approach that I’ve introduced before. To resolve this problem, we'll use the SideEffects approach, but I'll write about that in my next article. And I almost forgot about one important thing. Why is the architecture called unidirectional? To provide a clear explanation, I’ll need to make you familiar with the SideEffects as well. So, stay tuned!

结果,我们有了一个简单的单向架构。 但是,还有很多问题。 我们不生活在同步世界中,在同步世界中,可以通过reduce函数同步地将每个变异都放入。 的确,无法将互联网请求或计时器添加到我之前介绍的方法中。 为了解决此问题,我们将使用SideEffects方法,但我将在下一篇文章中对此进行介绍。 而且我几乎忘记了一件重要的事情。 为什么将该架构称为单向? 为了提供清晰的解释,我还需要使您熟悉SideEffects 。 所以,请继续关注!

PS: This is already the second article about reactive programming, and I haven’t used any framework such as RxSwift or Combine… It actually means that most of you have already been using reactive approaches, without even noticing it. If you don't want to lose any new articles subscribe to my twitter account))

PS:这已经是有关React式编程的第二篇文章,并且我还没有使用任何框架,例如RxSwiftCombine ……这实际上意味着你们中的大多数人已经在使用React式方法,甚至没有注意到它。 如果您不想丢失任何新文章,请subscribe我的Twitter帐户 ))

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

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

react 单向响应

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值