[React-Quellcode-Implementierung] Implementierungsprinzip des Element-Renderings

Vorwort

In diesem Artikel werden die Designideen von React kombiniert, um das Rendern von Elementen zu realisieren, JSX语法d Implementierungsidee von React, daher sehr gut für kleine Kinder geeignet. Das Lernen ist kostenlos. Es wird empfohlen, die Schritte zum Eingeben des Codes zu befolgen. Wenn Fehler auftreten, kritisieren und korrigieren Sie diese bitte!

Anregung:

  1. Wenn Sie nicht wissen, was JSX ist, oder React nicht verstehen, wird empfohlen, zunächst die offizielle React-Dokumentation zu lesen und der Dokumentation zum Erstellen eines Spiels zu folgen, um ein allgemeines Verständnis von JSX zu erlangen.
  2. Wenn Sie auch den Quellcode von Vue erfahren möchten, können Sie auch diesen Blog lesen. Er steht auch im Einklang mit der Implementierungsidee von Vue, die virtuelles DOM in echtes DOM umwandelt.
  3. Verstricken Sie sich nicht zu sehr in die Art und Weise, wie die einzelnen Methoden implementiert werden. Wenn Sie sich zu sehr verstricken, geraten Sie in die Hölle einer rekursiven Endlosschleife. Das Gleiche gilt für den React-Quellcode.

Offizielle Dokumentation

Versuchen wir zunächst, ein React-Projekt zu erstellen:

npx create-react-app meine-app

Umsetzungsideen

Hier diskutieren wir nur die Implementierungsprinzipien des Element-Renderings

Fügen Sie hier eine Bildbeschreibung ein
React verwendet Babel, um JSX-Syntaxdateien in die Funktion React.createElement zu übersetzen, ruft die Funktion React.createElement auf, um JSX in ein virtuelles Dom (d. h. ein Vnode-Objekt) zu konvertieren, und verwendet dann die Funktion ReactDOM.render, um das virtuelle DOM umzuwandeln in einen echten DOM-Mount. zur Seite

  • Implementieren Sie die Funktion React.createElement
  • Implementieren Sie die Render-Funktion
  • Vollständige Darstellung und Anzeige auf der Seite

Projekt initialisieren

Wenn Sie mit der oben genannten Methode ein React-Projekt erstellen, können Sie auch zuerst die überflüssigen Dateien löschen und daraus die einfachste jsx-Datei machen.
Hier behalte ich nur eine Datei.
Fügen Sie hier eine Bildbeschreibung ein

import React from 'react';
import ReactDOM from 'react-dom/client';




let element = <h1>Hello, world</h1>;


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  element
);

Wenn Sie ein Hello, world erfolgreich ausdrucken, ist der erste Schritt erfolgreich

React.createElement

Babels Übersetzung erfordert Kenntnisse über AST-Syntaxbäume. Sie können meinen vorherigen Blog lesen. Ich werde hier nicht auf Details eingehen. Hier werden wir direkt über die Implementierungsschritte sprechen, in denen Babel jsx-Syntaxdateien in React.createElement-Funktionsaufrufe konvertiert und virtuelles DOM generiert .

Datenstruktur des virtuellen Doms

Hier schauen wir uns zunächst die Datenstruktur des von React.createElement generierten virtuellen Doms an, was für uns von Vorteil ist, um einen virtuellen Dom per Handschrift zu erstellen.

Wir drucken das virtuelle Dom-Element direkt aus

import React from 'react';
import ReactDOM from 'react-dom/client';




let element = <h1>Hello, world</h1>;


console.log(element);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  element 
);

Fügen Sie hier eine Bildbeschreibung ein
Wie Sie sehen, ist sein Wesen ein Objekt. Babel übersetzt es in die Funktion createElement. Nach dem Aufruf wird ein Objekt zurückgegeben. Dieses Objekt ist der virtuelle Dom, der mehrere Schlüsselwerte enthält.

Das heißt, es wird zu einem Aufruf dieser Funktion

	React.createElement("h1",{
    
    className:"title",style:{
    
    color:'red'}},"hello")

Diese Funktion akzeptiert drei Parameter:

  • Einer davon ist der Typ des Elements
  • Die zweite ist die Konfiguration von Elementen
  • Der dritte ist der Inhalt des Elements (möglicherweise nicht nur Text, sondern auch ein Elementknoten).

Schlüsselschlüsselwert

  • Schlüssel: Wird für React verwendet, um den Diff-Algorithmus zu implementieren
  • Ref: Wird verwendet, um den echten Dom zu erhalten
  • Typ: Elementtyp
  • Requisiten: Elementkonfiguration (z. B. untergeordnete Knoten, Stile)
  • $$typeof: der eindeutige Bezeichner des Elements

Implementierung

Wie bereits erwähnt, akzeptiert diese Methode drei Parameter

  • Einer davon ist der Typ des Elements
  • Die zweite ist die Konfiguration von Elementen
  • Der dritte ist der Inhalt des Elements (möglicherweise nicht nur Text, sondern auch ein Elementknoten).
import React from 'react';
import ReactDOM from 'react-dom';






let element2 = React.createElement("h1", {
    
    
  className: "title",
  style: {
    
    
    color: 'red'
  }
}, 'hello world','hi');




console.log(element2);

ReactDOM.render(
  element2,
  document.getElementById('root')
);

Hinweis 1 : Wenn Sie versuchen, nach „Hallo Welt“ einen weiteren Text „hi“ anzuhängen, werden Sie feststellen, dass 子节点有多个das Kinderattribut in seinen Requisiten lautet 从一个字符串类型变成数组类型. Das ist sehr wichtig!

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Hinweis 2 : Wenn Sie kein Text, sondern ein Elementobjekt sind, handelt es sich um ein Objekt. Wenn es sich um mehrere Elementobjekte handelt, wird es zu einem Array mit darin enthaltenen Elementobjekten.

import React from 'react';
import ReactDOM from 'react-dom';






let element2 = React.createElement("h1", {
    
    
  className: "title",
  style: {
    
    
    color: 'red'
  }
}, React.createElement("span", null, "hello"));




console.log(element2);

ReactDOM.render(
  element2,
  document.getElementById('root')
);

Fügen Sie hier eine Bildbeschreibung ein

Initialisierungsfunktion

Wir erstellen eine neue Datei „react.js“ und stellen dieses React-Objekt bereit. Darin befindet sich eine Funktion „createElement“. Wir werden diese Funktion verwenden, um einen virtuellen Dom zurückzugeben.


//接受三个参数,元素的类型、元素的配置、元素的节点

function createElement(type,config,children) {
    
    
    //返回一个虚拟dom
    return {
    
    

    }
}


const React = {
    
    
    createElement
}

export default React;

Umgang mit Schlüsseln und Refs

Unser Schlüssel und unsere Referenz sind beide in der Konfiguration geschrieben, daher müssen wir den Schlüssel und den Wert separat extrahieren und aus der Konfiguration löschen


    //第一步,处理key和ref
    let key, ref
    
    if (config) {
    
    
        key = config.key || null
        ref = config.ref || null
        delete config.key
        delete config.ref
    }

Umgang mit Requisiten und Kindern

Wir haben im Quellcode herausgefunden, dass er das Children-Attribut und alle Elemente in der Konfiguration in das Props-Attribut eingefügt hat.

Fügen Sie hier eine Bildbeschreibung ein
Der zweite Schritt besteht darin, alle Elemente in der Konfiguration in Requisiten zu packen

    let props =  {
    
    ...config}

Der dritte Schritt besteht darin, den untergeordneten Knoten zu verarbeiten. Hier gibt es drei Situationen:

  • kein untergeordneter Knoten
  • Es gibt einen untergeordneten Knoten – Textknoten/Elementknoten
  • Es gibt mehrere untergeordnete Knoten

    //第二步,处理children
    if (props) {
    
    
        //有多个儿子
        if (arguments.length > 3) {
    
    
           //多个儿子,就把他们变成一个数组
            props.children = Array.prototype.slice.call(arguments, 2)
            //有一个儿子  (1)文本  (2)元素
        }else if(arguments.length === 3){
    
    
            props.children = children;
        }
        //没有儿子,不需要去处理
    }

Behandeln Sie $$typeof

Dieser Schlüssel wird von React verwendet, um Elemente zu identifizieren. Wir erstellen eine stant.jsDatei, um alle Identifikationstypen offenzulegen


//用于标识元素
export const REACT_ELEMENT = Symbol('react.element')

export const REACT_TEXT = Symbol('react.text')

Optimierung

Wenn wir bei der Verarbeitung des untergeordneten Knotens nur einen untergeordneten Knoten haben und dieser ein Text ist, ist dieser vom Typ String. Wir verarbeiten ihn einheitlich in einen Objekttyp, um nachfolgende Aktualisierungsvorgänge über die toObject-Methode zu erleichtern

import {
    
     REACT_TEXT } from "./stants";


export function toObject(element) {
    
    
    return typeof element === 'string' || typeof element === 'number' ? {
    
    type:REACT_TEXT,content:element} : element
}

Gesamtcode

reagieren.js



//实现以下:
// let element2 = React.createElement("h1", {
    
    
//   className: "title",
//   style: {
    
    
//     color: 'red'
//   }
// }, React.createElement("span", null, "hello"));

import {
    
     REACT_ELEMENT } from "./stants"
import {
    
     toObject } from "./utils"






function createElement(type,config,children) {
    
    
    

    if (config == null) {
    
     
        config = {
    
    }
    }

    //第一步,处理key和ref
    let key, ref
    
    if (config) {
    
    
        key = config.key || null
        ref = config.ref || null
        delete config.key
        delete config.ref
    }





   // 第二步,就是将config中的所有元素都放入到props中
    let props =  {
    
    ...config}


    //第三步,处理children
    if (props) {
    
    
        //有多个儿子
        if (arguments.length > 3) {
    
    
           //多个儿子,就把他们变成一个数组
            props.children = Array.prototype.slice.call(arguments, 2).map(toObject)
            //有一个儿子  (1)文本  (2)元素
        }else if(arguments.length === 3){
    
    
            props.children =  toObject(children)  ;  //统一转变成对象
        }
        //没有儿子,不需要去处理
    }





    //返回一个虚拟dom
    return {
    
      //vnode
        key,
        ref,
        $$typeof:REACT_ELEMENT,
        props,
        type: type,

    }
}





const React = {
    
    
    createElement
}

export default React;

Führen Sie zum Ausprobieren unsere eigene Reaktionsdatei in index.js ein. Hier haben wir die Funktion React.createElement implementiert und einen virtuellen Dom generiert
Fügen Sie hier eine Bildbeschreibung ein

React.render-Funktion

Diese Funktion ist die Schlüsselfunktion zum Konvertieren eines virtuellen Doms in einen realen Dom. Hier akzeptieren wir zwei Parameter, einer ist der virtuelle Dom und der zweite ist der Montageknoten, der diese Funktion implementieren soll.

 ReactDOM.render(
   element2,
  document.getElementById('root')
 );

Initialisierungsfunktion


//将虚拟dom转变成真实dom的方法
function createDOM(vnode) {
    
     
	let dom //真实dom


    return dom
}


function render(vnode, container) {
    
    
    
    //将虚拟dom转变成真实dom
    let dom = createDOM(vnode)

    //将真实dom挂载到container上
    container.appendChild(dom)


}


const ReactDOM = {
    
    
    render
}

export default ReactDOM;

Verarbeiten Sie den Typ und generieren Sie entsprechende Elementknoten

Bitte schauen Sie sich noch einmal die Struktur des von uns generierten virtuellen Knotens an

  • Schlüssel: Wird für React verwendet, um den Diff-Algorithmus zu implementieren
  • Ref: Wird verwendet, um den echten Dom zu erhalten
  • Typ: Elementtyp
  • Requisiten: Elementkonfiguration (z. B. untergeordnete Knoten, Stile)
  • $$typeof: der eindeutige Bezeichner des Elements

Wir haben oben eine Optimierung vorgenommen. Wenn es sich um Text handelt, verarbeiten wir ihn in die Datenstruktur des Objekts

{
    
    
	type:REACT_TEXT,
	content:element
}
    //将虚拟dom转变成真实dom的方法
function createDOM(vnode) {
    
     
  
            let {
    
     type, props, content } = vnode

            let Ndom;
            //1、判断type是什么类型的,是文本还是元素并生成对应的节点
            if (type === REACT_TEXT) {
    
       //如果是一个文本类型的
                 Ndom = document.createTextNode(content)  //注意:我们在前面已经把所有的文件节点处理为一个对象类型的了
            } else {
    
    
                  Ndom = document.createElement(type)  //div
            }


            //2、处理属性   {
    
    children  style:{
    
    color:red,fontsize:16px} className="title" }
            if (props) {
    
     
                console.log("props",props)
                //为了后续处理更新操作
                updateProps(Ndom, {
    
    }, props)
            }





        //3、处理子节点
        
        
        return Ndom

}

Umgang mit Attributen




//初始化和更新props的方法
function updateProps(dom, oldProps, newProps) {
    
    
    //初始化
    if (newProps) {
    
    
         //遍历新的属性对象
    for (let key in newProps) {
    
    
        if (key === 'children') {
    
    
            continue
        } else if (key === 'style') {
    
      //如果是style的话就一个个追加进去
            let styleObj = newProps[key]
            for (let attr in styleObj) {
    
    
                dom.style[attr] = styleObj[attr]
            }
        } else {
    
       //例如className就直接放上去即可
            dom[key] = newProps[key]
        }

    }
    }
   

    //更新操作,如果有旧节点
    if (oldProps) {
    
    
        //旧的属性在新的属性中没有,则删除
        for (let key in oldProps) {
    
     
            if(!newProps[key]){
    
    
               dom[key] = null
        }
    }

}

            //2、处理属性   {
    
    children  style:{
    
    color:red,fontsize:16px} className="title" }
            if (props) {
    
     
                //为了后续处理更新操作
                updateProps(dom, {
    
    }, props)
            }

Untergeordnete Knoten verarbeiten

//处理子节点
//接收两个参数,一个是子节点,另一个是挂载节点
function changeChildren(children, dom) {
    
    

     //有一个儿子的情况  对象
    if (typeof children == 'object'&& children.type ) {
    
    
        render(children, dom)  //递归调用
            //有多个儿子的情况  数组
    } else if (Array.isArray(children)) {
    
    
        //循环处理
        children.forEach(child =>  
            render(child, dom)
        )
     }


}

Gesamtcode

import {
    
     REACT_TEXT } from "./stants"


    //初始化和更新props的方法
function updateProps(dom, oldProps, newProps) {
    
    
        //初始化
        if (newProps) {
    
    
            //遍历新的属性对象
            for (let key in newProps) {
    
    
                if (key === 'children') {
    
    
                    continue
                } else if (key === 'style') {
    
      //如果是style的话就一个个追加进去
                    let styleObj = newProps[key]
                    for (let attr in styleObj) {
    
    
                        dom.style[attr] = styleObj[attr]
                    }
                } else {
    
       //例如className就直接放上去即可
                    dom[key] = newProps[key]
                }

            }
        }
   

        //更新操作,如果有旧节点
        if (oldProps) {
    
    
            //旧的属性在新的属性中没有,则删除
            for (let key in oldProps) {
    
    
                if (!newProps[key]) {
    
    
                    dom[key] = null
                }
            }

        }
}
    

//处理子节点
//接收两个参数,一个是子节点,另一个是挂载节点
function changeChildren(children, dom) {
    
    

     //有一个儿子的情况  对象
    if (typeof children == 'object'&& children.type ) {
    
    
        render(children, dom)  //递归调用
            //有多个儿子的情况  数组
    } else if (Array.isArray(children)) {
    
    
        //循环处理
        children.forEach(child =>  
            render(child, dom)
        )
     }


}


    //将虚拟dom转变成真实dom的方法
function createDOM(vnode) {
    
     
  
            let {
    
     type, props,content } = vnode
            let Ndom; //新的dom节点
            //1、判断type是什么类型的,是文本还是元素并生成对应的节点
             if (type === REACT_TEXT) {
    
       //如果是一个文本类型的
                Ndom = document.createTextNode(content)  //注意:我们在前面已经把所有的文件节点处理为一个对象类型的了
            } else {
    
    
                Ndom = document.createElement(type)  //div
            }


            //2、处理属性   {
    
    children  style:{
    
    color:red,fontsize:16px} className="title" }
             if (props) {
    
    
                //为了后续处理更新操作
                updateProps(Ndom, {
    
    }, props)

                
                //3、处理子节点
                let children = props.children
                 if (children) {
    
    
                    changeChildren(children, Ndom)
                }

            }




        
        
        return Ndom

}




function render(vnode, container) {
    
    

    //将虚拟dom转变成真实dom
    let dom = createDOM(vnode)

    //将真实dom挂载到container上
    container.appendChild(dom)

}



const ReactDOM = {
    
    
    render
}

export default ReactDOM;

Zusammenfassen

Seitdem haben wir im Grunde verstanden, wie React den Prozess des Renderns von Elementen in Ansichten implementiert.

Guess you like

Origin blog.csdn.net/m0_46983722/article/details/132475777