In this paper, the React component design modes of container and presentation components, high-order components, and render props are summarized
HBaseCon Asia 2019 Track 3
Container and Presentational components
The concept is introduced
The sample
Let’s look at a simple example. Construct a component that takes text and displays it.
export default class GetText extends React.Component { state = { text: null, } componentDidMount() { fetch('https://api.com/', { headers: { Accept: 'application/json' } }).then(response => { return response.json() }).then(json => { this.setState({ text: json.joke }) }) } render() { return(< div > < div > external access to data: {this. State. The text} < / div > < div > UI code < / div > < / div >)}}Copy the code
Seeing the GetText component above, there is logic to communicate with external data sources. Then we can split this component into two parts.
One part is dedicated to external communication (container components) and one part is responsible for UI logic (presentation components). Let’s break down the above example.
Container components:
export default class GetTextContainer extends React.Component { state = { text: null, } componentDidMount() { fetch('https://api.com/', { headers: { Accept: 'application/json' } }).then(response => { return response.json() }).then(json => { this.setState({ text: json.joke }) }) } render() { return (<div><GetTextPresentational text={this.state.text}/></div> ) }}Copy the code
Display components:
export default class GetTextPresentational extends React.Component { render() { return(< div > < div > external access to data: {this. Props. Text} < / div > < div > UI code < / div > < / div >)}}Copy the code
Problems solved by patterns
Second, high order components
The concept is introduced
The sample
As we’ve already said, higher-order components actually use a function that takes the React component as an argument and returns the new component.
We’re going to create a new judgeWoman function here, take the specific presentation component, and determine if it’s female,
Const judgeWoman = (Component) => {const NewComponent = (props) => {// Determine if the user is femaleletIsWoman = math.random () > 0.5?true : false if (isWoman) { const allProps = { add: 'Properties added to higher-order components'. props }return<Component {... allProps} />; }else { return<span> <span> }}returnNewComponent; };Copy the code
Pass the List and ShoppingCart components as arguments to this function. At this point, we have two enhanced components WithList and WithShoppingCart. The logic of whether it was female was repeated.
const List = (props) => { return<div> <div> <div>{props. Add}</div></div>)}const WithList = judgeWoman(List)const ShoppingCart = (props) => {return<div> <div>{props. Add}</div></div>) const WithShoppingCart = judgeWoman(ShoppingCart)Copy the code
Const judgeWoman = (Woman,Man) => {const NewComponent = (props) => {// Determine if the user is femaleletIsWoman = math.random () > 0.5?true : false if (isWoman) { const allProps = { add: 'Properties added to higher-order components'. props }return<Woman {... allProps} />; }else { return <Man/> } } returnNewComponent; };Copy the code
Even more powerful, since functions also return components, higher-order components can be nested for use! For example, we judge gender first and then age.
const withComponet =judgeAge(judgeWoman(ShoppingCart))Copy the code
The code is visible SRC /pattern2(http://t.cn/AiYbYy5g)
Problems solved by patterns
We should never write the same logic more than once. Higher-order components serve to extract common logic. At the same time, the nesting of higher-order components makes code reuse more flexible.
React-redux uses this pattern. Look at the code below. Connect (mapStateToProps, mapDispatchToProps) generates a higher-order component function that takes TodoList as an argument. Finally, the high-level component VisibleTodoList is returned.
import { connect } from 'react-redux'const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps)(TodoList)Copy the code
Precautions for Use
Advanced components are good, but we should pay attention to the following points when using them.
1, packaging display name for easy debugging
Debugging is cumbersome when using higher-order components. When React renders an error, the component’s displayName static property is used to determine the error component class. Container components created by HOC will show up in React Developer Tools just like any other component. To facilitate debugging, we need to select a display name to indicate that it is a product of HOC.
The most common approach is to use HOC to wrap the display name of the wrapped component. For example, a higher-order component called withSubscription and the display name for the wrapped component called CommentList would be withSubscription (CommentList) :
function withSubscription(WrappedComponent) { class WithSubscription extends React.Component {/* ... */} WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)}) `;returnWithSubscription; }function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; }Copy the code
2. Don’t use HOC in render methods
The React diff algorithm (called coordination) uses component identifiers to determine whether it should update an existing subtree or drop it and mount a new one. If the component returned from Render is the same as the component in the previous render (===), React updates the subtree recursively by differentiating it from the new one. If they are not equal, the previous subtree is completely unloaded.
Usually, you don’t have to think about that. But this is important with HOC, because it means you shouldn’t apply HOC to a component in its render method:
render// EnhancedComponent is created every time the render function is called // EnhancedComponent1! == EnhancedComponent2 const EnhancedComponent = enhance(MyComponent); // This will cause subtrees to be unmounted and remounted every time they are rendered!return<EnhancedComponent />; }Copy the code
/ / define static function WrappedComponent staticMethod =function() {/ *... }// now use HOCconst EnhancedComponent = enhance(WrappedComponent); . / / enhancement components without staticMethodtypeof EnhancedComponent staticMethod = = ='undefined' // trueCopy the code
functionenhance(WrappedComponent) { class Enhance extends React.Component {/*... * /} / / must be accurate know which way to copy: (Enhance) staticMethod. = WrappedComponent staticMethod;returnEnhance; }Copy the code
import hoistNonReactStatic from 'hoist-non-react-statics';functionenhance(WrappedComponent) { class Enhance extends React.Component {/*... */} hoistNonReactStatic(Enhance, WrappedComponent);returnEnhance; }Copy the code
// Use this method instead of... MyComponent.someFunction = someFunction;exportdefault MyComponent; / /... Export the method separately... export { someFunction }; / /... And import them import MyComponent, {someFunction} from among the components to use'./MyComponent.js';Copy the code
Refs will not be passed
Although the convention for higher-order components is to pass all props to the wrapped component, this does not apply to Refs. That’s because a REF is not really a prop, just like a key, which is handled specifically by React. If you add ref to HOC’s return component, the REF reference points to the container component, not the wrapped component.
The solution to this problem is to use the React. ForwardRef API (introduced in React 16.3).
Three, Render the props
The concept is introduced
The sample
A component with render props expects the subcomponent to be a function, and all it does is call the subcomponent as a function, with the parameters being the props passed in, and render the returned result.
<Provider> {props => <List add={props.add} />}</Provider>Copy the code
Let’s look at how a Provider component is defined. With this code, we call the function passed in. Props. Children (allProps).
Const Provider = (props) => {// Check whether the user is femaleletIsWoman = math.random () > 0.5?true : false if (isWoman) { const allProps = { add: 'Properties added to higher-order components'. props }return props.children(allProps) } else { return<div> for women only, men are not allowed to browse </div>; }}Copy the code
It seems that any higher-order component that render props can do can also do it, and the higher-order component is easier to understand. Let’s look at one of the more powerful aspects of render props: more flexibility for new props. Assuming that our List component accepts a Plus attribute and our ShoppingCart component accepts an Add attribute, we can write this directly without changing the List component or the Provider itself. Achieving the same effect with higher-order components is much more complicated.
<Provider> {props => { const { add } = props return < List plus={add} /> }}</Provider><Provider> {props => <ShoppingCart add={props.add} />}</Provider>Copy the code
Const Provider = (props) => {// Check whether the user is femaleletIsWoman = math.random () > 0.5?true : false if (isWoman) { const allProps = { add: 'Properties added to higher-order components'. props }return props.test(allProps) } else { return<div> for women only, men are not allowed to browse </div>; }}const ExampleRenderProps = () => {return ( <div> <Provider test={props => <List add={props.add} />} /> <Provider test={props => <ShoppingCart add={props.add} />} /> </div> )}Copy the code
Problems solved by patterns
Precautions for Use
Be careful when using Render Props with react. PureComponent! If you create functions in the Provider property, using render props will offset the advantages of using react. PureComponent. Because shallow comparisons of props always give false, and in this case each render will generate a new value for render props.
For example, continuing with the <List> component we used earlier, if List inherited from react. PureComponent instead of react.ponent, our example would look like this:
class ExampleRenderProps extends React.Component { render() { return(<div> {/* This is not good! Each render of 'testThe value of prop will be different. */} <Providertest={props => <List add={props.add} />} /> </div> ) }}Copy the code
is rendered, it generates a new function as a prop for the
-
, thus cancelling the effects of the
-
component inherited from the react. PureComponent!
class ExampleRenderProps extends React.Component { renderList=()=>{ return <List add={props.add} /> } render() { return ( <div> <Provider test={this.renderList} /> </div> ) }}Copy the code
If you cannot define a prop statically (for example, because you need to turn off props and/or state for the component), then <List> should be extended from react.component.component.component.props.
summary
-
React
-
React Component Patterns
-
React Combat: Design patterns and best practices
-
Presentational and Container Components