React元素只读性

Object.freeze方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。Object.freeze() | MDN

1
2
3
4
let element = { name: '邓立' }
Object.freeze(element)
element.sex = '男'
console.log(element);
  • Object.freeze()原理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Object.defineProperties(Object, 'freeze', {//数据劫持
    value: function (obj) {
    var i;
    for (i in object) {//遍历属性和方法
    if (Object.hasOwnProperty(i)) {
    Object.defineProperty(obj, i, {//数据劫持
    writable: false//把所有属性改为不可修改 只读
    })
    }
    }
    Object.seal(obj)//让此对象不能添加额外的属性
    }
    })

    使用相关函数解释

  • Object.defineProperties 方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。Object.defineProperties | MDN
  • hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。hasOwnProperty() | MDN
  • Object.seal() 方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要原来是可写的就可以改变。Object.seal() | MDN

    JSX转换

    React 17 之前会使用 React.createElement() 进行转换,React17之则引用了新的jsx处理转换方法

  • 源代码
    1
    2
    3
    4
    import React from 'react';
    function App() {
    return <p>Hello World</p>;
    }
  • React16 转换
    1
    2
    3
    4
    5
    import React from 'react';

    function App() {
    return React.createElement('p', null, 'Hello world');
    }
  • React17 转换
    1
    2
    3
    4
    5
    import {jsx as _jsx} from 'react/jsx-runtime';

    function App() {
    return _jsx('p', { children: 'Hello world' });
    }
  • 想使用之前的方式转换需要在package.json里设置
    1
    2
    3
    4
    5
    6
    "scripts": {
    "start": "set DISABLE_NEW_JSX_TRANSFORM=true&&react-scripts start",
    "build": "set DISABLE_NEW_JSX_TRANSFORM=true&&react-scripts build",
    "test": "set DISABLE_NEW_JSX_TRANSFORM=true&&react-scripts test",
    "eject": "set DISABLE_NEW_JSX_TRANSFORM=true&&react-scripts eject"
    },

    React源码解析

    页面结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import React from './react'
    import ReactDOM from 'react-dom'
    /**
    * React 自定义组件
    * 1.自定义的组件首字母大写;
    * 2.组件使用前先定义
    * 3.组件需要返回并且只能返回一个根元素
    * @param {*} props
    */
    function ReactTest(props) {
    return (<div className='title' style={{ background: 'pink', color: 'purple' }}>
    <span>{props.name}</span>
    </div>)
    }
    ReactDOM.render(
    <ReactTest name='前端了了liaoliao' />,
    document.getElementById('root')
    );

    效果展示

    React之createElement源码分析

    React.createElement('p', null, 'Hello world')通过传递的数据,转成树形数据(即虚拟DOM)的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    *
    * @param {*} type 元素类型
    * @param {*} config 元素配置
    * @param {*} children 子类信息
    */
    function createElement(type, config, children) {
    let props = { ...config }
    if (arguments.length > 3) {
    //截取有效数据
    children = Array.prototype.slice.call(arguments, 2)
    }
    props.children = children
    return {
    type,
    props
    }
    }
    const React = { createElement }
    export default React

    ReactDOM之render源码分析

    想要了解render源码,必须清楚render的目的是把虚拟dom转换为真实DOM的过程。

    原生组件渲染处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    /**
    * 1.把vdom(虚拟DOM)变成真实DOM dom
    * 2.把虚拟DOM上的属性更新(同步)到dom上
    * 3.把此虚拟DOM的儿子们也都变成真实DOM挂载到自己的dom上dom.appendChlid
    * 4.把自己挂载到容器上
    * @param {*} vdom 要渲染的虚拟DOM
    * @param {*} container 要把虚拟DOM转换真实DOM并插入到容器中去
    */
    function render(vdom, container) {
    const dom = createDOM(vdom)
    container.appendChild(dom)
    }
    /**
    * 把虚拟DOM变成真实DOM
    * @param {*} vdom
    */
    function createDOM(vdom) {
    //如果是数字或者字符串,就直接返回文本节点
    if (typeof vdom === 'number' || typeof vdom === 'string') {
    return document.createTextNode(vdom)
    }
    //否则就是一个虚拟DOM对象,即React元素
    let { type, props } = vdom
    let dom = null
    if (typeof type === 'function') {//自定义函数组件
    return momentFunctionComponent(vdom)
    } else {
    if (type) {//原生
    dom = document.createElement(type)
    }
    }
    //使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
    updateProps(dom, props)
    //在这里处理props.children属性
    if (typeof props.children === 'string' || typeof props.children === 'number') {
    //如果只有一个子类,并且是数字或者字符串
    dom.textContent = props.children
    } else if (typeof props.children === 'object' && props.children.type) {
    //如果只有一个子类,并且是虚拟dom元素
    render(props.children, dom)
    //如果是数组
    } else if (Array.isArray(props.children)) {
    reconcileChildren(props.children, dom)
    } else {
    console.log('baocuo');
    dom.textContent = props.children ? props.children.toString() : ''
    }
    return dom
    }
    /**
    * 把一个类型为自定义函数组件的虚拟DOM转换为真实DOM并返回
    * @param {*} vdom 类型为自定义函数组件的虚拟DOM
    */
    function momentFunctionComponent(vdom) {
    let { type: FunctionComponent, props } = vdom
    let renderVdom = FunctionComponent(props)
    return createDOM(renderVdom)
    }
    /**
    * 遍历数组
    * @param {*} childrenVdom 子类们的虚拟dom
    * @param {*} parentDOM 父类的真实DOM
    */
    function reconcileChildren(childrenVdom, parentDOM) {
    for (let i = 0; i < childrenVdom.length; i++) {
    let childVdom = childrenVdom[i]
    render(childVdom, parentDOM)
    }
    }
    /**
    * 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
    * @param {*} dom 真实DOM
    * @param {*} newProps 新属性对象
    */
    function updateProps(dom, newProps) {
    for (let key in newProps) {
    if (key === 'children') continue;
    if (key === 'style') {
    let styleObj = newProps.style
    for (let attr in styleObj) {
    dom.style[attr] = styleObj[attr]
    }
    } else {//js支持dom.title='设置'
    dom[key] = newProps[key]
    }
    }
    }
    const ReactDOM = { render }
    export default ReactDOM

    React 类组件渲染

    首先我们要创建一个React class需要继承的类声明

1
2
3
4
5
6
7
8
9
10
import { createDOM } from './react-dom'
class Component {
static isReactComponent = true
constructor(props) {
this.props = props
this.state = {}
}

}
export default Component

添加处理此类的方法

在判断是函数后,根据创建类时的添加的 isReactComponent 参数判断

1
2
3
4
5
6
7
if (typeof type === 'function') {//自定义函数组件
if (type.isReactComponent) {//类组件
return mountClassComponent(vdom)
} else {//函数组件
return momentFunctionComponent(vdom)
}
}

处理类的方法 mountClassComponent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 创建类组件实例
* 调用类组件实例的render方法获得返回的虚拟DOM(react元素)
* 把返回的虚拟DOM转成真实DOM进行挂载
* @param {Component} vdom 类型为类组件的虚拟DOM
*/
function mountClassComponent(vdom) {
//解构类的定义及类的属性
let { type, props } = vdom
//创建类的实例
let classInstance = new type(props)
//调用实例的render方法返回要渲染的虚拟DOM对象
let renderVdom = classInstance.render()
//根据虚拟DOM对象创建真实DOM对象
let dom = createDOM(renderVdom)
//为以后类组件的更新,把真实DOM挂载到了类的实例上
classInstance.dom = dom
return dom
}

页面测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ReactComponent extends React.Component {
render() {
return (
<div>
<p>测试React类是否展示</p>
<p>{this.props.name}</p>
</div>

)
}
}
ReactDOM.render(
<ReactComponent name='前端了了liaoliao' />,
document.getElementById('root')
);

页面展示

下期预告:合成时间及状态更新的源码解析

项目地址github