Difference between revisions of "React"
(Created page with " =Clean Code vs. Dirty Code: React Best Practices= This article will focus on clean code practices as they apply to modern React software development. I’ll also talk abou...") |
|||
Line 1: | Line 1: | ||
− | + | =[[ReactJs]]= | |
=Clean Code vs. Dirty Code: React Best Practices= | =Clean Code vs. Dirty Code: React Best Practices= |
Revision as of 14:35, 19 March 2019
Contents
- 1 ReactJs
- 2 Clean Code vs. Dirty Code: React Best Practices
- 2.1 Clean code is DRY
- 2.2 Clean code is predictable and testable
- 2.3 Clean code is self-commenting
- 2.4 Naming things
- 2.5 Clean code follows proven design patterns and best practices
- 2.6 Clean code doesn’t (necessarily) take longer to write
- 2.7 Practical examples of dirty code vs. clean code
- 2.8 Default values
- 2.9 Separate stateful aspects from rendering
- 2.10 Use stateless functional components
- 2.11 Rest/spread
- 2.12 Destructure when applicable
ReactJs
Clean Code vs. Dirty Code: React Best Practices
This article will focus on clean code practices as they apply to modern React software development. I’ll also talk about some of the “sugar” that ES6/ES2015 brings to the table.
Clean code is DRY
DRY is an acronym that stands for “Don’t Repeat Yourself.” If you are doing the same thing in multiple places, consolidate the duplicate code. If you see patterns in your code, that is an indication it is prime for DRYing. Sometimes this means standing back from the screen until you can’t read the text and literally looking for patterns.
// Dirty
const MyComponent = () => (
<div>
<OtherComponent type="a" className="colorful" foo={123} bar={456} />
<OtherComponent type="b" className="colorful" foo={123} bar={456} />
</div>
);
// Clean
const MyOtherComponent = ({ type }) => (
<OtherComponent type={type} className="colorful" foo={123} bar={456} />
);
const MyComponent = () => (
<div>
<MyOtherComponent type="a" />
<MyOtherComponent type="b" />
</div>
);
Sometimes – as in our example above – DRYing your code may actually increase code size. However, DRYing your code also generally improves maintainability.
Be warned that it’s possible to go too far with DRYing up your code, so know when to say when.
Clean code is predictable and testable
Writing unit tests is not just a good idea, it’s become almost mandatory. After all, how can you be sure that your latest shiny new feature didn’t introduce a bug somewhere else?
Many React developers rely on Jest for a zero-configuration test runner and to produce code coverage reports. And if you’re interested in visual before/after comparison testing, please check out American Express’s own Jest Image Snapshot.
Clean code is self-commenting
Has this happened to you before? You wrote some code and made sure that it was fully commented. As will happen, you found a bug, so you went back and changed the code. Did you remember to change your comments as well to reflect the new logic? Maybe. Maybe not. The next person who looked at your code then may have gone down a rabbit hole because they focused on the comments.
Add comments only to explain complex thoughts; that is, don’t comment on the obvious. Fewer comments also reduces visual clutter.
// Dirty
const fetchUser = (id) => (
fetch(buildUri`/users/${id}`) // Get User DTO record from REST API
.then(convertFormat) // Convert to snakeCase
.then(validateUser) // Make sure the the user is valid
);
In the clean version, we rename some of the functions to better describe what they do, thus eliminating the need for comments and reducing visual clutter. This limits the potential confusion of the code not matching the comments later.
// Clean
const fetchUser = (id) => (
fetch(buildUri`/users/${id}`)
.then(snakeToCamelCase)
.then(validateUser)
);
Naming things
In my previous article Function as Child Components Are an Anti-Pattern, I stressed the importance of naming things. We should all give serious thought to variable names, function names, and even filenames.
Here are a few guidelines:
- Boolean variables, or functions that return a boolean value, should start with “is,” “has” or “should.”
// Dirty
const done = current >= goal;
// Clean
const isComplete = current >= goal;
Functions should be named for what they do, not how they do it. In other words, don’t expose details of the implementation in the name. Why? Because how you do it may change some day, and you shouldn’t need to refactor your consuming code because of it. For example, you may load your config from a REST API today, but you may decide to bake it into the JavaScript tomorrow.
// Dirty
const loadConfigFromServer = () => {
...
};
// Clean
const loadConfig = () => {
...
};
Clean code follows proven design patterns and best practices
Here are some best practices to follow when architecting your React applications.
- Use small functions, each with a single responsibility. This is called the single responsibility principle. Ensure that each function does one job and does it well. This could mean breaking up complex components into many smaller ones. This also will lead to better testability.
- Be on the lookout for leaky abstractions. In other words, don’t impose your internal requirements on consumers of your code.
- Follow strict linting rules. This will help you write clean, consistent code.
Clean code doesn’t (necessarily) take longer to write
don’t discount the “rewrite factor” and time spent fixing comments from code reviews. If you break your code into small modules, each with a single responsibility, it’s likely that you’ll never have to touch most modules again. There is time saved in “write it and forget it.”
Practical examples of dirty code vs. clean code
DRY up this code
Take a look at the code sample below. Go ahead and step back from your monitor as I described above. Do you see any patterns? Notice that the component Thingie is identical to ThingieWithTitle with the exception of the Title component. This is a perfect candidate for DRYing.
// Dirty
import Title from './Title';
export const Thingie = ({ description }) => (
<div class="thingie">
<div class="description-wrapper">
<Description value={description} />
</div>
</div>
);
export const ThingieWithTitle = ({ title, description }) => (
<div>
<Title value={title} />
<div class="description-wrapper">
<Description value={description} />
</div>
</div>
);
Here we’ve allowed the passing of children to Thingie. We’ve then created ThingieWithTitle that wraps Thingie, passing in the Title as its children.
// Clean
import Title from './Title';
export const Thingie = ({ description, children }) => (
<div class="thingie">
{children}
<div class="description-wrapper">
<Description value={description} />
</div>
</div>
);
export const ThingieWithTitle = ({ title, ...others }) => (
<Thingie {...others}>
<Title value={title} />
</Thingie>
);
Default values
Take a look at the following code snippet. It defaults the className to “icon-large” using a logical OR statement, similar to the way your grandfather might have done it.
// Dirty
const Icon = ({ className, onClick }) => {
const additionalClasses = className || 'icon-large';
return (
<span
className={`icon-hover ${additionalClasses}`}
onClick={onClick}>
</span>
);
};
Here we use ES6’s default syntax to replace undefined values with empty strings. This allows us to use ES6’s single statement form of the fat-arrow function, thus eliminating the need for the return statement.
// Clean
const Icon = ({ className = 'icon-large', onClick }) => (
<span className={`icon-hover ${className}`} onClick={onClick} />
);
In this even cleaner version, the default values are set in React.
// Cleaner
const Icon = ({ className, onClick }) => (
<span className={`icon-hover ${className}`} onClick={onClick} />
);
Icon.defaultProps = {
className: 'icon-large',
};
Why is this cleaner? And is it really better? Don’t all three versions do the same thing? For the most part, yes. The advantage of letting React set your prop defaults, however, is that it produces more efficient code, defaults props in a Class based lifecycle component, as well as allows your default values to be checked against propTypes. But there is one more advantage: it declutters the default logic from that of the component itself.
For example, you could do the following, storing all of your default props in one place. I’m not suggesting that you do; I’m just saying that you have the flexibility to do so.
import defaultProps from './defaultProps';
...
Icon.defaultProps = defaultProps.Icon;
Separate stateful aspects from rendering
Mixing your stateful data-loading logic with your rendering (or presentation) logic can lead to component complexity. Instead, write a stateful container component whose single responsibility is to load the data. Then write another component whose sole responsibility is to display the data. This is called the Container Pattern. https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
In the example below, user data is loading and is displayed in a single component.
// Dirty
class User extends Component {
state = { loading: true };
render() {
const { loading, user } = this.state;
return loading
? <div>Loading...</div>
: <div>
<div>
First name: {user.firstName}
</div>
<div>
First name: {user.lastName}
</div>
...
</div>;
}
componentDidMount() {
fetchUser(this.props.id)
.then((user) => { this.setState({ loading: false, user })})
}
}
In the clean version, the concerns – loading data, displaying a loading spinner, and displaying data – have been separated. Not only does this make the code easier to understand, but testing will require a lot less effort as you can test each concern independently. And because RenderUser is a stateless functional component, the results are predictable.
// Clean
import RenderUser from './RenderUser';
class User extends Component {
state = { loading: true };
render() {
const { loading, user } = this.state;
return loading ? <Loading /> : <RenderUser user={user} />;
}
componentDidMount() {
fetchUser(this.props.id)
.then(user => { this.setState({ loading: false, user })})
}
}
Use stateless functional components
Stateless functional components (SFCs) were introduced in React v0.14.0, and they are used to greatly simplify a render-only component. But some developers haven’t let go of the past. For example, the following component is ripe for converting to an SFC.
// Dirty
class TableRowWrapper extends Component {
render() {
return (
<tr>
{this.props.children}
</tr>
);
}
}
The clean version clears a lot of the screen clutter of the dirty version. Through optimization of React’s core, it’s possible to use far less memory, as no instance is created.
// Clean
const TableRowWrapper = ({ children }) => (
<tr>
{children}
</tr>
);
Rest/spread
About a year ago, it was my belief that Object.assign would become everyone’s new best friend. Well times have changed. Enter the rest/spread spec in ES2016/ES7.
Take the case where you pass some props to a component. You’d like to use className in the component itself, but pass all other props down the chain. You would do something like this.
// Dirty
const MyComponent = (props) => {
const others = Object.assign({}, props);
delete others.className;
return (
<div className={props.className}>
{React.createElement(MyOtherComponent, others)}
</div>
);
};
Not a very elegant solution, is it? But with rest/spread, it’s a piece of cake!
// Clean
const MyComponent = ({ className, ...others }) => (
<div className={className}>
<MyOtherComponent {...others} />
</div>
);
We take the “rest” of the properties and we “spread” them as new props to MyOtherComponent. (Sometimes things just name themselves…)
Destructure when applicable
ES6 introduced the concept of destructuring, which really is your best friend. Destructuring allows you to “pull apart” properties of an object or elements of an array.
Object destructuring In this example, componentWillReceiveProps is passed newProps, and we set state.active to the new active prop.
// Dirty
componentWillReceiveProps(newProps) {
this.setState({
active: newProps.active
});
}
In this clean version, we destructure newProps into active. Not only do we no longer need to reference newProps.active, but we also can use ES6 object property shorthand in setState.
// Clean
componentWillReceiveProps({ active }) {
this.setState({ active });
}
Array destructuring
An often overlooked ES6 feature is array destructuring. Take the following code for example. It takes in a locale such as “en-US” and breaks it into language (en) and country (US).
// Dirty
const splitLocale = locale.split('-');
const language = splitLocale[0];
const country = splitLocale[1];
In the clean version, ES6 has you covered.
// Clean
const [language, country] = locale.split('-');
resource : https://americanexpress.io/clean-code-dirty-code/