若是你是一個 React 愛好者,開始在各類站點聽到有人談論 Reason 這個新語言,也看見 Jordan(React 做者)說 ReasonReact 將是將來,但你倒是不知道從哪下手,那麼這篇小教程就是爲你準備的。
ps. 有條件的話仍是儘可能看 Reason 和 ReasonReact 的官方文檔吧javascript
pps. Jared 寫的 A ReasonReact Tutorial 是 ReasonReact 最棒的入門指南。本文也是經由他容許,參考了不少其中的內容。能看的懂英語的都直接去他那裏吧~html
Reason 是一門基於 OCaml 的語言,它爲 Ocaml 帶來了新的語法和工具鏈。它既能夠經過 BuckleScript 被同編譯爲 JavaScript,也支持直接編譯爲原生的二進制彙編。Reason 提供了和 JavaScript 類似的語法,也可使用 npm 來安裝依賴。長江後浪推前浪,Reason 丟掉了歷史包袱,比 JavaScript 多了可靠的靜態類型,也更快更簡潔!前端
「爲啥我要花時間學一門全新的語言呢?是 JavaScript 哪裏很差仍是大家要求過高?」
錯!Reason 不是一門全新的語言,事實上 80% 的語義均可以直接對應到現代的 JavaScript 上,反之也差很少。你只須要丟棄掉一丟丟的 JavaScript 邊角語法,再學一點點好東西,就能夠得到也許 ES2030 纔有的特性。對於大部分人來講,學習 Reason 也不會比學習 JavaScript 和一個其餘的類型系統(好比 Flow)來的慢。java
不相信的話,先本身去看看 JS -> Reason 速查表,而後去 playground 體驗一下吧。node
若是你體驗了一下,仍是提不起興趣,你能夠再出門右轉逛逛隔壁家 elm 和 ClojureScript 試試。但若是你以爲 ok,殊不知道從哪下手,那不妨和我同樣,從我們熟悉的 React 開始。Jordan 從新發起了 ReasonReact 這個新項目,讓咱們能夠換一種更簡單優雅的方式寫 React。react
ReasonReact 提供了一些和 React 腳手架相似的工具,好比 reason-scripts。不過爲了理解的深刻一點,不妨從零開始搭起咱們的第一個 ReasonReact 項目。新建一個項目目錄,名字隨意,讓咱們開始吧~ 固然,你也能夠直接 clone 已經準備好了的 simple-reason-react-demo 項目來參考。webpack
首先,初始化 package.json
git
{ "name": "simple-reason-react-demo", "version": "0.1.0", "scripts": { "start": "bsb -make-world -w", "build": "webpack -w" }, "dependencies": { "react": "^16.2.0", "react-dom": "^16.2.0", "reason-react": "^0.3.0" }, "devDependencies": { "bs-platform": "^2.1.0", "webpack": "^3.10.0" } }
而後安裝一下依賴:es6
npm install --registry=https://registry.npm.taobao.org
項目裏安裝了最新的 React 和 ReactDOM,以及額外的 ReasonReact。而編譯工具使用了前端業界標準 Webpack 和 張宏波 開發的 bs-platform。你可能暫時還弄不清 BuckleScript 在這裏將要扮演怎樣的角色,不過不要緊,暫時你只要把他理解成 Reason -> JavaScript 的編譯器就行了,就像 Babel 把 ES2016 編譯成了 ES5 同樣。github
而後,咱們添加一個 BuckleScript 的配置文件 bsconfig.json
{ "name" : "simple-reason-react-demo", "reason" : {"react-jsx" : 2}, "refmt": 3, "bs-dependencies": ["reason-react"], "sources": "src" }
能夠大概猜出來,項目用到了 reason 的 react-jsx
語法,依賴了 reason-react
,源代碼存放在 src
目錄。時間有限,就先不展開研究了,詳細配置能夠查看 bsconfig.json 結構。再建立下 src 目錄,咱們的項目應該長成這樣了
. ├── bsconfig.json ├── src ├── node_modules └── package.json
是否是很容易的就到這裏了,讓咱們正式開始寫 Reason 吧!在 src 裏新建 Main.re
文件,寫下 Hello World
ReactDOMRe.renderToElementWithId( <div>(ReasonReact.stringToElement("Hello ReasonReact"))</div>, "root" );
幾乎和 React 代碼同樣不是麼?而後咱們運行編譯命令
# 至關於以前寫好的 'bsb -make-world -w' npm start
一切正常的話,能夠看到編譯成功的提示,不然就要辛苦你按錯誤提示排查一下了,注意 bsb 的輸出對咱們的很重要,一些錯誤提示和類型檢查的信息都要經過它來看。由於咱們開啓了 -w
的 watch 模式,接下來還要用到,就先不用退出了。bsb 將代碼編譯到了 lib 目錄下
lib ├── bs └── js └── src └── Main.js
目前咱們要關注一下的是 lib/js/src/Main.js
,打開它咱們能夠看到編譯好的 JavaScript 代碼,很是漂亮是吧?這都是 BuckleScript 的功勞。爲了讓代碼能在瀏覽器裏運行,咱們還須要用 Webpack 打包一下模塊化,這些你都應該很是熟悉了。
建立 public/index.html
<!doctype html> <meta charset=utf8> <title>你好</title> <body> <div id="root"></div> <script src="./bundle.js"></script>
以及 webpack.config.js
const path = require('path'); module.exports = { entry: './lib/js/src/Main.js', output: { path: path.join(__dirname, "public"), filename: 'bundle.js', }, };
Webpacck 配置裏入口是 bsb 編譯生成的 './lib/js/src/Main.js'。再打開一個終端運行 npm run build
,咱們的準備工做就所有就緒了。咱們只利用 webpack 作很簡單的打包,因此你基本能夠忽略這個終端的輸出,仍是把精力放在剛剛的 start 命令上。接下來直接在瀏覽器裏打開 index.html 文件,就能夠看到 「Hello ReasonReact」 了~
讓咱們開始第一個組件的開發,一個只能加加減減的步進器。新建一個組件文件:src/Stepper.re
let component = ReasonReact.statelessComponent("Stepper"); let make = (children) => ({ ...component, render: (self) => <div> <div>(ReasonReact.stringToElement("I'm a Stepper! "))</div> </div> });
ReasonReact.statelessComponent
會返回一個默認的組件定義,裏面包含了你熟悉的那些生命週期函數以及其餘一些方法和屬性。這裏咱們定義了 make
方法,目前它只接受一個 children
參數,返回了一個組件。咱們利用了相似 es6 的 ... 對象展開操做符
重寫了 component
中的 render
方法。神奇的是這段代碼竟然徹底符合 JavaScript 的語法...接下來,讓咱們再修改一下 Main.re
,讓他渲染這個 Stepper 組件
ReactDOMRe.renderToElementWithId(<Stepper />, "root");
刷新下瀏覽器,你應該能夠看到剛寫好的組件就這麼成功的 render 出來了。
你可能很好奇爲何這裏沒有寫 require()
或 import
。這是由於 Reason 的跨文件依賴是自動從你的代碼中推導出來的,當編譯器看到 Stepper
這個在 Main.re
中並無定義的量,它就會自動去找 Stepper.re
這個文件並引入該模塊。
熟悉 ReactJS 的同窗都應該知道,jsx 並非什麼特殊的語法,只是會被編譯成普通的函數調用,好比
<div>Hello React</div> // to React.createElement( "div", null, "Hello React" );
而在 ReasonReact 中,jsx 會被翻譯成
<Stepper /> /* to */ Stepper.make([||]) /* [|1,2,3|] 是 Reason 中數組的語法 */
意思是調用 Stepper 模塊的 make 函數,參數是一個空的數組。這就和咱們以前寫好的 Stepper.re
中的 make 函數對應上了,這個空數組就對應於 make 的參數 children。再讓咱們看眼咱們的第一個組件
let component = ReasonReact.statelessComponent("Stepper"); let make = (children) => ({ ...component, render: (self) => <div> <div>(ReasonReact.stringToElement("I'm a Stepper! "))</div> </div> });
不一樣於 ReactJS 中組件的 render
,這裏的 render
方法須要一個參數:self
,暫且你能夠把它比做 this,由於咱們的 Stepper
是一個 stateless 組件,因此咱們還用不到它。render
方法裏返回的一樣是虛擬 DOM 節點,不一樣的是節點必須符合 ReasonReact 要求的節點類型。咱們不能再直接寫 <div>Hello</div>
,而得使用 ReasonReact 提供的 stringToElement
包裝一層。嫌函數名太長?先忍着吧...
思來想去,咱們的步進器還須要一個狀態,就是要顯示的數字。在 Reason 中,咱們須要先定義 state
的類型(type
)
type state = { value: int };
若是你寫過 flow 或者 typescript,必定不會以爲奇怪,這標識咱們的 state 中包含 int
類型的 value
字段。而後,咱們須要開始把原先的 statelessComponent
替換成 reducerComponent
,原先的組件代碼也須要略微改動一下
type state = { value: int }; let component = ReasonReact.reducerComponent("Stepper"); let make = (children) => ({ ...component, initialState: () => { value: 0 }, reducer: ((), state) => ReasonReact.NoUpdate, render: (self) => <div> <div>(ReasonReact.stringToElement(string_of_int(self.state.value)))</div> </div> });
聰明的你確定一下就看懂了 initialState
和 ReactJS 的 getInitialState
簡直如出一轍。而在 render
這裏也很相似,組件當前的狀態能夠經過 self.state
獲取,仍是爲了類型匹配咱們套了一層 string_of_int
將 int
類型的 value
轉換成 string
。而新增的 reducer
函數可能就有點看不懂了。有意思的地方來啦~
在 ReactJS 中,咱們依靠 setState
去手動的更新 state
。ReasonReact 裏則引入了 「reducer
」 的概念,看上去很像 Redux 對吧?也許是 Jordan 本身也不是很喜歡 setState
這個非函數式的操做吧 …… ReasonReact 裏更新一個組件狀態分爲兩個步驟,首先發起一個 action
,而後在 reducer
中處理它並更新狀態。此時此刻,咱們尚未添加 action
,因此 reducer
仍是無操做的,咱們直接返回了一個 ReasonReact.NoUpdate
來標識咱們並無觸發更新。讓咱們繼續加上 action
type state = { value: int }; /* here */ type action = | Increase | Decrease; let component = ReasonReact.reducerComponent("Stepper"); let make = (children) => ({ ...component, initialState: () => { value: 0 }, reducer: (action, state) => { /* here */ switch action { | Decrease => ReasonReact.Update({value: state.value - 1}) | Increase => ReasonReact.Update({value: state.value + 1}) }; }, render: (self) => <div> /* and here */ <button onClick={self.reduce((evt) => Decrease)}>(ReasonReact.stringToElement("-"))</button> <div>(ReasonReact.stringToElement(string_of_int(self.state.value)))</div> <button onClick={self.reduce((evt) => Increase)}>(ReasonReact.stringToElement("+"))</button> </div> });
首先,咱們定義了 action
類型,它是一個 Variant(變體)。在 JavaScript 的世界裏咱們沒見過這種值,它用來表示這個變體(或者先叫它 "枚舉"?)可能的值。就像在 Redux 中推薦先聲明一堆 actionType
同樣,這個例子裏咱們定義了 +(Increase
) 和 -(Decrease
) 兩種 action
。
而後咱們就能夠給 button
增長點擊的回調函數。咱們使用了 self.reduce
這個函數(還記得 dispatch
麼),它接收一個函數 (evt) => Increase
作轉換,能夠把它看做將點擊的 event(在這裏咱們忽略掉了它由於用不到它...)換成一個 action
,而這個 action
會被 self.reduce
用於作一個反作用操做來更新 state
,更新 state
的操做就在 reducer
中。
reducer
內採用了模式匹配的形式,定義了對於全部可能的 action
須要如何更新 state
。例如,對於 Increase
這個類型的 action
,返回了 ReasonReact.Update({value: self.state.value + 1})
去觸發更新。值得注意的是,組件的 state
是不可變的,而目前 state
中只有 value
一個字段,因此咱們沒有 {...state, value: state.value + 1}
這樣去展開它。
若是你熟悉 Redux 的話,應該很是熟悉這一套範式了(雖然這其實來源於 Elm)。不一樣的是,咱們直接擁有不可變的數據,再也不須要過分的使用 JavaScript 的 String
來作 actionType
,reducer 也寫的更加優雅簡單了,看着真是舒服~
這篇文章到這裏也就暫時結束了,距離能作出通常的組件功能咱們還差了不少東西。目前我也只是在一些我的的小項目中使用 Reason,文章內容很淺,主要是但願能啓發下厲害的你去嘗試 Reason 這個還算新鮮的語言,相信它會讓你眼前一亮的。
對了,既然都看到這裏了,不如再去看看今年兩次 React Conf 上 chenglou 關於 Reason 的精彩演講吧~