前语

React.createElement 是React中一种创立React组件的方式,它陈旧而奥秘。

尽管日常开发中已经很少能够见到他的身影。可是将JSX用babel编译之后6 ( { s $ : 0 ,便是 createElement 函数

ReactDOM.render 是React实I ! F ) } x _ C U例烘托到dom的入口办法

React.createElement

参数

c; Z ) #reateElemeq x dnt1 ( 支撑传入n个参数。

  • type:表明你要烘托的元素类型。这儿能够传入一个元素Tag名称,也能够传入一个组件(如div9 z : u 0 1 & span ul li 等,也能够是是函数组件和D v 7 j v 8 1类组E t ,件)
  • config:创立React元素所需求的props。包括 style,className 等
  • childT y ! q y 9 qren:要烘托元素的子元素,这儿能够向后传入n个参数。参数类型皆为 React.createElement 回来的React元素目标。
React.createElement(type, config, ch$ Q l @ildren1, children2, children3...);

createElement 办法

咱们新建一个JS文件,导出一个 creaf | / 4 c ; q S DteElement 函数。

办法内置一个props变量。? * h将咱们的config目标自身一切的特点完全copy到 props

function createElement(type, conf( O | Aig, children) {
const props = {}2   E | V `;
for (let propName in config) {
// 假如目标自身存在该特点值,就copy
if (Object.prototype.hasOwnProperty.call(config, propName)) {
props[H ) K c H #propName] = config[propName];
}
}
}
export default {
createElement,
}

接着开始处理子元素。由于子元素的参数方位在 第2个 及其之后,所以咱们需求用到函数的 arguments 目标获取参数值。

在 createElement 中声明一个 chilz . D : OdrenLength 变量,值为 arguments.length - 2

  • 假如} ( c childrenLength === 1,也便是子元素只有1个,就将唯一的子元素挂到1 r C } 1 iprops.children上面。m u i q k R
  • 假如 childrenLength > 1,那就从第二个参数向后截取 arguments 目标。

这儿能够运用 Array.prototypeT 5 Z 1 } t ( 2 ,.slice+ U g.call 进行截取,当然也能够运用 React 的官方写法。如下方代码注释:

    // 取得子元素长度
cB 3 x 2onst childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = childr; = t J ] ~ ( q xen;
} else if (childrenLength &gH [ 6 | D Q 2 = -t; 1) {
// 1. React 官方完成,声明一个和 childrenLength 相同长的数组
// 然后遍历 argY E G O $ / juments目标,把第二个之后的参数项逐个赋值给 childrenArray
let childrenArray = Array(childrenLeQ & = # Nngth);
for (let i = 0; i < arguments.length; i++) {
childrenArray[i] = arguments[i + 2]
}
props.x f o w ) q G )children = children9 ( 7 h  f d gArray;
/b N Y $ h/ 2. 数组sliQ * , | 4 | J + `ce截取,截I M % P = r u ` j取第二个之后一切的C H a U参数项给props.children
props.child+ 6 O 5 J ~ M Iren = Array.prototype.slice.call(arguments, 2);
}

最终,咱们回来 ReactElement(type, pr9 x I * ,ops) 工厂函数,React元素目标创立完成。w 5 h

ReactEl9 h % e ? Cement 办法

ReactElement 办法是一个V M _ K c M 2 # @工厂函数,能F 1 L r J =够包装一个React虚拟Dom目标。

这儿完成也很简单,只需求回来一个目标即可:

function ReactElement(type, props) {
return {
$$typeof: REACT_ELEME& V vNT_TYPE,
type,
props
}
}

$$typeo4 ] E Kf: REACT_ELa W C Y sEMENT_TYPE

$$typeof: REACT_ELEMENT_TYPE 是React元素目标的标识特点

REACT_Ea h zLEMENT_TYPE 的值是一个Symbol类型,代表了I T 2 J ^ A e Q一个绝无仅2 : & K = B b q有的值。假如浏览器不支撑
Symbol类型,值便是一个二进制值A ] c = 9 s 8

为什么是 Symbol?首要避免XSS进犯伪造一个假的React组件。由于JSON中是不会存在Symbol类型的。

为什么是 0xeac7 ?由于 0xeac7 和单词 RR x h ^ ` n { _ #eact 长得很像。

const hasSymbol = typeof Symbol === 'fZ 2 X d eunction' && F l 4 U (amp;& SymN = w 1bol.for;
const REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;

这样咱们的 React.createElement 办法就完成了。

运用示例

import React f% H grom './react';
import Reaa | ]ctDOM fp = a | $ ! } w :rom 'react-dom';
let apple = React.createElement('li', { id: 'apple' }, 'apple');
let banana = React.creat0 { ueElement('li', { id: 2 H @ Z 6 # V w 3'banana' }, 'banana');
let list = React.createElement('ulv X n 7 R 1 |', {id: 'list'n : K C | W M N}, apple, banana);
ReactDOM.render(list, document.getElementById('root')A } m / K);
React.createElement 和 ReactDOM.render 的简易实现

完好完成

classf Z h R w ! E 6 C2 t 0 _ 7 6 W u !omponent {
static isReactComponent = true;
constructor(props) {
this.props = props
}
}
cK r ~ K Wonst hasSymbol = typeof Symbol === 'function' && Symb? s ^ _ L F g T Gol.for; // 浏览器是否支撑 Symbol
// 支撑Symbol的话,就创立一个Symbol类型的标识,否则就以二进制 0xeac7代替。
// 为什么是 Symbol?首要避免xss进犯伪造一个fake的react组件。由于json中是不会存在symbolP s S a /的.
// 为什么是 二进制 0xeac7 ?由于 0xeac7 和单词 React长得很像。
const REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;
funH 0 7 2 G }ction ReactElement(type, props) {Q @ |
retu* z q % 3 ? Xrn {
$$typeof:| A d  T ? REACT_ELEMENT_TYPE,
type,
propL p Q ( 8 b ` : @s
}
}
function crR I eeateElement(type, config, children) {
const props = {};
for (2 q B o % . j e ,let pro[  I h   L jpName in config) {
// 假如目标自身存在该特点值,就copy
if (Object.prototype.hasOwnProperty.call(config, propName)) {
props[propName] = config[propName];
}
}
const childrenLength = arguments.length - 2;
if (childr ) g M [ l W | |renLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
// 1. React 官方完成,声明一个和childrenLength相同长的数组
// 然后遍历 arguments目标,把第二个之后的参数项给 childrenArray
// let childrenArray = Array(childrenLength);
// for (let i = 0; i < arguments.length; i++) {
//     childrenAr9 c O = ` t f X ~ray[i] = arguments[i + 2]
// }
// props.children = childrenA2 m ; B = 3 5 Rrray;
// 2. 数组slice截取,截取第二个之后一切l l _ + m的参数项给C  tprops.children
props.children = Array.prototype.slice.caU 6 A l M = Fll(argumentsO % # G _ j, 2);
}
return ReactX e Z I u Element(type, prop@ [ b 0 N y t &s)
}
export defa? & _ P + * F n Oult {
createElement,
Component
}

官方源码

Github地址

ReactDOM.renda N G 8er

参数

render 支撑传入2个参数。

  • node:React组件
  • mountNode:要挂载的DOM目标
ReactDOM.render(9 . - Z E  _node{ g { ~ 5, mountNode);

rU T 5 Sender 办法

新建一个JS文件,导出一个 rendG N 6 Ler 函数。

& f判断,假如传入的组I V o ? C =件是一个字符串,直接运用 document.createTextNode办法创立一个文本节点,拼q z / ~ 9 P 接在要挂载的DOM元素里面。

新开辟 typeprops? # u t d P q | 个变量,将组件元素内的 typeprops赋值上去

function render(node,% q t : v e m4 T  x O rountNode) {
if (typx N _ Ceof node === 'string') { // 假如是字符串
return mountNode.z W n Wappend(document.createTextNode(node))
}
let typR v a 9 ~ J g R 5e = node.type;B ~ ` n 5 ?
let props = node.props;
}
export default {
render
}

接着,运用 type 创立一个相应的DOM元素节点,遍历 props 中的特点。

特点名为 children,判断 child t v T mren 是不是一个数组。假如不是的话,将其包装为一个数组。假如是的话,遍历子元素,并递归调用 render 函数

假如是style,代表是行B / l内样式。将style内的css目标,逐个复制到domElement.style上面

    let domU ~ z VElement = documex ? p ` { }nt.createElement(type);
foh 4 C I 0 ; B A )r (let propName in props) {
if (propName === 'children') {
let children = pro| R 5 Ips[propName];
children = Array.isAZ _ s A H C W * Grray(children) ? children : [chi, e d y S [ wldren];
children0 [ n l 8 8.forEach(child => rendeH w u . & D 2 0r(child, domElemen1 Y @ ^ ] ; Ct))
} else if (propName === 'style') {
let| 7 ^ : f } Q O ` st^ O 4 }yleObj = props[propName];
for (let attr in styleObj) {
domEld C ) K Iement.style[attr] = styleObj[attJ ; 5 Gr];
}
}
}

最终,调用 mountNode.appendChild 办法,将处理好的元素挂载到dom元素上

mounh [ K - * Z C CtNode.appendChildn v K ~ y 1(domElement);

函数组件的处理

` 2 d : w I 3们能够判断 type 的值是否为function。假如是function,执行函数。将执行后的回来值H l $ o q M ` x上的 props type特点赋值给 propsc f 1 v ? $ Ztype 变量

    let type = node.tK [ Q Z 1 Cype;
let propsM 3 L J / = node.props;
if (typeof type === 'function') {
let element = type(props); // 执行函数
pc 2 -rops = element.props;
type = element.type;
}
let domElement = document.createElement1 % . ! U 4 a $(type);

类组件的处理

咱们新建一个 Componet 类,模仿 React.Component 类的完成

Componet 类中有一个 isReactComponent 的静态特点,代表该类为一个React类组件。

class Component {
// 是K Y u D $ m # 3 !否为React组件
static is^ Z @ (ReactComponent = true;
constructor(pw F % h ~ 9 X 5 ,rops) {
this.propsY q x k d = props
}
}

咱们能够判断 type 上的 isReactComponent 是否为true。假如为true,代表该元素为一个 类i o x组件。

先运用new实例化类组件,然后调用render办法获取到React元素目标。

    let type = node.type;
let props = node.props;
// 是否为类组件
if (type.isReactCF o N 3 ~ 9 S Fomponent) {
//传入props,并实例化,调用render办法
let element = new type(props).render();
props = element.props;
type = element.type;
}

运用示例

React.createElement1 ` I * K 0

let apple = React.createElement('li', { id: 'apple' }, 'a ,  ! . Q 3 3apple');
let banana = React.creatK Q C KeElement('li', { id: 'banana' }, 'banana');
let listl b T & , W v 6 = React.createElement('ul', {id: 'list'}, apple, banana);
ReactDOM.render(list, docL J Qument.getElementById('root')p @ 1 2 ` V);

函数组件

function list(props) {
return (
<= k Z H #;ul>
<li style={{color: props.color}}>banana</li>
<li styleD v h={{color: props.color}}>app | o 3 + X Lle</li&] g x * 4 F c VgtL 8 a;
</ul>
)
}
ReactDOM.render(
React.createElement(List, {
coQ a { ; } ) Flor: 'red'
}),
docuV D % = E J o Vment.getElementById('root')
);

类组件

class List extends Rea| v O k R / V :ct.Component {
render() {
return (
<ul&k l y Tgt;
<Item name={'banana'}/>
<Item name={'Apple'}/>
</ul>
);
}
}
class Item extends React.Com} | Y g U 4 J Q iponent {
ren3 g k Q S  9 hder() {f e o B p h
return (
<li>{this.props.name}n / -</li>
)
}
}
ReactDOM.render(React.createElement(List), document.getElementById('root'));

完好完成S 1 r w o R

functiL ( ? F u A F ton render(node, mountNode) {
if (typeof node5 O = ~ + $ === 'string6 B /') {
return mountNode.append(documY ! } ( /ent.createTextNod! h l ?e& | I / i v t 6 _(node))
}
let type = node.type;
let props = node.pra G Oops;
if (type.isReactCompon8 ] } a H & $ent) {
let element = new type(props).render();
props = elemy l S j z [ent.props;
type = element.type;
} elseo P ! 3 4 @ $ S if (tyw O ~ Qpeof type ===, n + i [ i M X 'function') {
let element = type(props);
props =9 7 2 k % F 0 s element.props;
type = element.type;
}
let domElement = doc;  w R p bument.createElement(type);
for (4 J E y ylet propNameG h 7 J _ - g in props) {
if (propName === 'children') {
let children = props[propNaD * ] ~ U Mme];
children = Array.isArraL l U ~ ~ &y(children) ? children : [children];
children.forEach(child => render(child, domElement))
} else if (propName === 'style') {
let styleObj = propso L w . e 9 ~[propName& # * Z q t 4];
for (let attr in styleObj) {
domElement.style[attr] =h 9 c C N styleObj[attr];
}
}
}
mountNode.appendChild(domElh f B @ d : e *ement);
}
export default {
renB s Y p T V mder` a 5 h O K
}