简介:react源码的学习

1.react相关配置

安装parcel

1
npm i -g parcel-bundler --save-dev

打包

1
parcel build index.html

运行

1
parcel index.html

安装babel插件

目的

将jsx语法转换成js对象【虚拟DOM】

安装

babel插件可以将jsx语法转换为js

1
npm i babel-core babel-preset-env babel-plugin-transform-react-jsx --save-dev

创建.babelrc文件夹并配置

1
2
3
4
5
6
7
8
{
"presets": ["env"],
"plugins": [
["transform-react-jsx", {
"prama": "React.createElement"
}]
]
}

在package.json中配置npm的启动文件

1
2
3
"scripts": {
"start": "parcel index.html"
}

2.jsx和虚拟dom

配置是否成立

在这里必须用低版本的parcel-bunder,否则会出现错误

1
2
3
4
5
6
"devDependencies": {
"babel-core": "^6.26.3",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-preset-env": "^1.7.0",
"parcel-bundler": "^1.12.3"
}

index.html也必须使用module字段:

1
<script type="module" src="index.js"></script>

偶尔parcel没热加载,所以我们每次更新代码,都必须打包,再运行:

1
parcel build index.html

jsx部分

react.js

在这里,createElement很重要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// react.js

let React = {
createElement
}

function createElement (tag, attrs, ...childrens) {
return {
tag,
attrs,
childrens
}
}

export default React

react-dom.js

  • 在这里,我们需要了解元素是什么样的结构:
1
2
3
4
5
6
7
8
{
tag: 'div',
attrs: {
className: 'attrs',
onClick: 'kyo'
},
childrens: ['456']
}
  • 我们了解了这是一个对象,因此我们自己实现render函数的时候,我们可以采用递归构建一个元素对象,再进行一次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
function render (vnode, container) {
if (vnode === undefined) return
if (typeof vnode === 'number') {
vnode = String(vnode)
}
if (typeof vnode === 'string') {
const textNode = document.createTextNode(vnode)
// console.log(textNode)
return container.appendChild(textNode)
}

const { tag, attrs, childrens } = vnode
const dom = document.createElement(tag)

if (attrs) {
Object.keys(attrs).forEach(key => {
const value = attrs[key]
setAttribute(dom, key, value)
})
}

if (childrens?.length > 0) {
for (let item of childrens) {
render(item, dom)
}
}
return container.appendChild(dom)
}
  • 在react中,我们可以自设置属性,然后再根据这些自己设置的属性自己转化为html的属性,再设置自己的方法,比如:
    • className:class
    • onClick:onclick…
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
function setAttribute (dom, key, value) {
// 将属性名的className转换成class
if (key === 'className') {
key = 'class'
}

// 转换事件
if (/on\w+/.test(key)) {
key = key.toLowerCase()
dom[key] = value || ''
} else if (key === 'style') {
if (!value || typeof value === 'string') {
dom.style.cssText = value || ''
} else if (value && typeof value === 'object') {
for(let k in value) {
if (typeof value[k] === 'number') {
dom.style[k] = value[k] + 'px'
} else {
dom.style[k] = value[k]
}
}
}
} else {
if (key in dom) {
dom[key] = value || ''
}

if (value) {
dom.setAttribute(key, value)
} else {
dom.removeAttribute(key)
}
}
}

3.函数组件和类组件

普通组件和函数组件

  • 区别:tag有区别,tag是字符串为普通组件,是函数为函数组件或者类组件
1
2
3
4
5
6
7
8
9
10
11
12
13
// <App />
{
tag: App(),
attrs: {},
childrens: []
}

// <div />
{
tag: 'div',
attrs: {},
childrens: []
}
  • 我们都统一用类组件处理,函数组件也会被转为类组件

类组件和函数组件的创建

我们发现,类组件的tag标签和函数组件的tag标签的数据类型都是function,那么我们只能通过原型链有没有render方法来判断是函数组件还是类组件

以下为创建自定义组件的方法,其中comp为vnode中的tag,tag可以保留自定义组件的所有信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function createComponent (comp, props) {
let inst
// 原型链非空,还有render方法,那么一定是类组件
if (comp.prototype && comp.prototype.render) {
inst = new comp(props)
} else {
// 如果不是类组件,我们就也是用类组件创建
inst = new Component(props)
// 将构造函数赋值,函数组件原来的tag就是函数本身,因此用于构造函数十分切合
inst.constructor = comp
// 每一个类组件都有一个render方法,对该render进行赋值给其他函数,并且是返回一个构造函数内部的jsx
inst.render = function () {
return this.constructor(props)
}
}
return inst
}

设置组件的属性

组件的属性,就是props的设置

我们自行实现源码时,props是从attrs拿到的,且必须在render下才会将attrs传给props

1
2
3
4
5
6
function setComponentProps (comp, props) {
// 设置组件的属性
comp.props = props
// 添加自定义组件内部元素到comp中
renderComponent(comp)
}

拿到自定义组件内部的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function renderComponent (comp) {
// 定义组件内部的节点对象
let base
// 拿到了元素
const renderer = comp.render()

console.log(renderer)

// renderer是获取了类组件内部的元素,但还是需要一层_render()函数解析,不然还是无法解析
base = _render(renderer)
console.log('================')
console.log(base)
comp.base = base
}

组件的加载

  • 首先render函数需要重构,因为我们在render内部并没有考虑到函数组件和类组件的情况
  • 下面是在react-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
function _render (vnode) {
if (vnode === undefined || vnode === null || typeof vnode === 'boolean') vnode = ''
if (typeof vnode === 'number') {
vnode = String(vnode)
}
if (typeof vnode === 'string') {
return document.createTextNode(vnode)
}

// 判断是否为函数组件【根据tag,也就是当前组件来分析】
if (typeof vnode.tag === 'function') {
console.log("函数组件")
// 1.创建组件
const comp = createComponent(vnode.tag, vnode.attrs)
// 2.设置组件的属性
setComponentProps(comp, vnode.attrs)
// 3.组件渲染的节点对象返回
return comp.base
}

const { tag, attrs, childrens } = vnode
const dom = document.createElement(tag)

if (attrs) {
Object.keys(attrs).forEach(key => {
const value = attrs[key]
setAttribute(dom, key, value)
})
}

if (childrens?.length > 0) {
for (let item of childrens) {
render(item, dom)
}
}
return dom
}

function render (vnode, container) {
return container.appendChild(_render(vnode))
}

4.生命周期

render相关的生命周期

生命周期我们需要写在组件的创建和渲染过程中:

  • 对于一次加载的生命周期,我们只需要判断是否之前有comp.base就好,如果没,就可以加载这一类的生命周期
  • 有些生命周期需要更新数据才会使用,我们需要判定comp.base是存在的,再使用parentNode.replaceChild方法
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
export function renderComponent (comp) {
// 定义组件内部的节点对象
let base

// 拿到了元素
const renderer = comp.render()
// console.log(renderer)

// renderer是获取了类组件内部的元素,但还是需要一层_render()函数解析,不然还是无法解析
base = _render(renderer)

if (comp.base) {
if (comp.componentWillUpdate) {
comp.componentWillUpdate()
}
if (comp.componentDidUpdate) {
comp.componentDidUpdate()
}
} else if (comp.componentDidMount) {
comp.componentDidMount()
}

if (comp.base && comp.base.parentNode) {
// replaceChild是只能用于子组件,因此我们必须使用parentNode
// 将base赋值给comp.base
comp.base.parentNode.replaceChild(base, comp.base)
}
comp.base = base
}

加载前的生命周期

  • 在挂载之前的生命周期和props相关,我们就该在setComponent这个时候进行加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function setComponentProps (comp, props) {
if (!comp.base) {
if (comp.componentWillMount) {
comp.componentWillMount()
}
if (comp.componentWillReceiveProps) {
comp.componentWillReceiveProps(props)
}
}
// 设置组件的属性
comp.props = props
// 渲染组件
renderComponent(comp)
}

检验生命周期

  • 我们得在组件设置一个简单的setState
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { renderComponent } from '../react-dom/index'

class Component {
constructor (props = {}) {
this.props = props;
this.state = {}
}

setState (stateChange) {
// 浅拷贝,将stateChange浅拷贝给this.state
Object.assign(this.state, stateChange)
// 数据改变实时渲染,再次render一次组件
renderComponent(this)
}
}

export default Component
  • 示例index
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
import React from './react/index'
import ReactDOM from './react-dom/index'

const element = (
<div className="attrs">
123
<span>456</span>
</div>
)

function Home () {
return (
<div className="hello">
<span>123</span>
</div>
)
}

class Home_ extends React.Component{
constructor (props) {
super (props)
this.state = {
num: 0
}
}

// 生命周期函数,我个人觉得我们需要在Component内部进行构建

componentWillMount () {
console.log("组件开始加载")
}

componentWillReceiveProps (props) {
console.log('props')
}

componentWillUpdate () {
console.log("组件将要更新")
}

componentDidUpdate () {
console.log("组件已经更新")
}

componentDidMount () {
console.log("组件加载完成")
}

click = () => {
this.setState({
num: this.state.num + 1
})
}

render () {
return (
<div className="hello">
<span>123456--{this.state.num}</span>
<button onClick={this.click.bind(this)}>+</button>
</div>
)
}
}

ReactDOM.render(<Home_ name={'act'}/>, document.getElementById('root'))

// console.log(ele)

5.虚拟diff算法

  • 在前面有了_render函数,可以将虚拟DOM转换为真实DOM
  • 我们需要在_render中使用diff算法

diff算法的快速查找不同

  • 我们对每一个元素进行key标识,如果出现了增加或者改变,key值都会不同,这样子就可以无需大量的比较过程了
  • 下面是react.js的一些改进,增加了key索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Component from './component'

function createElement (tag, attrs, ...childrens) {
attrs = attrs || {}
return {
tag,
attrs,
childrens,
key: attrs.key || null
}
}

export default {
createElement,
Component
}

diff算法代码

  • diff.js
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import { setAttribute, setComponentProps, createComponent } from "./index"

export function diff (dom, vnode, container) {
console.log("diff被调用")
// 对比节点的变化
let ret = diffNode(dom, vnode)
if (container) {
container.appendChild(ret)
}

return ret
}

export function diffNode (dom, vnode) {
console.log("diffNode被调用")
console.log("dom", dom)
let out = dom
if (vnode === undefined || vnode === null || typeof vnode === 'boolean') vnode = ''
if (typeof vnode === 'number') {
vnode = String(vnode)
}
if (typeof vnode === 'string') {
if (dom && dom.nodeType === 3) {
if (dom.textContent !== vnode) {
dom.textContent = vnode
}
} else {
out = document.createTextNode(vnode)
if (dom && dom.parentNode) {
dom.parentNode.replaceNode(out, dom)
}
}
return out
}
if (typeof vnode.tag === 'function') {
return diffComponent(dom, vnode)
}

// 非文本dom节点
if (!dom) {
out = document.createElement(vnode.tag)
console.log('非文本dom节点:out', out)
}
// 比较子节点
if (vnode.childrens && vnode.childrens.length > 0 || (out.childNodes && out.childNodes.length > 0)) {
console.log("比较子节点")
diffChildren(out, vnode.childrens)
}

diffAttribute(out, vnode)
return out
}

function diffComponent (dom, vnode) {
console.log("diffComponent被调用")
let comp = dom
// 如果组件没有变化,只需要重新设置props即可
if (comp && comp.constructor === vnode.tag) {
// 重新设置props
setComponentProps(comp, vnode.attrs)
// 赋值
dom = comp.base
} else {
// 组件类型发生变化
if (comp) {
// 移除旧的组件
unmountComponent(comp)
// 释放
comp = null
}

// 创建新组件
comp = createComponent(vnode.tag, vnode.attrs)
console.log("diffComponent1", comp)
// 设置组件属性
setComponentProps(comp, vnode.attrs)
// 给当前挂载base
dom = comp.base
}
return dom
}

function unmountComponent (comp) {
removeNode(comp.base)
}

function removeNode (dom) {
if (dom && dom.parentNode) {
dom.parentNode.removeNode(dom)
}
}

function diffChildren (dom, vchildren) {
const domChildren = dom.childNodes
const children = []
const keyed = {}

// 将有key的节点和没有key的节点分开
if (domChildren.length > 0) {
[...domChildren].forEach(item => {
const key = item.key
if (key) {
keyed[key] = item
} else {
children.push(item)
}
})
}
if (vchildren && vchildren.length > 0) {
let min = 0
let childrenLen = children.length;
[...vchildren].forEach((vchild, i) => {
const key = vchild.key
let child
if (key) {
// 如果有key,找到对应key的节点
if (keyed[key]) {
child = keyed[key]
keyed[key] = undefined
}
} else if (childrenLen > min) {
for (let j = min; j < childrenLen; j++) {
let c = children[j]
if (c) {
child = c
children[j] = undefined
if (j === childrenLen - 1) {
childrenLen--
}
if (j === min) {
min++
}
break
}
}
}
// 对比
child = diffNode(child, vchild)
// 更新dom
const f = domChildren[i]

if (child && child !== dom && child !== f) {
if (!f) {
dom.appendChild(child)
} else if (child === f.nextSibling) {
removeNode(f)
} else {
dom.insertBefore(child, f)
}
}
})
}
}

function diffAttribute (dom, vnode) {
// 保存之前的dom所有的属性
const oldAttrs = {}
const newAttrs = vnode.attrs
// dom是原有的节点对象,vnode是虚拟dom
// 取出dom的属性
const domAttrs = dom.attributes
// console.log(domAttrs);
;[...domAttrs].forEach(item => {
oldAttrs[item.name] = item.value
})
// console.log(oldAttrs)

// 比较
// 如果原来的属性跟新属性对比不在新属性中,则将其移除【属性值undefined就行】
for (let key in oldAttrs) {
if (!(key in newAttrs)) {
setAttribute(dom, key, undefined)
}
}
// 如果旧的属性和新属性不同,就改变旧的
for (let key in newAttrs) {
if (oldAttrs[key] !== newAttrs[key]) {
setAttribute(dom, key, newAttrs[key])
}
}
}
  • react-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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import Component from '../react/component'

import { diff, diffNode } from './diff'

const ReactDOM = {
render
}

// 创建自定义组件
// vnode.tag, vnode.attrs
export function createComponent (comp, props) {
let inst
// 原型链非空,还有render方法,那么一定是类组件
if (comp.prototype && comp.prototype.render) {
// console.log("props", props)
inst = new comp(props)
// console.log("inst" ,inst)
} else {
// 如果不是类组件,我们就也是用类组件创建
inst = new Component(props)
// 将构造函数赋值,函数组件原来的tag就是函数本身,因此用于构造函数十分切合
inst.constructor = comp
// 每一个类组件都有一个render方法,对该render进行赋值给其他函数,并且是返回一个构造函数内部的jsx
inst.render = function () {
return this.constructor(props)
}
}
return inst
}

// 这是为了给comp内部添加base对象而设置的函数
// 组件更新可以设置生命周期
export function renderComponent (comp) {
// 定义组件内部的节点对象
let base

// 拿到了元素
const renderer = comp.render()
// console.log(renderer)

if (comp.base && comp.componentWillUpdate) {
comp.componentWillUpdate()
}

// renderer是获取了类组件内部的元素,但还是需要一层_render()函数解析,不然还是无法解析
// base = _render(renderer)
console.log("comp-", comp)
console.log("renderer", renderer)
base = diffNode(comp.base, renderer)
comp.base = base

console.log('base', base)

if (comp.base) {
if (comp.componentDidUpdate) {
comp.componentDidUpdate()
}
} else if (comp.componentDidMount) {
comp.componentDidMount()
}

// if (comp.base && comp.base.parentNode) {
// // replaceChild是只能用于子组件,因此我们必须使用parentNode
// // 将base赋值给comp.base
// comp.base.parentNode.replaceChild(base, comp.base)
// }
}

export function setComponentProps (comp, props) {
if (!comp.base) {
if (comp.componentWillMount) {
comp.componentWillMount()
}
if (comp.componentWillReceiveProps) {
comp.componentWillReceiveProps(props)
}
}
// 设置组件的属性
comp.props = props
// 渲染组件
renderComponent(comp)
}

function _render (vnode) {
console.log("_render被调用")
if (vnode === undefined || vnode === null || typeof vnode === 'boolean') vnode = ''
if (typeof vnode === 'number') {
vnode = String(vnode)
}
if (typeof vnode === 'string') {
return document.createTextNode(vnode)
}

// 判断是否为函数组件或类组件【根据tag,也就是当前组件来分析】
// 必须在render下才能将attrs变成props
if (typeof vnode.tag === 'function') {
// 1.创建组件
const comp = createComponent(vnode.tag, vnode.attrs)
// 2.设置组件的属性,在这里,所有的属性都在这里变成了props
setComponentProps(comp, vnode.attrs)
// 3.组件渲染的节点对象返回
return comp.base
}

const { tag, attrs, childrens } = vnode
const dom = document.createElement(tag)

if (attrs) {
Object.keys(attrs).forEach(key => {
const value = attrs[key]
setAttribute(dom, key, value)
})
}

if (childrens?.length > 0) {
for (let item of childrens) {
_render(item, dom)
}
}
return dom
}

function render (vnode, container, dom) {
return diff(dom, vnode, container)
console.log("render函数调用")
// return container.appendChild(_render(vnode))
}

// 设置属性【value为key对应的键值】
export function setAttribute (dom, key, value) {
// 将属性名的className转换成class
if (key === 'className') {
key = 'class'
}

// 转换事件
if (/on\w+/.test(key)) {
key = key.toLowerCase()
dom[key] = value || ''
} else if (key === 'style') {
if (!value || typeof value === 'string') {
dom.style.cssText = value || ''
} else if (value && typeof value === 'object') {
for(let k in value) {
if (typeof value[k] === 'number') {
dom.style[k] = value[k] + 'px'
} else {
dom.style[k] = value[k]
}
}
}
} else {
if (key in dom) {
dom[key] = value || ''
}

if (value) {
dom.setAttribute(key, value)
} else {
dom.removeAttribute(key)
}
}
}

export default ReactDOM
  • 其中,我们知道,有点和组件的创建类似,唯一不同的地方就是comp处会进行赋值与返回

6.setState的改进

真正的setState

在react中,我们有一个例子:

1
2
3
4
5
6
7
8
9
componentDidMount () {
for (let i = 0; i < 100; i++) {
this.setState({
num: this.state.num + 1
})

console.log(this.state.num)
}
}

答案为:(100) 0

1
2
3
4
5
6
7
8
9
10
11
componentDidMount () {
for (let i = 0; i < 100; i++) {
this.setState((prevState, prevProps) => {
console.log(prevState.num)
return {
num: this.state.num + 1
}
})
console.log(this.state.num)
}
}

答案为:(100) 0 , 0 1 ... 99

  • 为什么会有这样的差距?
    • setState通过队列机制实现state的更新,用状态队列实现了setState的异步更新,避免频繁的重复更新state

setState的实现

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
import { renderComponent } from '../react-dom/index'
// 异步更新state,短时间将多个setState合并成一个
// 一段时间之后,清空队列,渲染组件
// 我们可能需要ES6的Promise
const setStateQueue = []
const renderQueue = []

function defer (fn) {
return Promise.resolve().then(fn)
}

export function enqueueSetState (stateChange, component) {
if (setStateQueue.length === 0) {
defer(flush)
}
// 短时间内合并多个setState
setStateQueue.push({
stateChange,
component
})

// 如果renderQueue里面没有组件,添加到队列中
let r = renderQueue.some(item => {
return item === component
})

if (!r) {
// 证明是第一次添加
renderQueue.push(component)
}
}

// 更新状态函数
function flush () {
let item,
component
while (item = setStateQueue.shift()) {
const {
stateChange,
component
} = item
// 保存之前的状态
if (!component.prevState) {
component.prevState = Object.assign({}, component.state)
}

if (typeof stateChange === 'function') {
// 是一个函数
Object.assign(component.state, stateChange(component.prevState, component.props))
} else {
Object.assign(component.state, stateChange)
}
// 赋值状态
component.prevState = component.state
}

while (component = renderQueue.shift()) {
renderComponent(component)
}
}
  • component.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { renderComponent } from '../react-dom/index'
import { enqueueSetState } from './setStateQueue'

class Component {
constructor (props = {}) {
this.props = props;
this.state = {}
}

setState (stateChange) {
// 浅拷贝,将stateChange浅拷贝给this.state
Object.assign(this.state, stateChange)
// 数据改变实时渲染【不科学】
// renderComponent(this)
enqueueSetState(stateChange, this)
}
}

export default Component