From Mixin to React to Hook

  Front end, hook, javascript, Programmer, react.js

Introduction

Front-end development speed is very fast, pages and components become more and more complex, how to better implementState logic multiplexingIt has always been an important part of the application, which directly affects the quality of the application and the ease of maintenance.

This article introducesReactThree Implementations UsedState logic multiplexingThe paper also analyzes their implementation principles, usage methods, practical applications and how to choose and use them.

This article is a little longer. The following is the mind map of this article. You can read it from the beginning or select some interesting parts to read:

image

Mixin design pattern

image

Mixin(Blending) is a way to extend the collection function. It essentially copies the attributes of one object to another, but you can copy themAny numberOf objectsAny numberMethod to a new object, this isInheritanceWhat cannot be achieved. Its appearance is mainly to solve the problem of code reuse.

Many open source libraries offerMixinThe implementation of, such asUnderscoreThe_.extendMethods,JQueryTheextendMethods.

Use_.extendMethod to realize code reuse:

var LogMixin = {
 actionLog: function() {
 console.log('action...');
 },
 requestLog: function() {
 console.log('request...');
 },
 };
 function User() {  /*..*/  }
 function Goods() {  /*..*/ }
 _.extend(User.prototype, LogMixin);
 _.extend(Goods.prototype, LogMixin);
 var user = new User();
 var good = new Goods();
 user.actionLog();
 good.requestLog();

We can try to write a simple one manuallyMixinMethods:

function setMixin(target, mixin) {
 if (arguments[2]) {
 for (var i = 2, len = arguments.length;   i < len;  i++) {
 target.prototype[arguments[i]] = mixin.prototype[arguments[i]];
 }
 }
 else {
 for (var methodName in mixin.prototype) {
 if (!  Object.hasOwnProperty(target.prototype, methodName)) {
 target.prototype[methodName] = mixin.prototype[methodName];
 }
 }
 }
 }
 setMixin(User,LogMixin,'actionLog');
 setMixin(Goods,LogMixin,'requestLog');

You can usesetMixinThe method extends any method of any object to the target object.

Application of Mixin in React

ReactAlso providedMixinThe implementation of the, if completely different components have similar functions, we can introduce to achieve code reuse, of course, only in usecreateClassTo createReactComponent, because in theReactComponent ofes6It has been abandoned in writing.

For example, in the following example, many components or pages need to record user behaviors, performance indicators, etc. If we introduce the logic of writing logs into each component, we will generate a large number of duplicate codes throughMixinWe can solve this problem:

var LogMixin = {
 log: function() {
 console.log('log');
 },
 componentDidMount: function() {
 console.log('in');
 },
 componentWillUnmount: function() {
 console.log('out');
 }
 };
 
 var User = React.createClass({
 mixins: [LogMixin],
 render: function() {
 return (<div>...</div>)
 }
 });
 
 var Goods = React.createClass({
 mixins: [LogMixin],
 render: function() {
 return (<div>...</div>)
 }
 });

Harm of Mixin

ReactOfficial documents are available atMixins Considered HarmfulAs mentioned in the articleMixinHas brought harm:

  • MixinMay be interdependent and coupled, which is not conducive to code maintenance.
  • DifferentMixinMethods in may conflict with each other
  • MixinFor a long time, components can be perceived and even processed, which will cause snowballing complexity to the code.

ReactIt is no longer recommendedMixinTo solve the problem of code reuse, becauseMixinThe harm is greater than the value he produces, andReactIt is fully recommended to replace it with high-level components. In addition, high-level components can realize more other and more powerful functions. Before learning high-level components, let’s look at a design pattern.

decorator pattern

image

A decorator (decorator) mode can dynamically add responsibilities to the image during program running without changing the object itself. Compared with inheritance, decorators are a lighter and more flexible method.

High-order component (HOC)

image

Higher order components can be seen asReactFor an implementation of decorator pattern, a high-order component is a function that takes a component as a parameter and returns a new component.

Higher order components (HOCYesReactAdvanced technology in to reuse component logic. But the higher-order components themselves are notReact API. It is only a mode, this mode is made up ofReactThe combination of its own nature is inevitable.

function visible(WrappedComponent) {
 return class extends Component {
 render() {
 const { visible, ...props } = this.props;
 if (visible === false) return null;
 return <WrappedComponent {...props} />;
 }
 }
 }

The code above is oneHOCThe function receives a component as a parameter and returns a new component. The new component can receive avisible propsAccording tovisibleTo determine whether to render Visible.

Let’s explore from the following aspectsHOC.

image

Implementation of HOC

Attribute proxy

TherenderTo return the components to be wrapped, so that we can proxy all incomingpropsIn fact, the high-level component generated in this way is the parent component of the original component, the function abovevisibleJust oneHOCImplementation of attribute proxy.

function proxyHOC(WrappedComponent) {
return class extends Component {
render() {
return <WrappedComponent {...this.props} />;
}
}
}

Items enhanced over native components:

  • Can operate all incomingprops
  • Lifecycle of Operable Components
  • Operable componentstaticMethod
  • Obtainrefs

Reverse inheritance

Returns a component, inherits the original component, and sets therenderThe that calls the original component in therender. Because it inherits the original component, the of the original component can be accessed through thisLife cycle, props, state, renderAnd so on, it can operate more attributes than attribute proxy.

function inheritHOC(WrappedComponent) {
 return class extends WrappedComponent {
 render() {
 return super.render();
 }
 }
 }

Items enhanced over native components:

  • Can operate all incomingprops
  • Lifecycle of Operable Components
  • Operable componentstaticMethod
  • Obtainrefs
  • Operablestate
  • Can render hijacking

What function can HOC achieve

Combination rendering

Any other component and the original component can be used for combined rendering to achieve the effects of style, layout reuse, etc.

Through attribute proxy

function stylHOC(WrappedComponent) {
return class extends Component {
render() {
return (<div>
<div className="title">{this.props.title}</div>
<WrappedComponent {...this.props} />
</div>);
}
}
}

Through reverse inheritance

function styleHOC(WrappedComponent) {
 return class extends WrappedComponent {
 render() {
 return <div>
 <div className="title">{this.props.title}</div>
 {super.render()}
 </div>
 }
 }
 }

Conditional rendering

Determines whether the original component is rendered or not based on specific attributes.

Through attribute proxy

function visibleHOC(WrappedComponent) {
return class extends Component {
render() {
if (this.props.visible === false) return null;
return <WrappedComponent {...props} />;
}
}
}

Through reverse inheritance

function visibleHOC(WrappedComponent) {
 return class extends WrappedComponent {
 render() {
 if (this.props.visible === false) {
 return null
 } else {
 return super.render()
 }
 }
 }
 }

Operating props

You can change thepropsTo add, modify, delete, or according to specificpropsCarry out special operations.

Through attribute proxy

function proxyHOC(WrappedComponent) {
return class extends Component {
render() {
const newProps = {
...this.props,
user: 'ConardLi'
}
return <WrappedComponent {...newProps} />;
}
}
}

Get refs

Of higher-order components that can obtain the original componentsrefThroughrefGet the component strength, such as the following code, and call the log method of the original component after the initialization of the program is completed. (I don’t know how to use refs, pleaseRefs & DOM)

Through attribute proxy

function refHOC(WrappedComponent) {
 return class extends Component {
 componentDidMount() {
 this.wapperRef.log()
 }
 render() {
 return <WrappedComponent {...this.props} ref={ref => { this.wapperRef = ref }} />;
 }
 }
 }

Note here: when calling high-level components, the authenticity of the original components cannot be obtained.refFor details, please seePass refs

State management

Select the status of the original assembly toHOCAs shown in the following code, we willInputThevalueExtract toHOCTo make it a controlled component without affecting its useonChangeThe method performs some other operations. Based on this way, we can realize a simpleBidirectional bindingPlease seeBidirectional binding.

Through attribute proxy

function proxyHoc(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = { value: '' };
}

onChange = (event) => {
const { onChange } = this.props;
this.setState({
value: event.target.value,
}, () => {
if(typeof onChange ==='function'){
onChange(event);
}
})
}

render() {
const newProps = {
value: this.state.value,
onChange: this.onChange,
}
return <WrappedComponent {...this.props} {...newProps} />;
}
}
}

class HOC extends Component {
render() {
return <input {...this.props}></input>
}
}

export default proxyHoc(HOC);

State of operation

The above example uses HOC’s state to enhance the original component through attribute proxy, but it cannot directly control the original component.stateThrough reverse inheritance, we can directly manipulate thestate. However, it is not recommended to directly modify or add the original componentsstateBecause this may conflict with operations inside the component.

Through reverse inheritance

function debugHOC(WrappedComponent) {
return class extends WrappedComponent {
render() {
console.log('props', this.props);
console.log('state', this.state);
return (
<div className="debuging">
{super.render()}
</div>
)
}
}
}

AboveHOCInrenderLt.propsAndstatePrint it out and use it as debugging phase, of course you can write more debugging code in it. Imagine just adding to the components we want to debug@debugYou can debug this component without writing a lot of redundant code each time you debug it. (If you don’t know how to use HOC yet, pleaseHow to use HOC)

Rendering hijacking

High-order components can do many operations in the render function, thus controlling the rendering output of the original components. As long as the rendering of the original component is changed, we call it aRendering hijacking.

In fact, the aboveCombination renderingAndConditional renderingAre allRendering hijackingOne of the, through reverse inheritance, not only can realize the above two points, but also directlyheightenBy the original componentrenderFunction generatedReact element.

Through reverse inheritance

function hijackHOC(WrappedComponent) {
 return class extends WrappedComponent {
 render() {
 const tree = super.render();
 let newProps = {};
 if (tree && tree.type === 'input') {
 NewProps = {value:' rendering hijacked'};
 }
 const props = Object.assign({}, tree.props, newProps);
 const newTree = React.cloneElement(tree, props, tree.props.children);
 return newTree;
 }
 }
 }

Pay attention to the above instructions. I usedheightenrather thanChange.renderFunction is actually calledReact.creatElementprocreantReact element

image
Although we can get it, but we can’t directly modify its properties, we passgetOwnPropertyDescriptorsFunction to print its configuration items:

image

It can be found that allwritableThe property is configured tofalseThat is, all attributes are immutable. (For questions about these configuration items, pleasedefineProperty)

Can’t directly modify, we can usecloneElementMethod to enhance a new component based on the original component:

React.cloneElement()Clone and return a newReact element, useelementAs a starting point. The generated element will have a shallow merge of the original element props and the new props. The new child replaces the existing child. Key and ref from the original element will be preserved.

React.cloneElement()Almost equivalent to:

<element.type {...element.props} {...props}>{children}</element.type>

How to use HOC

The above sample codes all write how to declare aHOC,HOCIt is actually a function, so we will call the enhanced component as a parameter.HOCFunction to get the enhanced component.

class myComponent extends Component {
 render() {
 Return (<span > original assembly < /span >)
 }
 }
 export default inheritHOC(myComponent);

compose

In practical applications, one component may be divided into multipleHOCEnhancements, we are using allHOCBorrow one of the enhanced componentsdecorator patternFigure to illustrate, may be easier to understand:

image

Suppose now we havelogger,visible,styleWait for manyHOC, now want to strengthen one at the same timeInputComponents:

logger(visible(style(Input)))

This code is very difficult to read, we can manually package a simple function combination tool, rewrite the writing as follows:

const compose = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
 compose(logger,visible,style)(Input);

composeThe function returns a combined function of all functions.compose(f, g, h)And(...args) => f(g(h(...args)))It’s the same.

Many third-party libraries offer similarcomposeFunction of, for examplelodash.flowRight,ReduxProvidedcombineReducersFunctions, etc.

Decorators

We can also useES7For usDecoratorsTo make our writing more elegant:

@logger
 @visible
 @style
 class Input extends Component {
 //   ...
 }

DecoratorsYesES7One of the proposals has not been standardized, but at presentBabelThe transcoder is already supported and we need to configure it in advance.babel-plugin-transform-decorators-legacy

"plugins": ["transform-decorators-legacy"]

It is also possible to combine the abovecomposeFunction usage:

const hoc = compose(logger, visible, style);
 @hoc
 class Input extends Component {
 //   ...
 }

Practical application of HOC

The following are some of my practical comments in the production environmentHOCDue to the length of the article, the code has been simplified a lot. If you have any questions, please point out in the comment area:

Log management

In fact, this is one of the most common applications. Multiple components have similar logic. We need to reuse the repeated logic.
Official documentsCommentListThe example of is also to solve the problem of code reuse, write very detailed, interested canSolving crosscutting concerns using high-order components (HOC).

Some pages need to record user behaviors, performance indicators, etc. Doing these things through high-level components can save a lot of duplicate code.

function logHoc(WrappedComponent) {
 return class extends Component {
 componentWillMount() {
 this.start = Date.now();
 }
 componentDidMount() {
 this.end = Date.now();
 Log (` $ {wrapped component.dispalyname} rendering time: ${this.end-this.start} ms');
 Log (` $ {user} enters $ {wrapped component.dispalyname} `);
 }
 componentWillUnmount() {
 Log (` $ {user} quit $ {wrapped component.dispalyname} `);
 }
 render() {
 return <WrappedComponent {...this.props} />
 }
 }
 }

Available, Privilege Control

function auth(WrappedComponent) {
return class extends Component {
render() {
const { visible, auth, display = null, ...props } = this.props;
if (visible === false || (auth && authList.indexOf(auth) === -1)) {
return display
}
return <WrappedComponent {...props} />;
}
}
}

authListIt is a list of all the permissions that we request from the backend when entering the program, when the permissions required by the component are not in the list, or set
visibleYesfalse, we display it as the passed-in component style, ornull. We can apply any component that needs permission verification.HOC

@auth
class Input extends Component {  ...  }
@auth
class Button extends Component {  ...  }

< Button auth="user/addUser "> add user < /Button >
< inputauth = "user/search" visible = {false} > add user < /Input >

Bidirectional binding

InvueIn, two-way data binding can be realized after binding a variable, that is, the bound variable will automatically change after the value in the form changes. AndReactThis is not done in. By default, all form elements areUncontrolled component. After binding a state to a form element, it is often necessary to write manually.onChangeMethod to rewrite it asControlled componentIn the case of many form elements, these repeated operations are very painful.

We can use high-level components to implement a simple two-way binding. The code is slightly longer and can be understood in conjunction with the following mind map.

image

First, let’s customize oneFormComponent, this component is used to wrap all form components that need to be wrapped, throughcontexExpose two attributes to subcomponents:

  • model: CurrentFormAll data controlled by the formnameAndvalueComposition, e.g.{name:'ConardLi',pwd:'123'}.modelIt can be imported from outside or controlled by itself.
  • changeModel: changemodelOne of themnameThe value of.
class Form extends Component {
static childContextTypes = {
model: PropTypes.object,
changeModel: PropTypes.func
}
constructor(props, context) {
super(props, context);
this.state = {
model: props.model || {}
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.model) {
this.setState({
model: nextProps.model
})
}
}
changeModel = (name, value) => {
this.setState({
model: { ...this.state.model, [name]: value }
})
}
getChildContext() {
return {
changeModel: this.changeModel,
model: this.props.model || this.state.model
};
}
onSubmit = () => {
console.log(this.state.model);
}
render() {
return <div>
{this.props.children}
< button onClick={this.onSubmit} > submit < /button >
</div>
}
}

The following defines the for bidirectional bindingHOCThat represents the of the formonChangeAttributes andvalueProperties:

  • HappenonChangeEvent to call the upper layerFormThechangeModelMethods to changecontexthit the targetmodel.
  • When rendering, willvalueReplace with fromcontextThe value retrieved from the.
function proxyHoc(WrappedComponent) {
 return class extends Component {
 static contextTypes = {
 model: PropTypes.object,
 changeModel: PropTypes.func
 }
 
 onChange = (event) => {
 const { changeModel } = this.context;
 const { onChange } = this.props;
 const { v_model } = this.props;
 changeModel(v_model, event.target.value);
 if(typeof onChange === 'function'){onChange(event);  }
 }
 
 render() {
 const { model } = this.context;
 const { v_model } = this.props;
 return <WrappedComponent
 {...this.props}
 value={model[v_model]}
 onChange={this.onChange}
 />;
 }
 }
 }
 @proxyHoc
 class Input extends Component {
 render() {
 return <input {...this.props}></input>
 }
 }

The above code is only a brief part, exceptinput, we can also will beHOCApply toselectSuch as other form components, and even the aboveHOCCompatible tospan、tableSuch as display components, this can greatly simplify the code, let us save a lot of state management work, use the following:

export default class extends Component {
render() {
return (
<Form >
<Input v_model="name"></Input>
<Input v_model="pwd"></Input>
</Form>
)
}
}

Form verification

Based on the above two-way binding example, let’s have another form validator. The form validator can contain validation functions and prompt information. When validation fails, it will display error information:

function validateHoc(WrappedComponent) {
 return class extends Component {
 constructor(props) {
 super(props);
 this.state = { error: '' }
 }
 onChange = (event) => {
 const { validator } = this.props;
 if (validator && typeof validator.func === 'function') {
 if (!  validator.func(event.target.value)) {
 this.setState({ error: validator.msg })
 } else {
 this.setState({ error: '' })
 }
 }
 }
 render() {
 return <div>
 <WrappedComponent onChange={this.onChange}  {...this.props} />
 <div>{this.state.error || ''}</div>
 </div>
 }
 }
 }
const validatorName = {
func: (val) => val && !  isNaN(val),
Msg:' Please enter a number'
}
const validatorPwd = {
func: (val) => val && val.length > 6,
Msg:' Password must be greater than 6 digits'
}
<HOCInput validator={validatorName} v_model="name"></HOCInput>
<HOCInput validator={validatorPwd} v_model="pwd"></HOCInput>

Of course, it can also be found inFormAt the time of submission, it is determined whether all validators pass or not, and the validators can also be set as arrays, etc. Due to the length of the article, the code is simplified a lot, and interested students can implement it by themselves.

Connect for Redux

image

From reduxconnect, in fact, is a.HOCThe following is a simplified versionconnectImplementation:

export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
 class Connect extends Component {
 static contextTypes = {
 store: PropTypes.object
 }
 
 constructor () {
 super()
 this.state = {
 allProps: {}
 }
 }
 
 componentWillMount () {
 const { store } = this.context
 this._updateProps()
 store.subscribe(() => this._updateProps())
 }
 
 _updateProps () {
 const { store } = this.context
 let stateProps = mapStateToProps ?  mapStateToProps(store.getState(), this.props): {}
 let dispatchProps = mapDispatchToProps?  mapDispatchToProps(store.dispatch, this.props) : {}
 this.setState({
 allProps: {
 ...stateProps,
 ...dispatchProps,
 ...this.props
 }
 })
 }
 
 render () {
 return <WrappedComponent {...this.state.allProps} />
 }
 }
 return Connect
 }

The code is very clear.connectFunction actually did one thing, will bemapStateToPropsAndmapDispatchToPropsDeconstruct them separately and then transfer them to the original component, so that we can directly use them in the original component.propsObtainstateas well asdispatchFunction.

Precautions for Using HOC

Caution-Static Attribute Copy

When we applyHOCTo enhance another component, the component we actually use is no longer the original component, so we can’t get any static properties of the original component, we canHOCCopy them manually at the end of:

function proxyHOC(WrappedComponent) {
 class HOCComponent extends Component {
 render() {
 return <WrappedComponent {...this.props} />;
 }
 }
 HOCComponent.staticMethod = WrappedComponent.staticMethod;
 //   ...
 return HOCComponent;
 }

If the original component has a lot of static properties, this process is very painful, and you need to know what the static properties of all components need to be enhanced, we can usehoist-non-react-staticsTo help us solve this problem, it can automatically help us to copy all the wrongReactThe static method of is used as follows:

import hoistNonReactStatic from 'hoist-non-react-statics';
function proxyHOC(WrappedComponent) {
class HOCComponent extends Component {
render() {
return <WrappedComponent {...this.props} />;
}
}
hoistNonReactStatic(HOCComponent,WrappedComponent);
return HOCComponent;
}

Caution-pass refs

After using high-level components, the obtainedrefIn fact, it is the outermost container component, not the original component, but in many cases we need to use the original component.ref.

Higher-order components are not transparentpropsThat willrefsTransparent transmission, we can use a callback function to completerefDelivery of:

function hoc(WrappedComponent) {
 return class extends Component {
 getWrappedRef = () => this.wrappedRef;
 render() {
 return <WrappedComponent ref={ref => { this.wrappedRef = ref }} {...this.props} />;
 }
 }
 }
 @hoc
 class Input extends Component {
 render() { return <input></input> }
 }
 class App extends Component {
 render() {
 return (
 <Input ref={ref => { this.inpitRef = ref.getWrappedRef() }} ></Input>
 );
 }
 }

React 16.3Version provides aforwardRef APITo help us carry outrefsTransfer, so that we get on high-order componentsrefIs the original componentref, and do not need to manually transfer, if yourReactVersion greater than16.3, you can use the following methods:

function hoc(WrappedComponent) {
 class HOC extends Component {
 render() {
 const { forwardedRef, ...props } = this.props;
 return <WrappedComponent ref={forwardedRef} {...props} />;
 }
 }
 return React.forwardRef((props, ref) => {
 return <HOC forwardedRef={ref} {...props} />;
 });
 }

Caution—Do not create high-level components within the render method

React DiffThe principle of the algorithm is:

  • Use component identification to determine whether to uninstall or update components
  • If the identity of the component is the same as that of the previous rendering, recursively update the subcomponent
  • If you identify a different unmount component, remount the new component

Every time a higher-level component is called, it will generate a completely new component, and the unique identification response of the component will also change, if it is found in therenderThe method calls a high-level component, which causes the component to be remounted every time it is unmounted.

Convention-do not change the original components

Description of high-level components in official documents:

A high-order component is a pure function with no side effects.

Let’s look at the definition of pure function again:

If the calling parameters of the function are the same, the same result will always be returned. It does not depend on any change of state or data outside the function during program execution. It must only depend on its input parameters.
This function does not produce any observable side effects, such as network requests, input and output devices, or data mutations.

If we modify the original components in high-level components, such as the following code:

InputComponent.prototype.componentWillReceiveProps = function(nextProps) { ... }

This has broken our agreement on high-level components and changed the original intention of using high-level components: we use high-level components toheightenRather thanChangeOriginal components.

Agreement-Pass Unrelated props

Using high-level components, we can represent allprops, but often specificHOCOnly one or several of them will be used.props. We need to put other irrelevantpropsPass through to the original component, such as the following code:

function visible(WrappedComponent) {
 return class extends Component {
 render() {
 const { visible, ...props } = this.props;
 if (visible === false) return null;
 return <WrappedComponent {...props} />;
 }
 }
 }

We only usevisibleProperty to control the display of components can be hidden, and the otherpropsPass it on.

Convention -displayName

In useReact Developer ToolsWhen debugging, if we useHOC, debugging interface may become very difficult to read, such as the following code:

@visible
 class Show extends Component {
 render() {
 Return <h1 > I am a label < /h1 >
 }
 }
 @visible
 class Title extends Component {
 render() {
 Return <h1 > I am a title < /h1 >
 }
 }

image

In order to facilitate debugging, we can manuallyHOCSpecify onedisplayName, officially recommendedHOCName(WrappedComponentName)

static displayName = `Visible(${WrappedComponent.displayName})`

image

This convention helps ensure maximum flexibility and reusability of high-level components.

Motivation to use HOC

Recalling the above mentionedMixinRisks:

  • MixinMay be interdependent and coupled, which is not conducive to code maintenance.
  • DifferentMixinMethods in may conflict with each other
  • MixinFor a long time, components can be perceived and even processed, which will cause snowballing complexity to the code.

image

AndHOCThe emergence of can solve these problems:

  • A high-order component is a pure function without side effects, and each high-order component will not depend on and couple with each other.
  • High-level components may also cause conflicts, but we can avoid these behaviors while abiding by the agreement.
  • High-level components do not care about how and why the data is used, and packaged components do not care where the data comes from. The addition of higher-order components will not add burden to the original components.

The flaw of HOC

  • HOCIt is necessary to wrap or nest the original components, if it is used in large quantitiesHOC, there will be a lot of nesting, which makes debugging very difficult.
  • HOCCan be hijackedprops, in the case of non-compliance with the agreement may also cause conflict.

Hooks

image

HooksYesReact v16.7.0-alphaNew features added to. It can let you inclassOutside usestateAnd otherReactCharacteristics.

UseHooks, you can in will containstateThe logic of is abstracted from the component, which will make the logic easy to test. At the same time,HooksIt can help you reuse the logic without rewriting the component structure. Therefore, it can also be used as an implementationState logic multiplexingThe plan.

Read the following chaptersMotivation to Use HookYou can find that it can be solved at the same time.MixinAndHOCThe problems brought about by this.

Hooks officially provided

State Hook

We want to useclassComponent implements aCounterFunction, we may write like this:

export default class Count extends Component {
constructor(props) {
super(props);
this.state = { count: 0 }
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => { this.setState({ count: this.state.count + 1 }) }}>
Click me
</button>
</div>
)
}
}

viauseState, we use functional components can also achieve such functions:

export default function HookTest() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => { setCount(count + 1);   setNumber(number + 1);  }}>
Click me
</button>
</div>
);
}

useStateIs a hook that can add some states to functional components and provide functions that change these states. At the same time, it receives a parameter as the default value of the states.

Effect Hook

Effect Hook allows you to perform some side effect operations in function components.

Parameter

useEffectThe method receives the incoming two parameters:

  • 1. callback function: in the first componentrenderAnd every time thereafterupdateAfter operation,ReactGuarantee inDOMThe callback will not run until the update is complete.
  • 2. State Dependency (Array): When the state dependency is configured, the callback function will be called only when the configured state change is detected.
useEffect(() => {
//As long as the component is render, it will be executed.
});
useEffect(() => {
//Only if count changes will it be executed
},[count]);

Callback return value

useEffectThe first parameter of the can return a function that executes the next time the page renders the results of the next update.useEffectPreviously, this function was called. This function is often used to call the last timeuseEffectClean up.

export default function HookTest() {
const [count, setCount] = useState(0);
useEffect(() => {
Log ('execute ...', count);
return () => {
Log ('clean ...', count);
}
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => { setCount(count + 1);   setNumber(number + 1);  }}>
Click me
</button>
</div>
);
}

Execute the above code and click the button several times to get the following results:

image

Note that if you add browser rendering, the result should look like this:

Page rendering ... 1
 Execute ... 1
 Page rendering ... 2
 Clean up ... 1
 Execute ... 2
 Page rendering ... 3
 Clean up ... 2
 Execute ... 3
 Page rendering ... 4
 Clean up ... 3
 Execute ... 4

Then why can you find the last one by cleaning up after the browser renderingstate? The reason is very simple, we are inuseEffectThe function returned in the form of a closure ensures that the variables stored in the last function execution will not be destroyed or contaminated.

You can try the following code, which may be better understood

var flag = 1;
 var clean;
 function effect(flag) {
 return function () {
 console.log(flag);
 }
 }
 clean = effect(flag);
 flag = 2;
 clean();
 clean = effect(flag);
 flag = 3;
 clean();
 clean = effect(flag);
 
 //Results of Implementation
 
 effect... 1
 clean... 1
 effect... 2
 clean... 2
 effect... 3

Analog componentDidMount

componentDidMountEquivalent touseEffectThe callback of is executed only once after the page initialization is completed, whenuseEffectThis effect can be achieved when the second parameter of the is passed into an empty array.

function useDidMount(callback) {
 useEffect(callback, []);
 }

Officials do not recommend the above wording, because it may lead to some mistakes.

Analog componentWillUnmount

function useUnMount(callback) {
useEffect(() => callback, []);
}

Unlike componentDidMount or componentDidUpdate, the effect used in useEffect does not prevent the browser from rendering the page. This makes your app look smoother.

ref Hook

UseuseRef Hook, you can easily getdomTheref.

export default function Input() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<div>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</div>
);
}

pay attention touseRef()Not only can it be used as acquisitionrefUseuseRefprocreantrefThecurrentThe property is variable, which means that you can use it to hold an arbitrary value.

Analog componentDidUpdate

componentDidUpdateIs equivalent to removing the first calluseEffect, we can useuseRefGenerate an ID to record whether it is the first execution:

function useDidUpdate(callback, prop) {
const init = useRef(true);
useEffect(() => {
if (init.current) {
init.current = false;
} else {
return callback();
}
}, prop);
}

Precautions for Using Hook

range of application

  • Only inReactFunctional Component or CustomHookUsed inHook.

HookThe proposal of is mainly to solve the problemclassA series of problems with components, so we canclassComponent.

Declare constraints

  • Do not call Hook in loops, conditions, or nested functions.

HookImplemented through arrays, each timeuseStateWill change the subscript,ReactYou need to use the call order to correctly update the corresponding state, ifuseStateWrapped in loops or conditional statements, each of them may cause confusion in the calling sequence, thus causing unexpected errors.

We can install oneeslintPlug-ins help us avoid these problems.

//install
 npm install eslint-plugin-react-hooks --save-dev
 //configuration
 {
 "plugins": [
 //   ...
 "react-hooks"
 ],
 "rules": {
 //   ...
 "react-hooks/rules-of-hooks": "error"
 }
 }

Custom Hook

As described aboveHOCAndmixinSimilarly, we can also use customHookExtract similar state logic from components.

customHookIt is very simple, we only need to define a function and add the corresponding required states andeffectEncapsulated, at the same time,HookThey can also be quoted from each other. UseuseBeginning naming customizationHook, this can be convenienteslintCheck.

Let’s look at a few specificHookPackage:

Log management

We can use the life cycle of the above package.Hook.

const useLogger = (componentName, ...params) => {
useDidMount(() => {
Log (` $ {componentname} initializes `, ... params);
});
useUnMount(() => {
Log (` $ {componentname} uninstall `, ... params);
})
useDidUpdate(() => {
Log (` $ {componentname} update `, ... params);
});
};

function Page1(props){
useLogger('Page1',props);
return (<div>...</div>)
}

Modify title

Modify pages according to different page namestitle:

function useTitle(title) {
useEffect(
() => {
document.title = title;
Return () => (document.title = "home page");
},
[title]
);
}
function Page1(props){
useTitle('Page1');
return (<div>...</div>)
}

Bidirectional binding

We will formonChangeThe logic of the extracted package into a.Hook, so that all form components that need bidirectional binding can be reused:

function useBind(init) {
let [value, setValue] = useState(init);
let onChange = useCallback(function(event) {
setValue(event.currentTarget.value);
}, []);
return {
value,
onChange
};
}
function Page1(props){
let value = useBind('');
return <input {...value} />;
}

Of course, you can go to theHOCThat way, combiningcontextAndformTo encapsulate a more general two-way binding, if you are interested, you can implement it manually.

Motivation to Use Hook

Reduce the risk of state logic reuse

HookAndMixinThere are some similarities in usage, butMixinThe logic and state introduced can cover each other, and manyHookThey do not affect each other, which eliminates the need for us to focus part of our energy on preventing conflicts in logic reuse.

Use without observing the agreementHOCThere may also be some conflicts, such aspropsOverwrite, etc. UseHookThese problems can be avoided.

Avoid hellish nesting

Mass useHOCIn this case, let our code become very nested level, usingHOC, we can implement flat state logic reuse, and avoid a large number of nested components.

Make components easier to understand

In useclassWhen components build our programs, they each have their own state. The complexity of business logic makes these components become larger and larger. More and more logic will be called in each life cycle, which is more and more difficult to maintain. UseHook, which allows you to separate common logic to a greater extent and divide a component into smaller functions, instead of forcing partitioning based on a life cycle approach.

Use function instead of class

Compared to functions, write aclassMay need to master more knowledge, need to pay attention to the more points, such asthisPointing, binding events, etc. In addition, computers understand a function better than oneclassFaster.HooksSo that you canclassesUse more besidesReactNew features of.

Rational choice

In fact,HookInreact 16.8.0Before it was officially releasedHookThe stable version has not been used in the production environment. At present, the most used version in the production environment is’ HOC’
`。

ReactThe government did not putclassesFromReactTo be removed from the,classComponents andHookIt can exist at the same time. Officials also suggest avoiding any “extensive refactoring”. After all, this is a very new version. If you like it, you can use it in new non-critical code.Hook.

Summary

mixinHas been abandoned,HOCIn his prime of life,HookAt first glance, the front-end circle is just like this. The iterative speed of technology is very fast, but when we learn these knowledge, we must understand why we should learn them, whether they are useful or not. Remain true to our original aspiration and keep our mission firmly in mind.

If there are any mistakes in the article, please correct them in the comment area. Thank you for reading.

Recommended reading

Recommended attention

If you want to read more high-quality articles, or if you need the mind map source files in the articles, please pay attention to mine.Github blogWelcome to star✨.

It is recommended to pay attention to my WeChat public number [code Secret Garden], and we will communicate and grow together.
图片描述