前言
之前看过react源码并在csdn上发过相关的文章,最近我打算再看一下react17的源码,因为任何书籍和文章每次读的感悟是不同的,源码亦是如此。并在掘金和github同时做每次学习的笔记。(计划历时半个月左右更新完毕)
github: github.com/China-forre…
- jsx & react元素的只读性
/*
编译 》 执行
显示编译成createElement的形式,执行的时候调用函数,成为虚拟DOM
render方法负责把虚拟DOM变成真实的DOM插入到容器里。
*/
/*
react元素是不可变的,我们每次都是创建新的react元素进行渲染
react只会更新必要的部分
React17之前, React规定React元素是不可变的、但是可以扩展添加属性,但是之后就不可以了Object.seal()
let element = {type: 'h1'}
Object.freeze(element)
而Object.freeze()是通过 Object.defineProperty()设置 writable: false实现的;
//自己手写实现一个Object.freeze()
Object.defineProperty(Object, 'freeze', {
value: function(obj) {
let i;
for(i in obj) {
if(obj.hasOwnproperty(i)) {
Object.defineProperty(obj, i, {
writable: false //不可修改
})
}
}
// 不可扩展
Object.seal(obj);
}
})
*/
- 实现原生组件的渲染
设置环境变量(我们写自己的react,所以我们要禁用一下react17的jsx转换器,这样我们才能看到createElement,原因在下面):
"start": "set DISABLE_NEW_JSX_TRANSTORM=true&&react-scripts start"设置开发模式进行打包(压缩之前)"start": "set NODE_ENV=development&&react-scripts start"我们可以观察到React17中启动了新的jsx转换器, 这样我们不引入import React from 'react'也可以编译jsx
由新的编译器自动引入: import { jsx as _jsx } from 'react/jsx-runtime.js' 新的jsx转换器(为了减少耦合): 一个单独的包require('jsx-transform')('h1'); 再有我们的新版本(17)也不用设置环境变量了,因为内部指定了:
实现创建虚拟DOM的方法React.createElement,实现渲染的方法render。 JSON.stringify()的常见用法: www.cnblogs.com/echolun/p/9… replacer是替换器
JSON.stringify(obj, replacer, 2);
function replacer(key, value) {
if(key === 'age'){
return value + 1;
}else {
return value;
}
}
{
"type": "h1",
"key": null,
"ref": null,
"props": {
"id": "title",
"className": "title",
"style": {
"color": "red"
},
"children": [
{
"type": "span",
"key": null,
"ref": null,
"props": {
"children": "成功了"
},
"_owner": null,
"_store": {}
},
"hello"
]
},
"_owner": null,
"_store": {}
}
createElement的实现
function createElement(type, config, children) {
if (config) {
delete config._source;
delete config._self;
}
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;
实现React.render()方法进行挂载
/*
把vdom虚拟DOM变成真实DOM
把虚拟DOM上的属性更新到dom上;
把虚拟DOM的儿子们也变成真实DOM挂载到自己的dom上 dom.appendChlid
把自己挂载到容器上
*/
function render(vdom, container) {
const dom = createDOM(vdom);
container.appendChild(dom);
}
/* 把虚拟DOM变成真实DOM */
function createDOM(vdom) {
if (typeof vdom === 'string' || typeof vdom === 'number') {
return document.createTextNode(vdom);
}
// 否则就是一个虚拟DOM对象,也就是React元素;
let { type, props } = vdom;
let dom;
if(typeof type === 'function') {
console.log('vdom', vdom);
console.log('type', type);
return mountFunctionComponent(vdom);
}else {
dom = document.createElement(type);
}
// 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
updateProps(dom, props);
// 单独在这里处理children
// 如果只有一个儿子,并且这个儿子是一个虚拟DOM元素
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 {
document.textContent = props.children ? props.children.toString() : '';
}
// 把真实DOM作为一个dom属性放到虚拟DOM,为以后的更新做准备
return dom;
}
// 把一个类型为自定义函数组件的虚拟DOM转换成为真实DOM并返回;
function mountFunctionComponent(vdom) {//类型为自定义函数组件的虚拟DOM
let {type: FunctionComponent, props} = vdom;
let renderVdom = FunctionComponent(props);
return createDOM(renderVdom);
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
let childVdom = childrenVdom[i];
render(childVdom, parentDOM)
}
}
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 {
dom[key] = newProps[key];
}
}
}
const ReactDOM = { render };
export default ReactDOM;
- 实现函数组件的渲染
let { type, porps} = vdom;
let dom;
if(typeof vdom === 'function') {
return mountFunctionComponent(vdom)
}else {
dom = document.createElement(type);
}
// 把一个类型为自定义函数组件的虚拟DOM转换成为真实DOM并返回;
function mountFunctionComponent(vdom) {//类型为自定义函数组件的虚拟DOM
let {type: FunctionComponent, props} = vdom;
let renderVdom = FunctionComponent(props);
return createDOM(renderVdom);
}
- 实现类组件的渲染和更新 github.com/China-forre…
- 状态的批量更新 我们都知道React类组件中的setState。并且都知道setState在合成事件和钩子函数中是异步的,在原生事件中是同步的。那其原因是什么呢,如何实现的呢?
在react中事件的更新可能是异步的、批量的;调用setState之后状态并没有立即更新,而是缓存起来
了,等事件函数处理完成后再进行批量更新,一次更新并重新渲染,避免了重复刷新组件, 这也是性能优
化的一个小点。
handlerClick = () => {
this.setState((lastState) => ({ number: lastState.number + 1 }), () => {
console.log("callback1", this.state.number);
});
console.log("this.state.number", this.state.number);
this.setState((lastState) => ({ number: lastState.number + 1 }, , () => {
console.log("callback1", this.state.number);
}));
console.log('this.state.number', this.state.number);
Promise.resolve().then(() => {
console.log('this.state,number', this.state.number);
this.setState((lastState) => ({ number: lastState.number + 1 } , () => {
console.log("callback1", this.state.number);
}));
console.log('this.state,number', this.state.number);
this.setState((lastState) => ({ number: lastState.number + 1 } , () => {
console.log("callback1", this.state.number);
}));
console.log('this.state,number', this.state.number);
});
}
// this.state.number: 0 0 2 3 4 (输出结果之后进行的批量更新)
//批量收集完毕之后进行更新,因此callback: 2 2 3 4 (回调函数式批量更新之后执行输出的结果)
-
合成事件和批量更新
-
生命周期 不管是属性变化和状态变化组件都是需要更新的。
现在的函数式组件可以不用从顶部引入import React from 'react了
React17以前babel通过React.createElement("div")进行转译;
React17以后:
因为: 新的runtime transformer
require('react/jsx-runtime')("div") 这样就是自己引入模块了,就不需要外部变量了(import React from 'react')