It was with a sense of incredible pride and relief that we turned in tonight to the printer a new book on React
On that note, here’s a slightly abridged translation of Dan Abramov’s article on the use of interceptors in React version 16.The book, which we’re already looking forward to, covers this in Chapter 5.
Last week, we and Sophie Alpert. presented the concept of "interceptors" at the React Conf conference, followed by a detailed breakdown of the topic from Ryan Florence
I highly recommend watching this plenary lecture to familiarize yourself with the range of problems we are trying to solve with interceptors.However, even an hour of your time is greatly appreciated, so I have decided to briefly outline the main considerations for interceptors in this article.
Note :Interceptors in React are still experimental. No need to get into them right now. Also keep in mind that this publication contains my personal views, which may not coincide with the position of React developers.
Why do we need interceptors?
Component-based organization and top-down data flow are known to help organize a large UI into small, independent and reusable fragments. However, it is often not possible to fragment complex components beyond some limit because the logic retains state and is unrecoverable in a function or some other component This is sometimes what those who complain that React fails to achieve "separation of duties" complain about.
Such cases are very common, and involve, for example, animations, form handling, connecting to external data sources, and many other operations we might need to do with our components. Trying to solve such problems with components alone usually gives us :
- Giant Components which are difficult to refactor and test.
- Duplication of logic between different components and lifecycle methods.
- Complex patterns , in particular, render props and higher-order components.
We believe that interceptors are the most promising for all of these problems. Interceptors help organize logic within a component as reusable isolated units :
Interceptors conform to React philosophy (explicit data flow and composition) and within a component, not just between components That’s why I think interceptors fit naturally into the React component model.
Unlike patterns like property rendering or higher-order components, interceptors don’t burden your component tree with unnecessarily deep attachments. They also don’t have those disadvantages that are inherent in impurities.
Even if at first glance interceptors gross you out (just like me at first!) I recommend giving this option a chance and experimenting with it. I think you’ll like it.
Isn’t React swelling because of interceptors?
Until we’ve looked at interceptors in detail, you may be worried that adding interceptors to React is just multiplying entities. That’s a fair criticism. I think so : while it does feel like an extra cognitive burden in the short term (to learn them), it will only make you feel better in the long run.
If interceptors take root in the React community, in fact will shrink The number of entities that have to be handled when writing React applications With interceptors, you can use functions all the time instead of switching between functions, classes, higher-order components, and rendering components.
As for increasing the size of your implementation, the React application only grows about ~1.5kB (min+gzip) when supported by interceptors. While that’s not too much by itself, it’s very likely that interceptors will even reduce the size of your assembly because interceptor code is usually minified better than equivalent code using classes. The example below is a bit extreme, but it nicely demonstrates why this is the case ( click to expand the entire thread):
There are no revolutionary changes in the interceptor proposal Your existing code will work fine even if you start using interceptors in new components. In fact, this is exactly what we recommend: do not globally rewrite anything! It is prudent to wait until the use of hooks has settled in all your critical code. Still, we would appreciate it if you could experiment with alpha 16.7 and give us some feedback on interceptor proposal , as well as report any bugs
What is it about interceptors?
To understand what interceptors are, you have to go back a step and think about what code overuse is.
There are many ways to reuse logic in React applications today. For example, to compute something, you can write simple functions and then call them. You can also write components (which can themselves be functions or classes). Components are more powerful, but when you work with them, you need to display some UI. That’s why it’s inconvenient to transfer non-visual logic with components. That’s how we come to complex patterns like property rendering and higher order components. Wouldn’t React be simpler if there was just one common way to reuse code instead of so many?
Functions seem to be ideal for reusable code. Transferring logic between functions is the least expensive. However, you can’t store local React state inside functions. You can’t extract behaviors like "monitor window size and update state" or "animate value over time" from a component-class without restructuring the code or introducing abstractions like Observables. Both approaches only complicate the code, and React is nice to us for its simplicity.
Interceptors solve exactly this problem. With interceptors, you can use React features (e.g., state) from a function – by calling it just once. React provides several built-in interceptors that correspond to the "building blocks" of React: state, lifecycle, and context.
Note: strictly speaking, your own interceptors are not a feature of React. The ability to write your own interceptors comes naturally from their internal organization itself.
Show me the code!
Suppose we want to subscribe a component to the current window width (for example, to display different content or a narrower viewing area).
This kind of code can be written in several ways today. For example, make a class, create multiple lifecycle methods, or maybe even resort to property rendering or apply a higher-order component if you expect to use it multiple times. However, I don’t think anything compares to that :
If you read this code, it means it does exactly what it says We use the width of the window inside our component, and React redraws your component if it changes. That’s what interceptors are for – to make components truly declarable, even if they contain state and side effects.
Consider how we could implement this proprietary interceptor. We could use React’s local state to keep the actual window width in it, and use a side effect to set this state when the window size changes :
As shown above, React’s built-in interceptors like
useEffect serve as "bricks." We can use them directly from our components, or we can build our own interceptors from them, for example,
useWindowWidth Using your own interceptors seems just as idiomatic as working with React’s built-in API.
For more on embedded interceptors, see this review
Interceptors are encapsulated – whenever an interceptor is called, it gets an isolated local state inside the component currently running In this particular example, it doesn’t matter (the window width is the same for all components!), but that’s the power of interceptors! They are not designed to share state, but rather the logic that preserves state. We don’t want to break the downstream data flow!
Here is an example React animation library where we experiment with interceptors :
Notice how the demonstrated source code implements a staggering animation : we pass values between several of our own interceptors within the same rendering function.
(This example is discussed in more detail in this manual )
The ability to transfer data between interceptors makes them very useful for embodying animations, data subscriptions, form management and working with other state-preserving abstractions. Unlike rendering higher-order properties or components in the case of interceptors, no "false hierarchy" is created in your rendering tree They are more like a two-dimensional list of "memory cells" attached to a component. No additional levels.
What about classes?
In our opinion, custom interceptors are the most interesting detail in the whole proposal. But for custom interceptors to work, React must provide a way to declare state and side effects at the function level. That’s what built-in interceptors like
useEffect For more on this, see documentation
It turns out that such built-in interceptors are not only handy for creating your own interceptors. They are also sufficient for defining components in general, since they provide us with the features we need – such as state. This is why we would like to see interceptors become the primary means for defining React components in the future.
No, we don’t plan to phase out classes. We have tens of thousands of class components in use at Facebook, and we (just like you) don’t want to rewrite them at all. But if the React community starts using interceptors, it will become impractical to keep the two recommended ways of writing components. Interceptors cover all those practical cases in which classes are used, but give you more flexibility in retrieving, testing, and reusing code. That’s why we associate our vision for the future of React with interceptors.
What if the interceptors are magic?
Possibly, interceptor rules Will baffle you.
Although it is not customary to call an interceptor at the top level, you probably would not want to determine the state in the condition yourself, even if you could For example, condition-linked state can’t be defined in a class either, and in four years of talking to React users, I haven’t heard any complaints about this.
This design is critical for implementing your own interceptors without introducing unnecessary syntactic noise or pitfalls in the process. We understand that it’s hard to do when you’re not used to it, but we think this compromise is acceptable, given the opportunities it opens up. If you don’t agree, I suggest you experiment and see for yourself how you like this approach.
We’ve been using interceptors in production for a month now, to see if the new rules will confuse programmers. Practice shows that people get used to interceptors in a matter of hours. I have to admit that I, too, thought these rules were heresy at first, but that feeling quickly passed. It was exactly the same impression I had when I first got to know React. (You didn’t like React? And I only liked it the second time.)
Note: There is no magic at the interceptor implementation level either. As Jamie writes , it turns out to be something like this :
We keep a component-by-component list of interceptors, and move to the next component in the list whenever we use an interceptor. Thanks to interceptor rules, their order is the same in any rendering mechanism, so we can provide a component with the correct state each time we call it.
( In this article from Rudy Yardley explains everything beautifully in pictures!)
You may have wondered where React stores interceptor state. The same place as the state of the classes. React has an internal update queue that contains the truth of last resort for every state, no matter exactly how you define your components.
array.pop (in which case the order of calls is also important!)
Whether your point of view is pragmatic or dogmatic, I hope that at least one of these options makes sense to you. Most importantly, in my opinion, interceptors make our jobs easier and users more comfortable. That’s what makes interceptors so appealing to me.