iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 6
0
自我挑戰組

Some thing with Reason系列 第 6

BuckleScript - 物件

Bukle Script Object

Javascript 中的物件有兩個主要的目的

  • 像是一個雜湊表(或是字典), Keys 可以動態的 新增/刪除 ,值是同樣的型態
  • 像是一個紀錄(Record),欄位是固定的(但是也允許是選擇性的),他的值可以是不同的形態

這部分 BuckleScript 的物件也是一樣的

雜湊表模式

直到最近 Javascript 才終於有正確的支援 Map

物件使用上和 Map 可以是一樣的

前提是

  • 可能會或是不會 新增/刪除 參數的鰎值
  • 值可以被 動態/計算 鍵值 瀏覽
  • 值得型態都是一樣

這時候可以利用 Js.Dict  來綁定這個 Javascript 物件

你可以就像 Javascript 中的 Object 來做操作

Js.Dict.keys 來取得所有的鍵值

Js.Divt.values 來取得所有的值

範例

let myMap = Js.Dict.empty();

Js.Dict.set(myMap, "Allison", 10);

[@bs.val] external studentAge: Js.Dict.t(int) = "student";

switch (Js.Dict.get(studentAge, "Joe")) {
| None => Js.log("Joe can't be found")
| Some(age) => Js.log("Joe is " ++ string_of_int(age));
};

轉譯後的結果

'use strict';

var myMap = { };

myMap["Allison"] = 10;

var match = student["Joe"];

if (match !== undefined) {
  console.log("Joe is " + String(match));
} else {
  console.log("Joe can't be found");
}

你可以看到其實 Js.Dict 是基於 Javascript 的物件

整個 API 都沒有使用到 external 所以在編譯之後 整個 API 都會消失

編譯結果中你也找不到 Dict 的相關字

這很便於你將 Js 檔案轉為 BuckleScript 檔案

Record 模式

如果你的 Js 物件:

  • 有固定並且已知的欄位
  • 包含的值有不同的型別

這時候大多數的程式語言中 你都可以使用 Record

例如:

{
  "John": 10,
  "Allison": 20,
  "Jimmy": 15
}

{
  name: "John",
  age: 10,
  job: "CEO"
}

的不同

前者會使用 雜湊表 的型態來處理

後者會使用 Record 來處理

而在 BuckleScript 中要使用 bs.deriving abstract 功能

後者則會這樣寫

[@bs.deriving abstract]
type person = {
  name: string,
  age: int,
  job: string
};

[@bs.val] external john: person = "john";

note person 並不是一個 Record 的型別

他只是看起來像是 Record 的型別

而且可以使用 Record 的形態檢查

bs.deriving abstract 就是在註解將 person 轉為 抽象型別

建立

上述的抽象型別因為不是 Record 型別

所以不能直接使用型態的方式來建立

[@bs.deriving abstract]
type person = {
  name: string,
  age: int,
  job: string,
};

let joe = person(~name="Joe", ~age=20, ~job="teacher")

轉譯後的結果

// Generated by BUCKLESCRIPT VERSION 4.0.5, PLEASE EDIT WITH CARE
'use strict';


var joe = {
  name: "Joe",
  age: 20,
  job: "teacher"
};

exports.joe = joe;
/* No side effect */

結果對於運行並不會有成本

欄位重新命名

有時候在綁定 JS object 的時候,欄位名稱在 BuckleScript/Reason 中是無效的名稱

有兩種範例 一個是 {type: "foo"} (BS/Reason 中的保留字) 和 {"aria-checked": true}

可以選擇另一個有效的名稱 使用 @bs.as 來規避這個問題

[@bs.deriving abstract]
type data = {
  [@bs.as "type"] type_: string,
  [@bs.as "aria-checked"] ariaLabel: string
};

let d = data(~type_="message", ~ariaLabel="hello");

輸出會是

'use strict';


var d = {
  type: "message",
  "aria-checked": "hello"
};
可選的標籤

可以建立可忽略的 欄位

[[@bs.deriving abstract]
type person = {
  [@bs.optional] name: string,
  age: int,
  job: string
};

let joe = person(~age=19, ~job="sleep", ());
Js.log(joe);

編譯後會是

var Js_primitive = require("bs-platform/lib/js/js_primitive.js");

function joe(param) {
  return (function (param$1) {
      var prim = param;
      var prim$1 = 19;
      var prim$2 = "sleep";
      var tmp = {
        age: prim$1,
        job: prim$2
      };
      if (prim !== undefined) {
        tmp.name = Js_primitive.valFromOption(prim);
      }
      return tmp;
    });
}

console.log(joe);

可以看到 name 會是可選的,若是沒有也不會造成錯誤

note:bs.optional 只是將 name 改為可選,但是如果輸入 Option(string) 不會有作用

Accessors

當你使用 bs.deriving abstract 隱含了 recode 的型態,你無法使用 joe.age 這樣的方式來取得值

原生會提供 gettersetter 來完成這個

取值

每一個 bs.deriving abstract 欄位 都會有一個 getter function

在上面的範例中會有三個 getter functionnameGet, ageGet, jobGet

他們會取得 person 的值並分別回傳 string, int, string

[@bs.deriving abstract]
type person = {
  [@bs.optional] name: string,
  age: int,
  job: string,
};

let joe = person(~age=20, ~job="teacher", ());

let twenty = ageGet(joe);
Js.log(twenty);

/**
也可以寫成這樣
joe-> ageGet-> Js.log
**/

至於改值則是

[@bs.deriving abstract]
type person = {
  [@bs.optional] name: string,
  mutable age: int,
  job: string,
};

let joe = person(~age=20, ~job="teacher", ());

joe-> ageGet-> Js.log; /* 20 */
joe-> ageSet(21)-> Js.log;
joe-> ageGet-> Js.log; /* 21 */

note: 要記得將需要修改的參數前面加上 mutable

Object Methods

可以附加任何function 在類型上(任何類型的 record 不僅止於 @bs.deriving abtract)

可以參閱 Object Method

這部分之後會再討埨

可變性

Test.re

[@bs.deriving abstract]
type cord = {
  [@bs.optional] mutable x: int,
  y: int,
};

Test.rei

[@bs.deriving abstract]
type cord = {
  [@bs.optional] x: int,
  y: int,
};

Object2

如果上述的 Object 並不符合您的需求

也有另外一種方式可以綁定 Javascript Object

有幾個前提

  • 你不想要預先宣告 type
  • 你希望你的 Object 是有結構性的

Ex: 你的類型希望可以接受所有含有 age 這個參數的物件,而不是只有特定參數的物件

陷阱

note: 不能使用普通的 Reason/OCaml 的物件類型,如下

type person = {
  .
  name: string,
  age: int,
  job: string
};

你仍然可以宣告這個類型

但是無法編譯成功

因為 Reason/OCaml 的對象工作方式是不一樣的

解決方案

BuckleScript 利用 Js.t 來做包裝類型

以便控制和追蹤可以編譯成 Javascript 的對象子集合

type person = Js.t({
  .
  name: string,
  age: int,
  job: string
});

let john = {"name": "john", "age": 11};

john##name -> Js.log;

從現在開始 BuckleScript 都會以 Js.t 來消除跟 一般物件 和 Javascript 物件的歧義

因為 Reason 有包覆一層語法糖 會將 Js.t({. name: string}) 轉為 {. "name": string}

 訪問 與 修改

取值

要取得值的話方法為 ##

範例如下

type person = Js.t({
  .
  name: string,
  age: int,
  job: string
});

let john = {
  "name": "john",
  "age": 11,
  "job": "development"
};

let johnName = john##name;

明天再來談談 SomeOption 的問題


上一篇
Reason 的好朋友
下一篇
BasicType - OptionsAndSomeAndNone
系列文
Some thing with Reason30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言