React學(xué)習(xí)篇-JSX(手寫一個JSX的插件)

學(xué)習(xí)和閱讀 vue 源碼有段時間了,最近在嘗試去學(xué)習(xí) react窑滞,由于眼前項目使用不上 react力穗,并不想一股腦的學(xué)習(xí)它的 API(長時間不用還是會忘)舞虱,所以此次的學(xué)習(xí)過程打算換種方式,對于 react 涉及到的每個點嘗試逐個深入类茂,了解其解析過程及整個框架的思路耍属。

對于每個點的學(xué)習(xí)和深入,將以文章的形式產(chǎn)出巩检,主要是對于學(xué)習(xí)的內(nèi)容的記錄(所以看來內(nèi)容有點多)厚骗,方便自己以后是用時查閱和回顧。

從demo開始

在此之前兢哭,曾多次的在 react 入門的邊緣來回試探领舰,每次都止于寫一個簡單的 demo,我相信下面這個大家肯定很熟悉迟螺,本文也是從這里開始的冲秽。

npx create-react-app my-app
cd my-app
npm install
npm start

然后應(yīng)該就能跑起來(環(huán)境和安裝沒有問題的話),簡化下代碼矩父,然后面對下面的這個代碼陷入了思考锉桑,雖然以前見過也寫過很多次了。

代碼如下:

import React from 'react';

function App() {
  return (
    <div>
        <h1>good good study, day day up</h1>
    </div>
  );
}

export default App;

APP 返回的乍一看很像 html窍株,當(dāng)然相信很多人都知道這個是 JSX 的語法民轴。那么問題來了:

JSX 語法寫的模版,如何生成真實的 dom夹姥?

類比我們先看看Vuetemplate - ast - code -vnode - dom的實現(xiàn)杉武。

簡單看下Vue 的parse過程

template 轉(zhuǎn)換成 ast 及 ast 轉(zhuǎn)換成 code 的過程推薦幾篇文章:

以下用一個簡單的例子來簡單說明下 parse 的過程

template 如下:

<template>
    <div>
        <h1>good good study, day day up</h1>
    </div>
</template>

對應(yīng)生成的 ast 如下:

// 簡化版,主要是看下結(jié)構(gòu)
{
    //...
    parent: undefined,
    children: [
        {
            parent: {
                //...
                tag: "div",
                type: 1
            },
            children: [
                // ...
                text: "good good study, day day up",
                type: 3
            ],
            tag: "h1",
            type: 1
        }
    ],
    tag: "div",
    type: 1
}

對應(yīng)生成的 code 如下:

with(this){
    return _c(
        'div',
        [
            _c(
                'h1',
                [_v("good good study, day day up")]
            )
        ]
    )
}

最終得到的結(jié)果就是這樣的渲染函數(shù)辙售。

我們再看看react的實現(xiàn)轻抱。首先直接看npm star后的main.chunk.js文件,可以看到如下的代碼(簡化版):

function App() {
    return createElement(
        "div", 
        {
            __source: {
                fileName: _jsxFileName,
                lineNumber: 5
            },
            __self: this
        }, 
        createElement(
            "h1", 
            {
                __source: {
                    fileName: _jsxFileName,
                    lineNumber: 6
                },
                __self: this
            }, 
            "good good study, day day up"
        )
    );
}

對比 Vue 生成的 code旦部,會發(fā)現(xiàn)很像祈搜,所以這里可以先總結(jié)一下:

react 也是通過一層轉(zhuǎn)換较店,把我們寫的 JSX 模版,轉(zhuǎn)換成對應(yīng)的函數(shù)容燕。

所以這就算完了梁呈?來,接著來蘸秘,JSX 是如何轉(zhuǎn)換的官卡?

JSX 的轉(zhuǎn)換過程

了解 Vue parse過程的就知道,轉(zhuǎn)換是發(fā)生在編譯的階段:在首次$mount的時候會執(zhí)行compileToFunctions(其中主要就是模版到渲染函數(shù)的過程)醋虏。

那 React 呢寻咒,嘗試去看了 ReactReactDOM 的源碼,根本找不到任何轉(zhuǎn)換的代碼颈嚼。而且大家也看到了main.chunk.js的代碼毛秘,我們寫的 JSX 已經(jīng)轉(zhuǎn)換成對應(yīng)的函數(shù)了。所以再此之前阻课,已經(jīng)完成了轉(zhuǎn)換叫挟。

好了不賣關(guān)子了,這里用的是 babel 解析器(什么是Babel限煞,Babel能做什么)抹恳,我們首先找到工程中配置的地方。

尋找 babel 配置的入口

由于本人對于工程配置及工程化不是很了解晰骑,所以我這里也是找了很久适秩,要想找到 babel 配置的入口,需先執(zhí)行(最好找個demo工程執(zhí)行硕舆,該命令不可逆)

yarn eject

找到 /config/webpack.config.js, 相關(guān)代碼如下:

module: {
    {
        test: /\.(js|mjs|jsx|ts|tsx)$/,
        include: paths.appSrc,
        loader: require.resolve('babel-loader'),
        options: {
        customize: require.resolve(
            'babel-preset-react-app/webpack-overrides'
        ),
        
        plugins: [
            [
            require.resolve('babel-plugin-named-asset-import'),
            {
                loaderMap: {
                svg: {
                    ReactComponent: '@svgr/webpack?-svgo,+ref![path]',
                },
                },
            },
            ],
        ],
        cacheDirectory: true,
        cacheCompression: isEnvProduction,
        compact: isEnvProduction,
        },
    },
    {
        test: /\.(js|mjs)$/,
        exclude: /@babel(?:\/|\\{1,2})runtime/,
        loader: require.resolve('babel-loader'),
        options: {
        babelrc: false,
        configFile: false,
        compact: false,
        presets: [
            [
            require.resolve('babel-preset-react-app/dependencies'),
            { helpers: true },
            ],
        ],
        cacheDirectory: true,
        cacheCompression: isEnvProduction,
        sourceMaps: false,
    },
}

看到這里相信就能知道秽荞,這里其實就是配置了 loader,試了看各個解析器的源碼抚官,但是仍然困難重重(各種引用)扬跋,這里也是換了種方式來學(xué)習(xí)解析的過程。

嘗試手寫一個 JSX 的插件凌节。

手寫 JSX 的插件

這里大家網(wǎng)上搜應(yīng)該能搜出一堆關(guān)于babel 插件的代碼钦听,我這里也是找到一個基礎(chǔ)的例子。

console的插件的例子

以下是一個將log處理成console.log的插件的代碼:

const babel = require('@babel/core')
const t = require('babel-types')

const code = `
    const a = 3 * 103.5 * 0.8;
    log(a);
    const b = a + 105 - 12;
    log(b);
`

const visitor = {
    CallExpression(path) {
        // 這里判斷一下如果不是log的函數(shù)執(zhí)行語句則不處理
        if (path.node.callee.name !== 'log') return
        // t.CallExpression 和 t.MemberExpression分別代表生成對于type的節(jié)點倍奢,path.replaceWith表示要去替換節(jié)點,這里我們只改變CallExpression第一個參數(shù)的值朴上,第二個參數(shù)則用它自己原來的內(nèi)容,即本來有的參數(shù)
        path.replaceWith(t.CallExpression(
            t.MemberExpression(t.identifier('console'), t.identifier('log')),
            path.node.arguments
        ))
    }
}

const result = babel.transform(code, {
    plugins: [{
        visitor: visitor
    }]
})

console.log(result.code)

處理結(jié)果:

const a = 3 * 103.5 * 0.8;
console.log(a);
const b = a + 105 - 12;
console.log(b);

看了代碼后應(yīng)該差不多能了解插件的編寫過程卒煞,大致如下:code 首先會解析成 AST痪宰,然后會遍歷整個 AST 樹,每個節(jié)點都有其特定的屬性,插件的vistor對象的處理函數(shù)會在解析的過程中被調(diào)用衣撬,插件要做的事情就是在合適的地方(這里是CallExpression),符合條件的情況下(這里是 path.node.callee.name === 'log'),對解析結(jié)果進(jìn)行更改乖订。知道原理以后,嘗試著寫 JSX 解析的插件具练。

照葫蘆畫瓢:

const code = `
    var html = <div>
        <h1>good good study, day day up</h1>
    </div>
`

const visitor = {
   
}

const result = babel.transform(code, {
    plugins: [
        {
            visitor: visitor
        }
    ]
})

console.log(result.code)

大致的結(jié)構(gòu)就是這樣乍构,期望達(dá)到的目標(biāo)code對應(yīng)的輸出如下:

var html = React.createElement(
    "div", 
    null, 
    React.createElement("h1", null, "good good study, day day up")
)

以上代碼執(zhí)行后,會報錯扛点,因為并不是js的標(biāo)準(zhǔn)語法哥遮,無法正常解析,所以這里首先需要引入一個插件 plugin-syntax-jsx占键,讓解析器其能識別該種語法昔善。

引入插件,修改的代碼如下:

babel.transform(code, {
    plugins: [
        '@babel/plugin-syntax-jsx',
        {
            visitor: visitor
        }
    ]
})

執(zhí)行的結(jié)果為:

var html = <div>
    <h1>good good study, day day up</h1>
</div>;

這里能看到我們能正常識別 JSX 模版畔乙,只是輸出并不是我們需要的,我們需要把它轉(zhuǎn)換成我們的函數(shù)翩概。接下來的一步就是需要找到合適的時機(jī)牲距。

尋找時機(jī)

這里我們只是知道我們能正常識別了,但是在解析的過程中钥庇,其對應(yīng)的 AST 具體長什么樣子呢牍鞠?

這里也是推薦一個網(wǎng)站,https://astexplorer.net/

AST的結(jié)構(gòu)圖

這里就能看到整個 AST 樹的結(jié)構(gòu)(這里還沒去看解析成 AST 生成的過程评姨,目測和 Vue 中 parseHTML 的過程原理一樣难述,這里后續(xù)會花點時間看下 babal 生成 AST 的過程),應(yīng)該很快就能找到我們想要的關(guān)鍵信息-JSXElement吐句,對照以上的 AST 和關(guān)鍵信息胁后,就當(dāng)前這個例子,我們就思考下‘合適的時機(jī)‘-JSXElement的變量賦值:

  • VariableDeclarator
  • init.type === 'JSXElement'

加入‘時機(jī)’代碼

加入‘時機(jī)’后代碼如下:

const babel = require('@babel/core')

const code = `
    var html = <div>
        <h1>good good study, day day up</h1>
    </div>
`

const visitor = {
    VariableDeclarator(path) {
        if (path.node.init.type === 'JSXElement'){
            console.log('start')
            // deal 
        }
    }
}

const result = babel.transform(code, {
    plugins: [
        '@babel/plugin-syntax-jsx',
        {
            visitor: visitor
        }
    ]
})

console.log(result.code)

得到的結(jié)果如下:

start
var html = <div>
        <h1>good good study, day day up</h1>
    </div>;

當(dāng)然這里只是輸入標(biāo)簽的信息嗦枢,其中還有很多其他的節(jié)點信息攀芯,其他的信息那么也就是 JSX 的語法規(guī)則了,如循環(huán)文虏、class侣诺、條件語句、邏輯代碼等語法規(guī)則了氧秘。本文只做簡單的實現(xiàn)年鸳。接下來要做的就是要整合節(jié)點的信息,生成對應(yīng)的函數(shù)代碼丸相。

生成代碼

... 未完待續(xù)

(這里涉及到babel-types的使用搔确,由于對此塊不是很熟悉,文章先進(jìn)行到這里,后續(xù)寫好會更新上來)


那了解了JSX的解析過程后妥箕,我們思考下滥酥,這個與vue的parse的過程差別在哪?

  • vue 是編譯階段生成對應(yīng)的渲染函數(shù)畦幢,react 是babel解析階段就生成了對應(yīng)的函數(shù)
  • 看過 vue parse階段源碼的同學(xué)應(yīng)該知道坎吻,vue 做了很多處理瀏覽器‘怪異’行為的操作(為了保持和瀏覽器行為的一致性),如:標(biāo)簽換行會有空格符宇葱、canBeLeftOpenTag標(biāo)簽如:p瘦真,會補(bǔ)全關(guān)閉標(biāo)簽等,也就是大家可以像寫普通的html來寫template黍瞧。而 react 的 JSX 就有很多的語法規(guī)則诸尽,如class必須寫className、標(biāo)簽之前的換行后的空格會被忽略等等(仍在學(xué)習(xí)JSX語法中印颤,后續(xù)會繼續(xù)補(bǔ)充完善這塊的區(qū)別)您机。

就第二點區(qū)別,可以看出來年局,如果是原有的html項目,想要遷移成 vue 或 react矢否,遷移成 Vue 的成本會小很多僵朗,Vue 不僅在寫法上验庙,還有對于瀏覽器特殊行為的處理上,都保持了和 html 規(guī)范的統(tǒng)一云矫。若要遷移成 react ,可能改造成本就會比較大让禀。

以上只是react初學(xué)者的主觀的看法巡揍,更多的特性和優(yōu)劣需要深入學(xué)習(xí)后才能了解腮敌。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末糜工,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子油坝,更是在濱河造成了極大的恐慌刨裆,老刑警劉巖帆啃,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诽偷,居然都是意外死亡慈俯,警方通過查閱死者的電腦和手機(jī)贴膘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來突梦,“玉大人宫患,你說我怎么就攤上這事娃闲』拾铮” “怎么了蛋辈?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長渐白。 經(jīng)常有香客問我尊浓,道長纯衍,這世上最難降的妖魔是什么栋齿? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任托酸,我火速辦了婚禮褒颈,結(jié)果婚禮上励堡,老公的妹妹穿的比我還像新娘谷丸。我一直安慰自己,他們只是感情好应结,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鹅龄,像睡著了一般扮休。 火紅的嫁衣襯著肌膚如雪玷坠。 梳的紋絲不亂的頭發(fā)上八堡,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天兄渺,我揣著相機(jī)與錄音,去河邊找鬼挂谍。 笑死叔壤,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凳兵。 我是一名探鬼主播百新,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼庐扫!你這毒婦竟也來了饭望?” 一聲冷哼從身側(cè)響起仗哨,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎铅辞,沒想到半個月后厌漂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡斟珊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年苇倡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囤踩。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡旨椒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出堵漱,到底是詐尸還是另有隱情综慎,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布勤庐,位于F島的核電站示惊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏愉镰。R本人自食惡果不足惜米罚,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望丈探。 院中可真熱鬧录择,春花似錦、人聲如沸碗降。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遗锣。三九已至,卻和暖如春嗤形,著一層夾襖步出監(jiān)牢的瞬間精偿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工赋兵, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留笔咽,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓霹期,卻偏偏與公主長得像叶组,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子历造,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容

  • babel官網(wǎng) babel 介紹 Babel 是一個通用的多用途 JavaScript 編譯器甩十。通過 Babel ...
    鋒享前端閱讀 1,817評論 0 10
  • What is AST 什么是AST?AST是Abstract Syntax Tree(抽象語法樹)的縮寫船庇。傳說中...
    ronniegong閱讀 3,735評論 0 11
  • ?“React 和 Vue 哪個更好?” 論壇上經(jīng)陈录啵看到這樣的問題鸭轮,然后評論區(qū)就直接開戰(zhàn)了。也有朋友轉(zhuǎn)行做前端橄霉,問...
    孤丶狼丶閱讀 3,351評論 0 1
  • 月近中秋刻意圓窃爷,遙想當(dāng)年賞月夜; 明眸軟語究何事姓蜂?濃情團(tuán)圓月餅節(jié)按厘。 喜怒哀樂人間事,情意綿綿訴相聚钱慢; 新炒板栗配柑...
    慕容蘭馨閱讀 360評論 7 6
  • 話說22歲左右的女同志們有怎么樣的選擇逮京? 我正處在這個年齡,花一樣的年紀(jì)滩字。 我有高中同學(xué)已經(jīng)結(jié)婚造虏,當(dāng)我接到婚禮請?zhí)?..
    誰打馬走過我的懷念閱讀 343評論 0 2