30秒学会 JavaScript 片段 · 2022年7月1日

30秒学会 JavaScript 片段 – Typechecking objects with Proxy in JavaScript

A while back, I was working on a project where some objects had rigid structure requirements. As I was really not in the mood to use TypeScript, I decided to create a typechecking mechanism for objects using the Proxy object.

Drawing inspiration from React’s PropTypes, I created a handful of typechecking functions for the most common types.

代码实现

const bool = v => typeof v === 'boolean';
const num = v => typeof v === 'number' && v === v;
const str = v => typeof v === 'string';
const date = v => v instanceof Date;

The next step was to decide on how an object’s shape would be defined. This proved an easy task, as I could simply use the names of the type checking functions as values for the keys of the object.

使用样例

const shape = { name: 'str', age: 'num', active: 'bool', birthday: 'date' };

Having decided how to define shapes, I needed to convert this shape definition into a function that would take an object and wrap it with a Proxy. The Proxy would in turn intercept any attempts to set a property and check if the value being set is of the correct type. If it is, the value is set as expected. If not, the trap returns false, which means the operation was not a success. Similarly, properties not in the shape definition should not be set, so the trap returns false for those as well.

const createShapeCheckerProxy = (types, shape) => {
  const validProps = Object.keys(shape);
  const handler = {
    set(target, prop, value) {
      if (!validProps.includes(prop)) return false;
      const validator = types[shape[prop]];
      if (!validator || typeof validator !== 'function') return false;
      if (!validator(value)) return false;
      target[prop] = value;
    }
  };
  return obj => new Proxy(obj, handler);
};

Having set everything up, it was time to test it out. Here’s an example of the whole thing put together:

const createShapeCheckerProxy = shape => {
  const types = {
    bool: v => typeof v === 'boolean',
    num: v => typeof v === 'number' && v === v,
    str: v => typeof v === 'string',
    date: v => v instanceof Date
  };
  const validProps = Object.keys(shape);

  const handler = {
    set(target, prop, value) {
      if (!validProps.includes(prop)) return false;
      const validator = types[shape[prop]];
      if (!validator || typeof validator !== 'function') return false;
      if (!validator(value)) return false;
      target[prop] = value;
    }
  };

  return obj => new Proxy(obj, handler);
};

const shapeCheckerProxy = createShapeCheckerProxy({
  name: 'str', age: 'num', active: 'bool', birthday: 'date'
});

const obj = {};
const proxiedObj = shapeCheckerProxy(obj);

// These are valid
proxiedObj.name = 'John';
proxiedObj.age = 34;
proxiedObj.active = false;
proxiedObj.birthday = new Date('1989-04-01');

// These will fail
proxiedObj.name = 404;
proxiedObj.age = false;
proxiedObj.active = 'no';
proxiedObj.birthday = null;
proxiedObj.whatever = 'something';

As you can see, createShapeCheckerProxy can be used with a plain object to create a reusable function that wraps an object with a type checking Proxy. The defined types are used to typecheck individual properties and could be extended to support more complex types and special rules. Overall, this can be a pretty useful tool for typechecking objects at runtime, without having to use TypeScript or similar tools.

翻译自:https://www.30secondsofcode.org/js/s/typecheck-proxy