博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【React源码解读】- 组件的实现
阅读量:6448 次
发布时间:2019-06-23

本文共 4867 字,大约阅读时间需要 16 分钟。

前言

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

回到最初

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

npx create-react-app my-appcd my-appnpm start复制代码

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

import React, { Component } from 'react';import logo from './logo.svg';import './App.css';class App extends Component {  render() {    return (      
logo

Edit src/App.js and save to reload.

); }}export default App;console.log(
)复制代码

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

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

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

console.log(
你好,这里是App组件
)复制代码

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

打开源码

/node_modules/react

首先打开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暴露的接口:

接着找到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/>组件已经有一个大概的雏形:

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

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

这下清楚了,还用到了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/>的结构,是不是很像

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

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

createElementWithValidation:

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

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

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

createElement

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

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

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

  • 也会对key和ref进行校验

ReactElement

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

组件的挂载

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

ReactDOM.render(
, document.getElementById('root'));复制代码

我们接着用babel编译一下:

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

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

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

这是打印出来的结果,

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

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

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

广而告之

本文发布于,欢迎Watch & Star ★,转载请注明出处。

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

你可能感兴趣的文章
远心镜头运用目的
查看>>
python中matplotlib绘制图形
查看>>
WdatePicker 日历控件使用方法+基本常用方法
查看>>
论文Express | 开网约车赚外块?MIT最新研究称多数Uber司机收入低于当地最低线...
查看>>
centos7.2的网络配置
查看>>
mac os正确刻录iso系统盘
查看>>
用户及用户管理 root忘记密码
查看>>
sort命令的用法
查看>>
sql语句
查看>>
linux系统日志
查看>>
LNMP_mysql安装_1
查看>>
tinyxml 用法
查看>>
医院成黑客勒索重灾区 阿里云表示:愿为医疗机构提供安全公益排查支持
查看>>
centos7 开机 自动连网
查看>>
BGREWRITEAOF
查看>>
关于grequests的session/cookie (python)
查看>>
HTML编辑器
查看>>
find 命令的参数详解
查看>>
MySQL数据库性能优化之存储引擎选择
查看>>
网络部署Xenserver6.5
查看>>