惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
雷峰网
雷峰网
Recent Announcements
Recent Announcements
月光博客
月光博客
G
Google Developers Blog
腾讯CDC
S
Secure Thoughts
大猫的无限游戏
大猫的无限游戏
T
Tenable Blog
云风的 BLOG
云风的 BLOG
W
WeLiveSecurity
博客园 - 【当耐特】
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 聂微东
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
人人都是产品经理
人人都是产品经理
P
Privacy International News Feed
MyScale Blog
MyScale Blog
K
Kaspersky official blog
T
The Blog of Author Tim Ferriss
Attack and Defense Labs
Attack and Defense Labs
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
aimingoo的专栏
aimingoo的专栏
I
Intezer
Vercel News
Vercel News
小众软件
小众软件
Simon Willison's Weblog
Simon Willison's Weblog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
N
Netflix TechBlog - Medium
P
Proofpoint News Feed
Latest news
Latest news
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tor Project blog
S
Security Affairs
P
Proofpoint News Feed
博客园 - 三生石上(FineUI控件)
博客园 - Franky
C
Cyber Attacks, Cyber Crime and Cyber Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
美团技术团队
Recent Commits to openclaw:main
Recent Commits to openclaw:main
S
Security @ Cisco Blogs
L
LINUX DO - 热门话题
Know Your Adversary
Know Your Adversary
Project Zero
Project Zero
D
Docker
L
Lohrmann on Cybersecurity
F
Full Disclosure

Dmitri Pavlutin Blog

Pure Functions in JavaScript: A Beginner's Guide Record Type in TypeScript: A Quick Intro How to Write Comments in React: The Good, the Bad and the Ugly 4 Ways to Create an Enum in JavaScript React forwardRef(): How to Pass Refs to Child Components TypeScript Function Types: A Beginner's Guide How to Use v-model to Access Input Values in Vue Mastering Vue refs: From Zero to Hero Environment Variables in JavaScript: process.env 5 Must-Know Differences Between ref() and reactive() in Vue How to Destructure Props in Vue (Composition API) Triangulation in Test-Driven Development How to Use nextTick() in Vue Programming to Interface Vs to Implementation A Smarter JavaScript Mapper: array.flatMap() Array Grouping in JavaScript: Object.groupBy() How to Access ES Module Metadata using import.meta JSON Modules in JavaScript How to Trim Strings in JavaScript TypeScript Function Overloading How to Debounce and Throttle Callbacks in Vue How to Show/Hide Elements in Vue Sparse vs Dense Arrays in JavaScript How to Fill an Array with Initial Values in JavaScript Covariance and Contravariance in TypeScript What are Higher-Order Functions in JavaScript? How to Use TypeScript with React Components Index Signatures in TypeScript How to Use React useReducer() Hook unknown vs any in TypeScript A Guide to React Context and useContext() Hook How to Use Promise.any() 2 Ways to Remove a Property from an Object in JavaScript 'return await promise' vs 'return promise' in JavaScript How to Use Promise.allSettled() How to Use fetch() with JSON JavaScript Promises: then(f,f) vs then(f).catch(f) What is a Promise in JavaScript? How to Use Promise.all() A Simple Guide to Component Props in React Don't Stop Me Now: How to Use React useTransition() hook A Simple Explanation of JavaScript Variables: const, let, var ES Modules Dynamic Import How to Memoize with React.useMemo() How to Cleanup Async Effects in React Why Math.max() Without Arguments Returns -Infinity How to Debounce and Throttle Callbacks in React Don't Confuse Function Expressions and Function Declarations in JavaScript How to Use ES Modules in Node.js Solving a Mystery Behavior of parseInt() in JavaScript How to Use Array Reduce Method in JavaScript 3 Ways to Merge Arrays in JavaScript A Guide to Jotai: the Minimalist React State Management Library The Difference Between Values and References in JavaScript How to Implement a Queue in JavaScript A Helpful Algorithm to Determine "this" value in JavaScript React useRef() Hook Explained in 3 Steps 7 Interview Questions on "this" keyword in JavaScript. Can You Answer Them? How to Greatly Enhance fetch() with the Decorator Pattern 7 Interview Questions on JavaScript Closures. Can You Answer Them? What's a Method in JavaScript? array.sort() Does Not Simply Sort Numbers in JavaScript How to Solve the Infinite Loop of React.useEffect() The New Array Method You'll Enjoy: array.at(index) What's the Difference between DOM Node and Element? Why Promises Are Faster Than setTimeout()? Everything About Callback Functions in JavaScript How React Updates State 5 Mistakes to Avoid When Using React Hooks 5 Best Practices to Write Quality JavaScript Variables Type checking in JavaScript: typeof and instanceof operators 3 Ways to Check if a Variable is Defined in JavaScript React Forms Tutorial: Access Input Values, Validate, Submit Forms Prototypal Inheritance in JavaScript How to Timeout a fetch() Request How to Learn JavaScript If You're a Beginner A Simple Explanation of React.useEffect() A Simple Explanation of JavaScript Iterators How to Use React Controlled Inputs Everything about null in JavaScript How to Use Fetch with async/await Getting Started with Arrow Functions in JavaScript An Interesting Explanation of async/await in JavaScript Front-end Architecture: Stable and Volatile Dependencies Is it Safe to Compare JavaScript Strings? How to Access Object's Keys, Values, and Entries in JavaScript What Actually is a String in JavaScript? 3 Ways to Shallow Clone Objects in JavaScript (w/ bonuses) Checking if an Array Contains a Value in JavaScript JavaScript Event Delegation: A Beginner's Guide How to Parse URL in JavaScript: hostname, pathname, query, hash 3 Ways to Detect an Array in JavaScript How to Get the Screen, Window, and Web Page Sizes in JavaScript 3 Ways to Check If an Object Has a Property/Key in JavaScript How to Compare Objects in JavaScript Object.is() vs Strict Equality Operator in JavaScript Own and Inherited Properties in JavaScript 5 Differences Between Arrow and Regular Functions How to Use Object Destructuring in JavaScript Your Guide to React.useCallback()
The Benefits of Orthogonal React Components
Dmitri Pavlutin · 2019-11-27 · via Dmitri Pavlutin Blog

1. Why good system design is important

About 5 years ago, I was developing a cross-platform mobile application for a European startup. The first series of features were easy to implement. I was progressing well and was happy about that.

6 months passed by. More features were added on top of previous ones. Step by step, making new changes to existing modules was becoming increasingly harder.

At some point, I started rejecting some new features and changes because they would require too much time to implement. The story had ended with a whole rewrite of the mobile apps to the native platform, mainly because further maintenance would be unreasonably expensive.

I blamed the bugs in the cross-platform framework, blamed that client was changing requirements, and so on.

But these weren't the main problem. Without realizing it, I was fighting tightly coupled components like Don Quijote was fighting the windmills.

Don Quijote

I overlooked the importance of making my components easy to change. I didn't follow the principles of good design and didn't make my components adaptable to potential changes.

Don't make my mistake: learn design principles. One particularly influential is the orthogonality principle, which says to isolate things that change for different reasons.

2. Orthogonal components

If A and B are orthogonal, then changing A does not change B (and vice-versa). That's the concept of orthogonality.

In a radio device, the volume and station selection controls are orthogonal. The volume control changes only the sound volume. The station selection control changes only the received radio station.

Radio device

Imagine the radio device has broken. The volume control changes the volume, but also diverts the selected radio station. The volume control and station selection controls are non-orthogonal — the volume control produces a side-effect.

It would be difficult to tune the broken radio device. The same happens when you try to add changes to tightly coupled components: you're forced to catch the side-effects of your changes.

Two or more components are orthogonal if a change in one component does not affect other components. For example, a component that displays a list of employees should be orthogonal to the logic that fetches the employees.

A good React application design would make orthogonal:

  • The UI elements (the presentational components)
  • Fetch details (fetch library, REST or GraphQL)
  • Global state management (Redux)
  • Persistence logic (local storage, cookies).

Make your components implement one task, be isolated, self-contained and encapsulated. This will make your components orthogonal, and any change you make is going to be isolated and focused on just one component. That's the recipe for predictable and easy to develop systems.

Let's see how to do that in 2 examples.

3. Making the component orthogonal to fetch details

Let's say you need to fetch a list of employees. One version of <EmployeesPage> could be as follows:


import React, { useState } from 'react';

import axios from 'axios';

import EmployeesList from './EmployeesList';

function EmployeesPage() {

const [isFetching, setFetching] = useState(false);

const [employees, setEmployees] = useState([]);

useEffect(function fetch() {

(async function() {

setFetching(true);

const response = await axios.get("/employees");

setEmployees(response.data);

setFetching(false);

})();

}, []);

if (isFetching) {

return <div>Fetching employees....</div>;

}

return <EmployeesList employees={employees} />;

}


Open the demo.

The problem with the current implementation is that <EmployeesPage> depends on how data is fetched. The component knows about axios library, knows that a GET request is performed.

What would happen if later you switch from axios and REST to GraphQL? If the application has dozens of components coupled with fetching logic, you would have to change them all manually.

There's a better approach. Let's isolate the fetch logic details from the component.

A good way to do this is to use the new Suspense feature of React:


import React, { Suspense } from "react";

import EmployeesList from "./EmployeesList";

function EmployeesPage({ resource }) {

return (

<Suspense fallback={<h1>Fetching employees....</h1>}>

<EmployeesFetch resource={resource} />

</Suspense>

);

}

function EmployeesFetch({ resource }) {

const employees = resource.employees.read();

return <EmployeesList employees={employees} />;

}


Open the demo.

<EmployeesPage> now suspends when until <EmployeesFetch> reads the resource async.

What's important is <EmployeesPage> is orthogonal to the fetching logic.

<EmployeesPage> doesn't care that axios implements fetching. You could easily change axios to native fetch, or move to GraphQL: <EmployeesPage> is not affected.

Let's say you want to a Jump to top button that shows when the user scrolls down more than 500px. When the button is clicked, the page automatically scrolls to the top.

Scroll to top demo

The first naive implementation of <ScrollToTop> can be:


import React, { useState, useEffect } from 'react';

const DISTANCE = 500;

function ScrollToTop() {

const [crossed, setCrossed] = useState(false);

useEffect(

function() {

const handler = () => setCrossed(window.scrollY > DISTANCE);

handler();

window.addEventListener("scroll", handler);

return () => window.removeEventListener("scroll", handler);

},

[]

);

function onClick() {

window.scrollTo({

top: 0,

behavior: "smooth"

});

}

if (!crossed) {

return null;

}

return <button onClick={onClick}>Jump to top</button>;

}


Open the demo.

<ScrollToTop> implements the scroll listener and renders a button that scrolls the page to top. The issue is that these concepts can change at different rates.

A better orthogonal design should isolate the scroll listener from the UI.

Let's extract the scroll listener logic into a custom hook useScrollDistance():


import { useState, useEffect } from 'react';

function useScrollDistance(distance) {

const [crossed, setCrossed] = useState(false);

useEffect(function() {

const handler = () => setCrossed(window.scrollY > distance);

handler();

window.addEventListener("scroll", handler);

return () => window.removeEventListener("scroll", handler);

}, [distance]);

return crossed;

}


Then let's use useScrollAtBottom() inside a component <IfScrollCrossed>:


function IfScrollCrossed({ children, distance }) {

const isBottom = useScrollDistance(distance);

return isBottom ? children : null;

}


<IfScrollCrossed> displays its children only if the user has scrolled a specific distance.

Finally, here's the button that scrolls to top when clicked:


function onClick() {

window.scrollTo({

top: 0,

behavior: 'smooth'

});

}

function JumpToTop() {

return <button onClick={onClick}>Jump to top</button>;

}


Now if you want to make everything work, just put <JumpToTop> as a child of <IfAtBottom>:


import React from 'react';

// ...

const DISTANCE = 500;

function MyComponent() {

// ...

return (

<IfScrollCrossed distance={DISTANCE}>

<JumpToTop />

</IfScrollCrossed>

);

}


Open the demo.

What's important is that <IfScrollCrossed> isolates the changes of scroll listener. As well as UI elements changes are isolated in <JumpToTop> component.

Scroll listener logic and UI elements are orthogonal.

An additional benefit is that you can combine <IfScrollCrossed> with any other UI. For example, you could show a newsletter form when user has scrolled down 300px:


import React from 'react';

// ...

const DISTANCE_NEWSLETTER = 300;

function OtherComponent() {

// ...

return (

<IfScrollCrossed distance={DISTANCE_NEWSLETTER}>

<SubscribeToNewsletterForm />

</IfScrollCrossed>

);

}


5. The "Main" component

While isolating changes into separate components is what orthogonality is all about, there could be components that can change for different reasons. These are so-called "Main" (aka "App") components.

You can find the "Main" component inside the index.jsx file: the one that starts the application. It knows all the "dirty" details about the application: initializes the global state provider (like Redux), configures the fetching libraries (like GraphQL Apollo), associates routes with components, and so on.

You might have several "Main" components: for the client side (to run inside a browser) and for server side (that implements Server-Side Rendering).

6. The benefits of orthogonal design

The orthogonal design provides lots of benefits.

Easy to change

When your components are orthogonally designed, any change you make to a component is isolated within the component.

Readability

Because the orthogonal component has one responsibility, it's much easier to understand what the component does. It is not cluttered with details that do not belong here.

Testability

Orthogonal components concentrate solely on implementing a single task. What you have to do is just test whether the component does the task correctly.

Often it happens that a non-orthogonal component requires lots of mocks and manual setup just to test it. And if something is hard to test, eventually tests are going to be skipped. You just refactor such components.

7. Think in principles

I like the new React features like hooks, suspense, etc. But I try to think wider, exploring whether these features help me follow the good design.

  • Why React hooks? They make UI rendering logic orthogonal to state and side-effects logic.
  • Why Suspense for fetching? It makes the fetch details and components orthogonal.

8. The balance

Let's recall a scene from "Star Wars Revenge of the Sith" movie. After Anakin Skywalker is defeated by his former mentor Obi-Wan Kenobi, the latter says:

Bring balance to the Force, not leave it in darkness!

Anakin Skywalker was chosen to become a Jedi and bring a balance between Dark and Light sides.

Balance

The orthogonal design is balanced by "You aren't gonna need it" (YAGNI) principle.

YAGNI emerges as a principle of Extreme Programming:

Always implement things when you actually need them, never when you just foresee that you may need them.

Avoid the extremes of both orthogonality and YAGNI.

Recall my story from the intro of the post: I ended up with an application that was difficult and costly to change. My mistake was that I unintentionally created components that were not designed for change. That's an extreme of YAGNI.

On the other side, if you make every piece of logic orthogonal, you will end up creating abstractions that are not going to be needed. That's an extreme of orthogonal design.

The practical approach is to foresee the changes. Study in detail the domain problem that your application solves, ask the client for a list of potential features. If you think that a certain place is going to change, then apply the orthogonal principle.

8. Key takeaway

Writing software is not only about implementing the application's requirements. It's equally important to put effort into designing well the components.

A key principle of a good design is the isolation of the logic that most likely will change: making it orthogonal. This makes your whole system flexible and adaptable to change or new features requirements.

If the orthogonal principle is overlooked, you risk creating components that are tightly coupled and dependent. A slight change in one place might unexpectedly echo in another place, increasing the cost of change, maintenance and creating new features.

Would you like to know more? Then your next step is to read The Pragmatic Programmer.