Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2018/10/26 -【React源码解读】- 组件的实现 #21

Open
Xpig4432xyx opened this issue Oct 26, 2018 · 0 comments
Open

2018/10/26 -【React源码解读】- 组件的实现 #21

Xpig4432xyx opened this issue Oct 26, 2018 · 0 comments

Comments

@Xpig4432xyx
Copy link
Contributor

Xpig4432xyx commented Oct 26, 2018

前言

react使用也有一段时间了,大家对这个框架褒奖有加,但是它究竟好在哪里呢?
让我们结合它的源码,探究一二!(当前源码为react16,读者要对react有一定的了解)

15397566862932

回到最初

根据react官网上的例子,快速构建react项目

npx create-react-app my-app

cd my-app

npm start

打开项目并跑起来以后,暂不关心项目结构及语法糖,看到App.js里,这是一个基本的react组件 我们console一下,看看有什么结果。

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
        </header>
      </div>
    );
  }
}

export default App;

console.log(<App/>)

15397572879758

可以看到,<App/>组件其实是一个JS对象,并不是一个真实的dom。

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。有兴趣的同学可以去阮一峰老师的ES6入门详细了解一下

上面有我们很熟悉的props,ref,key,我们稍微修改一下console,看看有什么变化。

console.log(<App key={1} abc={2}><div>你好,这里是App组件</div></App>)

15397577334580

可以看到,props,key都发生了变化,值就是我们赋予的值,props中嵌套了children属性。可是为什么我们嵌入的是div,实际上却是一个对象呢?

打开源码

/node_modules/react

15397580720896

首先打开index.js

'use strict';

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

可以知道目前用上的是./cjs/react.development.js,直接打开文件。
根据最初的代码,我们组件<App/>用到了React.Component。找到React暴露的接口:

15397617558881

接着找到Component: Component方法,

function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.isReactComponent = {};

Component.prototype.setState = function (partialState, callback) {
  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

Component.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

上面就是一些简单的构造函数,也可以看到,我们常用的setState是定义在原型上的2个方法。

至此,一个<App/>组件已经有一个大概的雏形:

15397595217487

到此为止了吗?这看了等于没看啊,究竟组件是怎么变成div的?render吗?
可是全局搜索,也没有一个function是render啊。

原来,我们的jsx语法会被babel编译的。

15397600724075

这下清楚了,还用到了React.createElement

createElement: createElementWithValidation,

通过createElementWithValidation,

function createElementWithValidation(type, props, children) {
······

  var element = createElement.apply(this, arguments);


  return element;
}

可以看到,return了一个element,这个element又是继承自createElement,接着往下找:

function createElement(type, config, children) {
  var propName = void 0;

  // Reserved names are extracted
  var props = {};

  var key = null;
  var ref = null;
  var self = null;
  var source = null;
······
  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}

这里又返回了一个ReactElement方法,再顺着往下找:

var ReactElement = function (type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner
  };

······
  return element;
};

诶,这里好像返回的就是element对象,再看我们最初的<App/>的结构,是不是很像

15397606651880验证一下我们的探索究竟对不对,再每一个方法上我们都打上console,(注意,将App里的子元素全部删空,利于我们观察)

15397611759810

React.createElement 、 createElementWithValidation 、 createElement 、 ReactElement,通过这些方法,我们用class声明的React组件在变成真实dom之前都是ReactElement类型的js对象

createElementWithValidation:

  • 首先校验type是否是合法的

15397657382603

  • 校验了props是否符合设置的proptypes

15397667118968

  • 校验了子节点的key,确保每个数组中的元素都有唯一的key

15397667422295

createElement

  • type是你要创建的元素的类型,可以是html的div或者span,也可以是其他的react组件,注意大小写
  • config中包含了props、key、ref、self、source等

15397667913454

  • 向props加入children,如果是一个就放一个对象,如果是多个就放入一个数组。

15397668352993

  • 那如果type.defaultProps有默认的props时,并且对应的props里面的值是undefined,把默认值赋值到props中

15397668766904

  • 也会对key和ref进行校验

15397669476655

ReactElement

ReactElement就比较简单了,创建一个element对象,参数里的type、key、ref、props、等放进去,然后return了。最后调用Object.freeze使对象不可再改变。

组件的挂载

我们上面只是简单的探究了<App/>的结构和原理,那它究竟是怎么变成真实dom的呢

15397616989193

ReactDOM.render(<App />, document.getElementById('root'));

我们接着用babel编译一下:

15397619877496

原来ReactDOM.render调用的是render方法,一样,找暴露出来的接口。

var ReactDOM = {
······
  render: function (element, container, callback) {
    return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
  },
······
};

它返回的是一个legacyRenderSubtreeIntoContainer方法,这次我们直接打上console.log

15397629379495

这是打印出来的结果,

15397633591876

legacyRenderSubtreeIntoContainer
这个方法除主要做了两件事:

  • 清除dom容器元素的子元素
while (rootSibling = container.lastChild) {
      {
        if (!warned && rootSibling.nodeType === ELEMENT_NODE && rootSibling.hasAttribute(ROOT_ATTRIBUTE_NAME)) {
          warned = true;
        }
      }
      container.removeChild(rootSibling);
    }
  • 创建ReactRoot对象

15397648731115

源码暂时只读到了这里,关于React16.1~3的新功能,以及新的生命周期的使用和原理、Fiber究竟是什么,我们将在后续文章接着介绍。

广而告之

本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。

欢迎讨论,点个赞再走吧 。◕‿◕。 ~

@wusb wusb changed the title 2018/10/26 - 【react源码解读】- 组件的实现 2018/10/26 -【React源码解读】- 组件的实现 Oct 31, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant