Reactのjsx構文は、最終的にBabelによってReact.createElement構文に変換されることがわかっています。この構文により、reactノードはvdomツリー構造にコンパイルされます。例を見てみましょう
let ele1 = <h1 id = "title">
<span> hello </ span>
<span> world </ span>
</ h1>
ReactDOM.render(ele1、document.getElementById( 'root'))
この文法は、baeblエスケープ後の次の文法です。
React.createElement( "h1" 、{
id: "title"
}、/ * #__PURE__ * / React.createElement( "span"、null、 "hello")、/ * #__PURE__ * / React.createElement( "span" 、null、「世界」));
React.createElementで書くことができます:
let ele2 = React.createElement( 'h1'、{id: 'title'}、React.createElement( 'span'、null、 'hello')、React.createElement( 'span'、null、 'world' ))
コンソール.log(ele2)
ReactDOM.render(ele2、document.getElementById( 'root'))
このele2がどのように見えるか見てみましょう
type
タグ名またはreact
コンポーネントを示しますprops
イベントとコンポーネントのいくつかのプロパティですchildren
子要素を表しstring
ます。vdom
vdomが何であるかがわかったので、vdomを作成するために独自のReact.createElementメソッドを実装できます。
まず、React.createElementを実装してノードのvdomを構築します
function createElement(type、config、children){
let props = {}
//構成内のすべてのプロパティをprops
に(config にキーを配置){
props [key] = config [key]
}
// 子を取得ノード数
デバッガー
const childrenLength = arguments.length-2
if(childrenLength === 1){ // 息子は1人だけ、props.childrenはオブジェクト、
props.children = children
} else if(childrenLength> 1 ){
/ / 息子の数が1より大きい場合、すべての息子を配列に入れます
props.children = Array.prototype.slice.call(arguments、2 )
}
return {type、props} // タイプ表示反応要素类型文字列番号関数クラス
} デフォルトの
エクスポート {createElement}
上記のReact.createElementの例を比較して、createElementのコードを実現してください。
typeは受信要素のタイプ、文字列番号、関数、Classを表し、上記と比較して 'h1'、configは受信プロパティ、id classNameなどを表し、configのすべてのプロパティをpropsにコピーし、子は子ノードを表します、プロップにコピーする必要もあります。子ノードの数はわかりませんが、パラメータの数を計算することで取得できます。息子が1人だけの場合、props.childrenはオブジェクトです。これは配列の形式で表現され、最後にtypeとpropsが返されて印刷結果が表示されます。
ソースコードの印刷結果とまったく同じであることがわかります
次に、ReactDOM.renderを実装 して、ノードをページにレンダリングします
function render(node,parent ){
//node react 节点, parant 父容器,是一个真实的dom元素
//1.首先要拿到节点的type 和 props
let type, props
type = node.type //h1, function ClassComponent
props = node.props
//2.根据虚拟dom创建真实dom插入到父容器
let domElement = document.createElement(type) //创建真实dom
parent.appendChild(domElement) //插入到父容器
}
export default {
render
}
对照下面的渲染案例我们来分析一下:
ReactDOM.render(ele2, document.getElementById('root'))
render函数需要两个参数,一个是需要要渲染的node节点,一个是父容器,那么我们如何获取到node节点呢?根据我们上面的实现,我们可以得到一个节点的vdom树的结构,就可以获取到它的type和props,type表示的是一个节点的元素类型,h1、div、span、Function、ClassComponent等,所以就可以根据这个type来创建一个真实dom插入到父容器中。我们可以看看现在的渲染结果:
可以看到,现在h1已经插入到了父容器里面,但是儿子节点和其他属性还没有处理,我们接下来处理儿子节点
function render(node,parent ){
//node react 节点, parant 父容器,是一个真实的dom元素
//1.首先要拿到节点的type 和 props
let type, props
type = node.type //h1, function ClassComponent
props = node.props
//2.根据虚拟dom创建真实dom插入到父容器
let domElement = document.createElement(type) //创建真实dom
// 处理属性和儿子节点
for (let propName in props) {
if(propName === 'children') {
let children = props.children //儿子节点可能是个对象也可能时个数组
//如果不是数组就要转成数组
if (!Array.isArray(children)) {
children = [children]
}
//递归创建儿子节点
children.forEach(child => {
render(child, domElement) //将儿子节点插入到自己的容器里面
});
}
}
parent.appendChild(domElement) //插入到父容器
}
export default {
render
}
通过递归的方式来创建儿子节点,看看渲染效果
可以看到儿子节点也被插入到了页面中,但是我们的文字哪里去了?根据我们前面的打印结果可知,文字也是儿子节点,只是string类型,所以我们需要判断一下,如果是string类型的就创建一个文本节点插入到页面中。
function render(node,parent ){
//node react 节点, parant 父容器,是一个真实的dom元素
//如果节点是个字符串,就创建一个文本节点插入到页面上
if(typeof node === 'string'){
return parent.appendChild(document.createTextNode(node))
}
//1.首先要拿到节点的type 和 props
let type, props
type = node.type //h1, function ClassComponent
props = node.props
//2.根据虚拟dom创建真实dom插入到父容器
let domElement = document.createElement(type) //创建真实dom
// 处理属性和儿子节点
for (let propName in props) {
if(propName === 'children') {
let children = props.children //儿子节点可能是个对象也可能时个数组
//如果不是数组就要转成数组
if (!Array.isArray(children)) {
children = [children]
}
//递归创建儿子节点
children.forEach(child => {
render(child, domElement) //将儿子节点插入到自己的容器里面
});
}
}
parent.appendChild(domElement) //插入到父容器
}
export default {
render
}
再看渲染结果
儿子节点全部渲染成功
接下来需要渲染id className style 等属性了
function render(node,parent ){
//node react 节点, parant 父容器,是一个真实的dom元素
//如果节点是个字符串,就创建一个文本节点插入到页面上
if(typeof node === 'string'){
return parent.appendChild(document.createTextNode(node))
}
//1.首先要拿到节点的type 和 props
let type, props
type = node.type //h1, function ClassComponent
props = node.props
//2.根据虚拟dom创建真实dom插入到父容器
let domElement = document.createElement(type) //创建真实dom
// 处理属性和儿子节点
for (let propName in props) {
if(propName === 'children') {
let children = props.children //儿子节点可能是个对象也可能时个数组
//如果不是数组就要转成数组,方便迭代
if (!Array.isArray(children)) {
children = [children]
}
//递归创建儿子节点
children.forEach(child => {
render(child, domElement) //将儿子节点插入到自己的容器里面
});
}else if(propName === 'className') { //处理类名
domElement.className = props.className
}else if (propName === 'style') { //处理style 值就是一个行内样式对象
let styleObj = props.style //{color: 'red',backgroundColor:'yellow'}
for(let attr in styleObj) {
domElement.style[attr] = styleObj[attr]
}
}else { //处理id
domElement.setAttribute(propName,props[propName])
}
}
parent.appendChild(domElement) //插入到父容器
}
export default {
render
}
我们可以打印一下vdom结构方便我们理解上面的代码
可以看看渲染结果
接下来实现组件渲染
我们首先看一下组件的vdom结构
function Welcome(props) {
return (
<h1 id={props.id}>
<span>hello</span>
<span>world</span>
</h1>
);
}
let ele3 = React.createElement(Welcome,{id:'title'})
console.log(ele3)
可以看到,type表示他是一个函数式组件
三、函数组件的渲染
接下来就可以写组件的渲染了
function render(node,parent ){ //node react 节点, parant 父容器,是一个真实的dom元素 //如果节点是个字符串,就创建一个文本节点插入到页面上 if(typeof node === 'string'){ return parent.appendChild(document.createTextNode(node)) } //1.首先要拿到节点的type 和 props let type, props type = node.type //h1, function ClassComponent props = node.props //3.处理组件的渲染 if (typeof type === 'function'){ //函数组件 let element = type(props) type = element.type props = element.props } //2.根据虚拟dom创建真实dom插入到父容器 let domElement = document.createElement(type) //创建真实dom // 处理属性和儿子节点 for (let propName in props) { if(propName === 'children') { let children = props.children //儿子节点可能是个对象也可能时个数组 //如果不是数组就要转成数组 if (!Array.isArray(children)) { children = [children] } //递归创建儿子节点 children.forEach(child => { render(child, domElement) //将儿子节点插入到自己的容器里面 }); }else if(propName === 'className') { //处理类名 domElement.className = props.className }else if (propName === 'style') { //处理style 值就是一个行内样式对象 let styleObj = props.style //{color: 'red',backgroundColor:'yellow'} for(let attr in styleObj) { domElement.style[attr] = styleObj[attr] } }else { //处理id domElement.setAttribute(propName,props[propName]) } } parent.appendChild(domElement) //插入到父容器 } export default { render }
通过typeof判断它的类型,如果是函数就去执行这个函数,并且用一个参数去接收它的返回值,再去拿到它的type和props属性进行渲染
看看它的渲染结果
不太明白__self 和__source是是啥?
接下来开始写类组件的构建和渲染过程
四、类组件的构建和渲染
先来回忆一下类组件的写法
welcome extends React.Component{
constructor(props){
super(props)
}
render() {
return (
<h1 id={this.props.id}>
<span>hello</span>
<span>world</span>
</h1>
);
}
}
let ele3 = React.createElement(Welcome,{id:'title'})
console.log(ele3)
可以看到所有的类组件都是继承于父类的,所以在类组件渲染之前要先创建一个父类,让所有的子类继承这个父类,另外我们知道,class类不能直接运行,必须要通过new关键字才能执行,所以在渲染的时候需要判断这个组件是类组件还是函数组件代码如下:
首先要在react/createElement中定义一个父类
//定义一个父类,让子组件继承这个父类
class Component {
constructor(props) {
props = this.props
}
static isReactComponent = true
}
function createElement(type,config,children){
let props = {}
//把config里面的所有属性都拷贝到props里面
// debugger
for(let key in config) {
props[key] = config[key]
}
//获取子节点的个数
const childrenLength = arguments.length - 2
if(childrenLength === 1){ //只有一个儿子,props.children是一个对象,
props.children = children
}else if(childrenLength > 1) {
//如果儿子数量大于1个的话,就把所有儿子都放到一个数组里
props.children = Array.prototype.slice.call(arguments,2)
}
return {type,props} //type 表示react元素类型 string number Function Class
}
export default {createElement,Component}
看看vdom的构建结果,
接着在渲染方法render中开始渲染
function render(node,parent ){
//node react 节点, parant 父容器,是一个真实的dom元素
//如果节点是个字符串,就创建一个文本节点插入到页面上
if(typeof node === 'string'){
return parent.appendChild(document.createTextNode(node))
}
//1.首先要拿到节点的type 和 props
let type, props
type = node.type //h1, function ClassComponent
props = node.props
//3.处理组件的渲染
if(type.isReactComponent){ //类组件
let element = new type(props).render() //创建实例调用它的render方法
type = element.type
props = element.props
}else if (typeof type === 'function'){ //函数组件
let element = type(props)
type = element.type
props = element.props
}
//2.根据虚拟dom创建真实dom插入到父容器
let domElement = document.createElement(type) //创建真实dom
// 处理属性和儿子节点
for (let propName in props) {
if(propName === 'children') {
let children = props.children //儿子节点可能是个对象也可能时个数组
//如果不是数组就要转成数组
if (!Array.isArray(children)) {
children = [children]
}
//递归创建儿子节点
children.forEach(child => {
render(child, domElement) //将儿子节点插入到自己的容器里面
});
}else if(propName === 'className') { //处理类名
domElement.className = props.className
}else if (propName === 'style') { //处理style 值就是一个行内样式对象
let styleObj = props.style //{color: 'red',backgroundColor:'yellow'}
for(let attr in styleObj) {
domElement.style[attr] = styleObj[attr]
}
}else { //处理id
domElement.setAttribute(propName,props[propName])
}
}
parent.appendChild(domElement) //插入到父容器
}
export default {
render
}
至此,react vdom的构建和渲染过程就全部结束了