创建react应用程序_如何使您的React应用程序具有完整的功能,完全的React性并能够处理所有这些疯狂的事情……...

 2023-09-06 阅读 19 评论 0

摘要:创建react应用程序by Luca Matteis 卢卡马蒂斯(Luca Matteis) 如何使您的React应用程序具有完整的功能,完全的React性并能够处理所有这些疯狂的副作用 (How to make your React app fully functional, fully reactive, and able to handle all those crazy side effect

创建react应用程序

by Luca Matteis

卢卡·马蒂斯(Luca Matteis)

如何使您的React应用程序具有完整的功能,完全的React性并能够处理所有这些疯狂的副作用 (How to make your React app fully functional, fully reactive, and able to handle all those crazy side effects)

Functional reactive programming (FRP) is a paradigm that has gained lots of attention lately, especially in the JavaScript front end world. It’s an overloaded term, but it describes a simple idea:

函数式React式编程 (FRP)是最近受到广泛关注的一种范例,尤其是在JavaScript前端世界中。 这是一个重载的术语,但它描述了一个简单的想法:

Everything should be pure so it’s easy to test and reason about (functional), and async behavior should be modeled using values that change over time (reactive).

一切都应该是纯净的,以便易于测试和推理(功能性) ,并且异步行为应该使用随时间变化的值(React性)来建模。

React in itself is not fully functional, nor is it fully reactive. But it is inspired by some of the concepts behind FRP. Functional components for instance are pure functions with respect to their props. And they are reactive to prop or state changes.

React本身并不完全起作用,也不是完全React性的。 但这是受到FRP背后的一些概念的启发。 例如, 功能组件是相对于其道具的纯功能。 它们对道具或状态的变化有React 。

But when it comes to handling side effects, React — being only the view layer — needs help from other libraries, such as Redux.

但是在处理副作用时 ,React(仅是视图层)需要其他库(例如Redux)的帮助 。

In this article I’ll talk about redux-cycles, a Redux middleware that helps you to handle side effects and async code in your React apps in a functional-reactive way — a trait which is not yet shared by other Redux side effect models — by leveraging the Cycle.js framework.

在本文中,我将讨论redux-cycles ,这是一个Redux中间件,可帮助您以功能响应式的方式处理React应用程序中的副作用和异步代码-尚未被其他Redux副作用模型共享的特征-通过利用Cycle.js框架。

有什么副作用? (What are side effects?)

A side effect modifies the outside world. Everything in your app that deals with making HTTP requests, writing to localStorage, or even manipulating the DOM, is considered a side effect.

副作用改变了外界。 应用程序中处理HTTP请求,写入localStorage甚至处理DOM的所有内容都被视为副作用。

Side effects are bad. They are hard to test, complicated to maintain, and generally they are where most of your bugs lie. Your goal is therefore to minimize/localize them.

副作用不好。 它们很难测试,维护起来很复杂,通常它们是您大多数bug所在的地方。 因此,您的目标是最小化/本地化它们。

“In the presence of side effects, a program’s behavior depends on past history; that is, the order of evaluation matters. Because understanding an effectful program requires thinking about all possible histories, side effects often make a program harder to understand.” — Norman Ramsey

“存在副作用时,程序的行为取决于过去的历史; 也就是说,评估的顺序很重要。 因为了解有效的程序需要考虑所有可能的历史,所以副作用通常会使程序更难以理解。” — 诺曼·拉姆西

Here are several popular ways to handle side effects in Redux:

以下是Redux中处理副作用的几种常用方法:

  1. redux-thunk — puts your side effects code inside action creators

    redux-thunk-将您的副作用代码放入动作创建者中

  2. redux-saga — makes your side effects logic declarative using sagas

    redux-saga-使用sagas使您的副作用逻辑声明

  3. redux-observable — uses reactive programming to model side effects

    redux-observable-使用React式编程来模拟副作用

The problem is that none of these are both pure and reactive. Some of them are pure (redux-saga) while others are reactive (redux-observable), but none of them share all of the concepts we introduced earlier about FRP.

问题在于这些都不是纯粹的和React性的。 它们中的一些是纯净的(redux-saga),而另一些是React性的(redux-observable),但是它们都没有共享我们之前介绍的有关FRP的所有概念。

Redux-cycles is both pure and reactive.

Redux循环 既纯又是React性的。

We’ll first explain in more details these functional and reactive concepts — and why you should care. We’ll then explain how redux-cycles works in detail.

我们将首先更详细地解释这些功能和React性概念,以及您为什么要关心它。 然后,我们将详细解释redux-cycles的工作原理。

使用Cycle.js进行纯副作用处理 (Pure side effects handling with Cycle.js)

An HTTP request is probably the most common side effect. Here’s an example of an HTTP request using redux-thunk:

HTTP请求可能是最常见的副作用。 这是使用redux-thunk的HTTP请求的示例:

function fetchUser(user) {  return (dispatch, getState) =>     fetch(`https://api.github.com/users/${user}`)}

This function is imperative. Yes it’s returning a promise and you can chain it together with other promises, but fetch() is doing a call, at that specific moment in time. It is not pure.

此功能势在必行。 是的,它正在返回一个承诺,您可以将其与其他承诺链接在一起,但是fetch()正在该特定时间进行调用。 这不是纯粹的。

The same applies to redux-observable:

同样适用于redux-observable:

const fetchUserEpic = action$ =>  action$.ofType(FETCH_USER)    .mergeMap(action =>      ajax.getJSON(`https://api.github.com/users/${action.payload}`)        .map(fetchUserFulfilled)    );

ajax.getJSON() makes this snippet of code imperative.

ajax.getJSON() 使此代码段势在必行。

To make an HTTP request pure, you shouldn’t think about “make an HTTP request now” but rather “let me describe how I want my HTTP request to look like” and not worry about when it actually happens or who makes it.

为使HTTP请求纯净,您不应该考虑“立即提出HTTP请求”,而应考虑“让我描述我希望HTTP请求的外观”,而不用担心它何时真正发生或由谁发出。

In Cycle.js this is essentially how you code all things. Everything you do with the framework is about creating descriptions about what you want to do. These descriptions are then sent to these things called drivers (via reactive streams) which actually take care of making the HTTP request:

在Cycle.js中,这基本上就是您编写所有代码的方式。 您使用框架所做的所有事情都是关于创建关于您想做什么的描述。 然后,将这些描述发送到称为驱动程序的这些东西(通过响应流),这些驱动程序实际上负责发出HTTP请求:

function main(sources) {  const request$ = xs.of({    url: `https://api.github.com/users/foo`,  });
return {    HTTP: request$  };}

As you can see from this snippet of code, there’s no function call to actually make the request. If you run this code you’ll see the request happen regardless. So what’s actually happening behind the scenes?

从这段代码中可以看到,没有函数调用可以实际发出请求。 如果运行此代码,则无论如何都会看到请求发生。 那么幕后到底发生了什么?

The magic happens thanks to drivers. Cycle.js knows that when your function returns an object with an HTTP key, it needs to handle the messages that it receives from this stream, and perform an HTTP request accordingly (via an HTTP driver).

多亏了驾驶员,魔术才得以实现。 Cycle.js知道,当函数返回带有HTTP密钥的对象时,它需要处理从此流接收到的消息,并相应地(通过HTTP驱动程序)执行HTTP请求。

The key point is that you didn’t get rid of the side effect — the HTTP request still needs to happen — but you localized it outside of your application code.

关键点是您没有摆脱副作用-HTTP请求仍然需要发生-但您将其本地化在应用程序代码之外。

Your functions are much easier to reason about, and are especially much easier to test because you can simply test whether your functions emit the right messages — no weird mocking or timing needed.

您的函数更容易推论,并且测试尤其容易,因为您可以简单地测试函数是否发出正确的消息-不需要怪异的模拟或计时。

React性副作用 (Reactive side effects)

In the earlier examples we touched on reactivity. There needs to be a way to communicate with these so called drivers about “doing things in the outside world” and be notified about “things that happen in the outside world”.

在前面的示例中,我们谈到了React性。 必须有一种方法与这些所谓的驾驶员进行交流,以“在外界做事”,并被告知“在外界发生的事情”。

Observables (aka streams) are the perfect abstraction for this sort of async communication.

Observables (又名流)是这种异步通信的完美抽象。

Whenever you want to “do something” you emit to an output stream a description of what you want to do. These output streams are called sinks in the Cycle.js world.

每当您想“做某事”时,都会向输出流发出您想做的描述。 这些输出流在Cycle.js世界中称为接收器

Whenever you want to “be notified about something that happened” you use an input stream (called sources) and simply map over the stream values to learn about what happened.

每当您想“收到有关发生的事情的通知”时,您都使用输入流(称为source ),只需映射流值即可了解发生了什么。

This forms a sort of reactive loop which requires a different thinking to understand than normal imperative code. Let’s model an HTTP request/response lifecycle using this paradigm:

这形成了一种React性 循环 ,与普通命令式代码相比,需要不同的思维来理解。 让我们使用以下范例为HTTP请求/响应生命周期建模:

function main(sources) {  const response$ = sources.HTTP    .select('foo')    .flatten()    .map(response => response);
const request$ = xs.of({    url: `https://api.github.com/users/foo`,    category: 'foo',  });
const sinks = {    HTTP: request$  };  return sinks;}

The HTTP driver knows about the HTTP key returned by this function. It’s a stream containing an HTTP request description for a GitHub url. It’s telling the HTTP driver: “I want to make a request to this url”.

HTTP驱动程序知道此函数返回的HTTP密钥。 这是一个流,其中包含针对GitHub URL的HTTP请求描述。 它告诉HTTP驱动程序:“我要对此URL发出请求”。

The driver then knows to perform the request, and sends the response back to the main function as a source (sources.HTTP) — note that sinks and sources use the same object key.

然后,驱动程序知道要执行请求,然后将响应作为源( sources.HTTP )发送回主函数—请注意,接收器和源使用相同的对象键。

Let’s explain that again: we use sources.HTTP to “be notified about HTTP responses”. And we return sinks.HTTP to “make HTTP requests”.

让我们再次解释一下: 我们使用sources.HTTP来“收到有关HTTP响应的通知”。 然后我们返回sinks.HTTP HTTP以“发出HTTP请求”。

To explain this important reactive loop here’s an animation:

为了说明这个重要的React式循环,下面是一个动画:

This seems counter-intuitive compared to normal imperative programming: why would the code for reading the response exist before the code responsible for the request?

与普通命令式编程相比,这似乎是违反直觉的:为什么在负责请求的代码之前存在读取响应的代码?

This is because it doesn’t matter where the code is in FRP. All you have to do is send descriptions, and listen for changes. Code order is not important.

这是因为代码在FRP中的位置无关紧要。 您所要做的就是发送说明,并听取更改。 代码顺序并不重要。

This allows for very easy code refactoring.

这使得代码重构非常容易。

介绍redux-cycles (Introducing redux-cycles)

At this point you might be asking, what does all of this have to do with my React app?

在这一点上,您可能会问,这与我的React应用程序有什么关系?

You’ve learned about the advantages of making your code pure, by only writing descriptions of what you want to do. And you’ve learned about the advantages of using Observables to communicate with the outside world.

您已经通过仅编写要执行的操作说明而了解了使代码变得纯净的优势。 您已经了解了使用Observables与外界通信的优势。

You’ll now see how to use these concepts within your existing React apps to, in fact, go fully functional and reactive.

现在,您将看到如何在现有的React应用程序中使用这些概念,从而使之完全发挥作用并具有React性。

拦截和调度Redux动作 (Intercepting and dispatching Redux actions)

With Redux you dispatch actions to tell your reducers that you want a new state.

使用Redux,您可以分派操作来告诉您的reducer您想要一个新状态。

This flow is synchronous, meaning that if you want to introduce async behavior (for side effects) you need to use some form of middleware that intercepts actions, does the async side effect, and emits other actions accordingly.

此流程是同步的,这意味着如果要引入异步行为(针对副作用),则需要使用某种形式的中间件来拦截操作,执行异步副作用并相应地发出其他操作。

This is exactly what redux-cycles does. It’s a middleware that intercepts redux actions, enters the Cycle.js reactive loop, and allows you to perform other side effects using other drivers. It then dispatches new actions based on the async dataflow described in your functions:

这正是redux-cycles所做的。 它是一个中间件,可拦截redux动作,进入Cycle.jsReact式循环,并允许您使用其他驱动程序执行其他副作用。 然后,它根据您的函数中描述的异步数据流调度新操作:

function main(sources) {  const request$ = sources.ACTION    .filter(action => action.type === FETCH_USER)    .map(action => ({      url: `https://api.github.com/users/${action.payload}`,      category: 'users',    }));  const action$ = sources.HTTP    .select('users')    .flatten()    .map(fetchUserFulfilled);  const sinks = {    ACTION: action$,    HTTP: request$  };  return sinks;}

In the above example there’s a new source and sink introduced by redux-cycles — ACTION. But the communication paradigm is the same.

在上述例子中有一个新的源和汇由终极版周期引入- ACTION 。 但是交流范例是相同的。

It listens to actions being dispatched from the Redux world using sources.ACTION. And it dispatches new actions to the Redux world by returning sinks.ACTION.

它使用sources.ACTION监听从Redux世界派出的动作。 并且它通过返回sinks.ACTION来向Redux世界派遣新的动作。

Specifically it emits standard Flux Actions objects.

具体来说,它发出标准的Flux Actions对象 。

The cool thing is that you can combine stuff happening from other drivers. In the earlier example things happening in the HTTP world actually trigger changes to the ACTION world, and vice-versa.

很酷的事情是,您可以结合其他驱动程序中发生的事情。 在前面的示例中, HTTP世界中发生的事情实际上触发了ACTION世界的更改,反之亦然

— Note that communicating with Redux happens entirely through the ACTION source/sink. Redux-cycles’ drivers handle the actual dispatching for you.

—请注意,与Redux的通信完全通过ACTION源/接收器进行。 Redux-cycles的驱动程序为您处理实际的调度。

那更复杂的应用程序呢? (What about more complex apps?)

How does one develop more complex apps if you’re just writing pure functions that transform streams of data?

如果您只是编写可转换数据流的纯函数,那么如何开发更复杂的应用程序?

Turns out you can do pretty much anything using already built drivers. Or you can easily build your own — here’s a simple driver which logs messages written to its sink.

事实证明,使用已构建的驱动程序 ,您几乎可以做任何事情。 或者,您可以轻松地构建自己的驱动程序-这是一个简单的驱动程序,用于记录写入其接收器的消息。

run(main, {  LOG: msg$ => msg$.addListener({    next: msg => console.log(msg)  })});

run is part of Cycle.js, which runs your main function (first argument) and passes along all the drivers (second argument).

run是Cycle.js的一部分,它运行您的主函数(第一个参数)并传递所有驱动程序(第二个参数)。

Redux-cycles introduces two drivers which allow you to communicate with Redux; makeActionDriver() & makeStateDriver():

Redux-cycles引入了两个驱动程序,使您可以与Redux进行通信。 makeActionDriver()makeStateDriver()

import { createCycleMiddleware } from 'redux-cycles';
const cycleMiddleware = createCycleMiddleware();const { makeActionDriver, makeStateDriver } = cycleMiddleware;
const store = createStore(  rootReducer,  applyMiddleware(cycleMiddleware));
run(main, {  ACTION: makeActionDriver(),  STATE: makeStateDriver()})

makeStateDriver() is a read-only driver. This means you can only read sources.STATE in your main function. You can’t tell it what to do; you can only read data from it.

makeStateDriver()是只读驱动程序。 这意味着您只能读取main函数中的sources.STATE 。 你不能告诉它该怎么做; 您只能从中读取数据。

Every time the Redux state changes, thesources.STATE stream will emit the new state object. This is useful when you need to write specific logic based on the current state of the app.

每当Redux状态改变时, sources.STATE流将发出新的状态对象。 当您需要根据应用程序的当前状态编写特定的逻辑时,这很有用。

复杂的异步数据流 (Complex async data flow)

Another great advantage of reactive programming is the ability to use operators to compose streams into other streams — effectively treating them as arrays of values over time: you can map, filter and even reduce them.

React式编程的另一个巨大优势是能够使用运算符将​​流组合成其他流-随时间有效地将它们视为值数组:您可以mapfilter甚至reduce它们。

Operators make explicit data-flow graphs possible; i.e., reasoning of dependencies between operations. Allowing you to visualize data flowing through various operators like the animation above.

运算符使明确的数据流图成为可能; 即,推理操作之间的依赖关系。 使您可以可视化流经各种运算符的数据,例如上面的动画。

Redux-observable also allows you to write complex async flows — they use a multiplex WebSocket example as their selling point — however, the power of writing these flows in a pure fashion is what really sets Cycle.js apart.

Redux-observable还允许您编写复杂的异步流-它们使用多路复用WebSocket示例作为卖点-但是,以纯净的方式编写这些流的能力真正使Cycle.js与众不同。

Since everything is pure dataflow we can imagine a future where programming will be nothing other than plugging together blocks of operators.
由于所有内容都是纯数据流,我们可以想象一个未来,编程就是将运算符块连接在一起。

用大理石图测试 (Testing with marble diagrams)

Last but not least comes testing. This is where redux-cycles (and generally all Cycle.js apps) really shines.

最后但并非最不重要的一点是测试。 这是redux-cycles(通常是所有Cycle.js应用程序)真正发光的地方。

Because everything is pure in your app code, to test your main function you simply give it streams as input and expect specific streams as output.

由于应用程序代码中的所有内容都是纯净的,因此要测试您的主要功能,您只需将其作为输入流,并期望将特定的流作为输出。

Using the wonderful @cycle/time project, you can even draw marble diagrams and test your functions in a very visual way:

使用精彩的@ cycle / time项目,您甚至可以绘制大理石图并以非常直观的方式测试您的功能:

assertSourcesSinks({  ACTION: { '-a-b-c----|': actionSource },  HTTP:   { '---r------|': httpSource },}, {  HTTP:   { '---------r|': httpSink },  ACTION: { '---a------|': actionSink },}, searchUsers, done);

This piece of code executes the searchUsers function, passing it specific sources as input (first argument). Given these sources it expects the function to return the provided sinks (second argument). If it doesn’t, the assertion fails.

这段代码执行searchUsers函数,并将特定的源作为输入(第一个参数)传递给它。 给定这些源,它期望函数返回提供的接收器(第二个参数)。 如果不是,则断言失败。

Defining streams graphically this way is especially useful when you need to test async behavior.

当您需要测试异步行为时,以图形方式定义流特别有用。

When the HTTP source emits r (response), you immediately expect a (action) to appear in the ACTION sink — they happen at the same time. However, when the ACTION source emits a burst of -a-b-c, you don’t expect anything to appear at that moment in the HTTP sink.

HTTP源发出r (响应)时,您会立即期望a (动作)出现在ACTION器中-它们同时发生。 但是,当ACTION源发出-abc ,您不希望此时在HTTP器中出现任何内容。

This is because searchUsers is meant to debounce the actions it receives. It’ll only send off an HTTP request after 800 milliseconds of inactivity on the ACTION source stream: it’s implementing an autocomplete functionality.

这是因为searchUsers旨在消除其收到的操作。 在ACTION源流上不活动800毫秒后,它只会发送HTTP请求:它正在实现自动完成功能。

Testing this sort of async behavior is trivial with pure and reactive functions.

使用纯函数和响应函数测试这种异步行为很简单。

结论 (Conclusion)

In this article we explained the true power of FRP. We introduced Cycle.js and its novel paradigms. The Cycle.js awesome list is an important resource if you want to learn more about this technology.

在本文中,我们解释了FRP的真正功能。 我们介绍了Cycle.js及其新颖的范例。 如果您想了解有关此技术的更多信息,那么Cycle.js 很棒的列表是重要的资源。

Using Cycle.js on its own — without React or Redux — requires a bit of a switch in mentality but can be done if you’re willing to abandon some of the technologies and resources in the React/Redux community.

在没有React或Redux的情况下独自使用Cycle.js会导致心态上的一些转变,但是如果您愿意放弃React / Redux社区中的某些技术和资源,则可以这样做。

Redux-cycles on the other hand allows you to continue using all of the great React stuff while getting your hands wet with FRP and Cycle.js.

另一方面,Redux-cycles允许您继续使用所有出色的React东西,同时通过FRP和Cycle.js弄湿双手。

Special thanks to Gosha Arinich and Nick Balestra for maintaining the project along with myself, and to Nick Johnstone for proof reading this article.

特别感谢Gosha Arinich和Nick Balestra与我一起维护该项目,并感谢Nick Johnstone为阅读本文提供了证明。

翻译自: https://www.freecodecamp.org/news/how-to-make-your-react-app-fully-functional-fully-reactive-and-able-to-handle-all-those-crazy-e5da8e7dac10/

创建react应用程序

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/7926.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息