全新的JSX转换
https://react.docschina.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
全新的好处
- 使用全新的转换,你可以单独使用 JSX 而无需引入 React。
- 根据你的配置,JSX 的编译输出可能会略微改善 bundle 的大小。
- 它将减少你需要学习 React 概念的数量,以备未来之需。
当你使用 JSX 时,编译器会将其转换为浏览器可以理解的 React 函数调用。旧的 JSX 转换会把 JSX 转换为 React.createElement(...)
调用。
源代码
import React from 'react';
function App() {
return <h1>Hello World</h1>;
}
旧的 JSX 转换会将上述代码变成普通的 JavaScript 代码:
import React from 'react';
function App() {
return React.createElement('h1', null, 'Hello world');
}
新的 JSX 转换不会将 JSX 转换为 **React.createElement**
,而是自动从 React 的 package 中引入新的入口函数并调用。
// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';
function App() {
return _jsx('h1', { children: 'Hello world' });
}
创建项目
npx create-react-app my-app # 如果安装失败,把yarn卸载掉
cd 01.basic
npm run start
先修改命令,禁用新的jsx转换 cross-env DISABLE_NEW_JSX_TRANSFORM=true
"scripts": {
"start": "react-scripts start",
"start2": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts start",
"build": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts build",
"test": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts test",
"eject": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts eject"
},
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
let element = (
<div className="title" style={{color: 'red', backgroundColor: 'green'}}>
<span>hello</span>
world
</div>
)
// React.createElement();
console.log(JSON.stringify(element, null, 2));
ReactDOM.render(element, document.getElementById('root'));
虚拟DOM元素结构打印出来,如下
{
"type": "div",
"key": null,
"ref": null,
"props": {
"className": "title",
"style": {
"color": "red",
"backgroundColor": "green"
},
"children": [
{
"type": "span",
"key": null,
"ref": null,
"props": {
"children": "hellp"
},
"_owner": null,
"_store": {}
},
"world"
]
},
"_owner": null,
"_store": {}
}
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>01.basic</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
实现元素的渲染
- 父组件先开始挂载,后挂载完成
- 子组件后开始挂载,先挂载完成
src/index.js
import React from './react';
import ReactDOM from './react-dom';
let element = (
<div className="title" style={{color: 'red', backgroundColor: 'green'}}>
<span>hello</span>
world
</div>
)
console.log(element);
console.log(JSON.stringify(element, null, 2));
// React.createElement 不需要自己手动引入去处理上面的jsx语法,
// @babel/preset-env会自动调用这个方法去处理。
ReactDOM.render(element, document.getElementById('root'));
src/react.js
/**
*
* @param {*} type 元素的类型
* @param {*} config 配置对象
* @param {*} children 元素的儿子或儿子们
*/
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;
src/react-dom.js
/**
* 1.把vdom虚拟DOM变成真实DOM dom
* 2.把虚拟DOM上的属性更新或者同步到dom上
* 3.把此虚拟DOM的儿子们也都变成真实DOM挂载到自己的dom上 dom.appendChild
* 4.把自己挂载到容器上
* @param {*} vdom 要渲染的虚拟DOM
* @param {*} container 要把虚拟DOM转换真实DOM并插入到哪个容器中去
*/
function render (vdom, container){
const dom = createDOM(vdom);
container.appendChild(dom);
}
/**
* 把虚拟DOM变成真实DOM
* @param {*} vdom 虚拟DOM
*/
function createDOM(vdom){
// TODO 处理vdom是数字或者字符串的情况,直接返回一个真实的文本节点
if (typeof vdom === 'string' || typeof vdom === 'number'){
return document.createTextNode(vdom);
}
// 否则 它就是一个虚拟DOM对象了,也就是React元素
let { type, props } = vdom;
let dom = document.createElement(type);
// 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
updateProps(dom, props);
// 处理儿子情况
let children = props.children;
if (typeof children === 'string' || typeof children === 'number'){
dom.textContent = children; // 如果只有一个儿子,且是文本,就直接把文本填进dom里即可
} else if (typeof children === 'object' && children.type){
// 如果只有一个儿子,并且这个儿子是一个虚拟DOM元素,就把儿子变成真实DOM插到自己身上
render(children, dom);
} else if (Array.isArray(children)){
reconcileChilren(children, dom); // 如果儿子是一个数组
} else {
dom.textContent = children ? children.toString() : '';
}
// 把真实DOM作为一个dom属性放在虚拟DOM上,为以后更新做准备
// Cannot add property dom, object is not extensible
// vdom.dom = dom;
return dom;
}
/**
* 处理儿子数组情况
* @param {*} childrenVdom 儿子们的虚拟DOM
* @param {*} parentDOM 父亲的真实DOM
*/
function reconcileChilren(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 {
dom[key] = newProps[key];
}
}
}
const ReactDOM = {
render,
}
export default ReactDOM;
实现函数组件的渲染
src/index.js
import React from './react';
import ReactDOM from './react-dom';
function App(props){
return (
<div className="title" style={{color: 'red', backgroundColor: 'green'}}>
<span>{props.name}</span>
{props.children}
</div>
)
}
let element = (
<App name="jack">
<span>world</span>
</App>
)
console.log(element);
//React.createElement(App, {name: 'jack'}, <span>world</span>);
ReactDOM.render(element, document.getElementById('root'));
src/react-dom.js
添加 vdom 的 type 是函数组件时的条件处理
function createDOM(vdom){
...
let dom;
if (typeof type === 'function'){ //如果是自定义的函数组件
return mountFunctionComponent(vdom);
} else { //如果原生组件
dom = document.createElement(type);
}
...
}
/** 把一个类型为自定义的函数组件的虚拟DOM转换成真实DOM并返回
*
* @param {*} vdom 类型为“自定义函数组件”的虚拟DOM
*/
function mountFunctionComponent(vdom){
let { type, props } = vdom;
let renderVdom = type(props);
return createDOM(renderVdom);
}
实现类组件的渲染
src/index.js
import React from './react';
import ReactDOM from './react-dom';
class App extends React.Component {
render(){
return (
<div className="title" style={{color: 'red'}}>
<span>{this.props.name}</span>
{this.props.children}
</div>
)
}
}
let element = <App name="hello">world</App>;
console.log(element);
ReactDOM.render(element, document.getElementById('root'));
src/Component.js
class Component {
static isReactComponent = true;
constructor(props){
this.props = props;
this.state = {};
}
}
export default Component;
src/react.js
添加 Component 类
import { Component } from './Component'
export { Component };
function createElement(type, config, children){
//...
}
const React = {
createElement,
Component,
}
export default React;
src/react-dom.js
添加 vdom 的 type 是类组件时的条件处理
function createDOM(vdom){
...
let dom;
if (typeof type === 'function'){
if (type.isReactComponent){ //如果是自定义类组件
return mountClassComponent(vdom);
} else { //如果是自定义的函数组件
return mountFunctionComponent(vdom);
}
} else { //如果原生组件
dom = document.createElement(type);
}
...
}
/** 把一个类型为 “自定义类组件” 的虚拟DOM转换成真实DOM并返回
*
* 1.创建类组件的实例
* 2.调用类组件实例的render方法,获得返回的虚拟DOM元素,即React元素
* 3.把返回的虚拟DOM转换成真实的DOM进行挂载
* @param {*} vdom 虚拟DOM
*/
function mountClassComponent(vdom){
let { type, props } = vdom; //解构类的属性和类的属性对象
let classInstance = new type(props); //创建类的实例
let renderVdom = classInstance.render(); //调用实例方法render,返回要渲染的虚拟DOM对象
let dom = createDOM(renderVdom); //根据虚拟DOM对象,返回真实的DOM对象
classInstance.dom = dom; //为以后类组件的更新,把真实DOM挂载到了类的实例上
return dom;
}
实现组件更新
src/index.js
import React from './react';
import ReactDOM from './react-dom';
class Counter extends React.Component {
constructor(props){
super(props);
this.state = {
name: props.name,
number: 0,
}
}
handleClick = () => {
this.setState({
number: this.state.number+1,
})
}
render(){
return (
<div>
<div>{`${this.props.name}: ${this.state.number}`}</div>
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
let element = <Counter name="计数器" />;
ReactDOM.render(element, document.getElementById('root'));
src/Component.js
添加setState实例方法
import { createDOM } from './react-dom.js'
class Component {
static isReactComponent = true;
constructor(props){
this.props = props;
this.state = {};
}
setState(partialState){
let state = this.state;
this.state = {...state, ...partialState};
let newVdom = this.render();
updateClassComponent(this, newVdom);
}
}
/** 更新类组件
*
* @param {*} classInstance
* @param {*} newVdom
*/
function updateClassComponent(classInstance, newVdom){
let oldDom = classInstance.dom; //取出类组件上次渲染出来的真实DOM
let newDom = createDOM(newVdom); //把新的虚拟DOM转换成真实DOM
oldDom.parentNode.replaceChild(newDom, oldDom); //用新的真实DOM替换旧的真实DOM
classInstance.dom = newDom;
}
export default Component;
src/react-dom.js
添加一个条件,把虚拟DOM的事件属性更新为真实DOM的事件属性。
function updateProps(dom, newProps){
...
else if (key.startsWith('on')){
dom[key.toLocaleLowerCase()] = newProps[key]; // 给真实的DOM加事件属性, onClick => onclick
}
...
}
实现合成事件和批量更新
src/index.js
// import React from 'react';
// import ReactDOM from 'react-dom';
import React from './react';
import ReactDOM from './react-dom';
class Counter extends React.Component {
constructor(props){
super(props);
this.state = {
name: props.name,
number: 0,
}
}
handleClick = (e) => {
e.stop();
this.setState({number: this.state.number+1 }, () => {
console.log('callback1', this.state.number);
});
console.log(this.state.number);
this.setState({number: this.state.number+1 });
console.log(this.state.number);
setTimeout(() => {
console.log(this.state.number);
this.setState({number: this.state.number+1 });
console.log(this.state.number);
this.setState({number: this.state.number+1 });
console.log(this.state.number);
}, 1000)
// this.setState(prevState => ({number: prevState.number + 1}), () => {
// console.log('callback', this.state.number); //2
// });
// console.log(this.state.number);
// this.setState(prevState => ({number: prevState.number + 1}));
// console.log(this.state.number);
// setTimeout(() => {
// console.log(this.state.number);
// this.setState(prevState => ({number: prevState.number + 1}));
// console.log(this.state.number);
// this.setState(prevState => ({number: prevState.number + 1}));
// console.log(this.state.number);
// }, 1000)
}
aa = (e) => {
console.log('aa');
}
render(){
return (
<div onClick={this.aa}>
<div>{`${this.props.name}: ${this.state.number}`}</div>
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
let element = <Counter name="计数器" />;
ReactDOM.render(element, document.getElementById('root'));
src/Component.js
import { createDOM } from './react-dom.js'
// 批量更新队列
export let updateQueue = {
isBatchingUpdate: false, //当前是否处于批量更新模式,默认值是false
updaters: new Set(),
batchUpdate(){ // 进行批量更新,之后重置为非批量模式
for (let updater of this.updaters){
updater.updateComponent();
}
this.isBatchingUpdate = false;
}
}
// 更新器
class Updater {
constructor(classInstance){
this.classInstance = classInstance; //类组件的实例
this.pendingStates = []; //等待生效的状态,可能是一个对象,也可能给是一个函数
this.cbs = []; //状态更新后的回调
}
addState(partialState, cb){
// 把该次 setState() 传递的数据和回调函数先存起来
this.pendingStates.push(partialState);
if (typeof cb === 'function') this.cbs.push(cb);
if (updateQueue.isBatchingUpdate){
updateQueue.updaters.add(this); //如果是批量模式,先不更新,缓存起来。本次setState就结束了
} else {
this.updateComponent(); //如果非批量模式,直接更新组件
}
}
// 更新类组件
updateComponent(){
let { classInstance, pendingStates, cbs } = this;
if (pendingStates.length > 0){ //如果有等待更新的状态对象
classInstance.state = this.getState(); //类组件的state更新为最新的state
classInstance.forceUpdate(); //强制重新更新渲染
cbs.forEach(cb => cb()); //更新后运行 setState() 传递的回调函数
cbs.length = 0; //回调函数运行后,清空cbs数组
}
}
// 获取最新的state状态
getState(){
let { classInstance, pendingStates }= this;
let { state } = classInstance;
// 合并本次更新的所有state
pendingStates.forEach(nextState => {
// 如果 nextState 是一个函数的话,传入老状态,返回新状态,再进行合并
if (typeof nextState === 'function') nextState = nextState.call(classInstance, state);
state = {...state, ...nextState};
})
pendingStates.length = 0; //state获取完后,清空 pendingStates 数组
return state;
}
}
class Component {
static isReactComponent = true;
constructor(props){
this.props = props;
this.state = {};
this.updater = new Updater(this);
}
setState(partialState, cb){
this.updater.addState(partialState, cb);
}
// 强制更新渲染
forceUpdate(){
let newVdom = this.render();
updateClassComponent(this, newVdom);
}
}
/** 更新类组件
*
* @param {*} classInstance
* @param {*} newVdom
*/
function updateClassComponent(classInstance, newVdom){
let oldDom = classInstance.dom; //取出类组件上次渲染出来的真实DOM
let newDom = createDOM(newVdom); //把新的虚拟DOM转换成真实DOM
oldDom.parentNode.replaceChild(newDom, oldDom); //用新的真实DOM替换旧的真实DOM
classInstance.dom = newDom;
}
export default Component;
src/react-dom.js
import { addEvent } from './event.js'
function updateProps(dom, newProps){
...
else if (key.startsWith('on')){
// dom[key.toLocaleLowerCase()] = newProps[key]; // 给真实的DOM加事件属性, onClick => onclick
addEvent(dom, key.toLocaleLowerCase(), newProps[key]); //给真实的dom添加委托事件
}
...
}
src/event.js
import { updateQueue } from './Component.js'
/** 给真实DOM添加事件处理函数
* 什么么要做合成事件?为什么要做事件委托或代理?
* 1.做兼容处理,兼容不同浏览器
* 2.可以在你写的事件处理函数之前和之后做一些事情
*
* @param {*} dom 真实DOM
* @param {*} eventType 事件类型
* @param {*} listener 监听函数
*/
export function addEvent(dom, eventType, listener){
let store;
if (dom.store){
store = dom.store;
} else{
dom.store={};
store = dom.store;
}
// let store = dom.store || (dom.store = {});
store[eventType] = listener;
if (!document[eventType]){
// 事件委托,不管你给哪个DOM元素上绑定事件,最后都统一代理到document上去了
document[eventType] = dispatchEvent; //document.onclick = dispatchEvent;
}
}
//创建一个单例的合成event对象,这个也是react包装后返回的event对象
let syntheticEvent = {
// 阻止冒泡
stopping: false,
stop(){
this.stopping = true;
}
};
// document的事件委托函数
function dispatchEvent(event){
// 事件源target=button那个DOM元素; 类型type=click
let { target, type } = event;
let eventType = `on${type}`;
updateQueue.isBatchingUpdate = true; // 把列队设置为批量更新模式
createSyntheticEvent(event); //包装event对象
while (target){
let { store } = target;
let listener = store && store[eventType];
listener && listener.call(target, syntheticEvent);
// 如果调用了 e.stop() 阻止了冒泡,就打断
if (syntheticEvent.stopping){
syntheticEvent.stopping = false;
break;
}
//如果父节点也绑定了事件, 继续向上冒泡执行
target = target.parentNode;
}
// 事件函数调用之后,清空 syntheticEvent 对象
for (let key in event){
syntheticEvent[key] = null;
}
updateQueue.batchUpdate(); // 进行批量更新,之后重置为非批量模式
}
function createSyntheticEvent(nativeEvent){
for (let key in nativeEvent){
syntheticEvent[key] = nativeEvent[key];
}
}
实现 ref
forwardRef 的实现见 hook源码篇
src/index.js
import React from './react';
import ReactDOM from './react-dom';
class Form extends React.Component {
refTextInput;
constructor(props){
super(props);
this.refTextInput = React.createRef();
}
getFocus = () => {
let refTextInput = this.refTextInput.current; //TextInput组件实例
refTextInput.getFocus();
}
render(){
return (
<div>
<TextInput ref={this.refTextInput}></TextInput>
<button onClick={this.getFocus}>获得焦点</button>
</div>
)
}
}
class TextInput extends React.Component {
refInput;
constructor(props){
super(props);
this.refInput = React.createRef();
}
getFocus = () => {
this.refInput.current.focus();
}
render(){
return <input ref={this.refInput} />
}
}
ReactDOM.render(<Form />, document.getElementById('root'));
src/react.js
function createElement(type, config, children){
+ let ref;
if (config){
delete config.__source;
delete config.__self;
+ ref = config.ref;
+ delete config.ref;
key = config.key;
delete config.key;
}
let props = {...config};
if (arguments.length > 3){
children = Array.prototype.slice.call(arguments, 2);
}
props.children = children;
//ref、key等都是react原生属性,不属于props属性,所以要单拎出来
return {
type,
props,
+ ref,
}
}
+function createRef(initialValue){
+ return {current: null};
+}
const React = {
createElement,
Component,
+ createRef,
}
export default React;
src/react-dom.js
export function createDOM(vdom){
...
+ let { type, props, ref } = vdom;
let dom; //创建真实dom对象
if (typeof type === 'function'){
if (type.isReactComponent){ //如果是自定义类组件
return mountClassComponent(vdom);
} else { //自定义的函数组件
+ if (ref) throw new Error('Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?')
return mountFunctionComponent(vdom);
}
} else { //原生组件
dom = document.createElement(type);
+ if (ref) ref.current = dom; //原生组件绑定的 ref.current 为真实dom
updateProps(dom, props); // 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
renderChildren(props.children, dom); // 挂载父节点的儿子或儿子们
}
return dom;
}
function mountClassComponent(vdom){
+ let { type, props, ref } = vdom; //解构类的属性和类的属性对象
let classInstance = new type(props); //创建类的实例
// 为类组件绑定ref
+ if (typeof ref === 'function'){ //<App ref={(node) => {this.refApp = node}} />
+ ref(classInstance);
+ } else {
+ if (ref){ //this.refApp = React.createRef() <App ref={this.refApp} />
+ ref.current = classInstance;
+ classInstance.ref = ref;
+ }
+ }
let renderVdom = classInstance.render(); //调用实例方法render,返回要渲染的虚拟DOM对象
let dom = createDOM(renderVdom); //根据虚拟DOM对象,返回真实的DOM对象
classInstance.dom = dom; //为以后类组件的更新,把真实DOM挂载到了类的实例上
return dom;
}
实现 React.Fragment
React.Fragment
等同于 <></>
src/index.js
import React from './react';
import ReactDOM from './react-dom';
class Form extends React.Component {
refTextInput;
constructor(props){
super(props);
}
render(){
return (
<React.Fragment>
<div className="a">hello</div>
<span>world</span>
<><p>today</p></>
</React.Fragment>
)
}
}
ReactDOM.render(<Form />, document.getElementById('root'));
src/constants.js
export const REACT_FRAGMENT = Symbol('react.fragment');
src/react.js
import { REACT_FRAGMENT } from './constants'
function createElement(type, config, children){
+ if (type.name === 'Fragment') type = REACT_FRAGMENT;
}
export function Fragment(props){
return <>{props.children}</>
}
const React = {
Fragment,
}
src/react-dom.js
import { addEvent } from './event.js'
+import { REACT_TEXT, REACT_FRAGMENT } from './constants'
function render (vdom, container){
const dom = createDOM(vdom, container);
+ if (dom) container.appendChild(dom);
}
/** 把虚拟DOM变成真实DOM
*
* @param {*} vdom 虚拟DOM
*/
export function createDOM(vdom, container){
// TODO 处理vdom是数字或者字符串的情况,直接返回一个真实的文本节点。
if (typeof vdom === 'string' || typeof vdom === 'number'){
return document.createTextNode(vdom);
}
// 否则 它就是一个虚拟DOM对象了,也就是React元素
let { type, props, ref } = vdom;
let dom; //创建真实dom对象
if (typeof type === 'function'){
if (type.isReactComponent){ //如果是自定义类组件
+ return mountClassComponent(vdom, container);
} else { //自定义的函数组件
if (ref) throw new Error('Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?')
+ return mountFunctionComponent(vdom, container);
}
} else { //原生组件
+ if (type === undefined || type === REACT_FRAGMENT){ //如果是Fragment
dom = null;
renderChildren(props.children, container);
} else {
dom = document.createElement(type);
if (ref) ref.current = dom; //原生组件绑定的 ref.current 为真实dom
updateProps(dom, props); // 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
renderChildren(props.children, dom); // 挂载父节点的儿子或儿子们
}
}
return dom;
}
//把一个类型为 “自定义函数组件” 的虚拟DOM转换成真实DOM并返回
+function mountFunctionComponent(vdom, container){
let { type, props } = vdom;
let renderVdom = type(props);
+ return createDOM(renderVdom, container);
}
//把一个类型为 “自定义类组件” 的虚拟DOM转换成真实DOM并返回
+function mountClassComponent(vdom, container){
let { type, props, ref } = vdom; //解构类的属性和类的属性对象
let classInstance = new type(props); //创建类的实例
let renderVdom = classInstance.render(); //调用实例方法render,返回要渲染的虚拟DOM对象
+ let dom = createDOM(renderVdom, container); //根据虚拟DOM对象,返回真实的DOM对象
if (ref) ref.current = classInstance; //类组件绑定的 ref.current 为类的实例
classInstance.dom = dom; //为以后类组件的更新,把真实DOM挂载到了类的实例上
return dom;
}
实现基本生命周期
src/index.js
import React from './react';
import ReactDOM from './react-dom';
class Counter extends React.Component {
static defaultProps = { //设置初始属性对象
name: '计数器',
}
constructor(props){
super(props);
this.state = { number: 0 };
console.log('Counter 1.constructor 初始化属性和状态对象');
}
componentWillMount(){
console.log('Counter 2.componentWillMount 组件将要挂载');
}
componentDidMount(){
console.log('Counter 4.componentDidMount 组件完成挂载');
}
handleClick = () => {
console.log('click add button!');
this.setState({number: this.state.number + 1});
}
// react可以shouldComponentUpdate方法中优化,PureComponent 可以帮我们做这件事
shouldComponentUpdate(nextProps, nextState){ //参数:下一次属性;下一次状态
console.log('Counter 5.shouldComponentUpdate 决定组件是否需要更新?');
return nextState.number % 2 === 0; //此函数只有返回了true,才会调用 render 方法
}
componentWillUpdate(nextProps, nextState){
console.log('Counter 6.componentWillUpdate 组件将要更新');
}
componentDidUpdate(prevProps, prevState){
console.log('Counter 7.componentDidUpdate 组件完成更新');
}
render(){
console.log('Counter 3.render 重新计算新的虚拟DOM');
let st = this.state;
return (
<div>
<p>{st.number}</p>
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
ReactDOM.render(<Counter />, document.getElementById('root'));
// Counter 1.constructor 初始化属性和状态对象
// Counter 2.componentWillMount 组件将要挂载
// Counter 3.render 重新计算新的虚拟DOM
// Counter 4.componentDidMount 组件完成挂载
// click add button!
// Counter 5.shouldComponentUpdate 决定组件是否需要更新?
// click add button!
// Counter 5.shouldComponentUpdate 决定组件是否需要更新?
// Counter 6.componentWillUpdate 组件将要更新
// Counter 3.render 重新计算新的虚拟DOM
// Counter 7.componentDidUpdate 组件完成更新
src/react-dom.js
function render (vdom, container){
const dom = createDOM(vdom, container);
if (dom) container.appendChild(dom);
+ dom.componentDidMount && dom.componentDidMount();
}
// 把一个类型为 “自定义类组件” 的虚拟DOM转换成真实DOM并返回
function mountClassComponent(vdom, container){
let { type, props, ref } = vdom; //解构类的属性和类的属性对象
let classInstance = new type(props); //创建类的实例
// 为类组件绑定ref
if (typeof ref === 'function'){ //<App ref={(node) => {this.refApp = node}} />
ref(classInstance);
} else {
if (ref){ //this.refApp=React.createRef() <App ref={this.refApp} />
ref.current = classInstance;
classInstance.ref = ref;
}
}
+ if (classInstance.componentWillMount) classInstance.componentWillMount();
let renderVdom = classInstance.render(); //调用实例方法render,返回要渲染的虚拟DOM对象
let dom = createDOM(renderVdom, container); //根据虚拟DOM对象,返回真实的DOM对象
+ if (classInstance.componentDidMount){
+ dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
+ }
classInstance.dom = dom; //为以后类组件的更新,把真实DOM挂载到了类的实例上
return dom;
}
src/Component.js
// 更新器
class Updater {
...
addState(partialState, cb){
// 把该次 setState() 传递的数据和回调函数先存起来
this.pendingStates.push(partialState);
if (typeof cb === 'function') this.cbs.push(cb);
+ this.emitUpdate();
}
// 发布更新(一个组件不管是属性变了,还是状态变了,都会更新)
emitUpdate(newProps){
if (updateQueue.isBatchingUpdate){
updateQueue.updaters.add(this); //如果是批量模式,先不更新,缓存起来。本次setState就结束了
} else {
this.updateComponent(); //如果非批量模式,直接更新组件
}
}
// 更新类组件
updateComponent(){
let { classInstance, pendingStates, cbs } = this;
if (pendingStates.length > 0){ //如果有等待更新的状态对象
shouldUpdate(classInstance, this.getState());
// cbs.forEach(cb => cb()); //更新后运行 setState() 传递的回调函数
// cbs.length = 0; //回调函数运行后,清空cbs数组
}
}
...
}
/** 是否应该更新
*
* @param {*} classInstance 组件实例
* @param {*} nextState 新的状态
*/
function shouldUpdate(classInstance, nextState){
//不管组件要不要刷新(是否执行render),其实组件的state属性一定会改变
classInstance.state = nextState;
// 如果有 shouldComponentUpdate 方法,并且该方法返回值为 false,到这就结束了。
let noUpdate = classInstance.shouldComponentUpdate && !classInstance.shouldComponentUpdate(classInstance.props, classInstance.state);
if (noUpdate) return;
classInstance.forceUpdate(); //强制重新更新渲染
}
class Component {
...
// 强制更新渲染
forceUpdate(){
+ if (this.componentWillUpdate) this.componentWillUpdate();
let newVdom = this.render();
updateClassComponent(this, newVdom);
+ if (this.componentDidUpdate) this.componentDidUpdate();
}
}
// 更新类组件
function updateClassComponent(classInstance, newVdom){
let oldDom = classInstance.dom; //取出类组件上次渲染出来的真实DOM
let newDom = createDOM(newVdom); //把新的虚拟DOM转换成真实DOM
oldDom.parentNode.replaceChild(newDom, oldDom); //用新的真实DOM替换旧的真实DOM
classInstance.dom = newDom;
}
实现完整生命周期
dom-diff 最基本算法
- 按索引一一对比,如果类型相同,则复用老的DOM节点,更新属性即可。
- 如果类型不同,删除老的DOM节点,添加新的DOM节点。
- 如果老节点为null,新的节点有,新建新的DOM节点,并插入。
- 如果老节点有,新节点为null, 移除老的DOM节点。
src/index.js
import React from './react';
import ReactDOM from './react-dom';
class Counter extends React.Component {
static defaultProps = { //设置初始属性对象
name: '计数器',
}
constructor(props){
super(props);
this.state = { number: 0 };
console.log('Counter 1.constructor 初始化属性和状态对象');
}
componentWillMount(){
console.log('Counter 2.componentWillMount 组件将要挂载');
}
componentDidMount(){
console.log('Counter 4.componentDidMount 组件完成挂载');
}
handleClick = () => {
console.log('click add button!');
this.setState({number: this.state.number + 1}, () =>{
console.log(this.state);
});
}
// react可以shouldComponentUpdate方法中优化,PureComponent 可以帮我们做这件事
shouldComponentUpdate(nextProps, nextState){ //参数:下一次属性;下一次状态
console.log('Counter 5.shouldComponentUpdate 决定组件是否需要更新?');
return nextState.number % 2 === 0; //此函数只有返回了true,才会调用 render 方法
}
componentWillUpdate(nextProps, nextState){ //参数:下一次属性;下一次状态
console.log('Counter 6.componentWillUpdate 组件将要更新');
}
componentDidUpdate(prevProps, prevState){ //参数:上一次属性;上一次状态
console.log('Counter 7.componentDidUpdate 组件完成更新', prevProps, prevState);
}
render(){
console.log('Counter 3.render 重新计算新的虚拟DOM');
let st = this.state;
return (
<div id={`counter-${st.number}`} >
<p>{st.number}</p>
{st.number === 4 ? null : <ChildCounter count={st.number} />}
<button onClick={this.handleClick}>+</button>
<FunctionCounter count={st.number} />
</div>
)
}
}
class ChildCounter extends React.Component {
componentWillUnmount(){
console.log('ChildCounter 8.componentWillUnmount 组件将要卸载');
}
componentWillMount(){
console.log('ChildCounter 1.componentWillMount 组件将要挂载');
}
componentDidMount(){
console.log('ChildCounter 3.componentDidMount 组件完成挂载');
}
// 第一次不会执行,之后属性更新时才会执行(即:组件挂载时不执行,组件挂载后,有属性变化,才执行)
componentWillReceiveProps(newProps){
console.log('ChildCounter 4.componentWillReceiveProps 组件将要接收到新的属性');
}
shouldComponentUpdate(nextProps, nextState){
console.log('ChildCounter 5.shouldComponentUpdate 决定组件是否需要更新?');
return nextProps.count % 3 === 0; //3的倍数就更新,否则就不更新
}
componentWillUpdate(){
console.log('ChildCounter 6.componentWillUpdate 组件将要更新');
}
componentDidUpdate(){
console.log('ChildCounter 7.componentDidUpdate 组件完成更新');
}
render(){
console.log('ChildCounter 2.render');
return (
<div id="sub-counter">{`ChildCounter: ${this.props.count}`}</div>
)
}
}
let FunctionCounter = (props) => <div id="counter-function">{props.count}</div>
ReactDOM.render(<Counter />, document.getElementById('root'));
src/constants.js
export const REACT_TEXT = Symbol('REACT_TEXT');
export const REACT_FRAGMENT = Symbol('react.fragment');
src/utils.js
import { REACT_TEXT } from './constants'
/** 为了方便后边的dom-diff,把文本节点进行单独封装或做一个标识
* 不管原来是什么,都全部包装成React元素的形式
* @param {*} element 可能是一个React元素,也可以是一个字符串或数字
* @returns
*/
export function wrapToVdom(element){
return (typeof element === 'string' || typeof element === 'number')
? {type: REACT_TEXT, props: {content: element}}: element;
}
src/react.js
import { Component } from './Component'
import { wrapToVdom } from './utils'
import { REACT_FRAGMENT } from './constants'
export { Component };
/**
* @param {*} type 元素的类型
* @param {*} config 配置对象
* @param {*} children 元素的儿子或儿子们
*/
function createElement(type, config, children){
let ref;
if (type.name === 'Fragment') type = REACT_FRAGMENT;
if (config){
delete config.__source;
delete config.__self;
ref = config.ref;
}
let props = {...config};
//为了方便进行dom-diff,用 wrapToVdom 把文本节点进行一个标识,方便判断
if (arguments.length > 3){
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else {
props.children = wrapToVdom(children);
}
return { type, props, ref };
}
export function createRef(){
return {current: null};
}
export function Fragment(props){
return <>{props.children}</>
}
const React = {
createElement,
Component,
createRef,
Fragment,
}
export default React;
src/Component.js
import { compareTwoVdom, findDOM } from './react-dom.js'
// 批量更新队列
export let updateQueue = {
isBatchingUpdate: false, //当前是否处于批量更新模式,默认值是false
updaters: new Set(),
add(updater){
this.updaters.add(updater);
},
batchUpdate(){ // 进行批量更新,之后重置为非批量模式
this.isBatchingUpdate = false;
for (let updater of this.updaters){
updater.emitUpdate();
}
updateQueue.updaters.clear();
}
}
// 更新器
class Updater {
constructor(classInstance){
this.classInstance = classInstance; //类组件的实例
this.pendingStates = []; //等待生效的状态,可能是一个对象,也可能给是一个函数
this.cbs = []; //状态更新后的回调
}
addState(partialState, cb){
// 把该次 setState() 传递的数据和回调函数先存起来
this.pendingStates.push(partialState);
if (typeof cb === 'function') this.cbs.push(cb);
if (updateQueue.isBatchingUpdate){
updateQueue.add(this); //如果是批量模式,先不更新,缓存起来。本次setState就结束了
} else {
this.emitUpdate(); //如果非批量模式,直接更新
}
}
// 发布更新(一个组件不管是属性变了,还是状态变了,都会更新)
emitUpdate(nextProps){
let { classInstance, pendingStates, cbs } = this;
if (nextProps || pendingStates.length > 0){ //如果有等待更新的状态对象
shouldUpdate(classInstance, nextProps, this.getState());
// 不管是否要更新,结束后,setState的回调函数都要运行
if (cbs.length > 0){
cbs.forEach(cb => cb()); //更新后运行 setState() 传递的回调函数
cbs.length = 0; //回调函数运行后,清空cbs数组
}
}
}
// 获取最新的state状态
getState(){
let { classInstance, pendingStates }= this;
let { state } = classInstance;
// 合并本次更新的所有state
pendingStates.forEach(nextState => {
// 如果 nextState 是一个函数的话,传入老状态,返回新状态,再进行合并
if (typeof nextState === 'function') nextState = nextState.call(classInstance, state);
state = {...state, ...nextState};
})
pendingStates.length = 0; //state获取完后,清空 pendingStates 数组
return state;
}
}
/** 是否应该更新
*
* @param {*} classInstance 组件实例
* @param {*} nextState 新的状态
*/
function shouldUpdate(classInstance, nextProps, nextState){
let willUpdate = true;
// 如果有 shouldComponentUpdate 方法,并且该方法返回值为 false,就只更新属性和状态,不调用render进行渲染了
if(classInstance.shouldComponentUpdate &&!classInstance.shouldComponentUpdate(nextProps, nextState)){
willUpdate = false;
}
// 调用 组件将要更新 生命周期函数
if(willUpdate && classInstance.componentWillUpdate) classInstance.componentWillUpdate(nextProps, nextState);
classInstance.prevProps = classInstance.props;
classInstance.prevState = classInstance.state;
// 不管组件要不要刷新(是否执行render),其实组件的state属性一定会改变
if(nextProps) classInstance.props = nextProps; //更新props
classInstance.state = nextState; //更新state
if (willUpdate) classInstance.forceUpdate();
}
class Component {
static isReactComponent = true; //是否类组件
constructor(props){
this.props = {...this.constructor.defaultProps, ...props};
this.state = {};
this.updater = new Updater(this);
}
setState(partialState, cb){
this.updater.addState(partialState, cb);
}
// 强制更新渲染
forceUpdate(){
let newRenderVdom = this.render();
let oldDom = findDOM(this.oldRenderVdom);
// 深度比较新旧两个虚拟DOM
let currentRenderVdom = compareTwoVdom(oldDom.parentNode, this.oldRenderVdom, newRenderVdom);
this.oldRenderVdom = currentRenderVdom;
this.componentDidUpdate && this.componentDidUpdate(this.prevProps, this.prevState);
}
}
export default Component;
src/react-dom.js
import { addEvent } from './event.js'
import { REACT_TEXT, REACT_FRAGMENT } from './constants'
/** 渲染vdom虚拟DOM
*
* 1.把vdom虚拟DOM变成真实DOM dom
* 2.把虚拟DOM上的属性更新或者同步到dom上
* 3.把此虚拟DOM的儿子们也都变成真实DOM挂载到自己的dom上 dom.appendChild
* 4.把自己挂载到容器上
* @param {*} vdom 要渲染的虚拟DOM
* @param {*} container 要把虚拟DOM转换真实DOM并插入到哪个父容器(父真实DOM节点)中去
*/
// 给根容器挂载的时候
function render (vdom, container){
mount(vdom, container);
}
// 挂载
function mount (vdom, container){
if (!vdom) return;
const dom = createDOM(vdom, container);
if (dom){
container.appendChild(dom);
dom.componentDidMount && dom.componentDidMount();
}
}
/** 把虚拟DOM变成真实DOM
*
* @param {*} vdom 虚拟DOM
*/
export function createDOM(vdom, container){
let { type, props, ref } = vdom; //解构虚拟DOM
let dom; //创建真实dom对象
if (type === REACT_TEXT){ // 字符串或文本
dom = document.createTextNode(props.content);
} else if (typeof type === 'function'){
if (type.isReactComponent){ //类组件
return mountClassComponent(vdom, container);
} else { //函数组件
if (ref) throw new Error('Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?')
return mountFunctionComponent(vdom, container);
}
} else { //原生组件(React元素)
if (type === undefined || type === REACT_FRAGMENT){ //如果是Fragment
dom = null;
renderChildren(props.children, container);
} else {
dom = document.createElement(type);
if (ref) ref.current = dom; //原生组件绑定的 ref.current 为真实dom
updateProps(dom, {}, props); // 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
renderChildren(props.children, dom); // 挂载父节点的儿子或儿子们
}
}
// 把真实DOM作为一个dom属性放在虚拟DOM上(dom-diff 时需要)
// 当根据一个vdom创建出来一个真实DOM之后,真实DOM挂载到vdom.dom属性上
vdom.dom = dom;
return dom;
}
/** 挂载父节点的儿子或儿子们
*
* @param {*} children 虚拟的儿子或儿子们元素
* @param {*} container 父容器(父真实DOM节点)
*/
function renderChildren (children, container){
if (typeof children === 'object' && children.type){ // 如果只有一个儿子
mount(children, container);
} else if (Array.isArray(children)){ // 如果有多个儿子
reconcileChilren(children, container);
}
}
/** 处理vdom儿子是数组情况
*
* @param {*} childrenVdom 儿子们的虚拟DOM
* @param {*} container 父容器(父真实DOM节点)
*/
function reconcileChilren(childrenVdom, container){
childrenVdom.forEach(childVdom => {
mount(childVdom, container);
})
}
/** 把一个类型为 “自定义函数组件” 的虚拟DOM转换成真实DOM并返回
*
* @param {*} vdom 虚拟DOM
*/
function mountFunctionComponent(vdom, container){
const { type, props } = vdom;
const renderVdom = type(props);
vdom.oldRenderVdom = renderVdom; //把 vdom的渲染DOM 挂载到 vdom 上 (dom-diff 时需要)
return createDOM(renderVdom, container);
}
/** 把一个类型为 “自定义类组件” 的虚拟DOM转换成真实DOM并返回
*
* 1.创建类组件的实例
* 2.调用类组件实例的render方法,获得返回的虚拟DOM元素,即React元素
* 3.把返回的虚拟DOM转换成真实的DOM进行挂载
* @param {*} vdom 虚拟DOM
*/
function mountClassComponent(vdom, container){
const { type, props, ref } = vdom; //解构类的属性和类的属性对象
const classInstance = new type(props); //创建类的实例
vdom.classInstance = classInstance; //把 类的实例 挂载到 vdom 上(dom-diff 时需要)
// 为类组件绑定ref
if (typeof ref === 'function'){ //<App ref={(node) => {this.refApp = node}} />
ref(classInstance);
} else {
if (ref){ //this.refApp=React.createRef() <App ref={this.refApp} />
ref.current = classInstance;
classInstance.ref = ref;
}
}
classInstance.componentWillMount && classInstance.componentWillMount();
const renderVdom = classInstance.render(); //调用实例方法render,返回要渲染的虚拟DOM对象
const dom = createDOM(renderVdom, container); //根据虚拟DOM对象,返回真实的DOM对象
//把 vdom的渲染DOM 挂载到 vdom 和 类的实例 上(dom-diff 时需要)
classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;
if (classInstance.componentDidMount){
dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
}
return dom;
}
/** 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
*
* @param {*} dom 真实DOM
* @param {*} oldProps 旧属性对象
* @param {*} newProps 新属性对象
*/
function updateProps(dom, oldProps={}, 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 if (key.startsWith('on')){
addEvent(dom, key.toLocaleLowerCase(), newProps[key]); //给真实的dom添加委托事件
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps){
if (!newProps.hasOwnProperty(key)){
dom[key] = '';
}
}
}
// 查找此虚拟DOM对应的真实DOM
export function findDOM(vdom){
let { type } = vdom;
let dom;
if (typeof type === 'function'){ //如果是 类组件 或者 函数组件
//类组件render后,有可能还是还是个类组件或函数组件,所有要递归处理
dom = findDOM(vdom.oldRenderVdom);
} else { //原生组件
dom = vdom.dom;
}
return dom;
}
/** 比较新老虚拟DOM(dom-diff)
*
* @param {*} container 当前组件挂载的父容器(父真实DOM节点)
* @param {*} oldVdom 上一次的虚拟DOM
* @param {*} newVdom 本次的虚拟DOM
*/
export function compareTwoVdom(container, oldVdom, newVdom, nextDOM){
// 如果老的虚拟DOM和新的虚拟DOM都是null
if (!oldVdom && !newVdom) return;
// 老的有,新的没有 => 要刪除此节点
if (oldVdom && !newVdom){
let oldDOM = findDOM(oldVdom); //先找到此虚拟DOM对应的真实DOM
if (oldDOM) container.removeChild(oldDOM);
if (oldVdom.classInstance && oldVdom.classInstance.componentWillUnmount){
oldVdom.classInstance.componentWillUnmount();
}
return;
}
// 老的没有,新的有 => 要新建DOM节点,并添加
if (!oldVdom && newVdom){
let newDOM = createDOM(newVdom, container);
if (nextDOM){
container.insertBefore(newDOM, nextDOM); //如果有下一个弟弟节点,插到其前面
} else {
container.appendChild(newDOM);
}
newDOM.componentDidMount && newDOM.componentDidMount();
return newVdom;
}
// 新旧都有虚拟DOM,但是类型type不相同 => 直接替换节点
if (oldVdom && newVdom && oldVdom.type !== newVdom.type){
let oldDOM = findDOM(oldVdom);
let newDOM = createDOM(newVdom);
container.replaceChild(newDOM, oldDOM); //直接用新的真实DOM,替换掉老的真实DOM
if (oldVdom.classInstance && oldVdom.classInstance.componentWillUnmount){
oldVdom.classInstance.componentWillUnmount();
}
newDOM.componentDidMount && newDOM.componentDidMount();
return newVdom;
}
// 新旧都有虚拟DOM,并且类型type也相同
if (oldVdom && newVdom && oldVdom.type === newVdom.type){
deepCompare(container, oldVdom, newVdom);
return newVdom;
}
}
/** 新旧都有虚拟DOM,并且类型type也相同,进行深度的比较(dom-diff)
*
* @param {*} oldVdom 老的虚拟DOM
* @param {*} newVdom 新的虚拟DOM
*/
function deepCompare(container, oldVdom, newVdom){
// 文本节点
if (oldVdom.type === REACT_TEXT){
let currentDOM = newVdom.dom = oldVdom.dom; //复用老组件的真实DOM节点,直接更换其文本
currentDOM.textContent = newVdom.props.content;
} else if (typeof oldVdom.type === 'string'){ //原生组件
let currentDOM = newVdom.dom = oldVdom.dom; //复用老组件的真实DOM
updateProps(currentDOM, oldVdom.props, newVdom.props); //更新自己的属性
updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children); //更新儿子们
} else if (typeof oldVdom.type === 'function'){
if (oldVdom.type.isReactComponent){ //老的和新的都是类组件,进行类组件更新
updateClassComponent(oldVdom, newVdom);
} else { //老的和新的都是函数组件,进行函数组件更新
updateFunctionComponent(container, oldVdom, newVdom);
}
} else if (oldVdom.type === REACT_FRAGMENT){
updateChildren(container, oldVdom.props.children, newVdom.props.children); //更新儿子们
}
}
/** 递归儿子们,进行深度比较(dom-diff)
*
* @param {*} container 父DOM节点
* @param {*} oldVChildren 老的儿子们
* @param {*} newVChildren 新的儿子们
*/
function updateChildren(container, oldVChildren, newVChildren){
// 因为children可能是对象,也可能是数组,为了方便按索引比较,全部格式化为数组
oldVChildren = Array.isArray(oldVChildren) ? oldVChildren : [oldVChildren];
newVChildren = Array.isArray(newVChildren) ? newVChildren : [newVChildren];
let maxLength = Math.max(oldVChildren.length, newVChildren.length);
for (let i=0; i<maxLength; i++){
// 在儿子里查找,大于当前索引的第一个兄弟
let nextVdom = oldVChildren.find((item, index) => index > i && item && item.dom);
compareTwoVdom(container, oldVChildren[i], newVChildren[i], nextVdom && nextVdom.dom); //递归对比每一个儿子
}
}
// 如果老的虚拟DOM节点和新的虚拟DOM节点都是类组件的话,进行类组件更新(dom-diff)
function updateClassComponent(oldVdom, newVdom){
let classInstance = newVdom.classInstance = oldVdom.classInstance; //更新的时候,实例复用
newVdom.oldRenderVdom = oldVdom.oldRenderVdom;
if (classInstance.componentWillReceiveProps) classInstance.componentWillReceiveProps(); //组件将要接收到新的属性
classInstance.updater.emitUpdate(newVdom.props); //触发组件的更新,把新的属性传过来
}
// 如果老的虚拟DOM节点和新的虚拟DOM节点都是函数组件的话,进行函数组件更新(dom-diff)
function updateFunctionComponent(container, oldVdom, newVdom){
// let container = findDOM(oldVdom).parentNode;
let { type, props } = newVdom;
let oldRenderVdom = oldVdom.oldRenderVdom;
let newRenderVdom = type(props);
newVdom.oldRenderVdom = newRenderVdom;
compareTwoVdom(container, oldRenderVdom, newRenderVdom);
}
const ReactDOM = {
render,
findDOM,
}
export default ReactDOM;
新的生命周期
实现getDerivedStateFromProps
src/Component.js
// 更新器
class Updater {
// 发布更新(一个组件不管是属性变了,还是状态变了,都会更新)
emitUpdate(nextProps){
if (nextProps || pendingStates.length > 0){ //如果有等待更新的状态对象
+ shouldUpdate(classInstance, nextProps, this.getState(nextProps));
}
}
// 获取最新的state状态
getState(nextProps){
...
pendingStates.length = 0; //state获取完后,清空 pendingStates 数组
+ if (classInstance.constructor.getDerivedStateFromProps){
+ let partialState = classInstance.constructor.getDerivedStateFromProps(nextProps, classInstance.state);
+ if (partialState) state = {...state, ...partialState};
+ }
return state;
}
}
class Component {
...
// 一般来说,组件的属性和状态变化了才会更新组件,
// 如果属性和状态没变,也想更新组件,怎么做?调用
forceUpdate(){
let nextProps = this.props;
let nextState = this.state;
if (this.constructor.getDerivedStateFromProps){
let partialState = this.constructor.getDerivedStateFromProps(nextProps, nextState);
if (partialState){
nextState = {...nextState, ...partialState};
}
}
this.state = nextState;
this.updateComponent();
}
// 更新渲染
updateComponent(){}
}
src/react-dom.js
function mountClassComponent(vdom, container){
...
classInstance.componentWillMount && classInstance.componentWillMount();
+ if (type.getDerivedStateFromProps){
+ let partialState = type.getDerivedStateFromProps(classInstance.props, classInstance.state);
+ if (partialState){
+ classInstance.state = {...classInstance.state, ...partialState};
+ }
+ }
const renderVdom = classInstance.render(); //调用实例方法render,返回要渲染的虚拟DOM对象
const dom = createDOM(renderVdom, container); //根据虚拟DOM对象,返回真实的DOM对象
...
}
实现getSnapshotBeforeUpdate
src/index.js
import React, { Component } from './react';
import ReactDOM from './react-dom';
let number = 0;
class Counter extends Component {
refUl = React.createRef();
state = {
list: [],
}
// 被调用于render之后,可以读取但无法使用DOM的时候。
// 它使您的组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。
// 此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
getSnapshotBeforeUpdate(){
return this.refUl.current.scrollHeight; // 拿到ul的真实dom,获取它的内容高度(渲染前)
}
componentDidUpdate(prevProps, prevState, prevScrollHeight){
let currentHeight = this.refUl.current.scrollHeight; //ul当前的内容高度(渲染后)
console.log('本次ul增加的高度', currentHeight - prevScrollHeight);
}
handleClick = () => {
let list = this.state.list;
list.push(list.length);
this.setState({list});
}
render(){
return (
<div>
<button onClick={this.handleClick}>+</button>
<ul ref={this.refUl}>
{this.state.list.map((item, index) => (
<li key={index}>{index}</li>
))}
</ul>
</div>
)
}
}
ReactDOM.render(<Counter />, document.getElementById('root'));
src/Component.js
class Component {
updateComponent(){
let newRenderVdom = this.render();
let oldDom = findDOM(this.oldRenderVdom);
+ let extraArgs = this.getSnapshotBeforeUpdate && this.getSnapshotBeforeUpdate();
// 深度比较新旧两个虚拟DOM
let currentRenderVdom = compareTwoVdom(oldDom.parentNode, this.oldRenderVdom, newRenderVdom);
this.oldRenderVdom = currentRenderVdom;
+ this.componentDidUpdate && this.componentDidUpdate(this.prevProps, this.prevState, extraArgs);
}
}
Context 上下文
src/react.js
// 创建上下文
export function createContext(initialValue){
let context = {Provider, Consumer};
// 提供者
function Provider(props){
context._currentValue = context._currentValue || initialValue;
// 保持 context._currentValue 的引用地址不要变,
// 这样父组件更新的时候,子组件 this.context 也会是最新的
Object.assign(context._currentValue, props.value);
return props.children;
}
// 消费者
function Consumer(props){
return props.children(context._currentValue);
}
return context;
}
const React = {
createContext,
}
export default React;
src/react-dom.js
//把一个类型为 “自定义类组件” 的虚拟DOM转换成真实DOM并返回
function mountClassComponent(vdom, container){
const { type, props, ref } = vdom; //解构类的属性和类的属性对象
const classInstance = new type(props); //创建类的实例
+ // 为类子组件绑定上下文
+ if (type.contextType){
+ classInstance.context = type.contextType._currentValue;
+ }
classInstance.componentWillMount && classInstance.componentWillMount();
...
}
高阶组件
https://www.yuque.com/zhuchaoyang/wrif6k/we9kxl
cloneElement
demo 参见 基础篇 反向继承的demo
src/react.js
/** 克隆
*
* @param {*} oldElement 老虚拟DOM
* @param {*} newProps 新属性
* @param {...any} newChildren 新的儿子们
* @returns 新虚拟DOM
*/
export function cloneElement(oldElement, newProps, ...newChildren){
// 老儿子可能是 undefined、对象、数组
let oldChildren = oldElement.props && oldElement.props.children;
let children = [...(Array.isArray(oldChildren) ? oldChildren : [oldChildren]), ...newChildren]
.filter(item => item !== undefined).map(wrapToVdom);
if (children.length === 0){
children = undefined;
} else if (children.length === 1){
children = children[0];
}
let props = {...oldElement.props, ...newProps, children};
return {...oldElement, props};
}
const React = {
cloneElement,
}
export default React;
shouldComponentUpdate 优化
实现PureComponent、memo
src/Component.js
export class PureComponent extends Component {
// 重写了此方法,只有状态或者属性变化了才会进行更新;否则,不更新。
shouldComponentUpdate(nextProps, nextState){
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
}
}
/** 浅比较 obj1 和 obj2 是否相等
* 只要内存地址一样,就认为是相等的,不一样就不相等
* @param {*} obj1
* @param {*} obj2
* @returns
*/
function shallowEqual(obj1, obj2){
// 如果引用地址一样,就相等,不关心属性变没变
if (obj1 === obj2) return true;
// 任何一方不是对象或者不是null,也不相等
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) return false;
// 属性的数量不相等,也不相等
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1){
if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) return false;
}
return true;
}
src/react.js
// export { Component, PureComponent } from './Component'
import { Component, PureComponent } from './Component'
export { Component, PureComponent };
// 返回组件要有一个功能:属性变了,重新渲染;属性不变,不更新
export function memo(FunctionComponent){
return class extends PureComponent{
render(){
return <FunctionComponent {...this.props}/>
}
}
}
const React = {
Component,
PureComponent,
memo,
}
export default React;