全新的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.basicnpm 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; //取出类组件上次渲染出来的真实DOMlet newDom = createDOM(newVdom); //把新的虚拟DOM转换成真实DOMoldDom.parentNode.replaceChild(newDom, oldDom); //用新的真实DOM替换旧的真实DOMclassInstance.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, //当前是否处于批量更新模式,默认值是falseupdaters: 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更新为最新的stateclassInstance.forceUpdate(); //强制重新更新渲染cbs.forEach(cb => cb()); //更新后运行 setState() 传递的回调函数cbs.length = 0; //回调函数运行后,清空cbs数组}}// 获取最新的state状态getState(){let { classInstance, pendingStates }= this;let { state } = classInstance;// 合并本次更新的所有statependingStates.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; //取出类组件上次渲染出来的真实DOMlet newDom = createDOM(newVdom); //把新的虚拟DOM转换成真实DOMoldDom.parentNode.replaceChild(newDom, oldDom); //用新的真实DOM替换旧的真实DOMclassInstance.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 => onclickaddEvent(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=clicklet { 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 为真实domupdateProps(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){ //如果是Fragmentdom = null;renderChildren(props.children, container);} else {dom = document.createElement(type);if (ref) ref.current = dom; //原生组件绑定的 ref.current 为真实domupdateProps(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); //创建类的实例// 为类组件绑定refif (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; //取出类组件上次渲染出来的真实DOMlet newDom = createDOM(newVdom); //把新的虚拟DOM转换成真实DOMoldDom.parentNode.replaceChild(newDom, oldDom); //用新的真实DOM替换旧的真实DOMclassInstance.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, //当前是否处于批量更新模式,默认值是falseupdaters: 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;// 合并本次更新的所有statependingStates.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; //更新propsclassInstance.state = nextState; //更新stateif (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);// 深度比较新旧两个虚拟DOMlet 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; //解构虚拟DOMlet 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){ //如果是Fragmentdom = null;renderChildren(props.children, container);} else {dom = document.createElement(type);if (ref) ref.current = dom; //原生组件绑定的 ref.current 为真实domupdateProps(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 时需要)// 为类组件绑定refif (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对应的真实DOMexport 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都是nullif (!oldVdom && !newVdom) return;// 老的有,新的没有 => 要刪除此节点if (oldVdom && !newVdom){let oldDOM = findDOM(oldVdom); //先找到此虚拟DOM对应的真实DOMif (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,替换掉老的真实DOMif (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; //复用老组件的真实DOMupdateProps(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();// 深度比较新旧两个虚拟DOMlet 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;

