Writing in the front
React
In thejsx
Rendering principles.React
In aboutstate
Existing “problems”.- A simple
React
In thestate/setState
Mechanism.
The knowledge involved in this article is gradually explained and developed. Of course, if you are not interested in the content (already understood), you can also directly enter the content of this article, because each chapter will not have a strong coupling with the previous one.
For the code addresses involved in the article, go here to 👇.
The content of the article will be divided into three steps:
- implementation
React
The nativeDOM
Elements of theRef
– to obtainDOM
Node. - implementation
React
In theClass Component
theRef
– to obtainClass Component
Instance. - implementation
React
In theFunction Component
theRef
—forwordRef
.
nativeDom
theref
basis
React allows you to obtain an instance of a native Dom node by using a Ref.
Let’s take a look at how it works:
class ClassComponent extends React.Component {
constructor() {
super(a);this.refInputPrefix = React.createRef();
this.refInputSuffix = React.createRef();
this.ref = React.createRef();
}
handleClick = () = > {
const prefix = this.refInputPrefix.current.value;
const suffix = this.refInputSuffix.current.value;
const result = parseInt(prefix) + parseInt(suffix);
this.ref.current.value = result;
};
render() {
return (
<div>
<input ref={this.refInputPrefix}></input>
<input ref={this.refInputSuffix}></input>
<button onClick={this.handleClick}>Click the calculation result</button>
<input ref={this.ref}></input>
</div>); }}const element = <ClassComponent></ClassComponent>;
ReactDOM.render(element, document.getElementById('root'));
Copy the code
React.createref () creates a ref object with the current attribute, and then assigns it to the Dom node on the JSX template with ref={ref}. .current got the corresponding Dom element.
The REF of the Dom node retrieves the actual Dom element nodes rendered on the page.
implementation
Now that we understand the usage, let’s implement the API, which is actually very simple to implement. (Of course, it is recommended to have a little understanding of the pre-knowledge in the article, and of course, if you don’t understand the code before the article, you can refer to the relevant article before.)
First, we know that to use a ref in the class component we need to create a ref object mounted on this via react.createref ().
So let’s start with crateRef. We’ll create a ref.js at the same level as the react.js:
export function createRef() {
return { current: null };
}
Copy the code
Its implementation is simple enough to return an empty reference object with a current property.
import { createRef } from './ref';
const React = {
// Convert JSX to vDOM with createElement
createElement(type, config, children) {
let ref; // Define the Ref attribute in addition
if (config) {
// They don't belong to props
ref = config.ref;
}
constprops = { ... config, };if (arguments.length > 3) {
props.children = Array.prototype.slice
.call(arguments.2)
.map((i) = > wrapTextNode(i));
} else {
props.children = wrapTextNode(children);
}
return {
type,
props,
ref,
};
},
/ / introduction
createRef
};
Copy the code
We then introduce this method in the peer react.js.
Let’s see what the ref for JSX in Babel will compile to look like:
The createElement method in the React. Js is used to render a vDom element, and the ref passed in is kept on the props of the vDom.
.createElement(type, config, children) {
let ref; // Define the Ref attribute in addition
if (config) {
// They don't belong to props
ref = config.ref;
}
constprops = { ... config, };if (arguments.length > 3) {
props.children = Array.prototype.slice
.call(arguments.2)
.map((i) = > wrapTextNode(i));
} else {
props.children = wrapTextNode(children);
}
return {
type,
props,
ref,
};
},
Copy the code
The react. createElement method does a separate processing for the ref property, and the object returned has a ref at the same level as type and props.
CreateRef () => {current:null}
The react.createElement () method returns a vDom object and adds a Ref object {current:null} to the vDom object.
Consider what we need to achieve in the end: to point {current:null}’s current property to the actual Dom node rendered by the corresponding vDom.
Remember that when we implemented setState earlier, in the createDom method, we added a DOM attribute to each vDom render pointing to the actual DOM node.
It’s not hard to imagine,
- in
vDom
Rendering becomedom
When we pass inReact.createElement
Method returnvDom
Object. - The incoming
vDom
Object, ownsprops
.type
.ref
These three properties. ref
Is aobject
It’s aReference types.- So we’re going to
vDom
Render to realityDom
In the process, just need to{ current:null }
In thecurrent
Property points to the corresponding generated realityDom
Node.
Let’s get a sense of how the code should be implemented:
- = >
jsx
The incomingref
Property of the{ current:null }
- = >
jsx
Elements bybabel
Translated intoReact.createElement(...)
- => We implement
React.createElement(...)
Returns avDom
Object,{ ref:... , props:... , type:... }
- = > when calling
createDom(vDom)
The incomingvDom
willvDom
Render to realityDom
Element after we modify the passedref.current
The direction of is trueDom
Elements. - Because of the reference type, this is inside the component instance
React.creatRef
The return of{ current:null }
Has become a{ current: [Dom] }
- => Finally we can pass in the component instance
this.xxx
To get the realDom
Elements.
// react-dom.js.// Convert vDom to real Dom
// Convert vDom to real Dom
function createDom(vDom) {
const { type, props, ref } = vDom;
let dom;
if (type == REACT_TEXT) {
dom = document.createTextNode(props.content);
} else if (isPlainFunction(type)) {
if (isClassComponent(type)) {
return mountClassComponent(vDom);
} else {
returnmountFunctionComponent(vDom); }}else {
dom = document.createElement(type);
}
/ / update the props
if (props) {
updateProps(dom, {}, props);
/ / update the children
if (props.children) {
// Update the recursive call to children
if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom);
} else{ render(props.children, dom); }}}// The DOM attribute on the virtual DOM points to the real DOM. Only renderVDom will mount the DOM
vDom.dom = dom;
// If Ref exists on the Ref attribute, the corresponding real DOM element will be assigned to ref.current after each real DOM creation
if (ref) {
ref.current = dom;
}
returndom; }...Copy the code
The createDom method determines that if ref is passed, ref. Current = dom.
So the ref attribute for normal Dom elements is already implemented, and it’s actually quite simple. It uses the reference address of the object to modify the attribute value of the object so that the corresponding DOM element can be accessed in the instance.
class
The component’sref
We already implemented Dom refs, so class Component refs are much easier to implement
basis
As always, let’s look at what a ref is in class Component:
// The ref implementation of the class component
class ChildrenComponent extends React.Component {
constructor() {
super(a);this.inputRef = React.createRef();
}
handleFocus = () = > {
this.inputRef.current.focus();
};
render() {
return <input ref={this.inputRef}>children</input>; }}class ClassComponent extends React.Component {
constructor() {
super(a);this.childrenCmp = React.createRef();
}
handleClick = () = > {
this.childrenCmp.current.handleFocus();
};
render() {
return (
<div>
<ChildrenComponent ref={this.childrenCmp}></ChildrenComponent>
<button onClick={this.handleClick}>Focus on the son node input</button>
</div>); }}const element = <ClassComponent></ClassComponent>;
ReactDOM.render(element, document.getElementById('root'));
Copy the code
Running this code, the input in ChildComponent will be focused when we click the button.
React uses the ref attribute on the class component to get the instance of the class component.
The instance method on the class component can then be called from the class component instance obtained by this ref.
implementation
CreateDom = ref (vDom); createDom = ref (vDom); createDom = ref (vDom);
If a ref points to an instance of a class component, then we can point the ref.current to the corresponding component instance of the class component.
If that’s what you think, then I’ll tell you. It is easy to implement a ref for a class component.
Let’s take a look at the related react-dom.js:
// Convert vDom to real Dom
function createDom(vDom) {
const { type, props, ref } = vDom;
let dom;
if (type == REACT_TEXT) {
dom = document.createTextNode(props.content);
} else if (isPlainFunction(type)) {
if (isClassComponent(type)) {
return mountClassComponent(vDom);
} else {
returnmountFunctionComponent(vDom); }}else {
dom = document.createElement(type);
}
/ / update the props
if (props) {
updateProps(dom, {}, props);
/ / update the children
if (props.children) {
// Update the recursive call to children
if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom);
} else{ render(props.children, dom); }}}// The DOM attribute on the virtual DOM points to the real DOM. Only renderVDom will mount the DOM
vDom.dom = dom;
// If Ref exists on the Ref attribute, the corresponding real DOM element will be assigned to ref.current after each real DOM creation
if (ref) {
ref.current = dom;
}
return dom;
}
Copy the code
In createDom, when we encounter class Component, we go directly to the return mountFunctionComponent(vDom) branch.
Let’s make a few changes to the mountFunctionComponent(vDom) method:
/ / mount ClassComponent
function mountClassComponent(vDom) {
// Ref is an instance object of the ref class component
const { type, props, ref } = vDom;
const instance = new type(props);
if (ref) {
// If the ref attribute has an instance of the class assigned to ref.current
ref.current = instance;
}
const renderVDom = instance.render();
RenderVDom. OldRenderVDom = renderVDom
instance.oldRenderVDom = vDom.oldRenderVDom = renderVDom; // Mount the current RenderVDom on the class instance object
return createDom(renderVDom);
}
Copy the code
After we initialize the class component instance, we just need to assign the generated class component instance to the ref.current property.
On the outer component instance, this.[XXX] can be placed into the corresponding ref object, and then this.[XXX].current can be accessed to call the corresponding instance method.
Function Component
theref
In React, we know there are no refs in Function Component. If you use refs directly on FC components, you get a warning like this:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Function Component is not allowed to use refs.
- native
Dom
The node’sref
Property can point to the correspondingdom
Stored in theclass
On an instance of class
Components can also pass throughref
Get the corresponding force object stored on the corresponding parent component instance as a property call
In combination with the above two conclusions, it is not difficult to understand why FC is allowed to have the Ref attribute:
The function component has no instance, which means that the function is destroyed at the end of each run and no instance is returned. Naturally, the root node of the function component is not rendered as a real DOM element so it is not consistent with the native DOM, and we cannot obtain the instance of the function component through ref.
What if we want to use ref for function components at this point? I believe some students have already used the forwardRef API. It means to do a layer of forwarding.
Ref Forwarding is a technique for automatically passing a Ref from a component to its children. This is usually not required for most components in your application. However, it can be useful for certain types of components, especially in reusable component libraries
His specific practical is very simple, is through a layer of forwarding. The ref is passed to the function component, which takes the ref argument internally and passes it on to other elements for use.
basis
// The Ref of the function component
const Child = React.forwardRef(function (props, ref) {
return <input ref={ref}>{props.name}</input>;
});
class Parent extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
}
handleClick = () = > {
this.ref.current.focus();
};
render() {
return (
<div>Ref and name */} exist on {/* class components<Child ref={this.ref} name="wang.haoyu">Class components</Child>
<button onClick={this.handleClick}>Click on the focus</button>
</div>); }}const element = <Parent></Parent>;
ReactDOM.render(element, document.getElementById('root'));
Copy the code
When we click button, the input inside the function component is focused.
The forwardRef forwards the ref passed to the function component to the corresponding input component.
implementation
As mentioned above, FC has no instance concept, so if we want to implement FC refs, we need to implement the corresponding forwardRef first.
React uses the forwardRef Api to pass in a function component as a class component. Then the forwardRef returns an instance of the class component that can be forwarded through the ref.
Let’s look at the forwardRef implementation code first:
// react.js
import { wrapTextNode } from '.. /utils/index';
import Component from './component';
import { createRef } from './ref';
const React = {
// Convert JSX to vDOM with createElement
createElement(type, config, children) {
let ref; // Define the Ref attribute in addition
if (config) {
// They don't belong to props
ref = config.ref;
}
constprops = { ... config, };if (arguments.length > 3) {
props.children = Array.prototype.slice
.call(arguments.2)
.map((i) = > wrapTextNode(i));
} else {
props.children = wrapTextNode(children);
}
return {
type,
props,
ref,
};
},
// FunctionComponent's ref forwarding
forwardRef(functionComponent) {
return class extends Component {
render() {
return functionComponent(this.props, this.props.ref); }}; }, createRef,/ / class components
Component,
};
export default React;
Copy the code
The forwardRef implementation is very simple, like HOC, and takes a function component as an argument and returns a class component.
A call to this function component in the render method of the class component returns the JSX return value of the corresponding function component, passing in the corresponding props and props. Ref objects.
To tease out the process a bit:
When the forwardRef wraps a function component, the outer layer passes a ref to the component returned by the forwardRef:
const Child = function (props, ref) {
return <input ref={ref}>{props.name}</input>;
};
class Parent extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
}
handleClick = () = > {
this.ref.current.focus();
};
render() {
return (
<div>Ref and name */} exist on {/* class components<Child ref={this.ref} name="wang.haoyu">Class components</Child>
<button onClick={this.handleClick}>Click on the focus</button>
</div>); }}Copy the code
We passed the props name=’wang.haoyu,ref={current:null} to the forwardRef wrapped component Child.
When the forwardRef returns a class component, the props is
{
name:'wang.haoyu'.ref: { current:null}}Copy the code
In the class component, in the createDom method we create an instance of the class component and pass in the props.
We then return the result of a function call via the render method of the class component, passing two arguments: this.props and this.props. Ref => {current:null}.
Next inside our function component:
const Child = function (props, ref) {
return <input ref={ref}>{props.name}</input>;
};
Copy the code
We use the ref object that was passed in, and then the input element calls createDom during rendering and redirects the ref. Current so that its current points to the actual Dom node of the input element.
On the Parent layer, we can use this.ref.current to get the input of the corresponding Child component, which is the real DOM element.
At this point, our three types of REFs have been basically implemented, which may be a little confusing when you look at them all at once.
Never mind, the learning path for source code is always steep and gradual, read it a few times and try it out with the demo yourself. I believe you can do it!
All the code in the article is inconsistent with the source code, because the real source code will be more than in the article many branches pulled out at once will have a lot of questions. So the article has been simplified to take out the core ideas and the most simple way to realize the source code of ideas and core principles.
doubt
The forwardRef is essentially using {current:null} to change the direction of the current and forward the ref.
So why not just have all function components support the second argument as ref when you mount the function component, then you don’t need the operation in the source code at all.
In the native code, I tried to modify it directly to look like this, but it can actually implement the function component’s ref forwarding directly without needing the forwardRef API at all.
// react-dom.js
/ / mount FunctionComponent
function mountFunctionComponent(vDom) {
const { type, props, ref } = vDom;
// It exists here
const renderDom = type(props, ref);
// Consider the FunctionComponent as the root node
vDom.oldRenderVDom = renderDom;
const dom = createDom(renderDom);
return dom;
}
Copy the code
With this modification, each mount of the function component at this point checks if the REF attribute was passed in.
If ref is passed in, we can also change the ref. Current parameter. If ref is passed in, we can change the ref.
Of course, AFTER this, I will continue to dig into the mechanism of React to try to answer this question.