每天推荐一个 GitHub 优质开源项目和一篇精选英文科技或编程文章原文,欢迎关注开源日报。交流QQ群:202790710;微博:https://weibo.com/openingsource;电报群 https://t.me/OpeningSourceOrg


今日推荐开源项目:《别人家的 HTML 和 CSS(Ver2.0) purecss-vignes》传送门:GitHub链接

推荐理由:不知道各位还记不记得那个拿着 HTML 和 CSS 画画的人,如果没有印象的话,可以去看看之前的日报(传送门),保证会给你留下一个深刻的印象。这次介绍的是他画的复古海报,如果喜欢的话希望能够给这个项目一个 star,然后当然还是要提醒一下,用 chrome 能够获得最佳体验,浏览器不兼容看起来不太对记得换浏览器。

观赏链接:http://diana-adrianne.com/purecss-vignes/


今日推荐英文原文:《Effective Data Validation in JavaScript》作者:A. Sharif

原文链接:https://medium.com/javascript-inside/effective-data-validation-in-javascript-5c2f3e75249e

推荐理由:在 JS 中验证数据通常使用外部库或者自己写个函数,而这篇文章介绍了如何对一个简单的验证函数做出改进使得它更易用

Effective Data Validation in JavaScript

Introduction

Most applications will need to validate data at some point. This data might be provided by an external service or via user interaction or input. This is nothing special and in most cases we might have a helper library or common functions for validating that data.

This write-up is intended to provide another way of validating data.


Basic Validation Functions

Let’s take a look at the following example, where we want to guarantee that the provided user name has a minimum length of 8 characters:

const checkUserName = data =>
  data && data.length > 8 
    ? data 
    : “Minimum of 8 characters required!”;

// validate the username
checkUserName(“User A”); // => “Minimum 8 characters required!”

This validation function already works as expected and returns the appropriate error message if the provided input is invalid. Now let’s take a look at another situation, where we might want to define a different message.

const checkUserName = (data, errorMsg) =>
  data && data.length > 8 
    ? data 
    : errorMsg;

// validate the username
checkUserName(“User A”, “Minimum of 8 characters is required!”); 
// => “Minimum of 8 characters is required!”

Again, we can dynamically define the error message when needed, but we also might want to be able to define the validation function itself. This would leave our checkUserName function to have no concept of what a user name actually is.

const runValidation = (data, validation, errorMsg) =>
  validation(data) ? data : errorMsg;

// validate the username
runValidation(
  “User A”,
  data => data && data.length > 8,
  “Minimum 8 of characters required!”
); // => “Minimum of 8 characters required!”

We renamed our checkUserName function to runValidation, as this function is independent of any context. It expects the data, a validation function and an error message.

Taking a closer look at runValidation, we might notice that if we wanted to run checkUserName with different data, we would have to redefine the validation function and error message every time as well.
But we’re mainly interested in defining the specific validation function and error message once, and then running that function with different data.

Our next attempt is to enable providing data as against a defined validation function.

const runValidation = (validation, errorMsg) => data =>
  validation(data) ? data : errorMsg;

// validate the username
const validateUserName = runValidation(
  data => data && data.length > 8,
  “Minimum 8 characters required!”
);

validateUserName(“User A”); // => “Minimum 8 characters required!”

This approach ensures that the validation function can be defined once and run multiple times with different inputs. If you recall, we’re calling validateUserName with just the data, just like in the very first example now.

Our current implementation ensures validation functions to be built dynamically and this is already quite useful. But in the next section, we will see how we can improve this concept and the overall convenience.


Error Handling

Now that we have a better understanding of how to build dynamic validations, let’s take a look at how we would work with the results of said validations.

We would probably assign the result to a value and depending on the expected outcome do something with the result. f.e. display an error message or perform some other actions. Let’s see how we might approach this for the validateUserName case.

const result = validateUserName(“User A”);

In the example above we would get back an error message. So we would either display an error message or the actual user name. But we can see a problem here, as we can’t distinguish between the two result types. We can’t highlight the error, as the result doesn’t provide any further information. We need to refactor our runValidation to return true if the provided data is valid.

const runValidation = (validation, errorMsg) => data =>
  validation(data) ? true : errorMsg;

// validate the username
const validateUserName = runValidation(
  data => data && data.length > 8,
  “Minimum 8 characters required!”
);

validateUserName(“User A”); // => “Minimum 8 characters required!”

By changing our function, we are able to distinguish between an error and a successful validation.

const result = validateUserName(“User A”);

if (result === true) {
 // work with the data
} else {
 // display an error message
}

We still need to do a lot of work and we might even need to handle multiple validations and return all errors at once. This would mean we have to collect these results and then combine them. We can improve the overall handling.


Improved Validation Constructors

In the following section we will see how we might be able to add more convenience when working with the validation results.
What if we were able to build a pipeline, not needing to manually handle the case, but define what should happen when the validation is run?

Let’s see how this might look like.

const createUserName = createValidation(
  a => a && a.length > 6,
  “Minimum of 7 characters required”
);

This looks very similar to the validateUserName function, but this function states that we are creating a user name in this case. To better understand why this might make sense let’s take a look at the following example.

const createUserName = createValidation(
  a => a && a.length > 6, 
  “Minimum of 7 characters required”
);

createUserName(“User A”).cata({
  error: e => e // error message,
  success: data => data // the valid data
});

We are providing a quasi constructor function. All we need to do is pass in a branching function, telling createUserName how to handle each case.
Now that we have a better understanding of the benefits, let’s see how we might implement the createValidation function.

const createValidation = (fn, errorMsg, type) => data => {
  const result = fn(data);
  return {
    cata: branch =>
      result 
        ? branch.right(result, type) 
        : branch.left(errorMsg, type),
    type
  };
};

The function itself needs the validation function and optionally an error message and a type. The type might be useful if we want to distinguish between as well as do something depending on that type. Type can also be replaced with something else that might provide context. The actual implementation might vary, depending on the specific case, but the important aspect is that result is an object enabling user land to define how to handle the result outcome via the cata function.


Example: Form Validation

To see why this approach is useful, let’s take a look at form validating.
We might have a form, where we might provide a user name, a phone and an email address.

const EMAIL = “EMAIL”;
const PHONE = “PHONE”;

const createUserName = createValidation(
  a => a && a.length > 5, 
  “Minimum of 6 characters required”
);

const createEmail = createValidation(
  a => a && a.length > 6,
  “Minimum of 7 characters required”,
  EMAIL
);

const createPhone = createValidation(
  a => a && a.length > 7,
  “Minimum of 8 characters required”,
  PHONE
);

Ignore the naive validation functions, these depend on the specific case you’re trying to validate. But more interestingly we can map the results of a form to the different validation constructors.

const formValues = {
  email: createEmail(“foo@”),
  phone: createPhone(“987654321”),
  userName: createUserName(“UserA”)
};

What’s missing is a custom validation function, that expects the form values and returns an object that maps the field names to the result. The implementation is up to user land, but the following example should highlight the fact, that we can implement the mapping as needed.

const validateFormValues = values =>
  Object.keys(values).map(key => ({
    [key]: values[key].cata({
      left: e => e,
      right: a => a
    })
  })
);

Now we can call our newly created validateFormValues function with the form values and get back an array of objects containing a result for each field value.

const errors = validateFormValues(formValues);

The result will have the following structure:

[
  { email: “Minimum of 7 characters required” }, 
  { phone: true }, 
  { userName: “Minimum of 6 character required” }
]

The result can be a single object or an array of error messages etc. This depends on our specific case and can be implemented as needed via defining a specific mapping function.

Example: Safely Accessing Deeply Nested Data

We will take a look at one final example, just to clarify the benefits of this way of validating data. Our createValidation can be used to create a function that can safely access deeply nested data for example. There are better and more efficient ways to handle this, but it’s useful for demonstrating that we can use this approach in different situations.

const getValue = createValidation(a => {
  try {
    return a();
  } catch (e) {
    return false;
  }
});

const safelyAccessDeepNestedData = (data, defaultValue) => {
  return getValue(data).cata({
    left: () => defaultValue,
    right: a => a
  });
};

This enables us to access a deep nested value.

const a = 1;
const b = { c: { d: 2 } };

safelyAccessDeepNestedData(() => a.b.c); // => undefined
safelyAccessDeepNestedData(() => b.c.d); // => 2

Summary

This approach might be useful in situations where we want to control the outcome of the validation result. We might even use this function to validate data inside a React component f.e., either rendering an error message or nothing depending on the validation result.

If there is interest, there is a possibility of a follow-up to this write-up showing how to leverage these ideas directly in React f.e.


If you have any questions or feedback please leave a comment here or connect via Twitter: A. Sharif


每天推荐一个 GitHub 优质开源项目和一篇精选英文科技或编程文章原文,欢迎关注开源日报。交流QQ群:202790710;微博:https://weibo.com/openingsource;电报群 https://t.me/OpeningSourceOrg