若是你是一个 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 的精彩演讲吧~