来自 API (Rails) 的 Svelte 的正确错误处理

Proper error handling with Svelte from API (Rails)

我正在开发一个与 rails API 服务器通信的 Svelte 前端应用程序。 我喜欢 rails 的抽象方式(尤其是 simple_form gem),我想为 Svelte 带来一些想法。我遇到的特殊问题是如何将 rails 服务器的错误与 svelte 端的输入相关联

首先,我调整了服务器的错误响应;一般来说,它们看起来像这样:

{ errors: 
  { generic: [<messages without association with particular field>], 
    email: ["Already taken", <msg_2>, ...], 
    password: ["Too short", ...],
    field_N: [...]
  }
}

generic 错误可以是 "Invalid Login Credentials"、"Your account is locked"、"Internal Server Error" 等。 其他错误必须与表单字段相关,并且必须显示在它们旁边。

这是我目前的做法signup.svelte:

<script>
let email;
let password;
function registerRequest() {
  let regURL = baseURL+'/api/users/';
  let data = {"user": {"email": email, "password": password}};
  fetch(regURL, {
    method: 'POST',
    body: JSON.stringify(data),
    headers: {
      'Content-Type': 'application/json'
    }
  })
  .then(function(response) {
    if (!response.ok) {
      return response.json()
    } 
  })
  // if errors present then iterate through errors object
  .then(function(error) {
    // find id of the message field and insert actual messages
    for (let [key, value] of Object.entries(error.errors)) {
 document.getElementById(key).insertAdjacentHTML('beforeend',value);
    }
  });
}
</script>

<style> .errormessage { color: red; } </style>

<form class="col-sm-12 col-lg-6" on:submit|preventDefault={registerRequest}>
  <!-- here I need to create errormessage container for each field. -->
  <!-- first one is generic to display generic errors -->
  <div class="errormessage" id="generic"></div>

  <label for="UserEmail">Email address</label>
  <input type="email" class="form-control" id="UserEmail" aria-describedby="emailHelp" placeholder="Enter your email here..." bind:value={email}>
  <div class="errormessage" id="email"></div>

  <label for="UserPassword">Password</label>
  <input type="password" class="form-control" id="UserPassword" placeholder="...and your password here" bind:value={password}>
  <div class="errormessage" id="password"></div>

  <button type="submit" class="btn btn-primary">Register</button>
</form>

我想将上面的代码与前端验证统一起来。如果出现 errors 对象 - 在有错误的字段旁边显示消息。在我看来,使用 getElementById 有点矫枉过正。如果我的 DOM 从不重新加载,我为什么要使用它?每次在 input 上它都会搜索 ID。也许应该由监听器来监听错误对象的变化?我尝试将自定义 event 与 Svelte 绑定 import { createEventDispatcher } from 'svelte';,但没有运气。

请帮忙分享你的想法。

您的直觉是正确的,在 Svelte 组件中使用 getElementById 感觉不对 - 通常 Svelte 希望您编写代码 声明式 ,其中 DOM是状态的函数,而不是 强制性 手动更改 DOM.

对于错误消息,我建议在您的组件中添加一个 errors 变量。当服务器错误再次出现时,您可以将它们分配给 errors,这将导致响应更新并重新渲染组件。然后标记可以知道在错误存在时呈现错误,而无需显式更改 DOM。像这样:

<script>
let email;
let password;
// New `errors` variable.
let errors = {};

function registerRequest() {
  // Reset the errors when the user submits a new request.
  errors = {};  

  let regURL = baseURL+'/api/users/';
  let data = {"user": {"email": email, "password": password}};
  fetch(regURL, {
    method: 'POST',
    body: JSON.stringify(data),
    headers: {
      'Content-Type': 'application/json'
    }
  })
  .then(function(response) {
    if (!response.ok) {
      return response.json()
    } 
  })
  .then(function(error) {
    // Just save the errors 
    errors = e.errors;
  });
}
</script>

<style> .errormessage { color: red; } </style>

<form class="col-sm-12 col-lg-6" on:submit|preventDefault={registerRequest}>
    <!-- first one is generic to display generic errors -->
    <!-- note the `|| []` part, which makes sure that
         `#each` won't throw an error when `errors.generic` doesn't exist -->
    {#each (errors.generic || []) as error}
    <div class="errormessage" id="generic">{error}</div>
    {/each}

  <label for="UserEmail">Email address</label>
  <input type="email" class="form-control" id="UserEmail" aria-describedby="emailHelp" placeholder="Enter your email here..." bind:value={email}>
    <!-- email errors -->
    {#each (errors.email || []) as error}
  <div class="errormessage" id="email">{error}</div>
    {/each}

  <label for="UserPassword">Password</label>
  <input type="password" class="form-control" id="UserPassword" placeholder="...and your password here" bind:value={password}>
    <!-- password errors -->
    {#each (errors.password || []) as error}
  <div class="errormessage" id="password">{error}</div>
    {/each}

  <button type="submit" class="btn btn-primary">Register</button>
</form>

这里有一个 Svelte REPL 代码可以正常工作。希望对您有所帮助!