After reading the SOLID Principles in JavaScript post by Subbu I thought about how our frameworks follow or don't follow these principles.
As you might have guessed, currently my framework of choice is React. So I checked if it adheres to these principles.
What does SOLID stand for?
SOLID is an acronym build by the first letter of 5 object oriented programming design principles. The basic idea is, if you follow these principles, your software gets better.
Single responsibility principle
Open/closed principle
Liskov substitution principle
Interface segregation principle
Dependency inversion principle
What do these principles imply and how does React adhere to it?
Single Responsibility Principle
What does it mean?
A class should only have a single responsibility.
How does React adhere to it?
React applications consist of components, which are classes that inherit from the React.Component
class. You can start building your application as a component and if it gets too complex, you can split this component up into multiple smaller components.
React doesn't force you to adhere to the principle, but you can split up your component classes into smaller components till you achieved single responsibility for all of your components.
For example, you could have a button component that just handles clicks and an input component that just handles user input. A level above you use a form component that uses multiple instances of the button and input component to get user credentials and above that a connection component that takes form data and sends it to a server.
Open Close Principle
What does it mean?
Software entities should be open for extension, but closed for modification. Which means you can extends it without modifying its source code.
How does React adhere to it?
Reacts component model is build around aggregation instead of inheritance. So you only extend the base React.Component
and not its children. This prevents you from overriding behavior of existing components directly. The only way is to wrap it with your own component.
You could for example wrap a Button
with a RedButton
that always applies specific styles to the basic Button
, but the Button
is closed for modification.
This is less flexible than inheritance, but it also simplifies the API. While you don't have direct access to the methods like in an extension, you only have to care about props
in your aggregation.
Liskov Substitution Principle
What does it mean?
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
How does React adhere to it?
Well, it doesn't use inheritance at all. Sure you extend React.Component
, but this class is essentially treated as abstract in React applications, you never directly create an object from it, so you never have to replace it with a child-class later.
On the other hand, you find yourself writing aggregations that should act like their wrapped components rather often. Like the Button
I mentioned before. You want that RedButton
to be already styled, but you also want it to act like the Button
, but since the API between components always is just props, it's often simple to add something while your wrappers props are passed down to the wrapped component. Because everything is dynamic, your wrapper doesn't even have to know everything about the data that would originally be passed down to the wrapped component, in the RedButton
example it would just have to know about the style.
Interface Segregation Principle
What does it mean?
Many client-specific interfaces are better than one general-purpose interface.
How does React adhere to it?
Because React is written in JavaScript, it benefits from the dynamic nature of this language. There are no formal interfaces. If you don't use refs
, which allow you to directly call class methods of a component, the only interaction between components is via props and nobody forces you to use props you don't need.
If you have a wrapper component that passes down an onClick
handler that shows an alert with the wrapped components class name, you can use this wrapper to wrap all components that use this onClick
prop and if they don't, the handler is just ignored.
My experience with this fact was that it simplified many things, you wouldn't get lost in defining many small interfaces beforehand. The drawback was that I often found me in situations where I passed down props the wrapped component did simply ignore silently. At least glamorous-native
threw a few warnings when I tried to pass down unknown CSS attributes. For this it often helps to use PropTypes or something.
Dependency Inversion Principle
What does it mean?
One should depend upon abstractions, not concretions.
How does React adhere to it?
In practice, this principle is often followed by removing class names from other classes. Like, you could have a List
that has Items
, so you could get the idea to create your Item
objects inside the List
class, now you have your List
tightly coupled with your Item
. Somewhere in your List
class is a new Item(...)
or Item.create(...)
etc.
React doesn't strictly adhere to it, you can pass an array of string to your List
component and create Item
children
from it no problem.
But you can also tell the List
it should simply render out its children
independent of what they are, maybe add some keys to it or justify them etc.
Now you can create an array of Item
s, sprinkle it with some HighlightItem
s, both created from different string arrays and put them inside your List
who won't be the wiser.
Conclusion
While React doesn't force the principles onto you, at least it often allows you to follow them. Sometimes it gets easier because of JavaScript, sometimes JavaScript makes it harder, but overall it is possible to write SOLID applications with React.
Top comments (7)
With regard to Liskov: on the one hand, you could see your component as accepting props that require certain fields, but entirely allow that the input has more fields than those. On the other hand, if we take the functional html as the output of your component, then your 'red button' will function just as well anywhere that a regular button would have worked.
So in relation to constraints of the information that is passed around, React does seem to inherently follow the L in SOLID.
I think the problem is, that you have to do this actively.
if you just do
The
RedButton
only behaves as aButton
in respect to theonPress
handler.You would have to do
to get all the props.
In the second case the location/context where you use RedButton would not need to be aware of the difference between a Button and a RedButton, so you'd be able to use a RedButton anywhere where you'd otherwise be able to use a Button (-> conforms to LSP there, I would think).
Thank you Kay!!. I haven't gotten opportunity to work on React yet, but I will remember this blog when I get a chance.
No problem :)
I think it's worth the effort. React has made my apps, web and native, much simpler to reason about.
This is a great post. I think you've got a chance to make it into the top 7 with this one π
YAY!