Phoenix LiveView를 통한 단일 정보 소스

저는 약 1년 동안 PhoenixLiveViewSurface-UI와 함께 일했습니다. 제가 힘들게 배운 것 중 일부를 공유하고 싶습니다.

소개



Marlus Saraivasingle source of truth라는 React 커뮤니티의 개념을 소개했습니다.
.

There should be a single source of truth for any data that changes in a React application. Usually, the state is first added to the component that needs it for rendering. Then, if other components also need it, you can lift it up to their closest common ancestor.
full reference - Lifting State Up



이 개념을 이해하면 반복해서 패턴으로 보게 될 것입니다.
.

설정



섭씨로 값을 입력하고 화씨로 표시하는 온도 계산기가 있는 Dummy라는 응용 프로그램을 상상해 보십시오.
.
응용 프로그램은 다음과 같습니다.
.

.

.
먼저 라이브 계산기의 라우터에 새 경로를 추가해야 합니다.
.
router.ex

scope "/", Dummy do
  pipe_through(:browser)

  live("/", Calculator)
end


.
이제 경로가 생성되었으므로 live_view에서 작업할 수 있습니다. mount/3paramssessions를 무시하고 소켓을 통해 기본 화씨 값을 할당할 수 있습니다.
.
render/1 템플릿에서 TemperatureInputlive_component을 호출하고 id와 Fahrenheit 값을 전달합니다. 마지막으로 handle_info/2을 추가합니다.
.
라이브/calculator.ex

defmodule DummyWeb.Calculator do
  use DummyWeb, :live_view

  alias DummyWeb.TemperatureInput

  @impl true
  def mount(_params, _session, socket), do: {:ok, assign(socket, :fahrenheit, 0)}

  @impl true
  def render(assigns) do
    ~H"""
    <main class="hero">
      <.live_component module={TemperatureInput} id={"celsius_to_fahrenheit"} fahrenheit={@fahrenheit}/>
    </main>
    """
  end

  @impl true
  def handle_info({:convert_temp, celsius}, socket) do
    fahrenheit = to_fahrenheit(celsius)

    {:noreply, assign(socket, :fahrenheit, fahrenheit)}
  end

  defp to_fahrenheit(celsius) do
    String.to_integer(celsius) * 9 / 5 + 32
  end

  defp to_celsius(fahrenheit) do
    (String.to_integer(fahrenheit) - 32) * 5 / 9
  end
end


.
이제 live_component입니다.

라이브/온도_input.ex
.

defmodule DummyWeb.TemperatureInput do
  use DummyWeb, :live_component

  def render(assigns) do
    ~H"""
    <div>
      <div class="row">
        <.form let={f} for={:temp} phx-submit="to_fahrenheit" phx-target={@myself} >
          <div>
            <%= label f, "Enter temperature in Celsius" %>
            <%= text_input f, :celsius %>
          </div>
          <div>
            <%= submit "Submit" %>
          </div>
        </.form>
      </div>
      <p>temperature in Fahrenheit: <%= @fahrenheit %></p>
    </div>
    """
  end

  def handle_event("to_fahrenheit", %{"temp" => %{"celsius" => celsius}}, socket) do
    send(self(), {:convert_temp, celsius})

    fahrenheit = to_fahrenheit(celsius)

    {:noreply, assign(socket, :fahrenheit, fahrenheit)}
  end

  defp to_fahrenheit(celsius) do
    String.to_integer(celsius) * 9 / 5 + 32
  end

  defp to_celsius(fahrenheit) do
    (String.to_integer(fahrenheit) - 32) * 5 / 9
  end
end


.
live_component 을 살펴보면 사용자가 섭씨 온도로 값을 입력하고 제출 버튼을 클릭하여 이 이벤트를 handle_event/3 이 있는 서버로 보내는 입력 필드가 있는 양식만 있고 두 가지 작업을 수행합니다.
.
  • 부모(live_view)에게 메시지를 보냅니다.
  • 화씨 온도를 계산하고 소켓을 통해 할당합니다.

  • .
    두 개의 LiveViewphx-* bindings를 사용하고 있습니다.
    .
  • phx-submit - to_fahrenheit라는 핸들 이벤트에 양식을 보냅니다.
  • phx-target - 누가 이벤트를 처리해야 하는지 알려줍니다. 기본 동작은 항상 live_view입니다. 그렇지 않으면 대상을 설정할 수 있습니다. @myself를 사용하여 대상을 설정하는 것이 live_component입니다.

  • live_view의 handle_info와 live_component의 handle_event에 동일한 코드가 있음을 눈치채셨을 것입니다. 그들 사이의 유일한 차이점은 화씨 값을 업데이트하는 send() 함수입니다.
    .

    문제



    .
    논리의 이중성을 피해야 하며 예제에서 섭씨를 화씨로 변환하는 공식은 live_view와 구성 요소 모두에서 복제됩니다.
    .
    무엇이 잘못될 수 있는지 보여주기 위해 :timer.sleep를 사용하여 live_component에서만 수식을 변경합니다.
    .
    live_component에서 논리를 변경합니다.
    .
    라이브/온도_input.ex
    .

    def handle_event("to_fahrenheit", %{"temp" => %{"celsius" => celsius}}, socket) do
      send(self(), {:convert_temp, celsius})
    
      # wrong formula
      fahrenheit = to_celsius(celsius)
    
      {:noreply, assign(socket, :fahrenheit, fahrenheit)}
    end
    


    .
    라이브/calculator.ex
    .

    def handle_info({:convert_temp, celsius}, socket) do
      :timer.sleep(4000)
    
      fahrenheit = to_fahrenheit(celsius)
    
      {:noreply, assign(socket, :fahrenheit, fahrenheit)}
    end
    





    live_view에서 논리를 변경하십시오.
    .
    라이브/온도_input.ex
    .

    def handle_event("to_fahrenheit", %{"temp" => %{"celsius" => celsius}}, socket) do
      send(self(), {:convert_temp, celsius})
    
      fahrenheit = to_fahrenheit(celsius)
    
      {:noreply, assign(socket, :fahrenheit, fahrenheit)}
    end
    


    .
    라이브/calculator.ex
    .

    def handle_info({:convert_temp, celsius}, socket) do
      :timer.sleep(4000)
    
      # wrong formula
      fahrenheit = to_celsius(celsius)
    
      {:noreply, assign(socket, :fahrenheit, fahrenheit)}
    end
    





    해결책



    .
    논리를 둘 이상의 위치에 두지 마십시오. 이 경우 우리는 로직을 live_view에만 유지하기로 결정했지만 live_view에 있을 필요는 없었습니다. 목표는 논리를 복제하지 않는 것입니다.
    .
    라이브/온도_input.ex
    .

    def handle_event("to_fahrenheit", %{"temp" => %{"celsius" => celsius}}, socket) do
      send(self(), {:convert_temp, celsius})
    
      {:noreply, socket}
    end
    


    .
    라이브/calculator.ex
    .

    def handle_info({:convert_temp, celsius}, socket) do
      fahrenheit = to_fahrenheit(celsius)
    
      {:noreply, assign(socket, :fahrenheit, fahrenheit)}
    end
    


    .

    마무리



    .
    미래의 골칫거리와 수정하기 어려운 버그를 피하기 위해 데이터가 진실의 단일 소스로 정의되는 논리를 유지하십시오.
    .
    모두 감사합니다. 즐겁고 재미있게 보내시기 바랍니다. 그러니 다음에 무슨 일이 일어날지 계속 지켜봐 주십시오.

    그리고 내 블로그 게시물을 검토해 준 , , 에게 특별한 감사를 드립니다.

    좋은 웹페이지 즐겨찾기