发布于 2026-01-06 1 阅读
0

React 到 Elm 迁移指南

React 到 Elm 迁移指南

本指南将帮助您学习并迁移到 Elm,假设您已经掌握了 React 的基础知识。Elm指南非常出色,它将以清晰有序的方式,为您提供全面深入的理解。

本指南有所不同。我们将从 JavaScript 和React的基础知识入手,探讨如何在 Elm 中实现等效功能(如果有的话)。如果您已经熟悉 React,我们将利用这些扎实的基础,让您更容易理解 Elm 在描述 React 开发者熟悉的语言和概念时,其背后的含义。

内容

React是什么?

React 是一个用于确保 DOM 与数据同步的库。然而,也可以说它是一个框架,因为它提供了构建应用程序所需的许多基本要素。它提供了丰富的功能供你选择,因此使用起来非常灵活。只想使用 JSX 和变量?没问题。想要一个 Context 来模拟 Redux?没问题。想要用Preact之类的工具替换渲染器?没问题。

采用模块化设计,可添加和更换部件,并拥有庞大的社区支持,可根据您的需求进行修改。

假设您能够使用 JavaScript 编写 React 代码。React内置了组件属性的基本运行时类型定义。如果您需要更高级的类型定义,React也添加了对 TypeScript 的支持。

create-react-app 是一个热门项目,它之所以流行,是因为它能够自动处理编译器工具链。团队无需了解 Webpack 或 JavaScript 构建目标,例如 CommonJS、ES6 或 ES5。虽然他们无需维护核心代码,但出于网络安全或构建方面的考虑,您/团队仍然需要进行一些必要的升级。该项目提供开箱即用的简洁开发环境,支持保存文件并实时查看重新加载效果。测试已设置完毕,随时可用。此外,它还提供包含各种优化的生产版本。只需三个简单的基本命令:启动、测试和构建,即可构建大多数应用程序。

虽然您可以使用npm但 yarn也支持那些想要使用 yarn 提供的额外功能的用户。

顶部

什么是榆树?

Elm 是一种强类型函数式语言、编译器、包管理器和框架。您可以使用 Elm 语言编写代码,它会被编译成 JavaScript 以便在浏览器中使用。Elm 编译器有两种基本模式:开发模式和生产模式。它还可选地提供了一个 REPL(交互式解释器),方便您测试一些基本代码。包管理器使用自己的网站和结构,采用 elm.json 文件,而不是 package.json 文件。Elm 最广为人知的就是它的框架, Redux正是基于这个框架开发的

您可以使用 Elm 语言和 Elm 框架编写代码,安装 Elm 库,并使用 Elm 编译器将其编译成 JavaScript。大多数学习类应用都会编译成一个 HTML 页面,其中会自动包含 JavaScript 和 CSS。对于更高级的应用,您只需将其编译成 JavaScript 并嵌入到您自己的 index.html 文件中即可。当您需要对主 HTML 文件进行额外的 HTML 和 CSS 操作时,这种方法通常效果更好。虽然有一个 create-elm-app 工具,但它往往违背了 Elm 的设计理念,即不使用复杂且难以维护的 JavaScript 构建工具链。

JavaScript 和 Elm 语言类型

以下表格比较了 JavaScript 和 Elm 的基础知识。

顶部

字面意义

JavaScript 榆树
3 3
3.125 3.125
"Hello World!" "Hello World!"
'Hello World!' 字符串不能使用单引号。
'Multiline string.'(反引号,不是单引号) """Multiline string"""
字符和字符串之间没有区别。 'a'
true True
[1, 2, 3] [1, 2, 3]

顶部

对象/记录

JavaScript 榆树
{ x: 3, y: 4 } { x = 3,y = 4 }
x点 x点
point.x = 42 {点 | x = 42}

顶部

函数

JavaScript 榆树
function(x, y) { return x + y } \x y -> x + y
Math.max(3, 4) max 3 4
Math.min(1, Math.pow(2, 4)) min 1 (2^4)
numbers.map(Math.sqrt) List.map sqrt numbers
points.map( p => p.x ) List.map .x points

顶部

控制流

JavaScript 榆树
3 > 2 ? 'cat' : 'dog' if 3 > 2 then "cat" else "dog"
var x = 42; ... let x = 42 in ...
return 42 一切皆为表达,无需赘言return

顶部

细绳

JavaScript 榆树
'abc' + '123' "abc" ++ "123"
'abc'.length String.length "abc"
'abc'.toUpperCase() String.toUpper "abc"
'abc' + 123 "abc" ++ String.fromInt 123

顶部

空值和错误

JavaScript 榆树
undefined Maybe.Nothing
null Maybe.Nothing
42 Maybe.Just 42
throw new Error("b00m") Result.Err "b00m"
42 Result.Ok 42

顶部

JavaScript

你经常会看到 JavaScript 使用可选链来模拟上述操作。

// has a value
const person = { age: 42 }
const age = person?.age

// is undefined
const person = { }
const age = person?.age
Enter fullscreen mode Exit fullscreen mode

榆树

type alias Person = { age : Maybe Int }
-- has a value
let person = Person { age = Just 42 }
-- is nothing
let person = Person { age = Nothing }
Enter fullscreen mode Exit fullscreen mode

函数组合(即“管道”)

以下两种语言均可解析以下 JSON 字符串,以获取列表中的人名。

顶部

JavaScript

截至撰写本文时,JavaScript Pipeline Operator 提案处于第一阶段,因此我们将在下面使用Promise

const isHuman = peep => peep.type === 'Human'
const formatName = ({ firstName, lastName }) => `${firstName} ${lastName}`

const parseNames = json =>
  Promise.resolve(json)
  .then( JSON.parse )
  .then( peeps => peeps.filter( isHuman ) )
  .then( humans => humans.map( formatName ) ) 
Enter fullscreen mode Exit fullscreen mode

榆树

isHuman peep =
  peep.type == "Human"

formatName {firstName, lastName} =
  firstName ++ " " ++ lastName

parseNames json =
  parseJSON
  |> Result.withDefault []
  |> List.filter isHuman
  |> List.map formatName

Enter fullscreen mode Exit fullscreen mode

顶部

模式匹配

JavaScript

截至撰写本文时,JavaScript 当前的模式匹配提案处于第一阶段

switch(result.status) {
  case "file upload progress":
    return updateProgressBar(result.amount)
  case "file upload failed":
    return showError(result.error)
  case "file upload success":
    return showSuccess(result.fileName)
  default:
    return showError("Unknown error.")
}
Enter fullscreen mode Exit fullscreen mode

榆树

case result.status of
  FileUploadProgress amount ->
    updateProgressBar amount
  FileUploadFailed err ->
    showError err
  FileUploadSuccess fileName ->
    showSuccess filename
  _ ->
    showError "Unknown error."
Enter fullscreen mode Exit fullscreen mode

顶部

Hello World: React

ReactDOM.render(
  <h1>Hello, world!</h1>, document.getElementById('body')
)
Enter fullscreen mode Exit fullscreen mode

你好世界:榆树

type Msg = Bruh
type alias Model = {}

update _ model =
    model

view _ =
    h1 [][ text "Hello World!" ]

main =
    Browser.sandbox
        { init = (\ () -> {})
        , view = view
        , update = update
        }

Enter fullscreen mode Exit fullscreen mode

顶部

DOM模板

JSX元素

const element = <h1>Hello world!</h1>;
Enter fullscreen mode Exit fullscreen mode

榆树元素

let element = h1 [] [text "Hello World!"]
Enter fullscreen mode Exit fullscreen mode

JSX 动态数据

const name = 'Jesse';
<h1>Hello {name}</h1>
Enter fullscreen mode Exit fullscreen mode

榆树动态数据

let name = "Jesse"
h1 [] [text "Hello " ++ name ]
Enter fullscreen mode Exit fullscreen mode

JSX 函数

const format = ({ first, last }) => `${first} ${last}`;

const user = { first: 'Jesse', last: 'Warden' };

<h1>Hello {format(user)}</h1>
Enter fullscreen mode Exit fullscreen mode

Elm 函数

format {first, last} = first ++ " " ++ last

user = { first = "Jesse", last = "Warden" }

h1 [] [text (format user) ] 
Enter fullscreen mode Exit fullscreen mode

JSX图像

<img src={user.avatarUrl} />
Enter fullscreen mode Exit fullscreen mode

榆树图像

img [ src user.avatarUrl ] []
Enter fullscreen mode Exit fullscreen mode

JSX 儿童

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

榆树的孩子

let element =
  div [] [
    h1 [] [text "Hello!"]
    h2 [] [text "Good to see you here."]
  ]
Enter fullscreen mode Exit fullscreen mode

顶部

成分

React:定义

const Welcome = props => <h1>Hello {props.name}</h1>
Enter fullscreen mode Exit fullscreen mode

榆树:定义

welcome props = h1 [] [text "Hello " ++ props.name]
Enter fullscreen mode Exit fullscreen mode

React:使用

const element = <Welcome name="Sara" />
Enter fullscreen mode Exit fullscreen mode

榆树:使用

let element = welcome { name = "Sara" }
Enter fullscreen mode Exit fullscreen mode

React:儿童

const Greeting = ({ name }) => (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here, {name}!</h2>
  </div>
)
Enter fullscreen mode Exit fullscreen mode

榆树:儿童

greeting {name} =
  div [] [
    h1 [] [text "Hello!"]
    , h2 [] [text "Good to see you here, " ++ name ++ "!"]
  ]
Enter fullscreen mode Exit fullscreen mode

顶部

事件处理

React 事件处理器

<button onClick={activateLasers}>Activate Lasers</button>
Enter fullscreen mode Exit fullscreen mode

榆树留言

button [ onClick ActivateLasers ] [ text "Activate Lasers" ]
Enter fullscreen mode Exit fullscreen mode

React 事件参数

<button onClick={(e) => this.deleteRow(23, e)}>Delete Row</button>
Enter fullscreen mode Exit fullscreen mode

Elm 消息参数

type Msg = DeleteRow Int

button [ onClick (DeleteRow 23) ] [ text "Delete Row" ]
Enter fullscreen mode Exit fullscreen mode

顶部

带状态的事件处理

React

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
  }

  handleClick = () => {
    this.setState(state => ({ isToggleOn: !state.isToggleOn }));
  }

  render = () => (
      {this.state.isToggleOn ? 'ON' : 'OFF'}
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

榆树

type alias Model = { isToggleOn : Bool }

initialModel = { isToggleOn = True }

type Msg = Toggle

update _ model =
  { model | isToggleOn = not model.isToggleOn }

toggle model =
    div 
      [ onClick Toggle ]
      [ if model.isToggleOn then
          text "ON"
        else
          text "OFF" ]
Enter fullscreen mode Exit fullscreen mode

顶部

条件渲染

React

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}
Enter fullscreen mode Exit fullscreen mode

榆树

greeting props =
  let
    isLoggedIn = props.isLoggedIn
  in
  if isLoggedIn then
    userGreeting()
  else
    guestGreeting()
Enter fullscreen mode Exit fullscreen mode

顶部

列表

React

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);
Enter fullscreen mode Exit fullscreen mode

榆树

let numbers = [1, 2, 3, 4, 5]
let listItems =
  List.map
    (\number -> li [] [text (String.fromInt number)])
    numbers
Enter fullscreen mode Exit fullscreen mode

顶部

基本列表组件

React

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>    <li>{number}</li>  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
<NumberList numbers={numbers} />
Enter fullscreen mode Exit fullscreen mode

榆树

numberList props =
  let
    numbers = props.numbers
  in
  List.map
    (\number -> li [] [text (String.fromInt number)])
    numbers

let numbers = [1, 2, 3, 4, 5]
numberList numbers
Enter fullscreen mode Exit fullscreen mode

顶部

形式:受控组件

React

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};
  }

  handleChange = event => {
    this.setState({value: event.target.value});
  }

  handleSubmit = event => {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

榆树

type Msg = TextChanged String | Submit

type alias Model = { value : String }

initialModel = { value = "" }

update msg model =
    case msg of
        TextChanged string ->
            { model | value = string }
        Submit ->
            let
                _ = Debug.log "A name was submitted: " model.value
            in
            model

view model =
    form [ onSubmit Submit ][
        label
            []
            [ text "Name:"
            , input
              [type_ "text", value model.value, onInput TextChanged ] []]
        , input [type_ "submit", value "Submit"][]
    ]
Enter fullscreen mode Exit fullscreen mode

顶部

思考

React

React 的核心优势在于组件创建的便捷性,以及将这些组件组合成应用程序的轻松便捷。只需观察用户界面,在脑海中勾勒出各个组件之间的衔接,然后决定由谁来管理不同的状态即可。

  1. 嘲笑
  2. 组件层次结构
  3. 表示用户界面状态
  4. 确定州的所在地

1 – 模拟数据

在 React 中,你需要模拟从潜在的后端 API 或前端后端获取的数据。下面,我们硬编码了一些模拟 JSON 数据,以便我们的组件可以显示一些内容,并且我们可以围绕这些数据进行可视化设计和编码:

[
  {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];
Enter fullscreen mode Exit fullscreen mode

2 – 组件层次结构

接下来,你可以选择根据这些数据创建组件,并观察每个组件如何以各自的方式直观地呈现数据,以及如何处理用户输入……或者,你也可以对设计师提供的设计稿进行同样的操作。无论是创建树状结构中的小型组件,还是创建将所有信息整合在一起的大型组件,都取决于你。

通常情况下,你要么会目测数据,然后各个组件就会开始在你的脑海中形成图像;要么你会看到设计稿,然后开始在脑海中将各个部分切分成组件树。

1. FilterableProductTable(橙色):将所有组件整合在一起

  1. SearchBar(蓝色):接收所有用户输入
  2. ProductTable(绿色):根据用户输入显示和筛选数据集合
  3. ProductCategoryRow(青绿色):显示每个类别的标题
  4. ProductRow(红色):显示每个产品的一行

3 – 表示用户界面状态

第三,如果你在第二步没有“弄明白”,那么你就会认真考虑状态的问题。大多数数据都可以作为 props,但如果一个组件是受控的,它或许可以拥有自己的状态,以便与其他组件交互?尽量使用 props,但在需要将其封装到组件内部时,则使用状态。无论采用面向对象的类方法还是函数式方法,组件通常都会包含一些你认为最好由其内部管理的内容。

4 – 确定州的所在地

最后,确定谁拥有数据源。虽然许多组件可以拥有自己的内部状态,但“应用程序的整体状态”通常由一个或少数几个组件负责。这些组件之间的交互将帮助你弄清楚状态应该放在哪里,以及如何管理它(事件、Context、Hooks、Redux 等)。

顶部

榆树

虽然包括我在内的许多人都希望立即着手构建组件,但 Elm 鼓励先认真思考你的模型。Elm 的类型系统让你能够消除不可能的应用程序状态,并简化你表示事物的方式。好消息是,即使你搞砸了,Elm 编译器拥有业内最好的错误信息,让你能够毫无顾虑地进行重构。

  1. 模型数据
  2. 组件层次结构
  3. 模型数据变更
  4. 处理事件

1 – 模型数据

第一步是使用 Elm 的类型系统对数据进行建模。与 React 类似,有些类型会像 API 一样被定义,有些则需要通过 BFF 进行自定义。不过,这也会受到设计师设计稿的很大影响。

type alias Product = {
  category : String
  , price : String
  , stocked : Bool
  , name : String }

type alias Model = {
  products : List Product
}

initialModel =
[
  Product {category = "Sporting Goods", price = "$49.99", stocked = True, name = "Football"}
  , Product {category = "Sporting Goods", price = "$9.99", stocked = True, name = "Baseball"}
  , Product {category = "Sporting Goods", price = "$29.99", stocked = False, name = "Basketball"}
  , Product {category = "Electronics", price = "$99.99", stocked = True, name = "iPod Touch"}
  , Product {category = "Electronics", price = "$399.99", stocked = False, name = "iPhone 5"}
  , Product {category = "Electronics", price = "$199.99", stocked = True, name = "Nexus 7"}
]
Enter fullscreen mode Exit fullscreen mode

2 – 组件层次结构

它几乎与 React 完全相同,区别在于组件中没有状态;所有状态都包含在你的模型中。你的 `<component>` FilterableProductTableSearchBar`<component>` 等只是函数,它们通常将模型作为第一个也是唯一的参数。

3 – 模型数据变更

即使在 React 中使用 Redux,你仍然可以偶尔维护组件的内部状态。Elm 则不然;所有状态都保存在你的模型中。这意味着你的模型中SearchBar (blue)会有一个`state` 属性currentFilter : String来捕获当前筛选器(如果有)的状态。你还需要一个 `state` 属性onlyInStock : Bool来表示复选框的状态。在 React 中,这两者都可以实现:

  • 组件中的状态this.state
  • 组件中的状态FilterableProductTable可以通过事件传递。
  • Redux 中的状态
  • 钩子状态
  • 共享上下文中的状态

在 Elm 中,位置没有问题:就在模型中。

4 – 模型事件变更

在 Elm 中,你无需决定“UI 状态存放在哪里”,因为……所有数据都存在于模型(Model)中。相反,你需要决定如何更改这些数据。对于简单的应用程序,这与 Redux 中的操作非常相似:创建一个包含新数据的 Message,然后编写代码根据该消息更改模型。

type Msg = ToggleOnlyInStock Bool
Enter fullscreen mode Exit fullscreen mode

现在你已经有了消息,当用户点击复选框时,你就可以发送消息了:

label
        [ ]
        [ input [ type_ "checkbox", onClick (ToggleOnlyInStock not model.onlyInStock) ] []
        , text "Only show products in stock"]
Enter fullscreen mode Exit fullscreen mode

最后,根据消息内容更改数据:

update msg model =
  ...
  ToggleOnlyInStock toggle ->
    { model | onlyInStock = toggle }
Enter fullscreen mode Exit fullscreen mode

顶部

发展

React

使用create-react-app,运行后npm start,您的更改和编译错误将很快反映在打开的浏览器窗口中。

对于生产环境构建,请运行npm run build

榆树

使用elm-live,您运行 elm-live 后,您的更改和编译错误将很快反映在打开的浏览器窗口中。

对于生产环境构建,请elm make使用该--optimize标志运行。建议您先使用uglifyjs进行压缩,然后再使用 mangle,或者使用其他压缩器 + mangle 库。

顶部

测试

React

使用 create-react-app,你会运行一个内部npm test使用Jest 的测试环境。如果你的 UI 需要处理大量数据,或者使用了TypeScript,那么可以使用JSVerify来进行属性测试。对于端到端测试,Cypress是一个不错的选择。

榆树

对于 Elm 来说,由于编译器本身的正确性,单元测试通常意义不大。使用端到端测试效果更好,也更容易发现竞态条件。如果 UI 上需要处理大量数据,可以使用elm-test进行属性测试。虽然它通常用于单元测试,但内置了模糊测试和缩减器。对于端到端测试,Cypress 是一个不错的选择。

顶部

路由

React

虽然有很多选择,但react-router是许多人最终选择的一种。

function Home() {
  return <h2>Home</h2>;
}

function About() {
  return <h2>About</h2>;
}

function Users() {
  return <h2>Users</h2>;
}

function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about">About</Link>
            </li>
            <li>
              <Link to="/users">Users</Link>
            </li>
          </ul>
        </nav>
      </div>
    </Router>
  )
}
Enter fullscreen mode Exit fullscreen mode

榆树

Elm内置了使用Browser 库的路由功能。

home =
  h2 [] [ text "Home" ]

about =
  h2 [] [ text "About" ]

users =
  h2 [] [ text "Users" ]

app =
  div [] [
    nav [] [
      ul [] [
        li [] [
          a [ href "/home" ] [ text "Home" ]
        ]
        , li [] [
          a [ href "/about" ] [ text "About" ]
        ]
        , li [] [
          a [ href "/users" ] [ text "Users" ]
        ]
      ]
    ]
  ]

Enter fullscreen mode Exit fullscreen mode

顶部

误差边界

React

在 React 中,你需要构建一个或一组组件来封装常见的错误区域,这样,即使 UI 中某个不稳定的部分抛出异常,你也可以在 UI 中优雅地处理它。首先创建一个基本的封装组件:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children; 
  }
}
Enter fullscreen mode Exit fullscreen mode

一旦你有了包含日志记录和备用 UI 的组件,你只需要把那些危险的组件包裹起来即可:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>
Enter fullscreen mode Exit fullscreen mode

榆树

Elm 没有运行时错误(注意:下文将讨论移植风险)。编译器会确保处理所有可能的错误。这意味着你可以选择在模型中对这些错误状态进行建模,或者用空字符串忽略它们,或者为这些状态设计不同的用户界面。

数据不存在?您需要自行处理:

case dataMaybe of
  Just data ->
    addProduct data
  Nothing ->
    -- Your UI or data must compensate somehow here.
    -- For now we just return all the products unchanged
    model.products
Enter fullscreen mode Exit fullscreen mode

HTTP 操作失败?您必须妥善处理:

case result of
  Error err ->
    { model | result = ProductSaveFailed err }
  Ok data ->
    { mdoel | result = ProductSaveSuccess data }

-- in UI
case result of
  ProductSaveFailed err ->
    errorViewAndRetry err
  ProductSaveSuccess _ ->
    goToProductView
Enter fullscreen mode Exit fullscreen mode

顶部

HTTP

React

class Weather extends React.Component {
  constructor(props) {
    super(props);
    this.state = { temperature: undefined, loading: true };
  }

  componentDidMount = () => {
    this.setState({ loading: true })
    fetch("server.com/weather/temperature")
    .then( response => response.json() )
    .then( 
       ({ temperature }) => {
         this.setState({ temperature, loading: false, isError: false }) )
      }
    )
    .catch(
      error => {
        this.setState({ loading: false, isError: true, error: error.message })
      }
    )
  }

  render() {
    if(this.state.loading) {
      return <p>Loading...</p>
    } else if(this.state.isError === false) {
      return <p>Temperature: {this.state.temperature}</p>
    } else {
      return <p>Error: {this.state.error}</p>
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

榆树

type Msg = LoadWeather | GotWeather (Result Http.Error String)

type Model
    = Loading
    | Success String
    | Failure Http.Error

init : () -> (Model, Cmd Msg)
init _ =
  ( Loading
  , loadTemperature
  )

loadTemperature =
    Http.get
      { url = "server.com/weather/temperature"
      , expect = Http.expectJson GotWeather temperatureDecoder
      }

temperatureDecoder =
  field "temperature" string

update msg model =
    case msg of
        LoadWeather ->
            (Loading, loadTemperature)
        GotWeather result ->
            case result of
                Err err ->
                    ( Failure err, Cmd.none )
                Ok temperature ->
                    ( Success temperature, Cmd.none )

view model =
    case model of
        Loading ->
            p [][text "Loading..."]
        Success temperature ->
            p [][text ("Temperature: " ++ temperature) ]
        Failure _ ->
            p [][text "Failed to load temperature."]
Enter fullscreen mode Exit fullscreen mode

顶部

国家管理

重制版

// Action Creator
const addTodo = text => ({ type: 'ADD_TODO', text })

// Dispatch
const goSwimming = () => store.dispatch(addTodo('Go Swimming.'))

// trigger from button
<button onClick={goSwimming}>Add</button>

// update model
const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat([{ text: action.text, completed: false }])
    default:
      return state
  }
}
Enter fullscreen mode Exit fullscreen mode

榆树

-- Type for Todo
type alias Todo = { text : String, completed: Bool }

-- Message
type Msg = AddTodo String

-- trigger from button
button [ onClick (AddTodo "Go Swimming.")] [ text "Add" ]

-- update model
update msg model =
  case msg of
    AddTodo text ->
      { model | todos = List.append model.todos [Todo text, False] }
    ...
Enter fullscreen mode Exit fullscreen mode

顶部

文章来源:https://dev.to/jesterxl/react-to-elm-migration-guide-30np