React-props (1) React-props (2) React-props (2)
Using Hooks for some time, in conjunction with several documents, to sort out some notes 📒
Before we get to react-hooks, let’s get to know how to reuse react state logic. This will help you understand hooks
React components are the basic unit of code reuse, and the composite-based component reuse mechanism is quite elegant. However, it is not so easy to reuse more fine-grained logic (state logic, behavior logic, etc.), so we solve our state logic reuse problem by analyzing the following patterns
Mixin
Mixin design patterns
Mixin patterns are classes that provide functionality that can be simply inherited by one or a group of subclasses, with the intent to reuse the functionality
Many open source libraries provide
The realization of the Mixin
, such as
Underscore. Extend method
Implement the Minxin method by hand
const mixin = function (target, mixins) {
const newObj = target;
newObj.prototype = Object.create(target.prototype);
for (let prop in mixins) {
if (mixins.hasOwnProperty(prop)) {
newObj.prototype[prop] = mixins[prop];
}
}
return newObj;
}
const Person = {
run: (a)= > {
console.log('I can run');
}
};
const Son = function () {
console.log('hello world');
};
const User = mixin(Son, Person);
const vnues = new User(); // 'hello world'
vnues.run(); // 'I can run'
Copy the code
❗️ you can see that the mixin method can be used to extend any method of any object to the target object, that is to say, the mixin method can reuse state logic, behavior logic, etc
The React of the Mixin
The Mixin scheme was born out of OOP intuition. React itself was somewhat functional, but in the early days only the React.createclass () API was provided to define components for users:
/ / define a Mixin
var Mixin1 = {
getMessage: function() {
return 'hello react';
}
};
var Mixin2 = {
componentDidMount: function() {
console.log('Mixin2.componentDidMount()');
}
};
// Enhance existing components with mixins
var MyComponent = React.createClass({
mixins: [Mixin1, Mixin2],
render: function() {
return <div>{this.getMessage()}</div>;
}
});
Copy the code
The pitfalls of mixins
Mixins introduce ‘Mixins introduce implicit dependencies
You might write a stateful component, and then your colleague might add a mixin that reads the state of that component. After a few months, you may want to move the state to the parent component so that it can be shared with its siblings. Would you remember to update the mixin to read props instead of state? What if, at this point, other components are also using this mixin?
var RowMixin = {
renderHeader: function() {
return (
<div className='row-header'>
<h1>
{this.getheaderText ()} // Implicit dependency
</h1>
</div>
);
}
};
var UserRow = React.createClass({
mixins: [RowMixin], // Mix in the renderHeader method
getHeaderText: function() {
return this.props.user.fullName;
},
render: function() {
return (
<div>
{this.renderHeader()}
<h2>{this.props.user.biography}</h2>
</div>
)
}
});
Copy the code
Implicit dependencies lead to opaque dependencies and rapidly rising maintenance and understanding costs:
-
It is difficult to quickly understand component behavior and requires a comprehensive understanding of all mixin-dependent extension behavior and their interactions
-
The group value method and state field are not easy to delete, because it is difficult to determine whether mixins depend on it
-
Mixins are also difficult to maintain because Mixin logic is eventually flattened and merged together, making it difficult to figure out the inputs and outputs of a Mixin
Mixins caused name clashes
You defined getDefaultProps in that Mixin, and another Mixin defined the same name getDefaultProps, causing a conflict.
var DefaultNameMixin = {
// Name conflict
getDefaultProps: function () {
return {name: "Lizie"};
}
};
var DefaultFoodMixin = {
// Name conflict
getDefaultProps: function () {
return {food: "Pancakes"};
}
};
var ComponentTwo = React.createClass({
mixins: [DefaultNameMixin, DefaultFoodMixin],
render: function () {
return (
<div>
<h4>{this.props.name}</h4>
<p>Favorite food: {this.props.food}</p>
</div>
);
}
});
Copy the code
Mixins cause snowballing complexity
Even if mixins are simple to start with, they tend to get complicated over time
Mixin ‘tends to add more states’, which reduces The predictability of The application (‘ The more state in your application, The harder it is to reason about it.’), This leads to a huge increase in complexity, but we really want to try to simplify the state a little bit.
// someMixin
const someMixin = {
// Add 'newNumber state' to component
// If you use the createReactClass() method to create a component, you need to provide a separate getInitialState method that returns the initial state:
getInitialState() {
return {
newNumber: 1
}
},
setNewNumber(num) {
this.setState({
newNumber: num
})
}
}
// someOtherMixin
const someOtherMixin = {
someMethod(number) {
console.log(number)
}
}
const Composer = React.createClass({
mixins: [
someMixin,
someOtherMixin
].
render() {
return <p>{this.state.newNumber}</p>
},
someStaticMethod(num) {
this.setNewNumber(num)
this.someMethod(num)
}
})
class App extends React.Component {
constructor(props) {
super(props)
this.callChildMethod = this.callChildMethod.bind(this)
}
callChildMethod() {
this.refs.composer.someStaticMethod(5)
}
render() {
return (
<div>
<button onClick={this.callChildMethod}></button>
<Composer ref="composer" />
</div>
)
}
}
Copy the code
So React V0.13.0 ditches Mixin in favor of HOC, and ❗️ embraces ES6, whose classes support inheritance but not mixins
HOC high-level components
HOC is an advanced technique used in React to reuse component logic. HOC itself is not part of the React API; it is a design pattern based on the composite features of React.
High-order components can be seen as an implementation of the Decorator pattern with React. Specifically, high-order components are functions that take components as arguments and return new components.
export default (WrappedComponent) => {
class NewComponent extends Component {
// You can do a lot of custom logic
render () {
return <WrappedComponent />
}
}
return NewComponent
}
Copy the code
Decorator pattern
The decorator pattern is a design pattern that dynamically assigns responsibility to a class or object. It can add responsibilities dynamically during the running of a program without changing the class or object itself.
ES7 provides a similar syntactic sugar decorator for Java annotations to implement the Decorator pattern. Very simple to use:
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
Copy the code
HOC factory implementation
The property broker
This method is a combination in nature. It is probably the first implementation method that comes to our mind. The general implementation framework is as follows:
function proxyHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
return <WrappedComponent {. this.props} / >
}
}
}
Copy the code
Reverse inheritance
Inheritance Inversion is HOC to Inheritance WrappedComponent. Now that we have this component, we can decorate and modify it from the inside, starting with its implementation framework:
function inheritHOC(WrappedComponent) {
return class extends WrappedComponent {
render() {
return super.render();
}
}
}
Copy the code
What can HOC do ❓
Operates on all incoming props
You can read, add, edit, and delete props(properties) passed to WrappedComponent.
Be careful when deleting or editing important props(properties). You should use the namespace to ensure that higher-order components’ props don’t break the WrappedComponent.
// Example: Add new props(properties). The currently logged user in the application can be accessed in WrappedComponent via this.props. User.
function proxyHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
const newProps = {
user: currentLoggedInUser
}
return <WrappedComponent {. this.props} {. newProps} / >
}
}
}
Copy the code
Access component instances through refs
function refsHOC(WrappedComponent) {
return class RefsHOC extends React.Component {
proc(wrappedComponentInstance) {
wrappedComponentInstance.method()
}
render() {
const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
return <WrappedComponent {. props} / >
}
}
}
Copy the code
The Ref callback is executed when the WrappedComponent renders, and you get a reference to the WrappedComponent. This can be used to read/add props to an instance and call its methods.
In proc, wrappedComponentInstance is null if WrappedComponent is a stateless component, because stateless components do not have this and do not support ref.
To extract the state
You can extract state by passing in props and callback functions,
function proxyHOC(WrappedComponent) {
return class PP extends React.Component {
constructor(props) {
super(props)
this.state = {
name: ' '
}
this.onNameChange = this.onNameChange.bind(this)
}
onNameChange(event) {
this.setState({
name: event.target.value
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange
}
}
return <WrappedComponent {. this.props} {. newProps} / >
}
}
}
Copy the code
When used:
@proxyHOC
class Test extends React.Component {
render () {
return (
<input name="name" {. this.props.name} / >
);
}
}
export default proxyHOC(Test);
Copy the code
In this way, it is possible to convert the input into a controlled component.
Wrap the WrappedComponent with other elements
The easy way to understand this is to wrap the WrappedComponent around the desired nested structure
function proxyHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
return (
<div style={{display: 'block'}} >
<WrappedComponent {. this.props} / >
</div>
)
}
}
}
Copy the code
Render Highjacking
It’s called Render Highjacking because HOC controls the Render output of the WrappedComponent and can do all sorts of things with it.
In render hijacking, you can: state(state), props(properties)
- Read, add, edit, and delete props in any React element render output
- Read and modify the React element tree of the Render output
- Conditionally render the element tree
- Wrap styles in the element tree (as in the Props Proxy)
function inheritHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
const elementsTree = super.render()
let newProps = {};
if (elementsTree && elementsTree.type === 'input') {
newProps = {value: 'may the force be with you'}
}
const props = Object.assign({}, elementsTree.props, newProps)
const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children)
return newElementsTree
}
}
}
Copy the code
How to use HOC gracefully
Combination compose
function compose(. funcs) {
return funcs.reduce((a, b) = >(... args) => a(b(... args)))
}
Copy the code
usage
compose(fn1, fn2, fn3) (... Args is equivalent to FN1 (FN2 (FN3 (... args)))
Copy the code
A decorator
@proxyHOC
class Test extends React.Component {
render () {
return (
<input name="name" {. this.props.name} / >
);
}
}
Copy the code
Considerations for using HOC
⚠️ Do not use HOC in render methods
The React diff algorithm uses the component identity to determine whether it should update an existing subtree or discard 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.
render() {
// Each call to the Render function creates a new EnhancedComponent
// 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
This isn’t just a performance issue – remounting a component causes a loss of state for that component and all its children.
If you create HOC outside of the component, then the component will only be created once. Therefore, the same component is rendered each time. Generally speaking, this is in line with your expected performance.
⚠️ Be sure to copy static methods
Sometimes it’s useful to define static methods on the React component. For example, the Relay container exposes a static method getFragment to facilitate composing GraphQL fragments.
However, when you apply HOC to a component, the original component will be wrapped with a container component. This means that the new component does not have any static methods of the original component.
// Define a static function
WrappedComponent.staticMethod = function() {/ *... * /}
// Now use HOC
const EnhancedComponent = enhance(WrappedComponent);
// There is no staticMethod for the enhanced component
typeof EnhancedComponent.staticMethod === 'undefined' // true
Copy the code
To solve this problem, you can copy these methods to the container component before returning:
function enhance(WrappedComponent) {
class Enhance extends React.Component {/ *... * /}
// must know exactly which methods to copy :(
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}
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 isn’t really a prop – like a key, it’s 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.
Pass Refs using the Forwarding Refs API
Ref Forwarding: Is a technique for automatically passing the Ref hook to a component’s descendants
Consider a FancyButton component that renders a native Button DOM element:
function FancyButton(props) {
return (
<button className="FancyButton">
{props.children}
</button>
);
}
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Copy the code
The code above, the FancyButton component, renders an HTML element. React hides the process of rendering code into page elements for users. When other components use FancyButton, there is no direct way to obtain FancyButton elements. This design method facilitates component fragmentation management and reduces coupling.
But components like FancyButton simply encapsulate basic HTML elements. In some cases, upper-layer components may prefer to treat it as a basic HTML element, and some effects require direct manipulation of the DOM, such as the Focus, Selection, and animations effects.
In the following example, FancyButton uses the React. ForwardRef to get the ref passed to it and forward it to the DOM button it renders:
const FancyButton = React.forwardRef((props, ref) = > (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Copy the code
Convention: Do not change the original component
Don’t try to modify the component prototype (or otherwise change it) in HOC.
function logProps(InputComponent) {
InputComponent.prototype.componentDidUpdate = function(prevProps) {
console.log('Current props: '.this.props);
console.log('Previous props: ', prevProps);
};
// Returns the original input component, indicating that it has been modified.
return InputComponent;
}
// The enhanced component gets log output each time the logProps is called.
const EnhancedComponent = logProps(InputComponent);
Copy the code
There are some bad consequences. One is that input components can no longer be used in the way they were before HOC was enhanced. Worse, if you augment it with another HOC that also modifies componentDidUpdate, the previous HOC will fail! At the same time, HOC cannot be applied to functional components that have no life cycle.
Convention: Pass unrelated props to the wrapped component
HOC adds features to components. They should not change their agreements drastically. The component returned by HOC should maintain a similar interface to the original component.
HOC should pass through props that have nothing to do with itself. Most HOC should include a render method like the one below
render() {
// Higher-order components only need visible
const{ visible, ... props } =this.props;
// Pass props to the wrapped component
return (
<WrappedComponent
{. props}
/>
);
}
Copy the code
Convention: Wrap the display name for easy debugging
Container components created by HOC will show up in React Developer Tools just like any other component. To facilitate debugging, select a display name to indicate that it is a product of HOC.
❗️ To facilitate debugging, we can manually specify a displayName for HOC. HOCName(WrappedComponentName) is officially recommended:
function withSubscription(WrappedComponent) {
class WithSubscription extends React.Component {
WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)}) `;
return WithSubscription;
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
Copy the code
HOC defects
-
Scalability limitations: HOC is not a complete replacement for Mixin
-
Ref transfer problem: Ref is cut off and needs to be dealt with specially in development
-
Wrapper Hell: HOC flooding, Wrapper Hell appears
Render Props
As mentioned earlier, components are the basic unit of code reuse in React, and the composite-based component reuse mechanism is quite elegant. For more fine-grained logic (state logic, behavior logic, etc.), it is not easy to reuse logic. The mode for reuse logic is to process reuse logic into component forms, including the render props. Schemes such as Mixin, Render Props, HOC, etc. are all supermodels explored within the rules of an existing game (component mechanics)
Create a render props
The component doesn’t define its own render props. Instead, it passes in an externally defined render function called render props(children props if the name is children)
// render props
const Test = props= > props.render('hello world')
const App = (a)= > (
<Test
{/ * *
With render properties (Render Props) component requires a returnReactElement and call its functions instead of implementing your own rendering logic.
/ * *}
render={text= > <div>{text}</div>}
/>
)
ReactDOM.render((<App />, root) // Returns<div>hello world</div>
Copy the code
Technically, both of them are based on the component combination mechanism. Render Props have the same extensibility capability as HOC. The reason why it is called Render Props is not to say that it can only be used to reuse rendering logic, but to indicate that in this mode, components are combined by Render (). Similar to HOC mode with render() of the Wrapper, the two are very similar in form and both produce a layer of “Wrapper” (EComponent and RP).
/ / HOC definitions
const HOC = Component= > WrappedComponent;
/ / HOC use
const Component;
const EComponent = HOC(Component);
<EComponent />
// Render Props definition
const RP = ComponentWithSpecialProps;
// Render Props use
const Component;
<RP specialRender={() => <Component />} />
Copy the code
More interestingly, Render Props and HOC can even be converted to each other (they can coexist, not be mutually exclusive) :
function RP2HOC(RP) {
return Component= > {
return class extends React.Component {
static displayName = "RP2HOC";
render() {
return (
<RP
specialRender={renderOptions => (
<Component {... this.props} renderOptions={renderOptions} />
)}
/>
)
}
}
}
}
function HOC2RP(HOC) {
const RP = class extends React.Component {
static displayName = "HOC2RP";
render() {
return this.props.specialRender();
}
}
return HOC(RP);
}
class Hello extends React.Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
class RP extends React.Component {
render() {
return (
<div style={{ background: "#2b80b6" }}>{this.props.specialRender()}</div>
);
}
}
// RP -> HOC
const HOC = RP2HOC(RP)
const EComponent = HOC(Hello);
ReactDOM.render(
<EComponent name="RP -> HOC" />,
document.getElementById("container1")
);
// HOC -> RP
const NewRP = HOC2RP(HOC)
ReactDOM.render(
<NewRP specialRender={() => <Hello name="HOC -> RP" />} />,
document.getElementById("container2")
);
Copy the code
Render Props defects
-
Cumbersome to use: HOC can be reused using only one line of code with the help of decorator syntax, which Render Props cannot
-
Deep nesting: Render Props got rid of the problem of multi-layer nesting of components, but converted to nesting of function callbacks