Real World Halogen

2 篇文章 0 订阅
1 篇文章 0 订阅

Real World Halogen

原文地址

Why Halogen

purescript-halogen 是一个100%d的purescript virtual DOM实现,一个基于组件化的框架。如果不bind其他框架(React ),这是最OK的选择

Why Purescript

  • best-in-class type system
  • algebraic data types
  • generics
  • row types
  • type classes

函数式的Single-Page App设计

Single-Page App: 一种基于web的应用或者网站, 这种single page在和用户交互的时候当用户点击某个物件或者按键的时候不会跳转到其他的页面. 使H5应用使用起来跟原生应用一样流畅,没有页面的跳转,所有的交互都在一个页面完成,页面的切换通过route完成。

Strongly-typed languages make excellent choices for domain modeling and Domain-driven design

使用三个内容帮助我们设计应用

  1. 用例
    用户可以进行的操作
  2. 数据模型
    将应用中的信息实体抽象表达
  3. 转换
    使用函数转化数据,或从一个状态变化到另一个状态,这个函数应尽可能puresmall

模型设计原则

  1. 使用模型来支持业务处理

模型 支持 业务逻辑
业务逻辑 影响 模型设计

  1. 用类型授予值以意义
home :: String
home = "home"

navigate :: String -> Effect Unit
navigate r = setHashTo r

-- 但是String所代表的值远远多于应用中的route
data Route = Home | Setting

navigate :: Route -> Effect Unit
navigate Home = setHashTo "home"
navigate Settings = setHashTo "settings"

-- or
instance showRoute :: Show Route where 
	show Home = "home"
	show Settings = "settings"
	
navigate :: Route -> Effect Unit
navigate r = setHashTo $ show r

  1. 使用自定义类型来进行区别,尽管该类型只是包裹了基础类型
newtype CustomerId = CustomerId Natural
newtype OrderId = OrderId Natural

-- 尽管两种类型的值可能相同,但其意义绝不相同
  1. 排除掉不合法的状态
-- 联系方式可能是Email或Postal的一种 或两者都有
type ContactInfo = Tuple (Maybe Email) (Maybe Postal)
-- 这种可能会有 Tuple Nothing Nothing, 这并不是合法的数据

data ContactInfo
	= EmailOnly Email
	| PostalOnly Postal
	| Both Email Postal

只要我们保证某类型的所有数据都是合法的,那么所有处理该类型的函数均无需担心因不合法的数据而出错。

-- UserName: 长度在[1, 32]之内的任意字符串,(或更多规则)

-- 只暴露类型,不暴露构造函数
newtype Username = Username String

mkUsername :: String -> Maybe UserName
mkUsername = Username <=< lengthInRange 1 32 <=< otherPrinciple
	where
		lengthInRange :: Int -> Int -> String -> Maybe String
  		otherPrinciple :: String -> Maybe String

open record in purescript

-- This type is now an "open record", which means it can be extended
-- with more fields.
type UserProfileFields r =
  { username :: Username
  , bio :: Maybe String
  , image :: Maybe ProfilePhoto
  | r
  }

type UserProfile = UserProfileFields ()

data AuthUser = AuthUser Token (UserProfileFields (email :: Email))

表示带有状态的数据 (remote-data)

-- https://pursuit.purescript.org/packages/purescript-remotedata
data RemoteData err res
  = NotAsked
  | Loading
  | Failure err
  | Success res

框架设计原则

The ReaderT Design Pattern

  • 使用ReaderT Monad Transformer来实现一个通用Monad。获得: 一个global read-ony的环境,包含一个配置信息,例如:logging level, data base connection, credentials
  • 如果需要全局的可变状态,使用mutable reference, 而不要用StateT monad transformer, See this explanation in the Halogen repo for more details.
  • pure type classes: 可能在之后运行以副作用,或者在无副作用的环境之下 (Aff, Effect)

The Three-Layer App

1. Layer1: The ReaderT Pattern

你可以任意定义pure functions, 但是最终你的应有仍要产生副作用。 这一层就是为此,你的monadReaderT转换到Aff这一层应尽可能小, 这一层都是关于操作的:

  • 管理配置
  • 发起网络请求
  • 实现并发
  • 读取数据

2. Layer2: External Services & Dependencies

你需要layer1去实际管理数据库连接等,但是你不应在layer1实现与外部服务一一对应的接口。实际上,应定义纯函数,以便被被layer1调用,以产生副作用。这一层也应尽可能薄, 把业务代理到layer3.

3. Layer3: Pure Business Logic

应用中剩余逻辑应该都是无副作用的,使用纯函数和简单的数据类型来实现业务逻辑。

Halogen Components

Halogen is a type-safe, declarative UI library for PureScript applications.

declarative type-safe

  1. Design almost all code in a pure, functional style
  2. Use type classes to represent capabilities like reading and writing to local storage, routing, and logging
  3. Implement a thin layer of highly stateful code to actually perform requests, read and write to local storage, and so on — a layer which can easily be swapped out for mocks when testing without changing the functional core.
  4. Store application-wide information in ReaderT and represent global state with a Ref instead of a state monad
  5. Manage UI state and interactions with relatively monolithic components (compared to what you might see in React), preferring pure functions instead of components for parts of the application that don’t require internal state or communication with the browser or other components

Components 通常是带有副作用的外层壳的一部分,但是也不必非要如此,我们肯尽可能使得组件无副作用。一个如下的组件,是一个包裹了状态和事件循环的组件,但是在被Halogen执行之前,依然是无副作用的:

myComponent :: forall m. Component HTML Query Input Message m

虽然这个组件会与DOM交互,但是除此之外,你可以把它当做一个无副作用的状态机。

myComponent :: forall m. LocalStorage m => Component HTML Query Input Message m

现在你的组件可以访问在localstorage相关定义的纯函数了。

render函数也是无副作用的,并且大多数组件都会有几个这样的函数构成的。可以定义一个pure render, 然后被其他组件使用。

-- Given an user profile, render a header with their username (for example)
renderHeader :: forall p i. Profile -> HTML p i

一个通常的Halogen应用由主页组件,其他小组件(页头,时间选择器,图表等等)构成。主页由很多render构成(每个render都是renderPart)

仅有两种设计模式是可复用的,higher-order, renderless

There are only two design patterns that provide true reuse in this sense of the word: higher-order and renderless components.

  • Higher-order 组件是React社区常用的模式,其使用组件作为参数,赋予其新的行为和状态,然后返回一个新的组件
  • Renderless 组件不含有render函数,然后允许你扩展该组件的行为和状态,这个父组件里面提供render函数

renderless components talk


Useful Library

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值