全新的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(...) 调用。

源代码

  1. import React from 'react';
  2. function App() {
  3. return <h1>Hello World</h1>;
  4. }

旧的 JSX 转换会将上述代码变成普通的 JavaScript 代码:

  1. import React from 'react';
  2. function App() {
  3. return React.createElement('h1', null, 'Hello world');
  4. }

新的 JSX 转换不会将 JSX 转换为 **React.createElement**,而是自动从 React 的 package 中引入新的入口函数并调用。

  1. // 由编译器引入(禁止自己引入!)
  2. import {jsx as _jsx} from 'react/jsx-runtime';
  3. function App() {
  4. return _jsx('h1', { children: 'Hello world' });
  5. }

创建项目

  1. npx create-react-app my-app # 如果安装失败,把yarn卸载掉
  2. cd 01.basic
  3. npm run start

先修改命令,禁用新的jsx转换 cross-env DISABLE_NEW_JSX_TRANSFORM=true
  1. "scripts": {
  2. "start": "react-scripts start",
  3. "start2": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts start",
  4. "build": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts build",
  5. "test": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts test",
  6. "eject": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts eject"
  7. },

src/index.js
  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. let element = (
  4. <div className="title" style={{color: 'red', backgroundColor: 'green'}}>
  5. <span>hello</span>
  6. world
  7. </div>
  8. )
  9. // React.createElement();
  10. console.log(JSON.stringify(element, null, 2));
  11. ReactDOM.render(element, document.getElementById('root'));

虚拟DOM元素结构打印出来,如下

  1. {
  2. "type": "div",
  3. "key": null,
  4. "ref": null,
  5. "props": {
  6. "className": "title",
  7. "style": {
  8. "color": "red",
  9. "backgroundColor": "green"
  10. },
  11. "children": [
  12. {
  13. "type": "span",
  14. "key": null,
  15. "ref": null,
  16. "props": {
  17. "children": "hellp"
  18. },
  19. "_owner": null,
  20. "_store": {}
  21. },
  22. "world"
  23. ]
  24. },
  25. "_owner": null,
  26. "_store": {}
  27. }

public/index.html
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>01.basic</title>
  8. </head>
  9. <body>
  10. <div id="root"></div>
  11. </body>
  12. </html>

实现元素的渲染

  • 父组件先开始挂载,后挂载完成
  • 子组件后开始挂载,先挂载完成

1609002145166.png

src/index.js
  1. import React from './react';
  2. import ReactDOM from './react-dom';
  3. let element = (
  4. <div className="title" style={{color: 'red', backgroundColor: 'green'}}>
  5. <span>hello</span>
  6. world
  7. </div>
  8. )
  9. console.log(element);
  10. console.log(JSON.stringify(element, null, 2));
  11. // React.createElement 不需要自己手动引入去处理上面的jsx语法,
  12. // @babel/preset-env会自动调用这个方法去处理。
  13. ReactDOM.render(element, document.getElementById('root'));

image.png

src/react.js
  1. /**
  2. *
  3. * @param {*} type 元素的类型
  4. * @param {*} config 配置对象
  5. * @param {*} children 元素的儿子或儿子们
  6. */
  7. function createElement(type, config, children){
  8. if (config){
  9. delete config.__source;
  10. delete config.__self;
  11. }
  12. let props = {...config};
  13. if (arguments.length > 3){
  14. children = Array.prototype.slice.call(arguments, 2);
  15. }
  16. props.children = children;
  17. return {
  18. type,
  19. props,
  20. }
  21. }
  22. const React = {
  23. createElement,
  24. }
  25. export default React;

src/react-dom.js
  1. /**
  2. * 1.把vdom虚拟DOM变成真实DOM dom
  3. * 2.把虚拟DOM上的属性更新或者同步到dom上
  4. * 3.把此虚拟DOM的儿子们也都变成真实DOM挂载到自己的dom上 dom.appendChild
  5. * 4.把自己挂载到容器上
  6. * @param {*} vdom 要渲染的虚拟DOM
  7. * @param {*} container 要把虚拟DOM转换真实DOM并插入到哪个容器中去
  8. */
  9. function render (vdom, container){
  10. const dom = createDOM(vdom);
  11. container.appendChild(dom);
  12. }
  13. /**
  14. * 把虚拟DOM变成真实DOM
  15. * @param {*} vdom 虚拟DOM
  16. */
  17. function createDOM(vdom){
  18. // TODO 处理vdom是数字或者字符串的情况,直接返回一个真实的文本节点
  19. if (typeof vdom === 'string' || typeof vdom === 'number'){
  20. return document.createTextNode(vdom);
  21. }
  22. // 否则 它就是一个虚拟DOM对象了,也就是React元素
  23. let { type, props } = vdom;
  24. let dom = document.createElement(type);
  25. // 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
  26. updateProps(dom, props);
  27. // 处理儿子情况
  28. let children = props.children;
  29. if (typeof children === 'string' || typeof children === 'number'){
  30. dom.textContent = children; // 如果只有一个儿子,且是文本,就直接把文本填进dom里即可
  31. } else if (typeof children === 'object' && children.type){
  32. // 如果只有一个儿子,并且这个儿子是一个虚拟DOM元素,就把儿子变成真实DOM插到自己身上
  33. render(children, dom);
  34. } else if (Array.isArray(children)){
  35. reconcileChilren(children, dom); // 如果儿子是一个数组
  36. } else {
  37. dom.textContent = children ? children.toString() : '';
  38. }
  39. // 把真实DOM作为一个dom属性放在虚拟DOM上,为以后更新做准备
  40. // Cannot add property dom, object is not extensible
  41. // vdom.dom = dom;
  42. return dom;
  43. }
  44. /**
  45. * 处理儿子数组情况
  46. * @param {*} childrenVdom 儿子们的虚拟DOM
  47. * @param {*} parentDOM 父亲的真实DOM
  48. */
  49. function reconcileChilren(childrenVdom, parentDOM){
  50. for (let i=0; i<childrenVdom.length; i++){
  51. let childVdom = childrenVdom[i];
  52. render(childVdom, parentDOM);
  53. }
  54. }
  55. /**
  56. * 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
  57. * @param {*} dom 真实DOM
  58. * @param {*} newProps 新属性对象
  59. */
  60. function updateProps(dom, newProps){
  61. for (let key in newProps){
  62. if (key === 'children') continue; //儿子单独处理,不在此处处理
  63. if (key === 'style'){
  64. let styleObj = newProps.style;
  65. for (let attr in styleObj){
  66. dom.style[attr] = styleObj[attr];
  67. }
  68. } else {
  69. dom[key] = newProps[key];
  70. }
  71. }
  72. }
  73. const ReactDOM = {
  74. render,
  75. }
  76. export default ReactDOM;

实现函数组件的渲染

src/index.js
  1. import React from './react';
  2. import ReactDOM from './react-dom';
  3. function App(props){
  4. return (
  5. <div className="title" style={{color: 'red', backgroundColor: 'green'}}>
  6. <span>{props.name}</span>
  7. {props.children}
  8. </div>
  9. )
  10. }
  11. let element = (
  12. <App name="jack">
  13. <span>world</span>
  14. </App>
  15. )
  16. console.log(element);
  17. //React.createElement(App, {name: 'jack'}, <span>world</span>);
  18. ReactDOM.render(element, document.getElementById('root'));

image.png

src/react-dom.js

添加 vdom 的 type 是函数组件时的条件处理

  1. function createDOM(vdom){
  2. ...
  3. let dom;
  4. if (typeof type === 'function'){ //如果是自定义的函数组件
  5. return mountFunctionComponent(vdom);
  6. } else { //如果原生组件
  7. dom = document.createElement(type);
  8. }
  9. ...
  10. }
  11. /** 把一个类型为自定义的函数组件的虚拟DOM转换成真实DOM并返回
  12. *
  13. * @param {*} vdom 类型为“自定义函数组件”的虚拟DOM
  14. */
  15. function mountFunctionComponent(vdom){
  16. let { type, props } = vdom;
  17. let renderVdom = type(props);
  18. return createDOM(renderVdom);
  19. }

实现类组件的渲染

src/index.js
  1. import React from './react';
  2. import ReactDOM from './react-dom';
  3. class App extends React.Component {
  4. render(){
  5. return (
  6. <div className="title" style={{color: 'red'}}>
  7. <span>{this.props.name}</span>
  8. {this.props.children}
  9. </div>
  10. )
  11. }
  12. }
  13. let element = <App name="hello">world</App>;
  14. console.log(element);
  15. ReactDOM.render(element, document.getElementById('root'));

src/Component.js
  1. class Component {
  2. static isReactComponent = true;
  3. constructor(props){
  4. this.props = props;
  5. this.state = {};
  6. }
  7. }
  8. export default Component;

src/react.js

添加 Component 类

  1. import { Component } from './Component'
  2. export { Component };
  3. function createElement(type, config, children){
  4. //...
  5. }
  6. const React = {
  7. createElement,
  8. Component,
  9. }
  10. export default React;

src/react-dom.js

添加 vdom 的 type 是类组件时的条件处理

  1. function createDOM(vdom){
  2. ...
  3. let dom;
  4. if (typeof type === 'function'){
  5. if (type.isReactComponent){ //如果是自定义类组件
  6. return mountClassComponent(vdom);
  7. } else { //如果是自定义的函数组件
  8. return mountFunctionComponent(vdom);
  9. }
  10. } else { //如果原生组件
  11. dom = document.createElement(type);
  12. }
  13. ...
  14. }
  15. /** 把一个类型为 “自定义类组件” 的虚拟DOM转换成真实DOM并返回
  16. *
  17. * 1.创建类组件的实例
  18. * 2.调用类组件实例的render方法,获得返回的虚拟DOM元素,即React元素
  19. * 3.把返回的虚拟DOM转换成真实的DOM进行挂载
  20. * @param {*} vdom 虚拟DOM
  21. */
  22. function mountClassComponent(vdom){
  23. let { type, props } = vdom; //解构类的属性和类的属性对象
  24. let classInstance = new type(props); //创建类的实例
  25. let renderVdom = classInstance.render(); //调用实例方法render,返回要渲染的虚拟DOM对象
  26. let dom = createDOM(renderVdom); //根据虚拟DOM对象,返回真实的DOM对象
  27. classInstance.dom = dom; //为以后类组件的更新,把真实DOM挂载到了类的实例上
  28. return dom;
  29. }

实现组件更新

1609002644954.png

src/index.js
  1. import React from './react';
  2. import ReactDOM from './react-dom';
  3. class Counter extends React.Component {
  4. constructor(props){
  5. super(props);
  6. this.state = {
  7. name: props.name,
  8. number: 0,
  9. }
  10. }
  11. handleClick = () => {
  12. this.setState({
  13. number: this.state.number+1,
  14. })
  15. }
  16. render(){
  17. return (
  18. <div>
  19. <div>{`${this.props.name}: ${this.state.number}`}</div>
  20. <button onClick={this.handleClick}>+</button>
  21. </div>
  22. )
  23. }
  24. }
  25. let element = <Counter name="计数器" />;
  26. ReactDOM.render(element, document.getElementById('root'));

src/Component.js

添加setState实例方法

  1. import { createDOM } from './react-dom.js'
  2. class Component {
  3. static isReactComponent = true;
  4. constructor(props){
  5. this.props = props;
  6. this.state = {};
  7. }
  8. setState(partialState){
  9. let state = this.state;
  10. this.state = {...state, ...partialState};
  11. let newVdom = this.render();
  12. updateClassComponent(this, newVdom);
  13. }
  14. }
  15. /** 更新类组件
  16. *
  17. * @param {*} classInstance
  18. * @param {*} newVdom
  19. */
  20. function updateClassComponent(classInstance, newVdom){
  21. let oldDom = classInstance.dom; //取出类组件上次渲染出来的真实DOM
  22. let newDom = createDOM(newVdom); //把新的虚拟DOM转换成真实DOM
  23. oldDom.parentNode.replaceChild(newDom, oldDom); //用新的真实DOM替换旧的真实DOM
  24. classInstance.dom = newDom;
  25. }
  26. export default Component;

src/react-dom.js

添加一个条件,把虚拟DOM的事件属性更新为真实DOM的事件属性。

  1. function updateProps(dom, newProps){
  2. ...
  3. else if (key.startsWith('on')){
  4. dom[key.toLocaleLowerCase()] = newProps[key]; // 给真实的DOM加事件属性, onClick => onclick
  5. }
  6. ...
  7. }

实现合成事件和批量更新

setState2.jpg

src/index.js

  1. // import React from 'react';
  2. // import ReactDOM from 'react-dom';
  3. import React from './react';
  4. import ReactDOM from './react-dom';
  5. class Counter extends React.Component {
  6. constructor(props){
  7. super(props);
  8. this.state = {
  9. name: props.name,
  10. number: 0,
  11. }
  12. }
  13. handleClick = (e) => {
  14. e.stop();
  15. this.setState({number: this.state.number+1 }, () => {
  16. console.log('callback1', this.state.number);
  17. });
  18. console.log(this.state.number);
  19. this.setState({number: this.state.number+1 });
  20. console.log(this.state.number);
  21. setTimeout(() => {
  22. console.log(this.state.number);
  23. this.setState({number: this.state.number+1 });
  24. console.log(this.state.number);
  25. this.setState({number: this.state.number+1 });
  26. console.log(this.state.number);
  27. }, 1000)
  28. // this.setState(prevState => ({number: prevState.number + 1}), () => {
  29. // console.log('callback', this.state.number); //2
  30. // });
  31. // console.log(this.state.number);
  32. // this.setState(prevState => ({number: prevState.number + 1}));
  33. // console.log(this.state.number);
  34. // setTimeout(() => {
  35. // console.log(this.state.number);
  36. // this.setState(prevState => ({number: prevState.number + 1}));
  37. // console.log(this.state.number);
  38. // this.setState(prevState => ({number: prevState.number + 1}));
  39. // console.log(this.state.number);
  40. // }, 1000)
  41. }
  42. aa = (e) => {
  43. console.log('aa');
  44. }
  45. render(){
  46. return (
  47. <div onClick={this.aa}>
  48. <div>{`${this.props.name}: ${this.state.number}`}</div>
  49. <button onClick={this.handleClick}>+</button>
  50. </div>
  51. )
  52. }
  53. }
  54. let element = <Counter name="计数器" />;
  55. ReactDOM.render(element, document.getElementById('root'));

src/Component.js

  1. import { createDOM } from './react-dom.js'
  2. // 批量更新队列
  3. export let updateQueue = {
  4. isBatchingUpdate: false, //当前是否处于批量更新模式,默认值是false
  5. updaters: new Set(),
  6. batchUpdate(){ // 进行批量更新,之后重置为非批量模式
  7. for (let updater of this.updaters){
  8. updater.updateComponent();
  9. }
  10. this.isBatchingUpdate = false;
  11. }
  12. }
  13. // 更新器
  14. class Updater {
  15. constructor(classInstance){
  16. this.classInstance = classInstance; //类组件的实例
  17. this.pendingStates = []; //等待生效的状态,可能是一个对象,也可能给是一个函数
  18. this.cbs = []; //状态更新后的回调
  19. }
  20. addState(partialState, cb){
  21. // 把该次 setState() 传递的数据和回调函数先存起来
  22. this.pendingStates.push(partialState);
  23. if (typeof cb === 'function') this.cbs.push(cb);
  24. if (updateQueue.isBatchingUpdate){
  25. updateQueue.updaters.add(this); //如果是批量模式,先不更新,缓存起来。本次setState就结束了
  26. } else {
  27. this.updateComponent(); //如果非批量模式,直接更新组件
  28. }
  29. }
  30. // 更新类组件
  31. updateComponent(){
  32. let { classInstance, pendingStates, cbs } = this;
  33. if (pendingStates.length > 0){ //如果有等待更新的状态对象
  34. classInstance.state = this.getState(); //类组件的state更新为最新的state
  35. classInstance.forceUpdate(); //强制重新更新渲染
  36. cbs.forEach(cb => cb()); //更新后运行 setState() 传递的回调函数
  37. cbs.length = 0; //回调函数运行后,清空cbs数组
  38. }
  39. }
  40. // 获取最新的state状态
  41. getState(){
  42. let { classInstance, pendingStates }= this;
  43. let { state } = classInstance;
  44. // 合并本次更新的所有state
  45. pendingStates.forEach(nextState => {
  46. // 如果 nextState 是一个函数的话,传入老状态,返回新状态,再进行合并
  47. if (typeof nextState === 'function') nextState = nextState.call(classInstance, state);
  48. state = {...state, ...nextState};
  49. })
  50. pendingStates.length = 0; //state获取完后,清空 pendingStates 数组
  51. return state;
  52. }
  53. }
  54. class Component {
  55. static isReactComponent = true;
  56. constructor(props){
  57. this.props = props;
  58. this.state = {};
  59. this.updater = new Updater(this);
  60. }
  61. setState(partialState, cb){
  62. this.updater.addState(partialState, cb);
  63. }
  64. // 强制更新渲染
  65. forceUpdate(){
  66. let newVdom = this.render();
  67. updateClassComponent(this, newVdom);
  68. }
  69. }
  70. /** 更新类组件
  71. *
  72. * @param {*} classInstance
  73. * @param {*} newVdom
  74. */
  75. function updateClassComponent(classInstance, newVdom){
  76. let oldDom = classInstance.dom; //取出类组件上次渲染出来的真实DOM
  77. let newDom = createDOM(newVdom); //把新的虚拟DOM转换成真实DOM
  78. oldDom.parentNode.replaceChild(newDom, oldDom); //用新的真实DOM替换旧的真实DOM
  79. classInstance.dom = newDom;
  80. }
  81. export default Component;

src/react-dom.js

  1. import { addEvent } from './event.js'
  2. function updateProps(dom, newProps){
  3. ...
  4. else if (key.startsWith('on')){
  5. // dom[key.toLocaleLowerCase()] = newProps[key]; // 给真实的DOM加事件属性, onClick => onclick
  6. addEvent(dom, key.toLocaleLowerCase(), newProps[key]); //给真实的dom添加委托事件
  7. }
  8. ...
  9. }

src/event.js

  1. import { updateQueue } from './Component.js'
  2. /** 给真实DOM添加事件处理函数
  3. * 什么么要做合成事件?为什么要做事件委托或代理?
  4. * 1.做兼容处理,兼容不同浏览器
  5. * 2.可以在你写的事件处理函数之前和之后做一些事情
  6. *
  7. * @param {*} dom 真实DOM
  8. * @param {*} eventType 事件类型
  9. * @param {*} listener 监听函数
  10. */
  11. export function addEvent(dom, eventType, listener){
  12. let store;
  13. if (dom.store){
  14. store = dom.store;
  15. } else{
  16. dom.store={};
  17. store = dom.store;
  18. }
  19. // let store = dom.store || (dom.store = {});
  20. store[eventType] = listener;
  21. if (!document[eventType]){
  22. // 事件委托,不管你给哪个DOM元素上绑定事件,最后都统一代理到document上去了
  23. document[eventType] = dispatchEvent; //document.onclick = dispatchEvent;
  24. }
  25. }
  26. //创建一个单例的合成event对象,这个也是react包装后返回的event对象
  27. let syntheticEvent = {
  28. // 阻止冒泡
  29. stopping: false,
  30. stop(){
  31. this.stopping = true;
  32. }
  33. };
  34. // document的事件委托函数
  35. function dispatchEvent(event){
  36. // 事件源target=button那个DOM元素; 类型type=click
  37. let { target, type } = event;
  38. let eventType = `on${type}`;
  39. updateQueue.isBatchingUpdate = true; // 把列队设置为批量更新模式
  40. createSyntheticEvent(event); //包装event对象
  41. while (target){
  42. let { store } = target;
  43. let listener = store && store[eventType];
  44. listener && listener.call(target, syntheticEvent);
  45. // 如果调用了 e.stop() 阻止了冒泡,就打断
  46. if (syntheticEvent.stopping){
  47. syntheticEvent.stopping = false;
  48. break;
  49. }
  50. //如果父节点也绑定了事件, 继续向上冒泡执行
  51. target = target.parentNode;
  52. }
  53. // 事件函数调用之后,清空 syntheticEvent 对象
  54. for (let key in event){
  55. syntheticEvent[key] = null;
  56. }
  57. updateQueue.batchUpdate(); // 进行批量更新,之后重置为非批量模式
  58. }
  59. function createSyntheticEvent(nativeEvent){
  60. for (let key in nativeEvent){
  61. syntheticEvent[key] = nativeEvent[key];
  62. }
  63. }

实现 ref

forwardRef 的实现见 hook源码篇

src/index.js
  1. import React from './react';
  2. import ReactDOM from './react-dom';
  3. class Form extends React.Component {
  4. refTextInput;
  5. constructor(props){
  6. super(props);
  7. this.refTextInput = React.createRef();
  8. }
  9. getFocus = () => {
  10. let refTextInput = this.refTextInput.current; //TextInput组件实例
  11. refTextInput.getFocus();
  12. }
  13. render(){
  14. return (
  15. <div>
  16. <TextInput ref={this.refTextInput}></TextInput>
  17. <button onClick={this.getFocus}>获得焦点</button>
  18. </div>
  19. )
  20. }
  21. }
  22. class TextInput extends React.Component {
  23. refInput;
  24. constructor(props){
  25. super(props);
  26. this.refInput = React.createRef();
  27. }
  28. getFocus = () => {
  29. this.refInput.current.focus();
  30. }
  31. render(){
  32. return <input ref={this.refInput} />
  33. }
  34. }
  35. ReactDOM.render(<Form />, document.getElementById('root'));

src/react.js
  1. function createElement(type, config, children){
  2. + let ref;
  3. if (config){
  4. delete config.__source;
  5. delete config.__self;
  6. + ref = config.ref;
  7. + delete config.ref;
  8. key = config.key;
  9. delete config.key;
  10. }
  11. let props = {...config};
  12. if (arguments.length > 3){
  13. children = Array.prototype.slice.call(arguments, 2);
  14. }
  15. props.children = children;
  16. //ref、key等都是react原生属性,不属于props属性,所以要单拎出来
  17. return {
  18. type,
  19. props,
  20. + ref,
  21. }
  22. }
  23. +function createRef(initialValue){
  24. + return {current: null};
  25. +}
  26. const React = {
  27. createElement,
  28. Component,
  29. + createRef,
  30. }
  31. export default React;

src/react-dom.js
  1. export function createDOM(vdom){
  2. ...
  3. + let { type, props, ref } = vdom;
  4. let dom; //创建真实dom对象
  5. if (typeof type === 'function'){
  6. if (type.isReactComponent){ //如果是自定义类组件
  7. return mountClassComponent(vdom);
  8. } else { //自定义的函数组件
  9. + 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()?')
  10. return mountFunctionComponent(vdom);
  11. }
  12. } else { //原生组件
  13. dom = document.createElement(type);
  14. + if (ref) ref.current = dom; //原生组件绑定的 ref.current 为真实dom
  15. updateProps(dom, props); // 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
  16. renderChildren(props.children, dom); // 挂载父节点的儿子或儿子们
  17. }
  18. return dom;
  19. }
  20. function mountClassComponent(vdom){
  21. + let { type, props, ref } = vdom; //解构类的属性和类的属性对象
  22. let classInstance = new type(props); //创建类的实例
  23. // 为类组件绑定ref
  24. + if (typeof ref === 'function'){ //<App ref={(node) => {this.refApp = node}} />
  25. + ref(classInstance);
  26. + } else {
  27. + if (ref){ //this.refApp = React.createRef() <App ref={this.refApp} />
  28. + ref.current = classInstance;
  29. + classInstance.ref = ref;
  30. + }
  31. + }
  32. let renderVdom = classInstance.render(); //调用实例方法render,返回要渲染的虚拟DOM对象
  33. let dom = createDOM(renderVdom); //根据虚拟DOM对象,返回真实的DOM对象
  34. classInstance.dom = dom; //为以后类组件的更新,把真实DOM挂载到了类的实例上
  35. return dom;
  36. }

实现 React.Fragment

React.Fragment 等同于 <></>

src/index.js
  1. import React from './react';
  2. import ReactDOM from './react-dom';
  3. class Form extends React.Component {
  4. refTextInput;
  5. constructor(props){
  6. super(props);
  7. }
  8. render(){
  9. return (
  10. <React.Fragment>
  11. <div className="a">hello</div>
  12. <span>world</span>
  13. <><p>today</p></>
  14. </React.Fragment>
  15. )
  16. }
  17. }
  18. ReactDOM.render(<Form />, document.getElementById('root'));

src/constants.js
  1. export const REACT_FRAGMENT = Symbol('react.fragment');

src/react.js
  1. import { REACT_FRAGMENT } from './constants'
  2. function createElement(type, config, children){
  3. + if (type.name === 'Fragment') type = REACT_FRAGMENT;
  4. }
  5. export function Fragment(props){
  6. return <>{props.children}</>
  7. }
  8. const React = {
  9. Fragment,
  10. }

src/react-dom.js
  1. import { addEvent } from './event.js'
  2. +import { REACT_TEXT, REACT_FRAGMENT } from './constants'
  3. function render (vdom, container){
  4. const dom = createDOM(vdom, container);
  5. + if (dom) container.appendChild(dom);
  6. }
  7. /** 把虚拟DOM变成真实DOM
  8. *
  9. * @param {*} vdom 虚拟DOM
  10. */
  11. export function createDOM(vdom, container){
  12. // TODO 处理vdom是数字或者字符串的情况,直接返回一个真实的文本节点。
  13. if (typeof vdom === 'string' || typeof vdom === 'number'){
  14. return document.createTextNode(vdom);
  15. }
  16. // 否则 它就是一个虚拟DOM对象了,也就是React元素
  17. let { type, props, ref } = vdom;
  18. let dom; //创建真实dom对象
  19. if (typeof type === 'function'){
  20. if (type.isReactComponent){ //如果是自定义类组件
  21. + return mountClassComponent(vdom, container);
  22. } else { //自定义的函数组件
  23. 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()?')
  24. + return mountFunctionComponent(vdom, container);
  25. }
  26. } else { //原生组件
  27. + if (type === undefined || type === REACT_FRAGMENT){ //如果是Fragment
  28. dom = null;
  29. renderChildren(props.children, container);
  30. } else {
  31. dom = document.createElement(type);
  32. if (ref) ref.current = dom; //原生组件绑定的 ref.current 为真实dom
  33. updateProps(dom, props); // 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
  34. renderChildren(props.children, dom); // 挂载父节点的儿子或儿子们
  35. }
  36. }
  37. return dom;
  38. }
  39. //把一个类型为 “自定义函数组件” 的虚拟DOM转换成真实DOM并返回
  40. +function mountFunctionComponent(vdom, container){
  41. let { type, props } = vdom;
  42. let renderVdom = type(props);
  43. + return createDOM(renderVdom, container);
  44. }
  45. //把一个类型为 “自定义类组件” 的虚拟DOM转换成真实DOM并返回
  46. +function mountClassComponent(vdom, container){
  47. let { type, props, ref } = vdom; //解构类的属性和类的属性对象
  48. let classInstance = new type(props); //创建类的实例
  49. let renderVdom = classInstance.render(); //调用实例方法render,返回要渲染的虚拟DOM对象
  50. + let dom = createDOM(renderVdom, container); //根据虚拟DOM对象,返回真实的DOM对象
  51. if (ref) ref.current = classInstance; //类组件绑定的 ref.current 为类的实例
  52. classInstance.dom = dom; //为以后类组件的更新,把真实DOM挂载到了类的实例上
  53. return dom;
  54. }

实现基本生命周期

React基础(源码篇) - 图6

src/index.js

  1. import React from './react';
  2. import ReactDOM from './react-dom';
  3. class Counter extends React.Component {
  4. static defaultProps = { //设置初始属性对象
  5. name: '计数器',
  6. }
  7. constructor(props){
  8. super(props);
  9. this.state = { number: 0 };
  10. console.log('Counter 1.constructor 初始化属性和状态对象');
  11. }
  12. componentWillMount(){
  13. console.log('Counter 2.componentWillMount 组件将要挂载');
  14. }
  15. componentDidMount(){
  16. console.log('Counter 4.componentDidMount 组件完成挂载');
  17. }
  18. handleClick = () => {
  19. console.log('click add button!');
  20. this.setState({number: this.state.number + 1});
  21. }
  22. // react可以shouldComponentUpdate方法中优化,PureComponent 可以帮我们做这件事
  23. shouldComponentUpdate(nextProps, nextState){ //参数:下一次属性;下一次状态
  24. console.log('Counter 5.shouldComponentUpdate 决定组件是否需要更新?');
  25. return nextState.number % 2 === 0; //此函数只有返回了true,才会调用 render 方法
  26. }
  27. componentWillUpdate(nextProps, nextState){
  28. console.log('Counter 6.componentWillUpdate 组件将要更新');
  29. }
  30. componentDidUpdate(prevProps, prevState){
  31. console.log('Counter 7.componentDidUpdate 组件完成更新');
  32. }
  33. render(){
  34. console.log('Counter 3.render 重新计算新的虚拟DOM');
  35. let st = this.state;
  36. return (
  37. <div>
  38. <p>{st.number}</p>
  39. <button onClick={this.handleClick}>+</button>
  40. </div>
  41. )
  42. }
  43. }
  44. ReactDOM.render(<Counter />, document.getElementById('root'));
  45. // Counter 1.constructor 初始化属性和状态对象
  46. // Counter 2.componentWillMount 组件将要挂载
  47. // Counter 3.render 重新计算新的虚拟DOM
  48. // Counter 4.componentDidMount 组件完成挂载
  49. // click add button!
  50. // Counter 5.shouldComponentUpdate 决定组件是否需要更新?
  51. // click add button!
  52. // Counter 5.shouldComponentUpdate 决定组件是否需要更新?
  53. // Counter 6.componentWillUpdate 组件将要更新
  54. // Counter 3.render 重新计算新的虚拟DOM
  55. // Counter 7.componentDidUpdate 组件完成更新

src/react-dom.js

  1. function render (vdom, container){
  2. const dom = createDOM(vdom, container);
  3. if (dom) container.appendChild(dom);
  4. + dom.componentDidMount && dom.componentDidMount();
  5. }
  6. // 把一个类型为 “自定义类组件” 的虚拟DOM转换成真实DOM并返回
  7. function mountClassComponent(vdom, container){
  8. let { type, props, ref } = vdom; //解构类的属性和类的属性对象
  9. let classInstance = new type(props); //创建类的实例
  10. // 为类组件绑定ref
  11. if (typeof ref === 'function'){ //<App ref={(node) => {this.refApp = node}} />
  12. ref(classInstance);
  13. } else {
  14. if (ref){ //this.refApp=React.createRef() <App ref={this.refApp} />
  15. ref.current = classInstance;
  16. classInstance.ref = ref;
  17. }
  18. }
  19. + if (classInstance.componentWillMount) classInstance.componentWillMount();
  20. let renderVdom = classInstance.render(); //调用实例方法render,返回要渲染的虚拟DOM对象
  21. let dom = createDOM(renderVdom, container); //根据虚拟DOM对象,返回真实的DOM对象
  22. + if (classInstance.componentDidMount){
  23. + dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
  24. + }
  25. classInstance.dom = dom; //为以后类组件的更新,把真实DOM挂载到了类的实例上
  26. return dom;
  27. }

src/Component.js

  1. // 更新器
  2. class Updater {
  3. ...
  4. addState(partialState, cb){
  5. // 把该次 setState() 传递的数据和回调函数先存起来
  6. this.pendingStates.push(partialState);
  7. if (typeof cb === 'function') this.cbs.push(cb);
  8. + this.emitUpdate();
  9. }
  10. // 发布更新(一个组件不管是属性变了,还是状态变了,都会更新)
  11. emitUpdate(newProps){
  12. if (updateQueue.isBatchingUpdate){
  13. updateQueue.updaters.add(this); //如果是批量模式,先不更新,缓存起来。本次setState就结束了
  14. } else {
  15. this.updateComponent(); //如果非批量模式,直接更新组件
  16. }
  17. }
  18. // 更新类组件
  19. updateComponent(){
  20. let { classInstance, pendingStates, cbs } = this;
  21. if (pendingStates.length > 0){ //如果有等待更新的状态对象
  22. shouldUpdate(classInstance, this.getState());
  23. // cbs.forEach(cb => cb()); //更新后运行 setState() 传递的回调函数
  24. // cbs.length = 0; //回调函数运行后,清空cbs数组
  25. }
  26. }
  27. ...
  28. }
  29. /** 是否应该更新
  30. *
  31. * @param {*} classInstance 组件实例
  32. * @param {*} nextState 新的状态
  33. */
  34. function shouldUpdate(classInstance, nextState){
  35. //不管组件要不要刷新(是否执行render),其实组件的state属性一定会改变
  36. classInstance.state = nextState;
  37. // 如果有 shouldComponentUpdate 方法,并且该方法返回值为 false,到这就结束了。
  38. let noUpdate = classInstance.shouldComponentUpdate && !classInstance.shouldComponentUpdate(classInstance.props, classInstance.state);
  39. if (noUpdate) return;
  40. classInstance.forceUpdate(); //强制重新更新渲染
  41. }
  42. class Component {
  43. ...
  44. // 强制更新渲染
  45. forceUpdate(){
  46. + if (this.componentWillUpdate) this.componentWillUpdate();
  47. let newVdom = this.render();
  48. updateClassComponent(this, newVdom);
  49. + if (this.componentDidUpdate) this.componentDidUpdate();
  50. }
  51. }
  52. // 更新类组件
  53. function updateClassComponent(classInstance, newVdom){
  54. let oldDom = classInstance.dom; //取出类组件上次渲染出来的真实DOM
  55. let newDom = createDOM(newVdom); //把新的虚拟DOM转换成真实DOM
  56. oldDom.parentNode.replaceChild(newDom, oldDom); //用新的真实DOM替换旧的真实DOM
  57. classInstance.dom = newDom;
  58. }

实现完整生命周期

counterdomdiff.zip
counterdomdiff.jpg

dom-diff 最基本算法
  • 按索引一一对比,如果类型相同,则复用老的DOM节点,更新属性即可。
  • 如果类型不同,删除老的DOM节点,添加新的DOM节点。
  • 如果老节点为null,新的节点有,新建新的DOM节点,并插入。
  • 如果老节点有,新节点为null, 移除老的DOM节点。

src/index.js

  1. import React from './react';
  2. import ReactDOM from './react-dom';
  3. class Counter extends React.Component {
  4. static defaultProps = { //设置初始属性对象
  5. name: '计数器',
  6. }
  7. constructor(props){
  8. super(props);
  9. this.state = { number: 0 };
  10. console.log('Counter 1.constructor 初始化属性和状态对象');
  11. }
  12. componentWillMount(){
  13. console.log('Counter 2.componentWillMount 组件将要挂载');
  14. }
  15. componentDidMount(){
  16. console.log('Counter 4.componentDidMount 组件完成挂载');
  17. }
  18. handleClick = () => {
  19. console.log('click add button!');
  20. this.setState({number: this.state.number + 1}, () =>{
  21. console.log(this.state);
  22. });
  23. }
  24. // react可以shouldComponentUpdate方法中优化,PureComponent 可以帮我们做这件事
  25. shouldComponentUpdate(nextProps, nextState){ //参数:下一次属性;下一次状态
  26. console.log('Counter 5.shouldComponentUpdate 决定组件是否需要更新?');
  27. return nextState.number % 2 === 0; //此函数只有返回了true,才会调用 render 方法
  28. }
  29. componentWillUpdate(nextProps, nextState){ //参数:下一次属性;下一次状态
  30. console.log('Counter 6.componentWillUpdate 组件将要更新');
  31. }
  32. componentDidUpdate(prevProps, prevState){ //参数:上一次属性;上一次状态
  33. console.log('Counter 7.componentDidUpdate 组件完成更新', prevProps, prevState);
  34. }
  35. render(){
  36. console.log('Counter 3.render 重新计算新的虚拟DOM');
  37. let st = this.state;
  38. return (
  39. <div id={`counter-${st.number}`} >
  40. <p>{st.number}</p>
  41. {st.number === 4 ? null : <ChildCounter count={st.number} />}
  42. <button onClick={this.handleClick}>+</button>
  43. <FunctionCounter count={st.number} />
  44. </div>
  45. )
  46. }
  47. }
  48. class ChildCounter extends React.Component {
  49. componentWillUnmount(){
  50. console.log('ChildCounter 8.componentWillUnmount 组件将要卸载');
  51. }
  52. componentWillMount(){
  53. console.log('ChildCounter 1.componentWillMount 组件将要挂载');
  54. }
  55. componentDidMount(){
  56. console.log('ChildCounter 3.componentDidMount 组件完成挂载');
  57. }
  58. // 第一次不会执行,之后属性更新时才会执行(即:组件挂载时不执行,组件挂载后,有属性变化,才执行)
  59. componentWillReceiveProps(newProps){
  60. console.log('ChildCounter 4.componentWillReceiveProps 组件将要接收到新的属性');
  61. }
  62. shouldComponentUpdate(nextProps, nextState){
  63. console.log('ChildCounter 5.shouldComponentUpdate 决定组件是否需要更新?');
  64. return nextProps.count % 3 === 0; //3的倍数就更新,否则就不更新
  65. }
  66. componentWillUpdate(){
  67. console.log('ChildCounter 6.componentWillUpdate 组件将要更新');
  68. }
  69. componentDidUpdate(){
  70. console.log('ChildCounter 7.componentDidUpdate 组件完成更新');
  71. }
  72. render(){
  73. console.log('ChildCounter 2.render');
  74. return (
  75. <div id="sub-counter">{`ChildCounter: ${this.props.count}`}</div>
  76. )
  77. }
  78. }
  79. let FunctionCounter = (props) => <div id="counter-function">{props.count}</div>
  80. ReactDOM.render(<Counter />, document.getElementById('root'));

src/constants.js

  1. export const REACT_TEXT = Symbol('REACT_TEXT');
  2. export const REACT_FRAGMENT = Symbol('react.fragment');

src/utils.js

  1. import { REACT_TEXT } from './constants'
  2. /** 为了方便后边的dom-diff,把文本节点进行单独封装或做一个标识
  3. * 不管原来是什么,都全部包装成React元素的形式
  4. * @param {*} element 可能是一个React元素,也可以是一个字符串或数字
  5. * @returns
  6. */
  7. export function wrapToVdom(element){
  8. return (typeof element === 'string' || typeof element === 'number')
  9. ? {type: REACT_TEXT, props: {content: element}}: element;
  10. }

src/react.js

  1. import { Component } from './Component'
  2. import { wrapToVdom } from './utils'
  3. import { REACT_FRAGMENT } from './constants'
  4. export { Component };
  5. /**
  6. * @param {*} type 元素的类型
  7. * @param {*} config 配置对象
  8. * @param {*} children 元素的儿子或儿子们
  9. */
  10. function createElement(type, config, children){
  11. let ref;
  12. if (type.name === 'Fragment') type = REACT_FRAGMENT;
  13. if (config){
  14. delete config.__source;
  15. delete config.__self;
  16. ref = config.ref;
  17. }
  18. let props = {...config};
  19. //为了方便进行dom-diff,用 wrapToVdom 把文本节点进行一个标识,方便判断
  20. if (arguments.length > 3){
  21. props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
  22. } else {
  23. props.children = wrapToVdom(children);
  24. }
  25. return { type, props, ref };
  26. }
  27. export function createRef(){
  28. return {current: null};
  29. }
  30. export function Fragment(props){
  31. return <>{props.children}</>
  32. }
  33. const React = {
  34. createElement,
  35. Component,
  36. createRef,
  37. Fragment,
  38. }
  39. export default React;

src/Component.js

  1. import { compareTwoVdom, findDOM } from './react-dom.js'
  2. // 批量更新队列
  3. export let updateQueue = {
  4. isBatchingUpdate: false, //当前是否处于批量更新模式,默认值是false
  5. updaters: new Set(),
  6. add(updater){
  7. this.updaters.add(updater);
  8. },
  9. batchUpdate(){ // 进行批量更新,之后重置为非批量模式
  10. this.isBatchingUpdate = false;
  11. for (let updater of this.updaters){
  12. updater.emitUpdate();
  13. }
  14. updateQueue.updaters.clear();
  15. }
  16. }
  17. // 更新器
  18. class Updater {
  19. constructor(classInstance){
  20. this.classInstance = classInstance; //类组件的实例
  21. this.pendingStates = []; //等待生效的状态,可能是一个对象,也可能给是一个函数
  22. this.cbs = []; //状态更新后的回调
  23. }
  24. addState(partialState, cb){
  25. // 把该次 setState() 传递的数据和回调函数先存起来
  26. this.pendingStates.push(partialState);
  27. if (typeof cb === 'function') this.cbs.push(cb);
  28. if (updateQueue.isBatchingUpdate){
  29. updateQueue.add(this); //如果是批量模式,先不更新,缓存起来。本次setState就结束了
  30. } else {
  31. this.emitUpdate(); //如果非批量模式,直接更新
  32. }
  33. }
  34. // 发布更新(一个组件不管是属性变了,还是状态变了,都会更新)
  35. emitUpdate(nextProps){
  36. let { classInstance, pendingStates, cbs } = this;
  37. if (nextProps || pendingStates.length > 0){ //如果有等待更新的状态对象
  38. shouldUpdate(classInstance, nextProps, this.getState());
  39. // 不管是否要更新,结束后,setState的回调函数都要运行
  40. if (cbs.length > 0){
  41. cbs.forEach(cb => cb()); //更新后运行 setState() 传递的回调函数
  42. cbs.length = 0; //回调函数运行后,清空cbs数组
  43. }
  44. }
  45. }
  46. // 获取最新的state状态
  47. getState(){
  48. let { classInstance, pendingStates }= this;
  49. let { state } = classInstance;
  50. // 合并本次更新的所有state
  51. pendingStates.forEach(nextState => {
  52. // 如果 nextState 是一个函数的话,传入老状态,返回新状态,再进行合并
  53. if (typeof nextState === 'function') nextState = nextState.call(classInstance, state);
  54. state = {...state, ...nextState};
  55. })
  56. pendingStates.length = 0; //state获取完后,清空 pendingStates 数组
  57. return state;
  58. }
  59. }
  60. /** 是否应该更新
  61. *
  62. * @param {*} classInstance 组件实例
  63. * @param {*} nextState 新的状态
  64. */
  65. function shouldUpdate(classInstance, nextProps, nextState){
  66. let willUpdate = true;
  67. // 如果有 shouldComponentUpdate 方法,并且该方法返回值为 false,就只更新属性和状态,不调用render进行渲染了
  68. if(classInstance.shouldComponentUpdate &&!classInstance.shouldComponentUpdate(nextProps, nextState)){
  69. willUpdate = false;
  70. }
  71. // 调用 组件将要更新 生命周期函数
  72. if(willUpdate && classInstance.componentWillUpdate) classInstance.componentWillUpdate(nextProps, nextState);
  73. classInstance.prevProps = classInstance.props;
  74. classInstance.prevState = classInstance.state;
  75. // 不管组件要不要刷新(是否执行render),其实组件的state属性一定会改变
  76. if(nextProps) classInstance.props = nextProps; //更新props
  77. classInstance.state = nextState; //更新state
  78. if (willUpdate) classInstance.forceUpdate();
  79. }
  80. class Component {
  81. static isReactComponent = true; //是否类组件
  82. constructor(props){
  83. this.props = {...this.constructor.defaultProps, ...props};
  84. this.state = {};
  85. this.updater = new Updater(this);
  86. }
  87. setState(partialState, cb){
  88. this.updater.addState(partialState, cb);
  89. }
  90. // 强制更新渲染
  91. forceUpdate(){
  92. let newRenderVdom = this.render();
  93. let oldDom = findDOM(this.oldRenderVdom);
  94. // 深度比较新旧两个虚拟DOM
  95. let currentRenderVdom = compareTwoVdom(oldDom.parentNode, this.oldRenderVdom, newRenderVdom);
  96. this.oldRenderVdom = currentRenderVdom;
  97. this.componentDidUpdate && this.componentDidUpdate(this.prevProps, this.prevState);
  98. }
  99. }
  100. export default Component;

src/react-dom.js

  1. import { addEvent } from './event.js'
  2. import { REACT_TEXT, REACT_FRAGMENT } from './constants'
  3. /** 渲染vdom虚拟DOM
  4. *
  5. * 1.把vdom虚拟DOM变成真实DOM dom
  6. * 2.把虚拟DOM上的属性更新或者同步到dom上
  7. * 3.把此虚拟DOM的儿子们也都变成真实DOM挂载到自己的dom上 dom.appendChild
  8. * 4.把自己挂载到容器上
  9. * @param {*} vdom 要渲染的虚拟DOM
  10. * @param {*} container 要把虚拟DOM转换真实DOM并插入到哪个父容器(父真实DOM节点)中去
  11. */
  12. // 给根容器挂载的时候
  13. function render (vdom, container){
  14. mount(vdom, container);
  15. }
  16. // 挂载
  17. function mount (vdom, container){
  18. if (!vdom) return;
  19. const dom = createDOM(vdom, container);
  20. if (dom){
  21. container.appendChild(dom);
  22. dom.componentDidMount && dom.componentDidMount();
  23. }
  24. }
  25. /** 把虚拟DOM变成真实DOM
  26. *
  27. * @param {*} vdom 虚拟DOM
  28. */
  29. export function createDOM(vdom, container){
  30. let { type, props, ref } = vdom; //解构虚拟DOM
  31. let dom; //创建真实dom对象
  32. if (type === REACT_TEXT){ // 字符串或文本
  33. dom = document.createTextNode(props.content);
  34. } else if (typeof type === 'function'){
  35. if (type.isReactComponent){ //类组件
  36. return mountClassComponent(vdom, container);
  37. } else { //函数组件
  38. 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()?')
  39. return mountFunctionComponent(vdom, container);
  40. }
  41. } else { //原生组件(React元素)
  42. if (type === undefined || type === REACT_FRAGMENT){ //如果是Fragment
  43. dom = null;
  44. renderChildren(props.children, container);
  45. } else {
  46. dom = document.createElement(type);
  47. if (ref) ref.current = dom; //原生组件绑定的 ref.current 为真实dom
  48. updateProps(dom, {}, props); // 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
  49. renderChildren(props.children, dom); // 挂载父节点的儿子或儿子们
  50. }
  51. }
  52. // 把真实DOM作为一个dom属性放在虚拟DOM上(dom-diff 时需要)
  53. // 当根据一个vdom创建出来一个真实DOM之后,真实DOM挂载到vdom.dom属性上
  54. vdom.dom = dom;
  55. return dom;
  56. }
  57. /** 挂载父节点的儿子或儿子们
  58. *
  59. * @param {*} children 虚拟的儿子或儿子们元素
  60. * @param {*} container 父容器(父真实DOM节点)
  61. */
  62. function renderChildren (children, container){
  63. if (typeof children === 'object' && children.type){ // 如果只有一个儿子
  64. mount(children, container);
  65. } else if (Array.isArray(children)){ // 如果有多个儿子
  66. reconcileChilren(children, container);
  67. }
  68. }
  69. /** 处理vdom儿子是数组情况
  70. *
  71. * @param {*} childrenVdom 儿子们的虚拟DOM
  72. * @param {*} container 父容器(父真实DOM节点)
  73. */
  74. function reconcileChilren(childrenVdom, container){
  75. childrenVdom.forEach(childVdom => {
  76. mount(childVdom, container);
  77. })
  78. }
  79. /** 把一个类型为 “自定义函数组件” 的虚拟DOM转换成真实DOM并返回
  80. *
  81. * @param {*} vdom 虚拟DOM
  82. */
  83. function mountFunctionComponent(vdom, container){
  84. const { type, props } = vdom;
  85. const renderVdom = type(props);
  86. vdom.oldRenderVdom = renderVdom; //把 vdom的渲染DOM 挂载到 vdom 上 (dom-diff 时需要)
  87. return createDOM(renderVdom, container);
  88. }
  89. /** 把一个类型为 “自定义类组件” 的虚拟DOM转换成真实DOM并返回
  90. *
  91. * 1.创建类组件的实例
  92. * 2.调用类组件实例的render方法,获得返回的虚拟DOM元素,即React元素
  93. * 3.把返回的虚拟DOM转换成真实的DOM进行挂载
  94. * @param {*} vdom 虚拟DOM
  95. */
  96. function mountClassComponent(vdom, container){
  97. const { type, props, ref } = vdom; //解构类的属性和类的属性对象
  98. const classInstance = new type(props); //创建类的实例
  99. vdom.classInstance = classInstance; //把 类的实例 挂载到 vdom 上(dom-diff 时需要)
  100. // 为类组件绑定ref
  101. if (typeof ref === 'function'){ //<App ref={(node) => {this.refApp = node}} />
  102. ref(classInstance);
  103. } else {
  104. if (ref){ //this.refApp=React.createRef() <App ref={this.refApp} />
  105. ref.current = classInstance;
  106. classInstance.ref = ref;
  107. }
  108. }
  109. classInstance.componentWillMount && classInstance.componentWillMount();
  110. const renderVdom = classInstance.render(); //调用实例方法render,返回要渲染的虚拟DOM对象
  111. const dom = createDOM(renderVdom, container); //根据虚拟DOM对象,返回真实的DOM对象
  112. //把 vdom的渲染DOM 挂载到 vdom 和 类的实例 上(dom-diff 时需要)
  113. classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;
  114. if (classInstance.componentDidMount){
  115. dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
  116. }
  117. return dom;
  118. }
  119. /** 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
  120. *
  121. * @param {*} dom 真实DOM
  122. * @param {*} oldProps 旧属性对象
  123. * @param {*} newProps 新属性对象
  124. */
  125. function updateProps(dom, oldProps={}, newProps){
  126. for (let key in newProps){
  127. if (key === 'children') continue; //儿子单独处理,不在此处处理
  128. if (key === 'style'){
  129. let styleObj = newProps.style;
  130. for (let attr in styleObj){
  131. dom.style[attr] = styleObj[attr];
  132. }
  133. } else if (key.startsWith('on')){
  134. addEvent(dom, key.toLocaleLowerCase(), newProps[key]); //给真实的dom添加委托事件
  135. } else {
  136. dom[key] = newProps[key];
  137. }
  138. }
  139. for (let key in oldProps){
  140. if (!newProps.hasOwnProperty(key)){
  141. dom[key] = '';
  142. }
  143. }
  144. }
  145. // 查找此虚拟DOM对应的真实DOM
  146. export function findDOM(vdom){
  147. let { type } = vdom;
  148. let dom;
  149. if (typeof type === 'function'){ //如果是 类组件 或者 函数组件
  150. //类组件render后,有可能还是还是个类组件或函数组件,所有要递归处理
  151. dom = findDOM(vdom.oldRenderVdom);
  152. } else { //原生组件
  153. dom = vdom.dom;
  154. }
  155. return dom;
  156. }
  157. /** 比较新老虚拟DOM(dom-diff)
  158. *
  159. * @param {*} container 当前组件挂载的父容器(父真实DOM节点)
  160. * @param {*} oldVdom 上一次的虚拟DOM
  161. * @param {*} newVdom 本次的虚拟DOM
  162. */
  163. export function compareTwoVdom(container, oldVdom, newVdom, nextDOM){
  164. // 如果老的虚拟DOM和新的虚拟DOM都是null
  165. if (!oldVdom && !newVdom) return;
  166. // 老的有,新的没有 => 要刪除此节点
  167. if (oldVdom && !newVdom){
  168. let oldDOM = findDOM(oldVdom); //先找到此虚拟DOM对应的真实DOM
  169. if (oldDOM) container.removeChild(oldDOM);
  170. if (oldVdom.classInstance && oldVdom.classInstance.componentWillUnmount){
  171. oldVdom.classInstance.componentWillUnmount();
  172. }
  173. return;
  174. }
  175. // 老的没有,新的有 => 要新建DOM节点,并添加
  176. if (!oldVdom && newVdom){
  177. let newDOM = createDOM(newVdom, container);
  178. if (nextDOM){
  179. container.insertBefore(newDOM, nextDOM); //如果有下一个弟弟节点,插到其前面
  180. } else {
  181. container.appendChild(newDOM);
  182. }
  183. newDOM.componentDidMount && newDOM.componentDidMount();
  184. return newVdom;
  185. }
  186. // 新旧都有虚拟DOM,但是类型type不相同 => 直接替换节点
  187. if (oldVdom && newVdom && oldVdom.type !== newVdom.type){
  188. let oldDOM = findDOM(oldVdom);
  189. let newDOM = createDOM(newVdom);
  190. container.replaceChild(newDOM, oldDOM); //直接用新的真实DOM,替换掉老的真实DOM
  191. if (oldVdom.classInstance && oldVdom.classInstance.componentWillUnmount){
  192. oldVdom.classInstance.componentWillUnmount();
  193. }
  194. newDOM.componentDidMount && newDOM.componentDidMount();
  195. return newVdom;
  196. }
  197. // 新旧都有虚拟DOM,并且类型type也相同
  198. if (oldVdom && newVdom && oldVdom.type === newVdom.type){
  199. deepCompare(container, oldVdom, newVdom);
  200. return newVdom;
  201. }
  202. }
  203. /** 新旧都有虚拟DOM,并且类型type也相同,进行深度的比较(dom-diff)
  204. *
  205. * @param {*} oldVdom 老的虚拟DOM
  206. * @param {*} newVdom 新的虚拟DOM
  207. */
  208. function deepCompare(container, oldVdom, newVdom){
  209. // 文本节点
  210. if (oldVdom.type === REACT_TEXT){
  211. let currentDOM = newVdom.dom = oldVdom.dom; //复用老组件的真实DOM节点,直接更换其文本
  212. currentDOM.textContent = newVdom.props.content;
  213. } else if (typeof oldVdom.type === 'string'){ //原生组件
  214. let currentDOM = newVdom.dom = oldVdom.dom; //复用老组件的真实DOM
  215. updateProps(currentDOM, oldVdom.props, newVdom.props); //更新自己的属性
  216. updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children); //更新儿子们
  217. } else if (typeof oldVdom.type === 'function'){
  218. if (oldVdom.type.isReactComponent){ //老的和新的都是类组件,进行类组件更新
  219. updateClassComponent(oldVdom, newVdom);
  220. } else { //老的和新的都是函数组件,进行函数组件更新
  221. updateFunctionComponent(container, oldVdom, newVdom);
  222. }
  223. } else if (oldVdom.type === REACT_FRAGMENT){
  224. updateChildren(container, oldVdom.props.children, newVdom.props.children); //更新儿子们
  225. }
  226. }
  227. /** 递归儿子们,进行深度比较(dom-diff)
  228. *
  229. * @param {*} container 父DOM节点
  230. * @param {*} oldVChildren 老的儿子们
  231. * @param {*} newVChildren 新的儿子们
  232. */
  233. function updateChildren(container, oldVChildren, newVChildren){
  234. // 因为children可能是对象,也可能是数组,为了方便按索引比较,全部格式化为数组
  235. oldVChildren = Array.isArray(oldVChildren) ? oldVChildren : [oldVChildren];
  236. newVChildren = Array.isArray(newVChildren) ? newVChildren : [newVChildren];
  237. let maxLength = Math.max(oldVChildren.length, newVChildren.length);
  238. for (let i=0; i<maxLength; i++){
  239. // 在儿子里查找,大于当前索引的第一个兄弟
  240. let nextVdom = oldVChildren.find((item, index) => index > i && item && item.dom);
  241. compareTwoVdom(container, oldVChildren[i], newVChildren[i], nextVdom && nextVdom.dom); //递归对比每一个儿子
  242. }
  243. }
  244. // 如果老的虚拟DOM节点和新的虚拟DOM节点都是类组件的话,进行类组件更新(dom-diff)
  245. function updateClassComponent(oldVdom, newVdom){
  246. let classInstance = newVdom.classInstance = oldVdom.classInstance; //更新的时候,实例复用
  247. newVdom.oldRenderVdom = oldVdom.oldRenderVdom;
  248. if (classInstance.componentWillReceiveProps) classInstance.componentWillReceiveProps(); //组件将要接收到新的属性
  249. classInstance.updater.emitUpdate(newVdom.props); //触发组件的更新,把新的属性传过来
  250. }
  251. // 如果老的虚拟DOM节点和新的虚拟DOM节点都是函数组件的话,进行函数组件更新(dom-diff)
  252. function updateFunctionComponent(container, oldVdom, newVdom){
  253. // let container = findDOM(oldVdom).parentNode;
  254. let { type, props } = newVdom;
  255. let oldRenderVdom = oldVdom.oldRenderVdom;
  256. let newRenderVdom = type(props);
  257. newVdom.oldRenderVdom = newRenderVdom;
  258. compareTwoVdom(container, oldRenderVdom, newRenderVdom);
  259. }
  260. const ReactDOM = {
  261. render,
  262. findDOM,
  263. }
  264. export default ReactDOM;

新的生命周期

React基础(源码篇) - 图8

实现getDerivedStateFromProps

src/Component.js

  1. // 更新器
  2. class Updater {
  3. // 发布更新(一个组件不管是属性变了,还是状态变了,都会更新)
  4. emitUpdate(nextProps){
  5. if (nextProps || pendingStates.length > 0){ //如果有等待更新的状态对象
  6. + shouldUpdate(classInstance, nextProps, this.getState(nextProps));
  7. }
  8. }
  9. // 获取最新的state状态
  10. getState(nextProps){
  11. ...
  12. pendingStates.length = 0; //state获取完后,清空 pendingStates 数组
  13. + if (classInstance.constructor.getDerivedStateFromProps){
  14. + let partialState = classInstance.constructor.getDerivedStateFromProps(nextProps, classInstance.state);
  15. + if (partialState) state = {...state, ...partialState};
  16. + }
  17. return state;
  18. }
  19. }
  20. class Component {
  21. ...
  22. // 一般来说,组件的属性和状态变化了才会更新组件,
  23. // 如果属性和状态没变,也想更新组件,怎么做?调用
  24. forceUpdate(){
  25. let nextProps = this.props;
  26. let nextState = this.state;
  27. if (this.constructor.getDerivedStateFromProps){
  28. let partialState = this.constructor.getDerivedStateFromProps(nextProps, nextState);
  29. if (partialState){
  30. nextState = {...nextState, ...partialState};
  31. }
  32. }
  33. this.state = nextState;
  34. this.updateComponent();
  35. }
  36. // 更新渲染
  37. updateComponent(){}
  38. }

src/react-dom.js

  1. function mountClassComponent(vdom, container){
  2. ...
  3. classInstance.componentWillMount && classInstance.componentWillMount();
  4. + if (type.getDerivedStateFromProps){
  5. + let partialState = type.getDerivedStateFromProps(classInstance.props, classInstance.state);
  6. + if (partialState){
  7. + classInstance.state = {...classInstance.state, ...partialState};
  8. + }
  9. + }
  10. const renderVdom = classInstance.render(); //调用实例方法render,返回要渲染的虚拟DOM对象
  11. const dom = createDOM(renderVdom, container); //根据虚拟DOM对象,返回真实的DOM对象
  12. ...
  13. }

实现getSnapshotBeforeUpdate

src/index.js
  1. import React, { Component } from './react';
  2. import ReactDOM from './react-dom';
  3. let number = 0;
  4. class Counter extends Component {
  5. refUl = React.createRef();
  6. state = {
  7. list: [],
  8. }
  9. // 被调用于render之后,可以读取但无法使用DOM的时候。
  10. // 它使您的组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。
  11. // 此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
  12. getSnapshotBeforeUpdate(){
  13. return this.refUl.current.scrollHeight; // 拿到ul的真实dom,获取它的内容高度(渲染前)
  14. }
  15. componentDidUpdate(prevProps, prevState, prevScrollHeight){
  16. let currentHeight = this.refUl.current.scrollHeight; //ul当前的内容高度(渲染后)
  17. console.log('本次ul增加的高度', currentHeight - prevScrollHeight);
  18. }
  19. handleClick = () => {
  20. let list = this.state.list;
  21. list.push(list.length);
  22. this.setState({list});
  23. }
  24. render(){
  25. return (
  26. <div>
  27. <button onClick={this.handleClick}>+</button>
  28. <ul ref={this.refUl}>
  29. {this.state.list.map((item, index) => (
  30. <li key={index}>{index}</li>
  31. ))}
  32. </ul>
  33. </div>
  34. )
  35. }
  36. }
  37. ReactDOM.render(<Counter />, document.getElementById('root'));

src/Component.js
  1. class Component {
  2. updateComponent(){
  3. let newRenderVdom = this.render();
  4. let oldDom = findDOM(this.oldRenderVdom);
  5. + let extraArgs = this.getSnapshotBeforeUpdate && this.getSnapshotBeforeUpdate();
  6. // 深度比较新旧两个虚拟DOM
  7. let currentRenderVdom = compareTwoVdom(oldDom.parentNode, this.oldRenderVdom, newRenderVdom);
  8. this.oldRenderVdom = currentRenderVdom;
  9. + this.componentDidUpdate && this.componentDidUpdate(this.prevProps, this.prevState, extraArgs);
  10. }
  11. }

Context 上下文

src/react.js
  1. // 创建上下文
  2. export function createContext(initialValue){
  3. let context = {Provider, Consumer};
  4. // 提供者
  5. function Provider(props){
  6. context._currentValue = context._currentValue || initialValue;
  7. // 保持 context._currentValue 的引用地址不要变,
  8. // 这样父组件更新的时候,子组件 this.context 也会是最新的
  9. Object.assign(context._currentValue, props.value);
  10. return props.children;
  11. }
  12. // 消费者
  13. function Consumer(props){
  14. return props.children(context._currentValue);
  15. }
  16. return context;
  17. }
  18. const React = {
  19. createContext,
  20. }
  21. export default React;

src/react-dom.js
  1. //把一个类型为 “自定义类组件” 的虚拟DOM转换成真实DOM并返回
  2. function mountClassComponent(vdom, container){
  3. const { type, props, ref } = vdom; //解构类的属性和类的属性对象
  4. const classInstance = new type(props); //创建类的实例
  5. + // 为类子组件绑定上下文
  6. + if (type.contextType){
  7. + classInstance.context = type.contextType._currentValue;
  8. + }
  9. classInstance.componentWillMount && classInstance.componentWillMount();
  10. ...
  11. }

高阶组件

https://www.yuque.com/zhuchaoyang/wrif6k/we9kxl

cloneElement

demo 参见 基础篇 反向继承的demo

src/react.js
  1. /** 克隆
  2. *
  3. * @param {*} oldElement 老虚拟DOM
  4. * @param {*} newProps 新属性
  5. * @param {...any} newChildren 新的儿子们
  6. * @returns 新虚拟DOM
  7. */
  8. export function cloneElement(oldElement, newProps, ...newChildren){
  9. // 老儿子可能是 undefined、对象、数组
  10. let oldChildren = oldElement.props && oldElement.props.children;
  11. let children = [...(Array.isArray(oldChildren) ? oldChildren : [oldChildren]), ...newChildren]
  12. .filter(item => item !== undefined).map(wrapToVdom);
  13. if (children.length === 0){
  14. children = undefined;
  15. } else if (children.length === 1){
  16. children = children[0];
  17. }
  18. let props = {...oldElement.props, ...newProps, children};
  19. return {...oldElement, props};
  20. }
  21. const React = {
  22. cloneElement,
  23. }
  24. export default React;

shouldComponentUpdate 优化

实现PureComponent、memo

src/Component.js
  1. export class PureComponent extends Component {
  2. // 重写了此方法,只有状态或者属性变化了才会进行更新;否则,不更新。
  3. shouldComponentUpdate(nextProps, nextState){
  4. return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
  5. }
  6. }
  7. /** 浅比较 obj1 和 obj2 是否相等
  8. * 只要内存地址一样,就认为是相等的,不一样就不相等
  9. * @param {*} obj1
  10. * @param {*} obj2
  11. * @returns
  12. */
  13. function shallowEqual(obj1, obj2){
  14. // 如果引用地址一样,就相等,不关心属性变没变
  15. if (obj1 === obj2) return true;
  16. // 任何一方不是对象或者不是null,也不相等
  17. if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) return false;
  18. // 属性的数量不相等,也不相等
  19. let keys1 = Object.keys(obj1);
  20. let keys2 = Object.keys(obj2);
  21. if (keys1.length !== keys2.length) return false;
  22. for (let key of keys1){
  23. if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) return false;
  24. }
  25. return true;
  26. }

src/react.js
  1. // export { Component, PureComponent } from './Component'
  2. import { Component, PureComponent } from './Component'
  3. export { Component, PureComponent };
  4. // 返回组件要有一个功能:属性变了,重新渲染;属性不变,不更新
  5. export function memo(FunctionComponent){
  6. return class extends PureComponent{
  7. render(){
  8. return <FunctionComponent {...this.props}/>
  9. }
  10. }
  11. }
  12. const React = {
  13. Component,
  14. PureComponent,
  15. memo,
  16. }
  17. export default React;