Progressive react source code analysis - Implementation of ref API

19 groups of fresh air 2021-09-15 06:28:29
progressive react source code analysis


Write in the front

The knowledge involved in this article is a gradual explanation and development , Of course, if you are not interested in the content between ( Have understood ), You can also directly cut into the content of this article , Each chapter will not be strongly coupled with the previous one .

Code address involved in the article , Check here .

The content of the article will be divided into three steps :

  1. Realization React Zhongyuan DOM Elemental Ref --- obtain DOM node .
  2. Realization React in Class Component Of Ref --- obtain Class Component example .
  3. Realization React in Function Component Of Ref --- forwordRef.

Native Dom Of ref

Basics

As we all know React If you want to get native Dom An instance of a node needs to pass Ref In order to get the .

Let's see how it works first :

class ClassComponent extends React.Component {
constructor() {
super();
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 calculation result </button> <input ref={this.ref}></input> </div>
);
}
}
const element = <ClassComponent></ClassComponent>;
ReactDOM.render(element, document.getElementById('root'));
 Copy code 

We go through React.createRef() Method creates a with current Attribute ref object , And then in jsx Pass on the template ref={ref} Assign a value to Dom node , You can go through this.[ref...].current Get the corresponding Dom Element .

Dom Node ref What you get is the real rendering on the page Dom Element nodes .

Realization

After understanding the usage, let's implement this api, In fact, his implementation is very simple .( Of course, it is recommended to learn a little about the pre knowledge in the article , Of course, if you don't understand the previous code in the article, you can refer to the previous relevant articles ~)

First , We understand that in class Use... In components ref You need to pass React.createRef() To create a ref The object is mounted on this On .

So let's start with crateRef Lay hands on , We were before React.js Create a... At the same level ref.js:

export function createRef() {
return { current: null };
}
 Copy code 

Its implementation is very simple , Is to return an empty Reference object , To have a current attribute .

import { createRef } from './ref';
const React = {
// adopt createElement take jsx Transform into vdom
createElement(type, config, children) {
let ref; // Additional definitions Ref attribute 
if (config) {
// They don't belong to props
ref = config.ref;
}
const props = {
...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,
};
},
// introduce 
createRef
};
 Copy code 

Then we were at the same level React.js This method is introduced in .

Now let's see babel Targeted at jsx Of ref What will it compile into :

image.png

We can see that it is actually aimed at jsx Translated vDom Elements , Incoming ref Yes, it will be saved in vDom Of props Upper , Next, let's transform React.js Medium createElement Method :

...
createElement(type, config, children) {
let ref; // Additional definitions Ref attribute 
if (config) {
// They don't belong to props
ref = config.ref;
}
const props = {
...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 code 

We will React.createElement The method is aimed at ref Property is handled separately , On the final returned object and typeprops There is a peer ref.

( At this point ref It's actually what we introduced React.createRef() => { current:null } This object )

I believe the code above is not difficult to understand , Next, we are already React.createElement() Method returns a vDom object , And give this vDom Object added a {current:null} Of Ref object .

Think about the end result we need to achieve : take { current:null } Of this object current Property points to the corresponding vDom The rendered reality Dom node .

At this time, we think of realizing setState when , We are createDom In the method , To every one. vDom When rendering, a dom Property points to the real Dom node .

It's not hard to think of ,

  • stay vDom Render as dom when , We introduced React.createElement Method vDom object .
  • Incoming vDom object , Have props,type,ref These three attributes .
  • ref It's a object, It's a Reference type .
  • Then we will vDom Render to reality Dom In the process of , Only need to { current:null } Medium current Property points to the corresponding generated real Dom node .

Following the above idea, let's take a look at how the code should be implemented :

  • =>jsx In the middle of ref Properties of , The value is { current:null }
  • => jsx Elements pass through babel Translate into React.createElement(...)
  • => What we achieve React.createElement(...) Return to one vDom object ,{ ref:..., props:..., type:... }
  • => When calling createDom(vDom) Pass in vDom take vDom Render to reality Dom After the element , We modify the incoming ref.current The direction of is real Dom Elements .
  • => Due to the relationship between reference types , At this point, the component instance is internal React.creatRef Returned { current:null } Has become a { current: [Dom] }
  • => Finally, we can pass in the component instance this.xxx Get the truth Dom Elements .
// react-dom.js
...
// take vDom Turn into reality Dom
// take vDom Turn into reality 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 {
return mountFunctionComponent(vDom);
}
} else {
dom = document.createElement(type);
}
// to update props
if (props) {
updateProps(dom, {}, props);
// to update children
if (props.children) {
// Update recursive call children
if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom);
} else {
render(props.children, dom);
}
}
}
// fictitious DOM Upper dom Property points to the real dom There is only renderVDom Will mount dom
vDom.dom = dom;
// assignment Ref Property exists ref, So every time you create a real DOM after , Will correspond to real Dom Element assigned to ref.current
if (ref) {
ref.current = dom;
}
return dom;
}
...
 Copy code 

createDom Method to determine if ref Words , Then we will ref.current = dom.

So for ordinary Dom Elemental ref Property has been implemented , It's actually very simple . Is to use the reference address of the object , Modify the attribute value of the object so that the corresponding... Can be accessed in the instance dom Elements .

class Component's ref

Above we have achieved Dom Of ref, Then realize class component Of ref It's even simpler ~

Basics

The same as usual , Let's take a look at the class component Medium ref What is it? :


// Class component ref Realization 
class ChildrenComponent extends React.Component {
constructor() {
super();
this.inputRef = React.createRef();
}
handleFocus = () => {
this.inputRef.current.focus();
};
render() {
return <input ref={this.inputRef}>children</input>;
}
}
class ClassComponent extends React.Component {
constructor() {
super();
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 code 

image.png

Run this code , When we click the button ChildComponent Medium input Will be focused .

See here , Maybe you already understand : React Through... On class components ref attribute , You can get the corresponding class component instance .

So you can use this ref The obtained class component instance calls the instance method on the class component .

Realization

Write here , Above we realize Dom Of ref api when , It's through createDom The method is to vDom Generate reality Dom Later ref The corresponding assignment achieves the effect .

So here we can't help thinking , If it's for class component its ref Point to its instance , So we're going to Class Component When will ref.current Just point to the corresponding class component instance ?

If you think so , So I'll tell you . you 're right ~ With the above foundation, let's implement the class component ref It will be very simple .

Let's take a look at the related react-dom.js:

// take vDom Turn into reality 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 {
return mountFunctionComponent(vDom);
}
} else {
dom = document.createElement(type);
}
// to update props
if (props) {
updateProps(dom, {}, props);
// to update children
if (props.children) {
// Update recursive call children
if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom);
} else {
render(props.children, dom);
}
}
}
// fictitious DOM Upper dom Property points to the real dom There is only renderVDom Will mount dom
vDom.dom = dom;
// assignment Ref Property exists ref, So every time you create a real DOM after , Will correspond to real Dom Element assigned to ref.current
if (ref) {
ref.current = dom;
}
return dom;
}
 Copy code 

stay createDom In the method , When we meet class Component when , Go straight into return mountFunctionComponent(vDom) This branch statement .

We are here mountFunctionComponent(vDom) Just make a few changes to this method :

// mount ClassComponent
function mountClassComponent(vDom) {
// You should be able to get ref Class component ref Is an instance object of a class 
const { type, props, ref } = vDom;
const instance = new type(props);
if (ref) {
// If ref Attributes exist Class to ref.current
ref.current = instance;
}
const renderVDom = instance.render();
// Consider that the root node is class Components therefore vDom.oldRenderVDom = renderVDom
instance.oldRenderVDom = vDom.oldRenderVDom = renderVDom; // When mounting, mount the current instance on the class instance object RenderVDom
return createDom(renderVDom);
}
 Copy code 

After we initialize the class component instance , We just need to instance the generated class component instance Assign a value to ref.current attribute .

On an instance of an outer class component, you can use this.[xxx] Put it in the corresponding ref object , And then through this.[xxx].current You can access the corresponding class component instance and call the method of the corresponding instance .

Function Component Of ref

stay React in , We know Function Component There is no ref Of , If you give it directly to FC Use... On components ref Words , You will get such a warning :

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

in other words Function Component It is not allowed to be used ref Of , Combined with the above conclusion, let's think about .

  • Native Dom Node ref Property can point to the corresponding dom Save in class On an instance of
  • class Components can also be through ref Get the corresponding faction object, save it on the corresponding parent component instance, and call it as a property

Combined with the above two conclusions, it is not difficult for us to understand why FC Is allowed to have Ref attribute :

Function component has no instance , That is, the function will be destroyed at the end of each run , No instances will be returned , come very naturally , The function component root node does not render as real dom Element, so it can't be compared with native dom bring into correspondence with , At the same time, we can't pass ref Get an instance of a function component .

At this point, if we want to use... For function components ref What shall I do? ? I believe some students have used it forwardRef This api. Its meaning is to do a layer of forwarding .

Ref forwarding It is a method of automatically transferring ref The technology passed to its subcomponents . For most components in an application , This is usually not necessary . however , It is useful for certain types of components , Especially in reusable component libraries

His practicality is very simple , It's forwarding through one layer . Pass... To the function component ref, The function accepts this internally ref Parameters are then passed through Ref To forward to other elements using .

Basics

// Function components Ref
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> {/* Class component On There is ref and name */} <Child ref={this.ref} name="wang.haoyu"> Class component </Child> <button onClick={this.handleClick}> Click to focus </button> </div>
);
}
}
const element = <Parent></Parent>;
ReactDOM.render(element, document.getElementById('root'));
 Copy code 

At this point, we click button when , Functions inside components input Will focus on .

That is, we pass forwardRef Pass to the function component ref Forwarded to the corresponding input Components .

Realization

We talked about FC In fact, there is no concept of instance in , So we need to achieve FC Medium ref First, you need to implement the corresponding forwardRef.

in the light of FC Medium FC,React This is what the interior does , adopt forwardRef This Api Pass in a function component , Pass the passed in function component through forwardRef The package becomes a class component . Then return the class component , In this case, when rendering forwardRef In fact, it returns an instance of a class component , So you can get through ref To realize forwarding .

Let's take a look at forwardRef Implementation code :

// react.js
import { wrapTextNode } from '../utils/index';
import Component from './component';
import { createRef } from './ref';
const React = {
// adopt createElement take jsx Transform into vdom
createElement(type, config, children) {
let ref; // Additional definitions Ref attribute 
if (config) {
// They don't belong to props
ref = config.ref;
}
const props = {
...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 Of ref forward 
forwardRef(functionComponent) {
return class extends Component {
render() {
return functionComponent(this.props, this.props.ref);
}
};
},
createRef,
// Class component 
Component,
};
export default React;
 Copy code 

We see that in fact forwardRef The implementation here is very simple , similar HOC, Accepts a function component as a parameter and returns a class component .

In class component render The call that returns this function component in the method returns the corresponding function component jsx Return value , At the same time, the corresponding props and props.ref This object .

Let's sort out the process a little :

When we go through forwardRef Wrap a function component , Give this forwardRef The returned component is passed in ref:

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> {/* Class component On There is ref and name */} <Child ref={this.ref} name="wang.haoyu"> Class component </Child> <button onClick={this.handleClick}> Click to focus </button> </div>
);
}
}
 Copy code 

We give Child This forwardRef The package's components passed in props in name='wang.haoyu,ref={ current:null }.

At this point we pass through forwardRef What is returned is a class component , This class component is converted to vDom when ,props by

{
name:'wang.haoyu',
ref: { current:null }
}
 Copy code 

In a class component , stay createDom Method, we create an instance of this class component and pass in the corresponding props.

Then we pass the class component render Method returns a function call result , This function passes two parameters, namely 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 code 

We used the incoming ref object , then input Element is called when rendering createDom Method modifies this ref.current The direction of , Let his current Pointing to input The reality of the elements Dom node .

So in the outer layer Parent We can go through this.ref.current Get the corresponding Child Component's input This is true DOM Elements , So as to realize the function component ref Forwarding effect .

At the moment , We have three types of ref Have been basically realized , It may seem a little incomprehensible at one time .

No problem , The learning journey for the source code is always steep and gradual , Watch it several times and try to follow demo Have a try . I believe you can !

All the codes in the article are different from the source code , Because the real source code will have more branches than in the article, there will be a lot of questions when you pull it out at once . Therefore, the article has been streamlined to come up with the core ideas and the simplest way to realize the ideas and core principles in the source code .

doubt

In fact, I always have a question here , if forwardRef In essence, I understand is to use { current:null } modify current So as to achieve forwarding ref Words .

So why not directly let all function components support that the second parameter is passed in when mounting function components ref, In this way, the operation in the source code is completely unnecessary .

In the native code, I tried to modify it directly to look like this , In fact, you can directly implement function components ref Forward without having to forwardRef This api.

// react-dom.js
// mount FunctionComponent
function mountFunctionComponent(vDom) {
const { type, props, ref } = vDom;
// There is 
const renderDom = type(props, ref);
// Consider that the root node is FunctionComponent
vDom.oldRenderVDom = renderDom;
const dom = createDom(renderDom);
return dom;
}
 Copy code 

After this modification , At this time, each time the function component is mounted, it will detect whether it is passed in ref attribute .

If it is passed in, it will also modify the second parameter passed in by the synchronous calling function ref, We just need to modify... In the function component ref.current The direction of , The outer layer passes through the incoming ref Can't you also achieve the effect of forwarding ?

Of course , After that, I will continue to go deep react To try to answer this question .

版权声明
本文为[19 groups of fresh air]所创,转载请带上原文链接,感谢
https://qdmana.com/2021/09/20210909122358643p.html

  1. Mid Autumn Festival special! Use the simplest animation animation to make the most local and trendy holiday blessing greeting card. This romantic male and female tears of Xiao Chen.
  2. Wang Ou went back to the hotel with the man at night. It was suspected that his relationship was open. The netizen replied mercilessly: is the man single
  3. 借助HTML ping属性实现数据上报
  4. APNG在线制作、兼容、播放和暂停
  5. Apng production, compatibilité, lecture et pause en ligne
  6. Mise en œuvre de l'escalade des données avec l'attribut de Ping HTML
  7. Comment envoyer 100 000 requêtes http le plus rapidement possible
  8. JQuery Basics
  9. Front and back end data interaction (V) -- what is Axios?
  10. Serverless is a model architecture invented driven by economic benefits- Grady
  11. Les questions d'entrevue pour les ingénieurs Java d'Internet, les intervieweurs rencontrés sont tous de niveau architecte,
  12. Cinq ans d'entrevue d'expérience en développement Java, découvrez les questions que vous devez poser lors de l'entrevue d'embauche du printemps Java de cette année.
  13. La dernière collection de questions d'entrevue Java haute fréquence organisée cette année, 2021 Java Universal Popular Framework
  14. Intel selected Weilai es8 to promote driverless taxis in Europe
  15. JavaScript operator (1), Web Development Engineer
  16. Trier les questions d'entrevue Javascript, trier les points de connaissance des itinéraires d'apprentissage
  17. Song Mengjun's "sleepless night" triggered an upsurge of dance storm after 00
  18. Module management of "free and open source" front-end spa project crudapi background management system based on Vue and Quasar (14)
  19. Encapsulated PHP sends HTTP requests with curl. Get and post are very easy to use
  20. Front and back end data interaction (V) -- what is Axios?
  21. Flutter: résoudre le futur blocage en utilisant Isolate
  22. Résumé des opérations courantes pour les données de structure de l'arbre frontal
  23. Ant Design Transfer Twin Tree Shuttle box "make Wheels"
  24. De la carte de pensée à la base et à l'approfondissement, prenez note de l'expérience d'entrevue d'un octet sautant le poste de recherche et développement Java.
  25. Apprenez les composants d'implémentation de vue et Publiez - les à NPM
  26. [Questions d'entrevue à haute fréquence] À vous de choisir
  27. Une faible connaissance de beginpath () provoque une superposition de style lors de la peinture d'un dessin en toile
  28. React Hooks, laisse - moi t'emmener étudier.
  29. Comment la copie profonde résout - elle les références circulaires?
  30. JavaScript Advanced Programming (3rd Edition) Reading note 6
  31. Analyse de l'URL
  32. Discussion préliminaire sur xss
  33. Solution: développement de la page Web Wechat, obtenir la fosse Piétinée par le flux d'entrée de la caméra via navigator.mediadevice.getusermedia ()
  34. Des milliers de questions d'entrevue sélectionnées n'ont pas encore ét é effacées.
  35. Les questions d'entrevue de niveau intermédiaire et avancé d'Android au fil des ans sont entièrement incluses, et l'algorithme est distribué microservice
  36. J'ai résumé toutes les questions d'entrevue.
  37. Compréhension de la réactivité des données de vue
  38. Note de service CSS (vi): Flex, page mobile et mise en page réactive
  39. Non-ASCII character ‘\xe5‘ in file kf1.py on line 4, but no encoding declared; see http://python.or
  40. 手把手教你搭建微信小程序服务器(HTTPS)
  41. JavaScript Review sketch - 1
  42. Analyse du bootstrap webpack
  43. sqli-labs-less-18 http头user agent+报错注入
  44. Génération de code nest pour l'outil CLI de nestjs
  45. JS | This
  46. Augmentation des variables
  47. The sinking gs8 raises its flag again. GAC motor's sales are falling endlessly. Is it the car or the people?
  48. Ren Hao's lunch at work today is president Hao wearing a sleeveless coat! Clean and handsome!
  49. Summary of basic knowledge points of JavaScript language (mind map)
  50. The new front-end lady asked: there was a 404 problem refreshing the page in Vue routing history mode
  51. Sqli Labs - less - 18 http header user agent + Error Reporting Injection
  52. Vous apprendrez à construire un serveur d'applet Wechat (https) à la main
  53. Non - ASCII character 'xe5' in file kf1.py on Line 4, but no Encoding declared;Voirhttp://python.or
  54. The new front-end lady asked: there was a 404 problem refreshing the page in Vue routing history mode
  55. En tant que programmeur, quelle est la plus grande tristesse que vous ressentez? L'entrevue d'emploi Java de 2021 dans une grande usine vous demandera:
  56. En tant que programmeur, je n'oublie pas le dernier résumé de mon expérience d'entrevue de stage en Java.
  57. Experts suggested that performers work with certificates, which triggered a collective heated debate. It is meaningless to be accused of repeating the mistakes
  58. The new front-end lady asked: there was a 404 problem refreshing the page in Vue routing history mode
  59. The appearance value of 200000 "Odyssey" is less than 100000, and has become the "sales champion" of household MPV
  60. Les programmeurs Java qui sont entrés dans l'entreprise pendant trois mois ont dû faire face à une correction d'échelle, et les octets ont sauté dans le traitement des questions d'entrevue de JD 360 Netease.