Translator: miaoYu
The original link
In the past few years, I have handled many React projects, both large and small. In this process, I have repeatedly formed some patterns, which I would like to share with you here.
If you’re new to React, you’re lucky to be reading this :).
If you haven’t reacted before, you can skip section (3,6,8,10).
#1 Data is passed down and up
For newcomers to React, I usually tell them about the data transfer mode, which is a parent component passing data to a child component (such as an object, a string, etc.), or a method that allows the parent component to get data from the child component.
It’s like passing a bag of crisps and a walkie-talkie to a trapped miner.
The following is the simplest model
(This picture is worth a thousand words)
The parent component is on the left and the child component is on the right. The props connecting the two components allow information to flow freely in both directions.
One props is items, which passes information to child components. One props is deleteItem, which gives a child component a method to tell the parent component (” delete this item “).
#2 Fix HTML input tags
One important aspect of React componentization is that if the tag in the HTML doesn’t work the way you want it to, you can define it the way you want it to.
When I create a page that has a lot of user input fields, the first step is to deal with these tags.
Also note:
-
The input box should return the input value through the onChange method.
-
Make sure the input value is of the same type as the value returned by onChange. If typeof props. Value is of type number, then you need to cast e.target.
-
A set of radio buttons functions the same as
Bind a unique ID for the input
We bind unique ids for input, but defining a unique ID for each input is cumbersome.
You can generate a random ID for each pair of input/label tags, but the client-side and server-side rendered HTML will not match.
So, you can write a method to increment the generated ID as follows:
class Input extends React.Component { constructor(props) { super(props); this.id = getNextId(); this.onChange = this.onChange.bind(this); } onChange(e) { this.props.onChange(e.target.value); } render() { return ( <label htmlFor={this.id}> {this.props.label} <input id={this.id} value={this.props.value} onChange={this.onChange} /> </label> ); }}Copy the code
If getNextId() only incremented a number each time it was called, the number would be infinitely large when rendered on the server. This number needs to be reset every time a network request is made. The code is as follows:
let count = 1;
export const resetId = () => {
count = 1;
}
export const getNextId = () => {
return `element-id-${count++}`;
}
Copy the code
#4 Control the CSS with props
If you want to apply different CSS to different instances (for example, normal buttons and highlight buttons), you can pass props to control CSS.
It looks super simple on the surface, but I’m sure there are plenty of holes to jump into.
Personally, there are three ways to achieve this:
Use the theme
Package a lot of CSS together and use the property Themes to control it. Such as:
<Button theme=""secondary"">Hello</Button>
Set Boolean properties
For example, some special buttons require chamfering, but are not consistent with the theme you defined.
You can either go to the UI and make it consistent in design, or you can set a Boolean property to distinguish:
<Button theme=""secondary"" rounded>Hello</Button>
It’s a binary HTML property, so rounded = [true] is not needed.
Setting property values
In some cases, you may want to pass the value of a CSS property directly (in the case of a component, set it to inline style) :
<Icon width=""25"" height=""25"" type=""search"" />
Here’s an example:
Suppose you are creating a link component with three themes, and adding underscores is optional.
The code is as follows:
const Link = (props) => { let className = `link link--${props.theme}-theme`; if (! props.underline) className += ' link--no-underline'; return <a href={props.href} className={className}>{props.children}</a>; }; Link.propTypes = { theme: PropTypes.oneOf([ 'default', // primary color, no underline 'blend', // inherit surrounding styles 'primary-button', // primary color, solid block ]), underline: PropTypes.bool, href: PropTypes.string.isRequired, children: PropTypes.oneOfType([ PropTypes.element, PropTypes.array, PropTypes.string, ]).isRequired, }; Link.defaultProps = { theme: 'default', underline: false, };Copy the code
CSS:
.link--default-theme,
.link--blend-theme:hover {
color: #D84315;
}
.link--blend-theme {
color: inherit;
}
.link--default-theme:hover,
.link--blend-theme:hover {
text-decoration: underline;
}
.link--primary-button-theme {
display: inline-block;
padding: 12px 25px;
font-size: 18px;
background: #D84315;
color: white;
}
.link--no-underline {
text-decoration: none;
}
Copy the code
#5 Dynamic components
Dynamic components can render any component. It can be a dynamic page component that displays a single page from a bunch of pages, for example:
import HomePage from './HomePage.jsx'; import AboutPage from './AboutPage.jsx'; import UserPage from './UserPage.jsx'; import FourOhFourPage from './FourOhFourPage.jsx'; const PAGES = { home: HomePage, about: AboutPage, user: UserPage, }; const Page = (props) => { const Handler = PAGES[props.page] || FourOhFourPage; return <Handler {... props} /> }; Page.propTypes = { page: PropTypes.oneOf(Object.keys(PAGES)).isRequired, };Copy the code
If you replace home, about and user with /, /about and /user, congratulations, you’ve turned it into half a router.
#6 Optimize components
When a page is opened, the input field automatically retrieves the cursor, greatly improving the user experience.
For example, when you open a registration page, the cursor automatically retrits the username input field.
Someone writing this component would have thought to bind an ID to the input and implement it with document.getelementByID (‘user-name-input’).focus().
But I think this is not a very good way, I have a better way to achieve:
class Input extends Component { focus() { this.el.focus(); } render() { return ( <input ref={el=> { this.el = el; }} / >); }}Copy the code
Ok, an Input component with a focus() method is complete.
In the parent component, we can call the child component’s focus() method:
class SignInModal extends Component {
componentDidMount() {
this.InputComponent.focus();
}
render() {
return (
<div>
<label>User name: </label>
<Input
ref={comp => { this.InputComponent = comp; }}
/>
</div>
)
}
}
Copy the code
Note that when you use ref on a component, it is a reference to the component (not the underlying element), so you can access its methods.
#7 Don’t componentize too soon
A search component that sees a list of fuzzy queries as you type:
(I’m looking for people who like to satirize politics, and LIKE many people, I’m very interested in other people’s political views.)
When designing this component, you might be wondering if you need to create a new search results component, searchResult, to display search results, which might only require a few lines of HTML and CSS… But I have told myself that when I hesitate to build something new, I should not.
Instead of creating a separate component, I add a renderSearchResult method that returns the corresponding DOM. As follows:
const SearchSuggestions = (props) => { // renderSearchSuggestion() behaves as a pseduo SearchSuggestion component // keep it self contained and it should be easy to extract later if needed const renderSearchSuggestion = listItem => ( <li key={listItem.id}>{listItem.name} {listItem.id}</li> ); return ( <ul> {props.listItems.map(renderSearchSuggestion)} </ul> ); }Copy the code
If other components have similar requirements, you should copy and paste code into that component rather than prematurely componentize it.
Component for text formatting
This is a Price component that processes a number into a string with or without a decimal point or the $sign.
const Price = (props) => { const price = props.children.toLocaleString('en', { style: props.showSymbol ? 'currency' : undefined, currency: props.showSymbol ? 'USD' : undefined, maximumFractionDigits: props.showDecimals ? 2:0}); return <span className={props.className}>{price}</span> }; Price.propTypes = { className: React.PropTypes.string, children: React.PropTypes.number, showDecimals: React.PropTypes.bool, showSymbol: React.PropTypes.bool, }; Price.defaultProps = { children: 0, showDecimals: true, showSymbol: true, }; Const Page = () => {const lambPrice = 1234.567; Const jetPrice = 999999.99; Const bootPrice = 34.567; return ( <div> <p>One lamb is <Price className=""expensive"">{lambPrice}</Price></p> <p>One jet is <Price showDecimals={false}>{jetPrice}</Price></p> <p>Those gumboots will set ya back <Price showDecimals={false} showSymbol={false}>{bootPrice}</Price> bucks.</p> </div> ); };Copy the code
As you can see, I used a powerful string formatting library, linked here.
With less code (which I’m not a big fan of) :
function numberToPrice(num, options = {}) { const showSymbol = options.showSymbol ! == false; const showDecimals = options.showDecimals ! == false; return num.toLocaleString('en', { style: showSymbol ? 'currency' : undefined, currency: showSymbol ? 'USD' : undefined, maximumFractionDigits: showDecimals ? 2:0}); } const Page = () => {const lambPrice = 1234.567; Const jetPrice = 999999.99; Const bootPrice = 34.567; return ( <div> <p>One lamb is <span className=""expensive"">{numberToPrice(lambPrice)}</span></p> <p>One jet is {numberToPrice(jetPrice, { showDecimals: false })}</p> <p>Those gumboots will set ya back {numberToPrice(bootPrice, { showDecimals: false, showSymbol: false })} bucks.</p> </div> ); };Copy the code
Note: I did not check the validity of the incoming numbers because ………… LAN.
#9 Reduce component complexity
I’ve probably written this judgment a thousand times:
if (props.user.signInStatus === SIGN_IN_STATUSES.SIGNED_IN)...
Copy the code
This is a bad example, I should say “user logged in” instead of “user logged in status === logged in”
My component is complex enough that it should not worry about whether the price passed in is a number or whether the Boolean value is’ true ‘.
As you can see, if your data store matches your components, your components will be much simpler. As I’ve said before, “complexity” is where bugs hide. The simpler the component, the less likely it is to bug.
But complexity is inevitable. How to deal with it?
I recommend creating a module that handles input data, such as renaming props, strings to numbers, objects to arrays, data strings to data objects, and so on.
All processed and tested in one place.
If you set react/redux, you should write the request search results in the action like this:
fetch(`/api/search? ${queryParams} `). Then, the response = > response. The json ()). Then (normalizeSearchResultsApiData) / / processing all the data. Then (normalData = > { });Copy the code
This greatly reduces component complexity.
Import components without relative paths
Don’t write:
**import** Button **from** '.. /.. /.. /.. /Button/Button.jsx'; **import** Icon **from** '.. /.. /.. /.. /Icon/Icon.jsx'; **import** Footer **from** '.. /.. /Footer/Footer.jsx';Copy the code
Write like this:
`import {Button, Icon, Footer} from 'Components'; `Copy the code
In theory you could do this:
-
Create an index.js index so that all of your components can be referenced.
-
Use Webpack’s resolve.alias to redirect the component’s index file
When I implemented the above theory, IT didn’t work out well for three reasons:
-
Webpack 2 has been removed
alias
methods -
Eslint will report an error because the component is not in node_modules.
-
If you use WebStorm, CMD/CTRL + left click the component name, it will automatically open the component file. But this feature will not work.
(Edit: Matthew Hsiung found esLint and WebStorm solutions.)
The last
Wrote a lot, hope to help you!