本期精读的文章是：

[Exploring Reason and GraphQL](https://dev-blog.apollodata.com/exploring-reason-and-graphql-ff877df60d2a)

## 1 引言

2018 年了，Reason 生态发展了不少，而且正好看到一篇文章的作者也抱着这种心态尝鲜 React + graphql，索性调研一下，看看这套前沿的方案是否有落地对可能性。

## 2 内容概要

### 一切皆模块

在 reason 中，一切皆模块，而且不需要手动申明导出与引用，这个是 js 的痛点。以下面的代码为例：

```reason
open Data;

let typeDef = {|
  type Author {
    id: Int!
    firstName: String
    lastName: String
    posts: [Post] # the list of Posts by this author
  }
|};

type resolvers = {. "posts": Js.Array.t(post)};

let resolvers = {
  "posts": (author: Data.author) =>
    Js.Array.filter((post) => post##authorId === author##id, posts)
};
```

第一行的 `open` 类似 js 中的 `import`，不同的是，js 中需要通过 `Data.post` 访问对象，而 reason 可以直接访问 `post`。不过也可以补全引用，比如 `Data.author`。

在定义 graphQL 类型时，graphql-tools 允许通过 `[Post]` 的语法将文章对象关联到作者。

### 内置不可变数据类型检测

reason 中，一切类型都是 immutable 的，如果使用如下代码直接修改 `post.votes`，则会报错：

```reason
Mutation: {
  upvotePost: (_, { postId }) => {
    const post = find(posts, { id: postId });
    if (!post) {
      throw new Error(`Couldn't find post with id ${postId}`);
    }
    post.votes += 1;
    return post;
  },
},
```

可以通过 `ref` 告诉编译器，`votes` 可能是 mutable 的：

```reason
type post = {. "id": int, "authorId": int, "title": string, "votes": ref(int)};
```

最后作者介绍了如何通过 [apollo-server](https://www.apollographql.com/docs/apollo-server/example.html) 搭建后端代码，与 reason 结合使用。

我试了下，真的非常方便，后端定义好接口，会自动生成一份在线文档供前端查询，完全屏蔽了接口这一层，只要搜索要查询的元素即可。

## 3 精读

### graphql

前端后沟通成本一直是个问题，以至于很多团队想做一个 “接口查询平台” 之类的系统。

当然，无论是解析后端代码也好，平台录入也好，还是 mock 平台反推，都不太理想：

解析后端代码，工作量比较大，而且还需要约定一些格式，其实越做越像 graphql，投入的话还不如考虑使用 graphql。

一条条接口录入方案是可行的，技术成本也几乎为零，但问题是后续代码变动会导致平台与实际接口不一致，或者某些项目甚至绕过了接口录入，导致一些接口游离在平台之外，无法聚合管理。

先通过 mock 平台联调，再读取 mock 平台数据，生成接口列表同样存在后端代码变动导致 mock 结构过期的问题。

如果不考虑需求变动，后端采用 graphql 其实是成本最小的选择，其一是类似 `apollo-server` 这类框架做了一个 IDE 供查询实体，同时绕过了接口，直接暴露数据，效率更高。其二是可以做到代码变动后文档实时同步，只要后端代码更新，文档也会自动更新。

不过对于后端代码并不掌握在前端的团队来说，如果不推动后端改造成 graphql，是无法享受到这个好处的，这时如果搭建一个 node 版 graphql 桥梁，那又如何衔接这个桥梁与后端呢？所以使用 graphql 的若不是第一手后端代码，使用后也不会有多少效果。

更多细节可以访问 [GraphQL and Relay 浅析](https://zhuanlan.zhihu.com/p/20638731)，那篇是基于 relay 的，现在 `apollo-server` 看上去是更轻量级的方案。

### reason

最近的 3.0 版本使用 JavaScript 的 application/abstraction 语法代替了 OCaml 的语法，看上去稍微顺眼一些了：

```reason
myFunction(arg1, arg2) // 3.0 语法
myFunction arg1 arg2   // 2.0 语法
```

能看出来 reason 在往 js 开发社区靠，不过大部分语法对 js 开发者都比较陌生，相比于 typescript，跳跃性有点太大了。

#### reason react

使用 reason 写一个 react 组件是这样的：

```reason
let component = ReasonReact.reducerComponent("Greeting");

let make = (~name, _children) => {
  ...component,
  initialState: () => 0, /* here, state is an `int` */
  render: (self) => {
    let greeting =
      "Hello " ++ name ++ ". You've clicked the button " ++ string_of_int(self.state) ++ " time(s)!";
    <div>{ReasonReact.stringToElement(greeting)}</div>
  }
};
```

`~name` 称为 `Labeled Arguments`，也就是，调用函数时，可以无视顺序，显示指定入参名：`make(~name=5)`，`initialState` 对应 reactjs 中 `state`，其他与 reactjs 都很像。

#### reason react 更新 state

相比 react 的 `setState`，reason react 提供了 `reducer` 支持，这里可以类比到 redux：

```reason
let make = (_children) => {
  ...component,
  initialState: () => {count: 0, show: false},
  reducer: (action, state) =>
    switch (action) {
    | Click => ReasonReact.Update({...state, count: state.count + 1})
    | Toggle => ReasonReact.Update({...state, show: ! state.show})
    },
  render: (self) => {
    let message = "Clicked " ++ string_of_int(self.state.count) ++ " times(s)";
    <div>
      <MyDialog
        onClick={_event => self.send(Click)}
        onSubmit={_event => self.send(Toggle)}
      />
      {ReasonReact.stringToElement(message)}
    </div>
  }
};
```

除了类型提示支持模式匹配（[ts 也支持了](https://zhuanlan.zhihu.com/p/32112508)）比较完美之外，其他和 redux 还真没啥区别。

至于 immutable 特性，reason 本身也只支持 immutable 检测而已，同时支持了结构语法，可以较为方便进行 immutable 计算（es 也支持了）。

如果想在复杂场景深入使用 immutable，可以看看这个 [Reason + BuckleScript bindings to Immutable.js](https://github.com/reasonml-community/bs-immutablejs)。

## 4 总结

graphql 很惊艳，但如果不能应用到后端第一手代码就没什么用。

reason 整体看上去比初版 react + redux 生态强大了太多，但是与现在的前端生态链 typescript + react + redux* 最新特征比起来，唯一惊艳的地方，就是对 `ocaml` 用户较为友好，另外在各大支持编译到 js 语言，纷纷支持 Assembly 编译后，这些语言更加趋同了，相比之下 ts 更适合用在生产环境。

## 5 更多讨论

> 讨论地址是：[精读《初探 Reason 与 GraphQL》 · Issue #56 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/56)

**如果你想参与讨论，请[点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，每周五发布。**
