Common Design Patterns for Front End (2)-strategy

  Design pattern, javascript, Policy mode

For students who don’t know much about the front end, perhaps the two biggest uses of JS are:

1) let elements fly around ~

2) Verify the form …

This is a joke, but these two “Main“The design pattern behind the use isPolicy mode.

Before introducing what is the strategy mode, let’s implement a simple React animation component to fulfill the requirement of “let elements fly around ~”.

1. Design an animation component

There are two ways to realize animation in the browser.

1) Use the animation attributes provided by CSS3.

2) Use JS script to change the style or attribute of elements every time.

Let’s first briefly describe the advantages and disadvantages of the two methods.

The biggest advantage of using CSS3 to complete animation isOperational efficiencyIt is higher than using JS, because browsers support CSS natively, saving JS script interpretation time, and some browsers (such as Chrome) also provide GPU acceleration to further improve rendering performance. However, the shortcomings of CSS3 animation are also obvious. CSS3 animation can only be described“within a certain period of time, a certain style attribute changes according to a certain rhythm”In other words, the animation provided by CSS3 is defined based on time and motion curves. This definition of animation has two main disadvantages:One is that animation cannot be suspended midway: For example, I have a button. When hover started the animation, the mouse moved out of the button during the animation. At this time, I wanted the animation to stop, but the actual effect was not the same.Another disadvantage is that debugging animation is very difficult.When the transition-duration of the animation is set very short, the animation always flashes and cannot capture the intermediate state. It is very painful to verify the effect again and again by naked eyes.

Of course CSS3 animation has some shortcomings, but depending on its excellent performance, it is still useful when making some simple animations.

The principle of using JS to realize animation is basically like making animation. ItThe biggest advantage is flexibility., can not only control the rendering interval, but also control various attributes of each frame element, even attributes other than styles (such as node data).Of course, script animation also has its disadvantages. The browser explanation script will take up a lot of meta-computing resources. If it is not handled pro pe rly, animation cartoon will appear and even affect the interaction of the whole page.

Flexibility and performance can’t be both. How to choose and choose still needs developers to make repeated deliberation in actual requirements and use the most appropriate implementation method.

Let’s use JS to implement a simple react animation component.

First design APIThe API of react component is props, assuming our animation component is calledAnimate, like CSS3 animation, we need to specify the animation of theStart Time, Duration, Initial Value, End ValueAnd which one to useSlow motion functionFinally, we plan to use the popularSub-component function (PR)To add animation to other components.

This is probably what it looks like:

<Animate
 StartTime={+new Date()} // start time
 StartPos={-100} // initial value
 EndPos={300} // end position
 Duration={1000} // duration
 Easing="linear" // Ease function name
 >
 {({ position }) => {
 const style = {
 position: "absolute",
 top: "200px",
 right: `${position}px`
 };
 return <h2 style={style}>Hello World!  </h2>;
 }}
 </Animate>

Some students are rightSubcomponent functionI don’t know much about the model of, briefly, Animate’sProps.childrenNot a component but aFunction, its return value is the component that really wants to render (h2), the type render method. the advantage of this design is that the Animate component can pass the data that wants to be passed throughPositionThe method of is passed into the function instead of binding directly to the component you want to render. For example, the position here is actually the calculated result value for each frame of the animation. However, the Animate component does not know which style attribute this value is to act on (nor does it need to know). How to use the position to determine in the function, for example, here we set the position to change the right attribute of h2, so that the Animate component and h2 component are completely decoupled, enhancing flexibility and reusability.

Once the API is done, half of the development is completed, and the rest is the implementation of components. Don’t say gossip, just code it:

class Animate extends React.Component {
state = {
Position: 0 // position
};
componentDidMount() {
this.startTimer();
}
componentWillUnmount() {
this.clearTimer();
}

//Start timer
startTimer = () => (this.timer = setInterval(this.step, 19));

//Situation Timer
clearTimer = () => clearInterval(this.timer);

//Calculate each frame
step = () => {
const { startTime, duration, startPos, endPos, easing } = this.props;
const nowTime = +new Date();  //Current Time
//Judge whether the animation ends, and if it ends, correct the position
if (nowTime >= startTime + duration) {
return this.setState({ position: endPos });  //Update location;
}
const position = tween[easing](
nowTime - startTime,
startPos,
endPos - startPos,
duration
);
this.setState({ position });
};

render() {
const { children = () => {} } = this.props;
const { position } = this.state;
return <div>{children({ position })}</div>;
}
}

We use a state to record the position of each frame calculation result, use setInterval to realize animation refresh, create a timer (startTimer) when didMount, and remember to destroy the timer (clearTimer) when willUnmount. the function of step is to calculate the position and save it in state every time the timer is called. Finally, the position is passed to the children function in the render function, and an animation component is completed. is it very simple?

Careful students found that in step, the value of position is through

const position = tween[easing](
nowTime - startTime,
startPos,
endPos - startPos,
duration
);

This code is generated. easing is the name of a slow-motion function (such as linear) passed in through props. How does it translate into a position value? Very simply, we define a tween object, the key of the object is the name of these slow-motion functions, and the value corresponding to each key is the implementation of the slow-motion functions.

const tween = {
linear(t, b, c, d) {
return c * t / d + b;
},
easeIn(t, b, c, d) {
return c * (t /= d) * t + b;
},
strongEaseIn(t, b, c, d) {
return c * (t /= d) * t * t * t * t + b;
},
strongEaseOut(t, b, c, d) {
return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
},
sineaseIn(t, b, c, d) {
return c * (t /= d) * t * t + b;
},
......
}

Note that in order to ensure that these slow-motion functions can be replaced equivalently, the same parameters and return values are required:

// t: elapsed time, b: original position, c: end position, d: total duration
//Return to current time position

In this way, the implementation of this animation component is completely introduced. Take a look at the effect:

Good, Hello World can already float around in various postures ~ ~

Second, to talk about the strategic model

If you want to add a new animation effect to the Animate component, you only need to modify the tween object without modifying the Animate component itself. The reason is that all the slow-motion functions defined in the tween object can receive the same parameters and return the same results. These slow-motion functions can be replaced equivalently.

Encapsulate a series of algorithms so that they can replace each other.This design pattern isPolicy mode. The main purpose of the policy pattern is to encapsulate algorithms. Note that the algorithms here can also be extended into functions or rules, such as form verification rules, as long as the purposes of these algorithms are consistent.

The two main functions of JS mentioned at the beginning are exactly the best use occasions of strategy mode. Here we will not give an example of how form verification is implemented, leaving it to everyone to think for themselves.

Using the policy mode canEffectively avoid multiple conditional selection statements and enhance code readabilityAt the same time, we encapsulated the algorithm.Easy to expand and enhance reusabilityThe strategy pattern needs to abstract the algorithm and sort out a series of replaceable algorithms.The difficulty of code design is increasedIn addition, you need to know all the algorithms before using them.In violation of the principle of minimum knowledge. However, these disadvantages are acceptable compared with advantages.

III. Managing react Dialog with Policy Mode

Finally, we give another application of the strategy pattern in the project, hoping to enlighten everyone.

It is inevitable to use dialog boxes in a front-end project, especially in the middle and back-end projects. Usually our dialog box components are used like this (take antd as an example):

import { Modal, Button } from 'antd';

class App extends React.Component {
state = { visible: false }

showModal = () => {
this.setState({
visible: true,
});
}

handleOk = (e) => {
console.log(e);
this.setState({
visible: false,
});
}

handleCancel = (e) => {
console.log(e);
this.setState({
visible: false,
});
}

render() {
return (
<div>
<Button type="primary" onClick={this.showModal}>Open</Button>
<Modal
title="Basic Modal"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</div>
);
}
}

ReactDOM.render(<App />, mountNode);

We need to define a visible state to control whether the dialog box is displayed or not, also need to define showModal’s open dialog box, and define callback functions such as handleOK and handleCancel to handle dialog box interaction. when there are n different dialogs in a page, the above work will be doubled n times, and it is easy to make mistakes.

What is the solution to this problem? Let’s think about it, dialog boxes have mutually exclusive characteristics. Generally, it is not allowed to open two dialog boxes at the same time. Therefore, we can use the policy mode plus singleton pattern to create a global dialog box component with only one visible state control display. Specifically, which dialog box to display is passed in through parameters, just like we passed the slow-motion function before.

The following is the code implementation of the dialog box management component:

import React from 'react'
 import MODAL_MAP from './modalMap'
 import { simpleCloneObject as clone } from '@/js/utils'
 
 const DEFAULT_STATE = {
 Name: '', // popup name, look in map
 Visible: false, // whether the pop-up window is visible
 OnOk: () => {}, // ok callback
 OnCancel: () => {}, // close callback
 ExtProps: {} // transparent attributes
 }
 
 class Manager extends React.Component {
 state = clone(DEFAULT_STATE)
 
 componentDidMount() {
 On ref (this)//reference
 }
 //Open the pop-up window
 open = ({ name, onOk, onCancel, ...extProps }) =>
 this.setState({ name, onOk, onCancel, extProps, visible: true })
 
 //Close the pop-up window
 onOk = () => {
 const { onOk } = this.state
 onOk && onOk()
 this.setState(clone(DEFAULT_STATE))
 }
 
 onCancel = () => {
 const { onCancel } = this.state
 onCancel && onCancel()
 this.setState(clone(DEFAULT_STATE))
 }
 
 render() {
 const { name, visible, extProps } = this.state
 const Modal = MODAL_MAP[name]
 return Modal ?
 <Modal
 visible={visible} {...extProps}
 onOk={this.onOk} onCancel={this.onCancel}
 /> : null
 }
 }
 
 export default Manager

You can see that the final Modal to be rendered has been obtained from the MODAL_MAP object through name. All dialog boxes share the unique state and method of this component. Then the problem arises, the visible attribute becomes a state inside the component, and the opening of the dialog box is often decided outside the component. how can we allow external components to access the state inside the component?

Very simple, the code is as follows:

import React from 'react'
import ReactDOM from 'react-dom'
import Manager from './Manager'

const init = () => {
Let manager // instance reference
const onRef = ref => manager = ref
const dom = document.createElement('div')
dom.id = 'modal-manager'
document.querySelector('body').appendChild(dom)
ReactDOM.render(<Manager onRef={onRef} />, dom)
return manager
}

export default init()
We designed an init function, dynamically created a dom node in the function, and rendered the previous common dialog box component to this node. At the same time, we used ref attribute and closure to return the instance of this component, through which we can access the attributes and methods inside the component.

Finally, see how to apply our dialog manager. It only takes two steps and no more unnecessary states and methods. Is it refreshing?

4. Function is Strategy

Peter Norvig, a computer scientist at google, said in a speech. “In the language of function as a first-class citizen, the strategic mode is invisible.“Obviously JS meets such conditions, so I think the strategy mode is one of the design modes that can best embody the characteristics of JS. When we implemented the animation component and dialog box management component above, we specifically defined two objects to store slow-motion functions and dialog boxes. In fact, we can directly pass functions or components as parameters instead of taking values from objects through Key. Doing so can further improve flexibility, but reduces the robustness of the code (because it is not sure whether the passed-in value meets the standard), which poses a challenge to the developer’s architectural capability.

Finally, the code address in the article is attached:https://codesandbox.io/s/myl7j02p1x

If you have any questions, please leave a message for discussion, thank you [emoji]

Author: rosefinch