初始化渲染的过程,我们可以直接对应到React程序中ReactDOM.render函数调用后的结果。
1.环境准备
初始化项目:npx create-react-app simple-react
删除一些代码,最关键的内容就是:
src/index.js
public/index.html
package.json中的dependencies和scripts:
2.JSX介绍
JSX是JavaScript的一种语法扩展。
JSX到普通Javascript的代码的转化是通过babel完成的。
3.React.createElement编写
DISABLE_NEW_JSX_TRANSFORM=true 禁用掉新的jsxdev转换,使用React.createElement
这个迷你的包(cross-env)能够提供一个设置环境变量的scripts,让你能够以unix方式设置环境变量,然后在windows上也能兼容运行。
npm i cross-env -D
package.json
"start": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts start",
react17之前通过Babel把jsx转换为虚拟DOM
Hello Simple React
React.createElement("div", null, "Hello Simple React")
react.js
import { REACT_ELEMENT } from "./utils"
function createElement(type, properties, children) {
// debugger
let ref = properties.ref || null
let key = properties.key || null
// 属性可以是可读的、可写的或可删除的。
// 属性可以是可枚举的(可以通过for…in循环枚举)或不可枚举的。
// 可以通过Object.defineProperty()等方法定义属性的特性。
;['key', 'ref', '__self', '__source'].forEach(key => {
delete properties[key]
})
let props = { ...properties }
//传参大于3
if (arguments.length > 3) {
console.log(arguments)
props.children = Array.prototype.slice.call(arguments, 2)
} else {
props.children = children
}
return {
//$$--唯一性,jsx转换成为的类型
$$typeof: REACT_ELEMENT,
type,
ref,//操作DOM
key,//diff比较
props
}
}
const React = {
createElement
}
export default React
4.ReactDOM.render函数编写
初始化渲染第一步:实现函数React.createElement
初始化渲染第二步:实现函数ReactDOM.render
react-dom.js
import { REACT_ELEMENT } from './utils'
//初始化渲染(不止挂载操作)
function render(VNode, containerDOM) {
// 将虚拟DOM转化成真实DOM
// 将得到的真实DOM挂载到contanerDOM中
mount(VNode, containerDOM)
}
function mount(VNode, containerDOM) {
let newDOM = createDOM(VNode)
newDOM && containerDOM.appendChild(newDOM)
}
function createDOM(VNode) {
// 1.根据类型创建元素 2.处理子元素,VNode是树形结构 3.处理属性值
const { type, props } = VNode
let dom;
//typeof === REACT_ELEMENT代表虚拟DOM VNode
if (type && VNode.$$typeof === REACT_ELEMENT) {
dom = document.createElement(type)
}
if (props) {
//子节点存在
if (typeof props.children === 'object' && props.children.type) {
//递归,创建子节点
mount(props.children, dom)
} else if (Array.isArray(props.children)) {
//多个子节点
mountArray(props.children, dom)
} else if (typeof props.children === 'string') {
//文本节点
dom.appendChild(document.createTextNode(props.children))
}
}
// TODO:处理属性值
//setPropsForDOM(dom, props)
return dom
}
//多个子节点处理
function mountArray(children, parent) {
console.log('parent', parent, children)
if (!Array.isArray(children)) return
for (let i = 0; i < children.length; i++) {
if (typeof children[i] === 'string') {
//创建文本节点
parent.appendChild(document.createTextNode(children[i]))
} else {
mount(children[i], parent)
}
}
}
const ReactDOM = {
render
}
export default ReactDOM
5.实现函数setPropsForDOM进行属性更新
// 【这里需要注意jsx属性名称的写法】
ReactDOM.render(Simple React Appxx1xx2, document.getElementById('root'))
function setPropsForDOM(dom, VNodeProps = {}) {
if (!dom) return
for (let key in VNodeProps) {
if (key === 'children') continue
//^开头,on开头,A-Z之间一个,.匹配单个字符,
//*号匹配一个字母或者数字出现一次或者多次 onclick
if (/^on[A-Z].*/.test(key)) {
// TODO: 事件处理
} else if (key === 'style') {
Object.keys(VNodeProps[key]).forEach(styleName => {
dom.style[styleName] = VNodeProps[key][styleName]
})
} else {
dom[key] = VNodeProps[key]
}
}
}
最后总结:从JSX源代码(
Hello JSX
)到显示到界面上,需要经历哪些关键环节。JSX -> 转译成函数调用 -> 执行函数调用返回虚拟DOM -> 将虚拟DOM转化成DOM -> 将生成的DOM挂载到DOM上