每天推薦一個 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