摘要:前言是前端最受歡迎的框架之一,解讀其源碼的文章非常多,但是我想從另一個角度去解讀從零開始實現一個,從層面實現的大部分功能,在這個過程中去探索為什么有虛擬為什么這樣設計等問題。
前言
React是前端最受歡迎的框架之一,解讀其源碼的文章非常多,但是我想從另一個角度去解讀React:從零開始實現一個React,從API層面實現React的大部分功能,在這個過程中去探索為什么有虛擬DOM、diff、為什么setState這樣設計等問題。
提起React,總是免不了和Vue做一番對比
Vue的API設計非常簡潔,但是其實現方式卻讓人感覺是“魔法”,開發者雖然能馬上上手,但是為什么能實現功能卻很難說清楚。
相比之下React的設計哲學非常簡單,雖然經常有需要自己處理各種細節問題,但是卻讓人感覺它非常“真實”,能清楚地感覺到自己仍然是在寫js。
關于jsx在開始之前,我們有必要搞清楚一些概念。
我們來看一下這樣一段代碼:
const title =Hello, world!
;
這段代碼并不是合法的js代碼,它是一種被稱為jsx的語法擴展,通過它我們就可以很方便的在js代碼中書寫html片段。
本質上,jsx是語法糖,上面這段代碼會被babel轉換成如下代碼
const title = React.createElement( "h1", { className: "title" }, "Hello, world!" );
你可以在babel官網提供的在線轉譯測試jsx轉換后的代碼,這里有一個稍微復雜一點的例子
準備工作為了集中精力編寫邏輯,在代碼打包工具上選擇了最近火熱的零配置打包工具parcel,需要先安裝parcel:
npm install -g parcel-bundler
接下來新建index.js和index.html,在index.html中引入index.js。
當然,有一個更簡單的方法,你可以直接下載這個倉庫的代碼:
https://github.com/hujiulong/...
注意一下babel的配置
.babelrc
{ "presets": ["env"], "plugins": [ ["transform-react-jsx", { "pragma": "React.createElement" }] ] }
這個transform-react-jsx就是將jsx轉換成js的babel插件,它有一個pragma項,可以定義jsx轉換方法的名稱,你也可以將它改成h(這是很多類React框架使用的名稱)或別的。
準備工作完成后,我們可以用命令parcel index.html將它跑起來了,當然,現在它還什么都沒有。
React.createElement和虛擬DOM前文提到,jsx片段會被轉譯成用React.createElement方法包裹的代碼。所以第一步,我們來實現這個React.createElement方法
從jsx轉譯結果來看,createElement方法的參數是這樣:
createElement( tag, attrs, child1, child2, child3 );
第一個參數是DOM節點的標簽名,它的值可能是div,h1,span等等
第二個參數是一個對象,里面包含了所有的屬性,可能包含了className,id等等
從第三個參數開始,就是它的子節點
我們對createElement的實現非常簡單,只需要返回一個對象來保存它的信息就行了。
function createElement( tag, attrs, ...children ) { return { tag, attrs, children } }
函數的參數 ...children使用了ES6的rest參數,它的作用是將后面child1,child2等參數合并成一個數組children。
現在我們來試試調用它
// 將上文定義的createElement方法放到對象React中 const React = { createElement } const element = (helloworld!); console.log( element );
打開調試工具,我們可以看到輸出的對象和我們預想的一致
我們的createElement方法返回的對象記錄了這個DOM節點所有的信息,換言之,通過它我們就可以生成真正的DOM,這個記錄信息的對象我們稱之為虛擬DOM。
ReactDOM.render接下來是ReactDOM.render方法,我們再來看這段代碼
ReactDOM.render(Hello, world!
, document.getElementById("root") );
經過轉換,這段代碼變成了這樣
ReactDOM.render( React.createElement( "h1", null, "Hello, world!" ), document.getElementById("root") );
所以render的第一個參數實際上接受的是createElement返回的對象,也就是虛擬DOM
而第二個參數則是掛載的目標DOM
總而言之,render方法的作用就是將虛擬DOM渲染成真實的DOM,下面是它的實現:
function render( vnode, container ) { // 當vnode為字符串時,渲染結果是一段文本 if ( typeof vnode === "string" ) { const textNode = document.createTextNode( vnode ); return container.appendChild( textNode ); } const dom = document.createElement( vnode.tag ); if ( vnode.attrs ) { Object.keys( vnode.attrs ).forEach( key => { if ( key === "className" ) key = "class"; // 當屬性名為className時,改回class dom.setAttribute( key, vnode.attrs[ key ] ) } ); } vnode.children.forEach( child => render( child, dom ) ); // 遞歸渲染子節點 return container.appendChild( dom ); // 將渲染結果掛載到真正的DOM上 }
這里注意React為了避免類名class和js關鍵字class沖突,將類名改成了className,在渲染成真實DOM時,需要將其改回。
這里其實還有個小問題:當多次調用render函數時,不會清除原來的內容。所以我們將其附加到ReactDOM對象上時,先清除一下掛載目標DOM的內容:
const ReactDOM = { render: ( vnode, container ) => { container.innerHTML = ""; return render( vnode, container ); } }渲染和更新
到這里我們已經實現了React最為基礎的功能,可以用它來做一些事了。
我們先在index.html中添加一個根節點
我們先來試試官方文檔中的Hello,World
ReactDOM.render(Hello, world!
, document.getElementById("root") );
可以看到結果:
試試渲染一段動態的代碼,這個例子也來自官方文檔
function tick() { const element = (); ReactDOM.render( element, document.getElementById( "root" ) ); } setInterval( tick, 1000 );Hello, world!
It is {new Date().toLocaleTimeString()}.
可以看到結果:
這篇文章中,我們實現了React非常基礎的功能,也了解了jsx和虛擬DOM,下一篇文章我們將實現非常重要的組件功能。
最后留下一個小問題
在定義React組件或者書寫React相關代碼,不管代碼中有沒有用到React這個對象,我們都必須將其import進來,這是為什么?
例如:
import React from "react"; // 下面的代碼沒有用到React對象,為什么也要將其import進來 import ReactDOM from "react-dom"; ReactDOM.render(, document.getElementById( "editor" ) );
不知道答案的同學再仔細看看這篇文章哦
從零開始實現React系列React是前端最受歡迎的框架之一,解讀其源碼的文章非常多,但是我想從另一個角度去解讀React:從零開始實現一個React,從API層面實現React的大部分功能,在這個過程中去探索為什么有虛擬DOM、diff、為什么setState這樣設計等問題。
整個系列大概會有六篇左右,我每周會更新一到兩篇,我會第一時間在github上更新,有問題需要探討也請在github上回復我~
博客地址: https://github.com/hujiulong/blog
關注點star,訂閱點watch
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93531.html
摘要:在這篇文章中,我們就要實現的組件功能。這篇文章的代碼從零開始實現系列是前端最受歡迎的框架之一,解讀其源碼的文章非常多,但是我想從另一個角度去解讀從零開始實現一個,從層面實現的大部分功能,在這個過程中去探索為什么有虛擬為什么這樣設計等問題。 前言 在上一篇文章JSX和虛擬DOM中,我們實現了基礎的JSX渲染功能,但是React的意義在于組件化。在這篇文章中,我們就要實現React的組件功...
摘要:想要自己實現一個簡易版框架,并不是非常難。為了防止出現這種情況,我們需要改變整體的策略。上面這段話,說的就是版本和架構的區別。 showImg(https://segmentfault.com/img/bVbwfRh); 想要自己實現一個React簡易版框架,并不是非常難。但是你需要先了解下面這些知識點如果你能閱讀以下的文章,那么會更輕松的閱讀本文章: 優化你的超大型React應用 ...
摘要:想要自己實現一個簡易版框架,并不是非常難。為了防止出現這種情況,我們需要改變整體的策略。上面這段話,說的就是版本和架構的區別。 showImg(https://segmentfault.com/img/bVbwfRh); 想要自己實現一個React簡易版框架,并不是非常難。但是你需要先了解下面這些知識點如果你能閱讀以下的文章,那么會更輕松的閱讀本文章: 優化你的超大型React應用 ...
摘要:司徒正美的一款了不起的化方案,支持到。行代碼內實現一個胡子大哈實現的作品其實就是的了源碼學習個人文章源碼學習個人文章源碼學習個人文章源碼學習個人文章這幾片文章的作者都是司徒正美,全面的解析和官方的對比。 前言 在過去的一個多月中,為了能夠更深入的學習,使用React,了解React內部算法,數據結構,我自己,從零開始寫了一個玩具框架。 截止今日,終于可以發布第一個版本,因為就在昨天,我...
閱讀 3087·2021-10-12 10:20
閱讀 2821·2021-09-27 13:56
閱讀 796·2021-09-27 13:36
閱讀 1436·2021-09-26 09:46
閱讀 2423·2019-08-30 14:02
閱讀 2691·2019-08-28 18:14
閱讀 1269·2019-08-26 10:32
閱讀 1710·2019-08-23 18:25