React源码解析一(React的基础API)

404 阅读6分钟

前言:为什么我们要了解React的源码呢?

首先,拜读大神的代码是菜鸟进步最快的方法,从源码中学习别人的代码逻辑和规范,提升自己的整体编码能力;

其次,了解源码才能在排查bug时更快的知道问题根源;

最后,现在不懂点源码,出去面试肯定过不了,比如我…

总之,了解源码对职业发展的好处是大大的。

学习视频链接:慕课网-React源码深度解析

React的优势

为什么React很牛呢?作者提到了以下几点:

  • React中的UI渲染如下:UI = fn(x),利用state控制数据,数据映射成UI的方式,屏蔽了大量的DOM操作。
  • React的纯粹性:React的核心API只有 setState,没有vued的双向绑定等其他API,改变UI只能通过setState。
  • React16重写了大量核心代码,但用户完全无感知。对大佬的崇拜指数型上升。
  • React16中引入——Fiber,能有效解决JS单线程运行时,计算量太大造成的交互卡顿问题。

React基础API

React和React-DOM的区别

我刚开始用React时,经常分不清React、React-DOM,不懂两者之间的区别。只知道react-dom提供了API——ReactDOM.render();但书写React代码时,用到的API只需从react中引用即可。

其实,react核心代码只有1000行,而react-dom的核心代码有2w多行,大部分逻辑都在react-dom中,那react核心代码库到底提供了什么呢?

react的源码极少,只定义节点和表现行为。 react-dom控制页面的渲染、更新。

React核心代码库

本文引用的react版本是截止到2020.1.8最新的代码库github/react代码库

CreateElement

JSX的产物到底是什么?原React语法如下:

function NavDemo() {
    return (
        <ul className="nav">
            <li className="item" key="1">导航1</li>
            <li className="item" key="2">导航2</li>
        </ul>
    );
}

export default class CreateElementDemo extends React.Component {
    render() {
        return <div className="demo-wrap"
            key="demo"
            style={{
                color: 'red',
                fontSize: '20px'
            }}
            >
            <p style={{margin: '20px 0'}}>React.CreateElement</p>
            <NavDemo />
            {Demo}
        </div>;
    }
}

React会将HTML标签<>解析为React.CreateElement(type, config, children),上面的实例解析后的代码如下所示:

export const Demo = React.createElement(
    'div',
    {
        className: 'demo-wrap',
        key: 'demo',
        style: {
            color: 'green',
            fontSize: '30px'
        }
    },
    React.createElement(
        'p',
        {
            className: 'title'
        },
        '标题'
    ),
    React.createElement(
        NavDemo,
        null,
        ''
    )
);

在线babel转换工具

React书写规范要求:原生HTML节点为小写,自定义的组件必须以大写字母开头。React.CreateElement()接收的第一个参数type,就是标签名,会将小写type转为字符串,将大写type转为一个变量存储。

下面来看React.createElement的源码, 真正对我们有用的只有如下几个函数:

- createElement
- cloneElement
- hasValidRef:ref === undefined?
- hasValidKey:key === undefined?
- RESERVED_PROPS:React中的保留props,包含key 、ref、__self、__source
- isValidElement:判断是否是 REACT_ELEMENT_TYPE
// ReactElement的源码
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    ?typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
  };

  return element;
};

createElement

  • 参数:type,config,children
    • type:节点类型:是原生节点(string类型),还是一个组件(对象类型),还存在一些其他情况—React的原生组件(Fragment等)
    • Config:过滤内置属性key,ref,__self,__source后,然后将其他属性存成一个数组
    • Children:可以有多个兄弟节点,第三个及之后的元素都被视作children,各个子元素都会用React.createElement重新渲染。
    • 声明defaultProps时,需要处理对应的默认值,如果是undefined,则采用默认值,传null的时候,不用默认值哦!
  • 返回值:ReactElement对象
    • ReactElement()会将接收到的参数,组合为一个对象,直接返回,但会添加一个元素?typeof
    • ?typeof很重要,标识Element是什么类型,写JSX时,所有的html节点都是通过React.CreateElement创建的,都是REACT_ELEMENT_TYPE
    • 但会存在例外情况如React.createPortal就不是REACT_ELEMENT_TYPE,后面会介绍
  • 如何与DOM关联:后面会讲到

Component/PureComponent

import ReactNoopUpdateQueue from './ReactNoopUpdateQueue';
const emptyObject = {};
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.isReactComponent = {};

Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
  • Component(props,context,updater)
    • 只是一个构造函数,将接收的属性作为自己的属性,同时给Component添加一个空对象refs
    • 不包含任何生命周期函数!
    • Component原型链上的函数
      • isReactComponent:{}
      • setState(partialState, callback)
      • forceUpdate(callback)
      • setState和froceUpdate()调用的都是Component对象上的updater.enqueueForceUpdate(),该方法在react-dom中实现
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

// 此处和Component的构造函数相同
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
  • PureComponent(props, context, updater)
    • PureComponent的原理同上,只是PureComponent的原型链指向了Component.prototype

Ref

React中的Ref分为三种基本类型:String类型的Ref(即将废弃),回调函数类型的Ref,React.createRef()

  • React.createRef()
    • react源码中该函数的实现如下,我也很难相信!!!
    • 此处给自己提个问题 Ref给我们的这个对象,在后期如何使用?如何将DOM挂载呢?实现应该会在后面的react-dom源码中解释
export function createRef() {
  const refObject = {
    current: null,
  };
  return refObject;

forwardRef

  • 作用:可以把props.ref作为参数,传递给要被包装的子组件(只能是函数式组件)
  • 用处:经常会应用在高阶函数中,此处就不展开讲了,具体可以去React官网中了解
import {REACT_FORWARD_REF_TYPE} from 'shared/ReactSymbols';

export default function forwardRef(
  render: (props, ref) => React$Node,
) {
  return {
    ?typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
}

React.forwardRef()返回的对象包含属性?typeof、render,但此处的?typeof替换的不是ReactElement?typeof,而是替换React.Elementtype属性中的?typeof

通过React.createElement创建的所有组件,它的?typeof 都是REACT_ELEMENT_TYPE,不要搞混了哦!这里很重要!

Context

  • 作用:避免props的多层次嵌套,让子孙组件可以快速获取父组件传递的props
import {REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';

import {ReactContext} from 'shared/ReactTypes';

export function createContex (
  defaultValue,
  calculateChangedBits: ?(a, b) => number,
) {
    // 该组件有多个父级的context时,计算新老context叠加后的值
    if (calculateChangedBits === undefined) {
      calculateChangedBits = null;
    }
    const context: ReactContext<T> = {
        // 此处的?typeof替换的仍然是React.Element.type的?typeof,而不是 React.Element.?typeof
        ?typeof: REACT_CONTEXT_TYPE,
        _calculateChangedBits: calculateChangedBits,
        // React中最多支持两个同时渲染的render,ReactNative和Fabric 或者 ReactDOM和React ART
        _currentValue: defaultValue,
        _currentValue2: defaultValue, rendering.
        Provider: (null: any),
        Consumer: (null: any),
      };

      context.Provider = {
        ?typeof: REACT_PROVIDER_TYPE,
        _context: context,
      };
      
      // Consumer= context自身,所以当context中的值变化时,consumer就能直接通过_currentValue直接拿到值
      context.Consumer = context;
     return context;
}

ConcurrentMode

  • 作用:React16之后提出的一个概念呢,可以让渲染过程更可控,让浏览器优先执行级别较高的任务
// 使用Demo,复制粘贴后,在React项目中可直接运行
// 当async变化时,注意观察动画和num值的变化

// 动画
.mode-wrap {
    .move-wrap {
        animation: move 3s ease-in 4s infinite;
    }
    @keyframes move {
        0% {
            transform: translateX(0);
        }
        50% {
            transform: translateX(200px);
        }
        100% {
            transform: translateX(0);
        }
    }
}

// 组件
import React, {ConCurrentMode} from 'react';
import {flushSync} from 'react-dom';

export default class ConCurrentModeDemo extends React.Component {
    demoDom = null;
    interval = null;
    constructor(props) {
        super(props);
        this.state = {
            async: true,
            num: 1,
            length: 1000
        };
    }
    componentDidMount() {
        this.interval = setInterval(() => {
            this.updateNum();
        }, 1000);
    }
    componentWillUnmount() {
        if (this.interval) {
            clearInterval(this.interval);
        }
    }
    updateNum = () => {
        let {num} = this.state;
        const newNum = num === 3 ? 0 : num + 1;
        if (this.state.async) {
            this.setState({
                num: newNum
            });
        } else {
            // 提升setState的优先级
            flushSync(() => {
                this.setState({
                    num: newNum
                });
            });
        }
    }
    handleChange(e) {

    }
    render() {
        let {length, num, async} = this.state;
        const children = [];
        for (let i = 0; i < length; i++) {
            children.push(
                <div style={{width: '20px', height: '20px', background: '#9c92ec', color: 'white', display: 'inline-block'}} key={i}>
                    {num}
                </div>
            );
        }
        return <div className="mode-wrap">
            <p style={{margin: '20px 0', fontSize: '20px'}}>ConCurrentMode, FlushSync</p>
            <input type="checkbox" onChange={() => flushSync(() => this.setState({async: !async}))} checked={async} />强制优先更新num
            <div style={{width: '600px', height: '400px'}} className="move-wrap">
                {children}
            </div>
        </div>;
    }
}

最后

Context、Children、其他的React源码稍后更新,请持续关注哦!