前端技术如今蓬勃发展,时时刻刻都有新的技术栈诞生,这使得各位前端们常常大呼学不动了。既然如此笔者为何还要去学一门和 TypeScript 类似的新语言呢,难道 TypeScript 不够用嘛。
TypeScript 生态现在已经足够强大了,那么学习一个新的 ReScript 又有什么优势呢?ReScript 是对函数式友好的编程语言,函数默认自带柯里化,原生支持 pipe 功能,友好的类型推导,以及没有 null 和 undefined 的概念来消除了因其而产生的 Bug 。还有一点就是官方的 compiler 是不依赖于 node 的原生二进制程序,所以编译速度是 TypeScript 所不能及的,虽然目前已经有诸如 esbuild 之类的 TypeScript 编译器,速度是够快,但不支持类型判断。
Reducer.res
type action =
| AddTodo(string)
| RemoveTodo(int)
| ToggleTodo(int)
// reducer action 直接用 ReScript 自带的 Variant 结构来定义,可以利用其强大的模式匹配能力减少样板代码
type todo = {
id: int,
completed: bool,
text: string,
}
type state = {
todos: array<todo>,
nextId: int,
}
let reducer = ({todos, nextId}, action) => {
switch action {
| AddTodo(text) => {
todos: todos->Belt.Array.concat([
{
id: nextId,
text: text,
completed: false,
},
]),
nextId: nextId + 1,
}
| RemoveTodo(id) => {todos: todos->Belt.Array.keep(todo => todo.id != id), nextId: nextId}
| ToggleTodo(id) => {
todos: todos->Belt.Array.map(todo => {
...todo,
completed: todo.id == id ? !todo.completed : todo.completed,
}),
nextId: nextId,
}
}
}
不用手动指定 reducer
参数的类型,编译器会根据上下位自动推断出 {todos, nextId}: state, action: action
;
因为有强大的匹配模式能力,代码结构清晰来许都,也不用写得像 TypeScript 那么啰唆;
TypeScript 版本:
type Action = { type: "ADD_TODO" | "REMOVE_TODO" | "TOGGLE_TODO" id?: number text?: string } type Todo = { id: number text: string completed: boolean } type State = { todos: Todo[] nextId: number } let reducer = ({ todos, nextId }: State, action: Action) => { switch (action.type) { caTodoItemse "ADD_TODO": return { todos: [ ...todos, { id: nextId, text: action.text, completed: false, }, ], nextId: nextId + 1, } case "REMOVE_TODO": return { todos: todos.filter((todo) => todo.id != action.id), nextId: nextId } case "TOGGLE_TODO": return { todos: todos.map((todo) => ({ ...todo, completed: todo.id == action.id ? !todo.completed : todo.completed, })), nextId: nextId, } } }
@react.component
let make = (~onAdd: option<string> => unit) => {
let inputValueRef: React.ref<option<string>> = React.useRef(None)
let onSubmit = (e: ReactEvent.Form.t) => {
e->ReactEvent.Form.preventDefault
onAdd(inputValueRef.current)
}
<form className="rounded-lg w-full grid px-6 grid-cols-12" onSubmit>
<input
className="border rounded-l-lg bg-slate-50 h-8 text-xl py-6 px-4 ring-sky-200 col-span-10 focus:(outline-none border-sky-400 ring-2 z-10) "
onChange={e => e->ReactEvent.Form.target->(target => inputValueRef.current = target["value"])}
required={true}
/>
<button
type_="submit"
className="bg-white border rounded-r-lg cursor-pointer flex border-l-0 text-lg ring-sky-400 col-span-2 items-center justify-center hover:(bg-slate-100) focus:(ring-2 bg-slate-50) ">
<Icon.Plus size={36} stroke={1} />
</button>
</form>
}
使用 tailwindcss 来简化样式的书写
open Reducer
@react.component
let default = () => {
let (state, dispatch) = reducer->React.useReducer({
todos: [],
nextId: 1,
})
let onAdd = value => {
switch value {
| Some(str) =>
if Js.String.trim(str) != "" {
str->AddTodo->dispatch
}
| None => ()
}
}
<div className="bg-white rounded-md mx-auto border-1 shadow-md mt-20 pt-4 pb-8 w-[480px]">
<h1 className="text-center"> {React.string("Todo List")} </h1>
<Addtodo onAdd />
</div>
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o9T6vTOa-1655351428688)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/61f23e4dd9db4215bf6745d55d066ff7~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]
ReScript 的模块导入不用写特定的语法,只要用文件名就可以了,比如我有个模块文件名为 Foo.res
,在另外一个文件理要引入模块 Foo
里面的 name
值,因为 ReScript 默认导出所有变量,只需这么写就可以直接引入
// Foo.res
let name = "ReScript"
// Test.res
let lastName = Foo.name
type todo = Reducer.todo
@react.component
let make = (~todo: todo, ~onToggle: int => unit, ~onRemove: int => unit) => {
let {id, text, completed} = todo
<li className="border-b flex py-4 px-6 text-3xl items-center">
<input
type_="checkbox"
className="h-6 m-0 mr-4 w-6 accent-sky-600"
checked={completed}
onChange={_ => onToggle(id)}
/>
<span
className={`-mt-1 break-words max-w-[350px] ${completed
? "text-gray-500 line-through"
: "text-black"}`}>
{React.string(text)}
</span>
<button
className="bg-transparent cursor-pointer ml-auto h-6 p-0 w-6 hover:text-rose-600"
onClick={_ => onRemove(id)}>
<Icon.Trash size={24} />
</button>
</li>
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t5jkEybG-1655351428689)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/de56db53e7c0409c8af5faafb3df48e1~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]
open Reducer
@react.component
let make = (~todos: todos, ~onDispatch: action => unit) => {
<>
<ul className="border-t list-none py-0 px-0">
{todos
->Belt.Array.keep(({completed}) => {
switch filter {
| #showAll(_) => true
| #showActive(_) => !completed
| #showCompleted(_) => completed
}
})
->Belt.Array.map(todo =>
<TodoItem
key={Js.Int.toString(todo.id)}
todo
onToggle={id => id->ToggleTodo->onDispatch}
onRemove={id => id->RemoveTodo->onDispatch}
/>
)
->React.array}
</ul>
</>
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4NJG0ZAR-1655351428689)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6b049241d0147c1883ec906e34b0c13~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]
为 TodoList
加上标签切换功能
open Reducer
let filterTitles = [#showAll("All"), #showActive("Active"), #showCompleted("Completed")]
@react.component
let make = (~todos: todos, ~onDispatch: action => unit) => {
let (filter, setFilter) = React.useState(_ => #showActive("Active"))
let allNumber = todos->Belt.Array.length
let activeNumber = todos->Belt.Array.keep(({completed}) => !completed)->Belt.Array.length
let completedNumber = allNumber - activeNumber
<>
<div className="border-t flex mt-6 px-6 justify-end">
<ul className="flex list-none my-0 px-0">
{filterTitles
->Belt.Array.map(item => {
let #showAll(title) | #showActive(title) | #showCompleted(title) = item
let count = switch item {
| #showAll(_) => allNumber
| #showActive(_) => activeNumber
| #showCompleted(_) => completedNumber
}
let active = item == filter
<li
key={title}
className={`${active
? "bg-gray-50 border-t-sky-400"
: "bg-gray-200"} rounded-b-lg border border-t-2 border-gray-200 cursor-pointer flex ml-2 py-2 px-2 items-center`}
onClick={_ => setFilter(_ => item)}>
{React.string(title)}
<span className="rounded-md bg-sky-600 text-sm text-white ml-1 px-1 pb-[0.8px]">
{count->Js.Int.toString->React.string}
</span>
</li>
})
->React.array}
</ul>
</div>
<ul className="border-t list-none py-0 px-0">
{todos
->Belt.Array.keep(({completed}) => {
switch filter {
| #showAll(_) => true
| #showActive(_) => !completed
| #showCompleted(_) => completed
}
})
->Belt.Array.map(todo =>
<TodoItem
key={Js.Int.toString(todo.id)}
todo
onToggle={id => id->ToggleTodo->onDispatch}
onRemove={id => id->RemoveTodo->onDispatch}
/>
)
->React.array}
</ul>
</>
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WsaIpcbt-1655351428690)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e7bf504fc6e54e63b42815a126a0a805~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]
<div className="bg-white rounded-md mx-auto border-1 shadow-md mt-20 pt-4 pb-8 w-[480px]">
<h1 className="text-center"> {React.string("Todo List")} </h1>
<Addtodo onAdd />
<TodoList todos={state.todos} onDispatch={dispatch} />
</div>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nrtR2m2w-1655351428690)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/71f937e00a4f450bb1480f18f75b75ea~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]
ReScript 是对函数式友好的编程语言,有其独特的类型系统,不像 TypeScript 的类型是对 JavaScript 原有类型的补全。类型自动推导十分便利,不用再手动指定类型。Variant 数据结构和模式匹配极大方便了开发,减少样板代码的存在。 虽然类型推导很便利,但没有 TypeScript 那样的类型体操,遇到比较复杂的数据结构需要手动定义。