react發送和接收請求
by Luca Matteis
盧卡·馬蒂斯(Luca Matteis)
React行為編程簡介:請求,等待和阻止 (An intro to Behavioral Programming with React: request, wait, and block)
Behavioral Programming (BP) is a paradigm coined in the 2012 article by David Harel, Assaf Marron, and Gera Weiss.
行為編程(BP)是David Harel,Assaf Marron和Gera Weiss在2012年的文章中提出的范式。
Directly from the abstract:
直接摘自:
Behavioral programming simplifies the task of dealing with underspecification and conflicting requirements by enabling the addition of software modules that can not only add to but also modify existing behaviors.
行為編程通過啟用添加不僅可以添加而且可以修改現有行為的軟件模塊,簡化了處理規格不足和沖突需求的任務。
高級概念 (High-level concepts)
I’ll first explain the high-level concepts using an example of two React components MoviesList
and MoviesCount
. One displays a list of movies, the other a number of how many movies there are. Then I will dive into how exactly behavioral programming works.
我將首先使用兩個React組件MoviesList
和MoviesCount
的示例來解釋高級概念。 一個顯示電影列表,另一個顯示電影數量。 然后,我將深入探討行為編程的工作原理。
Both components fetch data from the same HTTP URL. They were developed by two different teams in a large organization. When we render both components on a page, we have a problem as they perform the same request:
這兩個組件都從相同的HTTP URL獲取數據。 它們是由大型組織中的兩個不同團隊開發的。 當我們將兩個組件都呈現在頁面上時,我們會遇到一個問題,因為它們執行相同的請求:
<> <MoviesList /> <MoviesCount /></>
Little did we know that these are behavioral components. This means that we can do something quite clever to avoid both requests firing:
我們幾乎不知道這些是行為成分 。 這意味著我們可以做一些非常聰明的事情來避免兩個請求都被觸發:
const MoviesCountFromList = withBehavior([ function* () { // block FETCH_COUNT from happening yield { block: ['FETCH_COUNT'] } }, function* () { // wait for FETCH_LIST, requested by the other // MoviesList component, and derive the count const response = yield { wait: ['FETCH_LIST'] } this.setState({ count: response.length }) }])(MoviesCount)
In the above example, we stepped inside the MoviesCount
component. We waited and requested for something to happen. And, more uniquely to behavioral programming, we also blocked something from happening.
在上面的示例中,我們進入了MoviesCount
組件。 我們等待并要求發生一些事情。 而且,對于行為編程而言,更獨特的是,我們還阻止了某些事情的發生。
Because we were trying to avoid both requests from firing, we blocked the FETCH_COUNT
event from being triggered (since the same data was already acquired by the FETCH_LIST
event).
因為我們試圖避免觸發兩個請求,所以我們阻止了FETCH_COUNT
事件的觸發(因為FETCH_LIST
事件已經獲取了相同的數據)。
<> <MoviesList /> <MoviesCountFromList /></>
Adding functionality to existing components without modifying their code is the novelty of the behavioral programming paradigm.
在不修改其代碼的情況下向現有組件添加功能是行為編程范例的新穎之處。
Intuitively, this can allow the creation of more reusable components.
直觀地講,這可以允許創建更多可重用的組件。
In the rest of the article, I’ll go more in depth into how behavioral programing (BP) works, specifically in the context of React.
在本文的其余部分,我將更深入地研究行為編程(BP)的工作方式,特別是在React的上下文中。
重新思考編程流程 (Rethinking programming flow)
To achieve the above functionality, we need to think about programming behaviors a bit differently. Specifically, events play a crucial role in orchestrating the synchronization between the various behaviors we define for our components.
為了實現上述功能,我們需要對編程行為有所考慮。 具體而言, 事件在協調我們為組件定義的各種行為之間的同步中起著至關重要的作用。
const addHotThreeTimes = behavior( function* () { yield { request: ['ADD_HOT'] } yield { request: ['ADD_HOT'] } yield { request: ['ADD_HOT'] } })
const addColdThreeTimes = behavior( function* () { yield { request: ['ADD_COLD'] } yield { request: ['ADD_COLD'] } yield { request: ['ADD_COLD'] } })
run( addHotThreeTimes, addColdThreeTimes)
When we run the above code, we get back a list of requested events:
當我們運行上面的代碼時,我們返回一個請求事件的列表:
ADD_HOTADD_HOTADD_HOTADD_COLDADD_COLDADD_COLD
As expected, the first behavior executes. Once it’s done, the second behavior continues. However, new specifications for our component require us to change the order in which both events are triggered. Rather than triggering ADD_HOT
three times, and then ADD_COLD
three times, we want them to interleave and trigger ADD_COLD
right after a ADD_HOT
. This will keep the temperature somewhat stable.
如預期的那樣,將執行第一個行為。 完成后,第二個行為繼續。 但是,針對組件的新規范要求我們更改觸發兩個事件的順序。 而不是觸發ADD_HOT
三次,然后ADD_COLD
三次,我們希望他們交織和觸發ADD_COLD
一個之后ADD_HOT
。 這樣可以使溫度保持穩定。
...
const interleave = behavior( function* () { while (true) { // wait for ADD_HOT while blocking ADD_COLD yield { wait: ['ADD_HOT'], block: ['ADD_COLD'] }
// wait for ADD_COLD while blocking ADD_HOT yield { wait: ['ADD_COLD'], block: ['ADD_HOT'] } } })
run( addHotThreeTimes, addColdThreeTimes, interleave)
In the above example, we introduce a new interleave behavior which does exactly what we need.
在上面的示例中,我們引入了一種新的交錯行為,該行為正是我們需要的。
ADD_HOTADD_COLDADD_HOTADD_COLDADD_HOTADD_COLD
We changed the order of when things get executed, without having to modify the code of already-written behaviors.
我們更改了事情執行的順序 ,而不必修改已經編寫的行為的代碼。
The process is summarized in the graphic below.
下圖總結了該過程。
The key concepts of this way of programming are the request, wait, and block operators. The semantics for these operators are:
這種編程方式的關鍵概念是request , wait和block運算符。 這些運算符的語義是:
Requesting an event: proposing that the event be considered for triggering, and asking to be notified when it is triggered
請求事件:建議考慮將事件觸發,并要求在事件觸發時得到通知
Waiting for an event: without proposing its triggering, asking to be notified when the event is triggered
等待事件:不建議觸發事件,而是要求在事件觸發時得到通知
Blocking an event: forbidding the triggering of the event, vetoing requests of other b-threads.
阻止事件:禁止事件觸發,否決其他b線程的請求。
Each b-thread (behavioral thread) lives on its own and is unaware of other threads. But they’re all interwoven at runtime, which allows them to interact with each-other in a very novel way.
每個b線程(行為線程)獨立存在,并且不知道其他線程。 但是它們都是在運行時交織在一起的,這使它們可以以一種非常新穎的方式彼此交互。
The generator syntax is essential to the functioning of a behavioral program. We need to control when to proceed to the next yield statement.
生成器語法對于行為程序的運行至關重要。 我們需要控制何時進行下一個yield語句。
回到React (Back to React)
How can these BP concepts be used in the context of React?
這些BP概念如何在React的上下文中使用?
Turns out that through high-order components (HOCs), you can add this behavioral idiom to existing components in a very intuitive fashion:
事實證明,通過高階組件(HOC),您可以以非常直觀的方式將此行為習慣用法添加到現有組件中:
class CommentsCount extends React.Component { render() { return <div>{this.state.commentsCount}</div> }}
const FetchCommentsCount = withBehavior([ function* () { yield { request: ['FETCH_COMMENTS_COUNT']} const comments = yield fetchComments() yield { request: ['FETCH_COMMENTS_COUNT_SUCCESS']} this.setState({ commentsCount: comments.length }) },])(CommentsCount)
Here we are using withBehavior
, from the b-thread library, to make CommentsCount
a behavioral component. Specifically, we are making it fetch the comments and display the data once the data is ready.
在這里,我們使用來自b線程庫的withBehavior
,使CommentsCount
成為行為組件。 具體來說,我們正在使它獲取注釋并在數據準備好后顯示數據。
For simple components, this might not be such a game-changer. But let’s imagine more complex components, with lots of logic and other components inside of them.
對于簡單的組件,這可能不會改變游戲規則。 但是,讓我們想象一下更復雜的組件,其中包含許多邏輯和其他組件。
We might imagine the entire Netflix website as a <Netflix
/> component:
我們可以將整個Netflix網站想象為<Netflix
/>組件:
When we use this component in our app, we’d like to interact with it. Specifically, when a movie is clicked, we don’t want to start the movie immediately, but instead we want to make an HTTP request, show other data about the movie, and then start the movie.
當我們在應用程序中使用此組件時,我們希望與其進行交互。 具體來說,當單擊電影時,我們不想立即啟動該電影,而是希望發出HTTP請求,顯示有關該電影的其他數據,然后啟動該電影。
Without changing code inside the <Netflix
/> component, I’d argue that this would be impossible to achieve without it being a behavioral component.
如果不更改<Netflix
/>組件內部的代碼,我認為如果沒有行為組件就無法實現。
Instead let’s imagine that <Netflix
/> was developed using behavioral programming:
相反,讓我們想象<Netflix
/>是使用行為編程開發的:
const NetflixWithMovieInfo = withBehavior([ function* () { // First, block the MOVIE_START from happening // within <Netflix /> until a new // FETCH_MOVIE_INFO_SUCCESS event has been requested. // The yield statement below can be read as: // wait for FETCH_MOVIE_INFO_SUCCESS while blocking MOVIE_START yield { wait: ['FETCH_MOVIE_INFO_SUCCESS'], block: ['MOVIE_START'] } }, function* () { // Here we wait for MOVIE_CLICKED, which is // triggered within <Netflix />, and we fetch our // movie info. Once that's done we request a new event // which the earlier behavior is waiting upon const movie = yield { wait: ['MOVIE_CLICKED'] } const movieInfo = yield fetchMovieInfo(movie) yield { request: ['FETCH_MOVIE_INFO_SUCCESS'], payload: movieInfo } }])(Netflix)
Above we’ve created a new NetflixWithMovieInfo
component which modifies the behavior of the <Netflix
/> component (again, without changing its source code). The addition of the above behaviors makes it so that MOVIE_C
LICKED will not trigger MOVIE
_START immediately.
上面,我們創建了一個新的NetflixWithMovieInfo
組件,該組件修改了<Netflix
/>組件的行為(同樣,不更改其源代碼)。 上述行為的添加使that MOVIE_C
LICKED不會立即igger MOVIE
_START。
Instead, it uses a combination of “waiting while blocking”: a wait and a block can be defined within a single yield statement.
相反,它使用“等待時阻塞”的組合:可以在單個yield語句中定義等待和阻塞 。
The picture above describes, more in detail, what is happening within our behavioral components. Each little box within the components is a yield statement. Each vertical dashed arrow represents a behavior (aka b-thread).
上圖更詳細地描述了我們的行為組件中正在發生的事情。 組件中的每個小方框都是yield語句。 每個垂直虛線箭頭代表行為(又稱b線程)。
Internally, the behavioral implementation will start by looking at all the yield statements of all b-threads at the current synchronization point, depicted using an horizontal yellow line. It will only continue to the next yield statement within a b-thread if no events in other b-threads are blocking it.
在內部,行為實現將從查看當前同步點處所有b線程的所有yield語句開始,并使用水平黃線描繪。 如果其他b線程中沒有任何事件阻止它,它將僅繼續到b線程中的下一個yield語句。
Since nothing is blocking MOVIE_CLICKED
, it will be requested. We can then continue to the next yield statement for the Netflix behavior. At the next synch point, the b-thread on the far right, which is waiting for MOVIE_CLICKED
, will proceed to its next yield statement.
由于沒有什么阻止MOVIE_CLICKED
,因此將請求它。 然后,我們可以繼續執行下一個關于Netflix行為的收益聲明。 在下一個同步點,最右邊的b線程正在等待MOVIE_CLICKED
,將繼續執行下一個yield語句。
The middle behavior that is waiting-and-blocking does not proceed. FETCH_MOVIE_INFO_SUCCESS
was not requested by other b-threads, so it still waits-and-blocks. The next synchronization point will look something like this:
等待和阻止的中間行為不會繼續。 其他b線程未請求FETCH_MOVIE_INFO_SUCCESS
,因此它仍在等待并阻塞。 下一個同步點將如下所示:
As before, we will look at all the yield statement at this synchronization point. This time, however, we cannot request MOVIE_START
because there’s another b-thread that is blocking it (the black yield statement). The Netflix component will therefore not start the movie.
和以前一樣,我們將在此同步點查看所有yield語句。 但是,這一次我們無法請求MOVIE_START
因為還有另一個b線程正在阻止它(黑色yield語句)。 Netflix組件因此將無法開始播放電影。
FETCH_MOVIE_INFO_SUCCESS
on the far right, however, is free to be requested. This will unblock MOVIE_START
at the next synch point.
但是,最右邊的FETCH_MOVIE_INFO_SUCCESS
是免費的。 這將在下一個同步點取消阻止MOVIE_START
。
All this in practice allowed us to change the order of things happening within other components, without directly modifying their code. We were able to block certain events from firing until other conditions were met in other components.
實際上,所有這些使我們能夠更改其他組件中發生的事情的順序,而無需直接修改它們的代碼。 我們能夠阻止某些事件觸發,直到在其他組件中滿足其他條件為止。
This changes the way we might think of programming: not necessarily a set of statements executed in order, but rather an interleaving of yield statements all synchronized through specific event semantics.
這改變了我們對編程的思考方式:不一定是按順序執行的一組語句,而是交織了所有通過特定事件語義同步的yield語句。
Here’s a simple animation depicting the way b-threads are executed and interwoven at runtime.
這是一個簡單的動畫,描繪了b線程在運行時執行和交織的方式。
無需更改舊代碼即可編程 (Programming without changing old code)
There is another way we can understand this programming idiom. We can compare the way we currently program as specifications change, versus how it would be done with behavioral programming.
我們可以用另一種方式來理解這種編程習慣。 我們可以將當前隨著規范變化而編程的方式與行為編程的方式進行比較。
In the above caption, we imagine how behavior may be added to a non-behavioral program. We start with a program described only using three black rectangles (on the left).
在上面的標題中,我們想象了如何將行為添加到非行為程序中。 我們從僅使用三個黑色矩形(左側)描述的程序開始。
As specifications change, we realize we need to modify the program and add new behavior in various sections of the program, depicted as newly added colored rectangles. We continue doing this as requirements for our software change.
隨著規范的變化,我們意識到我們需要修改程序并在程序的各個部分添加新行為,以新添加的彩色矩形表示。 作為軟件更改的要求,我們將繼續這樣做。
Every addition of behavior requires us to change code that was written, which possibly litters the old behavior with bugs. Furthermore, if the program we are changing is part of various other modules used by different people, we might be introducing unwanted behavior to their software. Finally, it may not be possible to change specific programs as they might be distributed as libraries with licensed source code.
行為的每次添加都要求我們更改所編寫的代碼,這可能會用錯誤掩蓋舊行為。 此外,如果我們要更改的程序是不同人使用的其他模塊的一部分,那么我們可能會在他們的軟件中引入不必要的行為。 最后,可能無法更改特定程序,因為它們可能作為具有許可源代碼的庫分發。
In the above figure, we see how the same program-modifications can be achieved using behavioral programming idioms. We still start with our three rectangles on the left as we did before. But as new specifications arise, we don’t modify them. Instead we add new b-threads, represented as columns.
在上圖中,我們看到了如何使用行為編程習慣來實現相同的程序修改。 我們仍然像以前一樣從左側的三個矩形開始。 但是隨著新規范的出現,我們不會對其進行修改。 相反,我們添加了新的b線程,以列表示。
The resulting program is the same, although constructed in a very different way. One of the advantages of the behavioral approach is that we don’t have to modify old code as requirements change.
生成的程序是相同的,盡管構造方式非常不同。 行為方法的優點之一是,隨著需求的變化,我們不必修改舊代碼。
You can also imagine developing each b-thread in parallel, possibly by different people in a large organization, since they do not directly depend on each other.
您也可以想象大型機構中的不同人員可能并行開發每個b線程,因為它們并不直接相互依賴。
The benefit of this approach also seems to be with packaging: we can change the behavior of a library without needing to access or modify its source-code.
這種方法的好處似乎還在于打包:我們可以更改庫的行為,而無需訪問或修改其源代碼。
API不僅作為道具,而且作為事件 (APIs not only as props, but as events)
Currently, the only way for a React component to communicate with the outside world is via props (apart from the Context API).
當前,React組件與外界通信的唯一方法是通過props(除了Context API)。
By making a component behavioral, instead of using props, we tell the outside world about when things happen within the component by yielding events.
通過使組件具有行為性,而不是使用道具,我們通過產生事件來告知外界組件中何時發生事件。
To allow other developers to interact with the behavior of a component, we must therefore document the events that it requests, the events it waits for, and finally the events it blocks.
因此,為了允許其他開發人員與組件的行為進行交互,我們必須記錄其請求的事件,其等待的事件以及最終阻止的事件。
Events become the new API.
事件成為新的API。
For instance, in a non-behavioral Counter
component, we tell the outside world when the counter is incremented and what the current count is, using an onIncrement
prop:
例如,在一個非行為Counter
組件中,我們使用onIncrement
道具告訴外界計數器何時遞增以及當前計數是onIncrement
:
class Counter extends React.Component { state = { currentCount: 0 } handleClick = () => { this.setState(prevState => ({ currentCount: prevState.currentCount + 1 }), () => { this.props.onIncrement(this.state.currentCount) }) } render() { {this.state.currentCount} <button onClick={this.handleClick}>+</button> }}
<Counter onIncrement={(currentCount) => console.log(currentCount) }/>
What if we want to do something else before the counter’s state gets incremented? Indeed we could add a new prop such as onBeforeIncrement
, but the point is that we don’t want to add props and refactor code every time a new specific arises.
如果我們想在計數器狀態遞增之前做其他事情怎么辦? 確實,我們可以添加一個新的道具,例如onBeforeIncrement
,但要點是,我們不想在每次出現新的道具時都添加道具和重構代碼。
If we transform it into a behavioral component we can avoid refactoring when new specifications emerge:
如果我們將其轉換為行為組件,則可以避免在出現新規范時進行重構:
class Counter extends React.Component { state = { currentCount: 0 } handleClick = () => { bp.event('CLICKED_INCREMENT') } render() { {this.state.currentCount} <button onClick={this.handleClick}>+</button> }}
const BehavioralCounter = withBehavior([ function* () { yield { wait: ['CLICKED_INCREMENT'] } yield { request: ['UPDATE_CURRENT_COUNT'] }
this.setState(prevState => ({ currentCount: prevState.currentCount + 1 }), () => { this.props.onIncrement(this.state.currentCount) }) }])(Counter)
Notice how we moved the logic for when the state is updated inside a b-thread. Furthermore, before the update actually takes place, a new event UPDATE_CURRENT_COUNT
is requested.
注意,如何在b線程中更新狀態時如何移動邏輯。 此外,在實際進行更新之前,請求一個新事件UPDATE_CURRENT_COUNT
。
This effectively allows other b-threads to block the update from happening.
這有效地允許其他b線程阻止更新的發生。
Components can also be encapsulated and shared as different packages, and users can add behavior as they see fit.
組件也可以封裝和共享為不同的包,用戶可以根據自己的喜好添加行為。
// package-name: movies-listexport const function MoviesList() { ...}
// package-name: movies-list-with-paginationexport const MoviesListWithPagination = pipe( withBehavior(addPagination))(MoviesList)
// package-name: movies-list-with-pagination-logicexport const MoviesListWithDifferentPaginationLogic = pipe( withBehavior(changePaginationLogic))(MoviesListWithPagination)
Again this is different from simply enhancing a component, as a regular HOC would do. We can block certain things from happening in the components we extend from, effectively modifying their behavior.
再次,這不同于常規HOC所做的簡單地增強組件。 我們可以阻止某些事情在擴展組件中發生,從而有效地修改其行為。
結論 (Conclusion)
This new programming idiom might feel uncomfortable at first, but it seems to alleviate a prominent issue we have when using UI components: it is hard to reuse components, because they don’t blend with the environment they were put into.
剛開始時,這種新的編程習慣可能會讓您感到不舒服,但是它似乎減輕了我們在使用UI組件時遇到的一個突出問題: 很難重用組件,因為它們不會與所放置的環境融合在一起。
In the future, perhaps using these behavioral concepts, we will be able to add new behavior to apps by simply mounting new components. Stuff like this will be possible:
將來,也許使用這些行為概念,我們將能夠通過簡單地安裝新組件來向應用程序添加新行為。 這樣的東西將是可能的:
<Environment> <Netflix /> <Twitter /> <WaitForTwitterBeforeNetflix /> <OnTwitterClickShowLoader /></Environment>
Additionally, events don’t need to pollute the whole app and can be broadcast only within a specific environment.
此外,事件并不需要污染整個應用程序,而只能在特定環境中廣播。
Thanks for reading! If you’re interested in an actual implementation of behavioral programming, please see my current work in progress library that works with React: https://github.com/lmatteis/b-thread. The Behavioral Programming homepage also contains various implementations.
謝謝閱讀! 如果您對行為編程的實際實現感興趣,請參見我當前與React一起使用的進度庫: https : //github.com/lmatteis/b-thread 。 行為編程主頁還包含各種實現。
For more information on this exciting new concept, I suggest you read the scientific papers on Behavioral Programming or check some of my other articles on the subject.
有關此令人興奮的新概念的更多信息,建議您閱讀有關行為編程的科學論文,或查看我 有關該主題的 其他文章 。
翻譯自: https://www.freecodecamp.org/news/an-intro-to-behavioral-programming-with-react-request-wait-and-block-ad876e2d235e/
react發送和接收請求