欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React前端開發(fā)createElement源碼解讀

 更新時(shí)間:2022年11月03日 15:10:52   作者:冴羽  
這篇文章主要為大家介紹了React前端開發(fā)createElement源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

React 與 Babel

元素標(biāo)簽轉(zhuǎn)譯

用過 React 的同學(xué)都知道,當(dāng)我們這樣寫時(shí):

<div id="foo">bar</div>

Babel 會(huì)將其轉(zhuǎn)譯為:

React.createElement("div", {id: "foo"}, "bar");

我們會(huì)發(fā)現(xiàn),createElement 的第一個(gè)參數(shù)是元素類型,第二個(gè)參數(shù)是元素屬性,第三個(gè)參數(shù)是子元素

組件轉(zhuǎn)譯

如果我們用的是一個(gè)組件呢?

function Foo({id}) {
  return <div id={id}>foo</div>
}
<Foo id="foo">
  <div id="bar">bar</div>
</Foo>

Babel 則會(huì)將其轉(zhuǎn)譯為:

function Foo({id}) {
  return React.createElement("div", {id: id}, "foo")}
React.createElement(Foo, {id: "foo"},
  React.createElement("div", {id: "bar"}, "bar")
);

我們會(huì)發(fā)現(xiàn),createElement 的第一個(gè)參數(shù)傳入的是變量 Foo

子元素轉(zhuǎn)譯

如果我們有多個(gè)子元素呢?

<div id="foo">
  <div id="bar">bar</div>
	<div id="baz">baz</div>
  <div id="qux">qux</div>
</div>

Babel 則會(huì)將其轉(zhuǎn)譯為:

React.createElement("div", { id: "foo"}, 
  React.createElement("div", {id: "bar"}, "bar"), 
  React.createElement("div", {id: "baz"}, "baz"),
  React.createElement("div", {id: "qux"}, "qux")
);

我們會(huì)發(fā)現(xiàn),子元素其實(shí)是作為參數(shù)不斷追加傳入到函數(shù)中

createElement

那 React.createElement 到底做了什么呢?

源碼

我們查看 React 的 GitHub 倉庫:github.com/facebook/re…,查看 pacakges/react/index.js文件,可以看到 createElement 的定義在 ./src/React文件:

// 簡化后
export {createElement} from './src/React';

我們打開 ./src/React.js文件:

import {
  createElement as createElementProd
} from './ReactElement';
const createElement = __DEV__
  ? createElementWithValidation
  : createElementProd;
export { createElement };

繼續(xù)查看 ./ReactElement.js文件,在這里終于找到最終的定義,鑒于這里代碼較長,我們將代碼極度簡化一下:

const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};
export function createElement(type, config, ...children) {
  let propName;
  // Reserved names are extracted
  const props = {};
  // 第一段
  let key = '' + config.key;
  let ref = config.ref;
  let self = config.__self;
  let source = config.__source;
  // 第二段
  for (propName in config) {
    if (config.hasOwnProperty(propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
      props[propName] = config[propName];
    }
  }
  // 第三段
  props.children = children;
  // 第四段
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  // 第五段
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

這里可以看出,createElement 函數(shù)主要是做了一個(gè)預(yù)處理,然后將處理好的數(shù)據(jù)傳入 ReactElement 函數(shù)中,我們先分析下 createElement 做了什么。

函數(shù)入?yún)?/h3>

我們以最一開始的例子為例:

<div id="foo">bar</div>
// 轉(zhuǎn)譯為
React.createElement("div", {id: "foo"}, "bar");

對(duì)于createElement 的三個(gè)形參,其中type 表示類型,既可以是標(biāo)簽名字符串(如 div 或 span),也可以是 React 組件(如 Foo)

config 表示傳入的屬性,children 表示子元素

第一段代碼 __self 和 __source

現(xiàn)在我們開始看第一段代碼:

  // 第一段
  let key = '' + config.key;
  let ref = config.ref;
  let self = config.__self;
  let source = config.__source;

可以看到在 createElement 函數(shù)內(nèi)部,key、ref、__self、__source 這四個(gè)參數(shù)是單獨(dú)獲取并處理的,keyref 很好理解,__self__source 是做什么用的呢?

通過這個(gè) issue,我們了解到,__self__sourcebabel-preset-react注入的調(diào)試信息,可以提供更有用的錯(cuò)誤信息。

我們查看 babel-preset-react 的文檔,可以看到:

development

boolean 類型,默認(rèn)值為 false. 這可以用于開啟特定于開發(fā)環(huán)境的某些行為,例如添加 __source 和 __self。 在與 env 參數(shù) 配置或 js 配置文件 配合使用時(shí),此功能很有用。

如果我們?cè)囍_啟 development 參數(shù),就會(huì)看到 __self__source 參數(shù),依然以 <div id="foo">bar</div> 為例,會(huì)被 Babel 轉(zhuǎn)譯成:

var _jsxFileName = "/Users/kevin/Desktop/react-app/src/index.js";
React.createElement("div", {
  id: "foo",
  __self: this,
  __source: {
    fileName: _jsxFileName,
    lineNumber: 5,
    columnNumber: 13
  }
}, "bar");

第二段代碼 props 對(duì)象

現(xiàn)在我們看第二段代碼:

// 第二段
for (propName in config) {
    if (config.hasOwnProperty(propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
      props[propName] = config[propName];
    }
}

這段代碼實(shí)現(xiàn)的功能很簡單,就是構(gòu)建一個(gè) props 對(duì)象,去除傳入的 key、ref、__self、__source屬性,這就是為什么在組件中,我們明明傳入了 keyref,但我們無法通過 this.props.key 或者 this.props.ref 來獲取傳入的值,就是因?yàn)樵谶@里被去除掉了。

而之所以去除,React 給出的解釋是,keyref 是用于 React 內(nèi)部處理的,如果你想用比如 key 值,你可以再傳一個(gè)其他屬性,用跟 key 相同的值即可。

第三段代碼 children

現(xiàn)在我們看第三段代碼,這段代碼被精簡的很簡單:

// 第三段
props.children = children;

這是其實(shí)是因?yàn)槲覀優(yōu)榱撕喕a,用了 ES6 的擴(kuò)展運(yùn)算法,實(shí)際的源碼里會(huì)復(fù)雜且有一些差別:

const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
}

我們也可以發(fā)現(xiàn),當(dāng)只有一個(gè)子元素的時(shí)候,children 其實(shí)會(huì)直接賦值給 props.children,也就是說,當(dāng)只有一個(gè)子元素時(shí),children 是一個(gè)對(duì)象(React 元素),當(dāng)有多個(gè)子元素時(shí),children 是一個(gè)包含對(duì)象(React 元素)的數(shù)組。

第四段代碼 defaultProps

現(xiàn)在我們看第四段代碼:

  // 第四段
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }

這段其實(shí)是處理組件的defaultProps,無論是函數(shù)組件還是類組件都支持 defaultProps,舉個(gè)使用例子:

// 函數(shù)組件
function Foo({id}) {
  return <div id={id}>foo</div>
}
 Foo.defaultProps = {
   id: 'foo'
 }
// 類組件
 class Header extends Component {
   static defaultProps = {
     id: 'foo'
   }
   render () {
     const { id } = this.props
     return <div id={id}>foo</div>
   }
 }

第五段代碼 owner

現(xiàn)在我們看第五段代碼:

  // 第五段
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );

這段就是把前面處理好的 typekey 等值傳入 ReactElement 函數(shù)中,那 ReactCurrentOwner.current是個(gè)什么鬼?

我們根據(jù)引用地址查看 ReactCurrentOwner定義的文件

/**
 * Keeps track of the current owner.
 *
 * The current owner is the component who should own any components that are
 * currently being constructed.
 */
const ReactCurrentOwner = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: null,
};
export default ReactCurrentOwner;

其初始的定義非常簡單,根據(jù)注釋,我們可以了解到 ReactCurrentOwner.current 就是指向處于構(gòu)建過程中的組件的 owner,具體作用在以后的文章中還有介紹,現(xiàn)在可以簡單的理解為,它就是用于記錄臨時(shí)變量。

ReactElement

源碼

現(xiàn)在我們開始看 ReactElement 函數(shù),其實(shí)這個(gè)函數(shù)的內(nèi)容更簡單,代碼精簡后如下:

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const 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;
};

如你所見,它就是返回一個(gè)對(duì)象,這個(gè)對(duì)象包括 $$typeof、typekey 等屬性,這個(gè)對(duì)象就被稱為 “React 元素”。它描述了我們?cè)谄聊簧峡吹降膬?nèi)容。React 會(huì)通過讀取這些對(duì)象,使用它們構(gòu)建和更新 DOM

REACT_ELEMENT_TYPE

REACT_ELEMENT_TYPE 查看引用的 packages/shared/ReactSymbols 文件,可以發(fā)現(xiàn)它就是一個(gè)唯一常量值,用于標(biāo)示是 React 元素節(jié)點(diǎn)

export const REACT_ELEMENT_TYPE = Symbol.for('react.element');

那還有其他類型的節(jié)點(diǎn)嗎? 查看這個(gè)定義 REACT_ELEMENT_TYPE 的文件,我們發(fā)現(xiàn)還有:

export const REACT_PORTAL_TYPE: symbol = Symbol.for('react.portal');
export const REACT_FRAGMENT_TYPE: symbol = Symbol.for('react.fragment');
export const REACT_STRICT_MODE_TYPE: symbol = Symbol.for('react.strict_mode');
export const REACT_PROFILER_TYPE: symbol = Symbol.for('react.profiler');
export const REACT_PROVIDER_TYPE: symbol = Symbol.for('react.provider');
export const REACT_CONTEXT_TYPE: symbol = Symbol.for('react.context');
// ...

你可能會(huì)自然的理解為 $$typeof 還可以設(shè)置為 REACT_FRAGMENT_TYPE等值。

我們可以寫代碼實(shí)驗(yàn)一下,比如使用 Portal,打印一下返回的對(duì)象:

import ReactDOM from 'react-dom/client';
import {createPortal} from 'react-dom'
const root = ReactDOM.createRoot(document.getElementById('root'));
function Modal() {
  const portalObject = createPortal(<div id="foo">foo</div>, document.getElementById("root2"));
  console.log(portalObject)
  return portalObject
}
root.render(<Modal />);

打印的對(duì)象為:

它的 $$typeof 確實(shí)是 REACT_PORTAL_TYPE

而如果我們使用 Fragment

import ReactDOM from 'react-dom/client';
import React from 'react';
const root = ReactDOM.createRoot(document.getElementById('root'));
function Modal() {
  const fragmentObject = (
    <React.Fragment>
      <div id="foo">foo</div>
    </React.Fragment>
    );
  console.log(fragmentObject)
  return fragmentObject
}
root.render(<Modal />);

打印的對(duì)象為:

我們會(huì)發(fā)現(xiàn),當(dāng)我們使用 fragment 的時(shí)候,返回的對(duì)象的 $$typeof 卻依然是 REACT_ELEMENT_TYPE 這是為什么呢?

其實(shí)細(xì)想一下我們使用 portals 的時(shí)候,我們用的是 React.createPortal 的方式,但 fragments 走的依然是普通的 React.createElement 方法,createElement 的代碼我們也看到了,并無特殊處理 $$typeof 的地方,所以自然是 REACT_ELEMENT_TYPE

那么 $$typeof 到底是為什么存在呢?其實(shí)它主要是為了處理 web 安全問題,試想這樣一段代碼:

let message = { text: expectedTextButGotJSON };
// React 0.13 中有風(fēng)險(xiǎn)
<p>
  {message.text}
</p>

如果 expectedTextButGotJSON是來自于服務(wù)器的值,比如:

// 服務(wù)端允許用戶存儲(chǔ) JSON
let expectedTextButGotJSON = {
  type: 'div',
  props: {
    dangerouslySetInnerHTML: {
      __html: '/* something bad */'
    },
  },
  // ...
};
let message = { text: expectedTextButGotJSON };

這就很容易受到 XSS 攻擊,雖然這個(gè)攻擊是來自服務(wù)器端的漏洞,但使用 React 我們可以處理的更好。如果我們用 Symbol 標(biāo)記每個(gè) React 元素,因?yàn)榉?wù)端的數(shù)據(jù)不會(huì)有 Symbol.for('react.element'),React 就可以檢測(cè) element.$$typeof,如果元素丟失或者無效,則可以拒絕處理該元素,這樣就保證了安全性。

回顧

至此,我們完整的看完了 React.createElement 的源碼,現(xiàn)在我們?cè)倏?React 官方文檔的這段:

以下兩種示例代碼完全等效:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

React.createElement() 會(huì)預(yù)先執(zhí)行一些檢查,以幫助你編寫無錯(cuò)代碼,但實(shí)際上它創(chuàng)建了一個(gè)這樣的對(duì)象:

// 注意:這是簡化過的結(jié)構(gòu)
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

這些對(duì)象被稱為 “React 元素”。它們描述了你希望在屏幕上看到的內(nèi)容。React 通過讀取這些對(duì)象,然后使用它們來構(gòu)建 DOM 以及保持隨時(shí)更新。

現(xiàn)在你對(duì)這段是不是有了更加深入的認(rèn)識(shí)?

React 系列的預(yù)熱系列,帶大家從源碼的角度深入理解 React 的各個(gè) API 和執(zhí)行過程

以上就是React前端開發(fā)createElement源碼解讀的詳細(xì)內(nèi)容,更多關(guān)于React前端開發(fā)createElement的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React Hooks: useEffect()調(diào)用了兩次問題分析

    React Hooks: useEffect()調(diào)用了兩次問題分析

    這篇文章主要為大家介紹了React Hooks: useEffect()調(diào)用了兩次問題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • react組件從搭建腳手架到在npm發(fā)布的步驟實(shí)現(xiàn)

    react組件從搭建腳手架到在npm發(fā)布的步驟實(shí)現(xiàn)

    這篇文章主要介紹了react組件從搭建腳手架到在npm發(fā)布的步驟實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2019-01-01
  • react native仿微信PopupWindow效果的實(shí)例代碼

    react native仿微信PopupWindow效果的實(shí)例代碼

    本篇文章主要介紹了react native仿微信PopupWindow效果的實(shí)例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-08-08
  • React Store及store持久化的使用教程

    React Store及store持久化的使用教程

    這篇文章主要介紹了React Store及store持久化的使用教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-01-01
  • React利用props的children實(shí)現(xiàn)插槽功能

    React利用props的children實(shí)現(xiàn)插槽功能

    React中并沒有vue中的?slot?插槽概念?不過?可以通過props.children?實(shí)現(xiàn)類似功能,本文為大家整理了實(shí)現(xiàn)的具體方,需要的可以參考一下
    2023-07-07
  • React彈窗使用方式NiceModal重新思考

    React彈窗使用方式NiceModal重新思考

    這篇文章主要為大家介紹了React彈窗使用方式NiceModal重新思考分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • react中form.setFieldvalue數(shù)據(jù)回填時(shí) value和text不對(duì)應(yīng)的問題及解決方法

    react中form.setFieldvalue數(shù)據(jù)回填時(shí) value和text不對(duì)應(yīng)的問題及解決方法

    這篇文章主要介紹了react中form.setFieldvalue數(shù)據(jù)回填時(shí) value和text不對(duì)應(yīng)的問題及解決方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-07-07
  • React大文件分片上傳原理及方案

    React大文件分片上傳原理及方案

    前端進(jìn)行大文件分片上傳的方案幾乎都是利用Blob.prototype.slice方法對(duì)文件進(jìn)行分片,用數(shù)組將每一個(gè)分片存起來,最后將分片發(fā)給后端,本文給大家介紹React大文件分片上傳方案,感興趣的朋友跟隨小編一起看看吧
    2023-08-08
  • 聊聊ant?design?charts?獲取后端接口數(shù)據(jù)展示問題

    聊聊ant?design?charts?獲取后端接口數(shù)據(jù)展示問題

    今天在做項(xiàng)目的時(shí)候遇到幾個(gè)讓我很頭疼的問題,一個(gè)是通過后端接口成功訪問并又返回?cái)?shù)據(jù),但拿不到數(shù)據(jù)值。其二是直接修改state中的data,console中數(shù)組發(fā)生變化但任然數(shù)據(jù)未顯示,這篇文章主要介紹了ant?design?charts?獲取后端接口數(shù)據(jù)展示,需要的朋友可以參考下
    2022-05-05
  • 在React 組件中使用Echarts的示例代碼

    在React 組件中使用Echarts的示例代碼

    本篇文章主要介紹了在React 組件中使用Echarts的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-11-11

最新評(píng)論