最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

React 與 Babel

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

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

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

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

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

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

組件轉(zhuǎn)譯

如果我們用的是一個組件呢?

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

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

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

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

子元素轉(zhuǎn)譯

如果我們有多個子元素呢?

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

Babel 則會將其轉(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")
);

我們會發(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ù)主要是做了一個預(yù)處理,然后將處理好的數(shù)據(jù)傳入 ReactElement 函數(shù)中,我們先分析下 createElement 做了什么。

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

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

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

對于createElement 的三個形參,其中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 這四個參數(shù)是單獨(dú)獲取并處理的,keyref 很好理解,__self__source 是做什么用的呢?

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

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

development

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

如果我們試著開啟 development 參數(shù),就會看到 __self__source 參數(shù),依然以 <div id="foo">bar</div> 為例,會被 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 對象

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

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

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

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

第三段代碼 children

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

// 第三段
props.children = children;

這是其實(shí)是因?yàn)槲覀優(yōu)榱撕喕a,用了 ES6 的擴(kuò)展運(yùn)算法,實(shí)際的源碼里會復(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)只有一個子元素的時候,children 其實(shí)會直接賦值給 props.children,也就是說,當(dāng)只有一個子元素時,children 是一個對象(React 元素),當(dāng)有多個子元素時,children 是一個包含對象(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,舉個使用例子:

// 函數(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,
  );

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

我們根據(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)在可以簡單的理解為,它就是用于記錄臨時變量。

ReactElement

源碼

現(xiàn)在我們開始看 ReactElement 函數(shù),其實(shí)這個函數(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;
};

如你所見,它就是返回一個對象,這個對象包括 $$typeof、typekey 等屬性,這個對象就被稱為 “React 元素”。它描述了我們在屏幕上看到的內(nèi)容。React 會通過讀取這些對象,使用它們構(gòu)建和更新 DOM

REACT_ELEMENT_TYPE

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

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

那還有其他類型的節(jié)點(diǎn)嗎? 查看這個定義 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');
// ...

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

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

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 />);

打印的對象為:

它的 $$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 />);

打印的對象為:

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

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

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

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

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

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

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

回顧

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

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

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

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

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

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

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

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

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

相關(guān)文章

  • 高性能React開發(fā)React Server Components詳解

    高性能React開發(fā)React Server Components詳解

    ReactServerComponents通過服務(wù)器端渲染、自動代碼分割等技術(shù),實(shí)現(xiàn)了高性能的React開發(fā),它解決了客戶端數(shù)據(jù)請求鏈?zhǔn)窖舆t、敏感數(shù)據(jù)暴露風(fēng)險等問題,提供了更好的用戶體驗(yàn)和安全性,本文介紹高性能React開發(fā)React Server Components詳解,感興趣的朋友一起看看吧
    2025-03-03
  • React項(xiàng)目中報(bào)錯:Parsing error: The keyword 'import' is reservedeslint的問題及解決方法

    React項(xiàng)目中報(bào)錯:Parsing error: The keyword &a

    ESLint 默認(rèn)使用的是 ES5 語法,如果你想使用 ES6 或者更新的語法,你需要在 ESLint 的配置文件如:.eslintrc.js等中設(shè)置 parserOptions,這篇文章主要介紹了React項(xiàng)目中報(bào)錯:Parsing error: The keyword 'import' is reservedeslint的問題及解決方法,需要的朋友可以參考下
    2023-12-12
  • React 組件中實(shí)現(xiàn)事件代理

    React 組件中實(shí)現(xiàn)事件代理

    React的事件系統(tǒng)和瀏覽器事件系統(tǒng)相比,主要增加了兩個特性:事件代理、和事件自動綁定,本文主要介紹了React 組件中實(shí)現(xiàn)事件代理,具有一定的參考價值,感興趣的可以了解一下
    2024-01-01
  • react 報(bào)錯Module build failed: BrowserslistError: Unknown browser query `dead`問題的解決方法

    react 報(bào)錯Module build failed: Browserslis

    這篇文章主要介紹了react 報(bào)錯Module build failed: BrowserslistError: Unknown browser query `dead`問題的解決方法,需要的朋友可以參考下
    2023-06-06
  • React跨路由組件動畫實(shí)現(xiàn)

    React跨路由組件動畫實(shí)現(xiàn)

    在 React 中路由之前的切換動畫可以使用 react-transition-group 來實(shí)現(xiàn),但對于不同路由上的組件如何做到動畫過渡是個很大的難題,目前社區(qū)中也沒有一個成熟的方案,本文給大家分享React跨路由組件動畫實(shí)現(xiàn),感興趣的朋友一起看看吧
    2023-10-10
  • React?中的列表渲染要加?key的原因分析

    React?中的列表渲染要加?key的原因分析

    這篇文章主要介紹了React?中的列表渲染為什么要加?key,在?React?中我們經(jīng)常需要渲染列表,比如展示好友列表,文中給大家介紹了列表渲染不提供?key?會如何,通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友一起看看吧
    2022-07-07
  • React-redux?中useSelector使用源碼分析

    React-redux?中useSelector使用源碼分析

    在一個 action 被分發(fā)(dispatch) 后,useSelector() 默認(rèn)對 select 函數(shù)的返回值進(jìn)行引用比較 ===,并且僅在返回值改變時觸發(fā)重渲染,,這篇文章主要介紹了React-redux?中useSelector使用,需要的朋友可以參考下
    2023-10-10
  • react-router4 嵌套路由的使用方法

    react-router4 嵌套路由的使用方法

    本篇文章主要介紹了react-router4 嵌套路由的使用方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • 探討JWT身份校驗(yàn)與React-router無縫集成

    探討JWT身份校驗(yàn)與React-router無縫集成

    這篇文章主要為大家介紹了JWT身份校驗(yàn)與React-router無縫集成的探討解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • 詳解如何使用React?Redux實(shí)現(xiàn)異步請求

    詳解如何使用React?Redux實(shí)現(xiàn)異步請求

    這篇文章主要為大家詳細(xì)介紹了如何使用React?Redux實(shí)現(xiàn)異步請求,文中的示例代碼講解詳細(xì),具有一定的借鑒價值,有需要的小伙伴可以參考一下
    2025-01-01

最新評論

南雄市| 高阳县| 哈巴河县| 林口县| 麦盖提县| 金秀| 新郑市| 新津县| 敦煌市| 南宫市| 河间市| 晋州市| 曲沃县| 沙湾县| 莲花县| 芮城县| 闵行区| 紫阳县| 建水县| 定南县| 图们市| 深州市| 崇州市| 苏尼特右旗| 航空| 雷山县| 无为县| 札达县| 高陵县| 明光市| 湾仔区| 新安县| 祁连县| 利川市| 曲沃县| 公安县| 韩城市| 香港| 白朗县| 香河县| 三明市|