提交学习笔记专用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

305 lines
8.7 KiB

  1. # is What? 🙉
  2. <a href="https://www.npmjs.com/package/is-what"><img src="https://img.shields.io/npm/v/is-what.svg" alt="Total Downloads"></a>
  3. <a href="https://www.npmjs.com/package/is-what"><img src="https://img.shields.io/npm/dw/is-what.svg" alt="Latest Stable Version"></a>
  4. Very simple & small JS type check functions. It's fully TypeScript supported!
  5. ```
  6. npm i is-what
  7. ```
  8. Or for deno available at: `"deno.land/x/is_what"`
  9. > Also check out [is-where 🙈](https://github.com/mesqueeb/is-where)
  10. ## Motivation
  11. I built is-what because the existing solutions were all too complex or too poorly built.
  12. I was looking for:
  13. - A simple way to check any kind of type (including non-primitives)
  14. - Be able to check if an object is a plain object `{}` or a special object (like a class instance) ‼️
  15. - Let TypeScript automatically know what type a value is when checking
  16. And that's exactly what `is-what` is! (what a great wordplay 😃)
  17. ## Usage
  18. is-what is really easy to use, and most functions work just like you'd expect.
  19. ```js
  20. // import functions you want to use like so:
  21. import { isString, isDate, isPlainObject } from 'is-what'
  22. ```
  23. 1. First I'll go over the simple functions available. Only `isNumber` and `isDate` have special treatment.
  24. 2. After that I'll talk about working with Objects (plain objects vs class instances etc.).
  25. 3. Lastly I'll talk about TypeScript implementation
  26. ### Simple type check functions
  27. ```js
  28. // basics
  29. isBoolean(true) // true
  30. isBoolean(false) // true
  31. isUndefined(undefined) // true
  32. isNull(null) // true
  33. // strings
  34. isString('') // true
  35. isEmptyString('') // true
  36. isFullString('') // false
  37. isHexDecimal('60adf084f0fbdcab42de841e') // true
  38. isHexDecimal('60adf084f0fbdcab42de841e', 24) // check specific length of 24 (eg. MongoDB ObjectId)
  39. // numbers
  40. isNumber(0) // true
  41. isNumber('0') // false
  42. isNumber(NaN) // false *
  43. isPositiveNumber(1) // true
  44. isNegativeNumber(-1) // true
  45. // * see below for special NaN use cases!
  46. // arrays
  47. isArray([]) // true
  48. isEmptyArray([]) // true
  49. isFullArray([1]) // true
  50. // objects
  51. isPlainObject({}) // true *
  52. isEmptyObject({}) // true
  53. isFullObject({ a: 1 }) // true
  54. // * see below for special object (& class instance) use cases!
  55. // functions
  56. isFunction(function () {}) // true
  57. isFunction(() => {}) // true
  58. // dates
  59. isDate(new Date()) // true
  60. isDate(new Date('invalid date')) // false
  61. // maps & sets
  62. isMap(new Map()) // true
  63. isSet(new Set()) // true
  64. isWeakMap(new WeakMap()) // true
  65. isWeakSet(new WeakSet()) // true
  66. // others
  67. isRegExp(/\s/gi) // true
  68. isSymbol(Symbol()) // true
  69. isBlob(new Blob()) // true
  70. isFile(new File([''], '', { type: 'text/html' })) // true
  71. isError(new Error('')) // true
  72. isPromise(new Promise((resolve) => {})) // true
  73. // primitives
  74. isPrimitive('') // true
  75. // true for any of: boolean, null, undefined, number, string, symbol
  76. // iterables
  77. isIterable([1, 2, 3]) // true
  78. isIterable('hello') // true
  79. isIterable(new Map()) // true
  80. isIterable(new Set()) // true
  81. isIterable(function* generator() {
  82. yield 1
  83. }) // true
  84. ```
  85. ### Let's talk about NaN
  86. `isNaN` is a built-in JS Function but it really makes no sense:
  87. ```js
  88. // 1)
  89. typeof NaN === 'number' // true
  90. // 🤔 ("not a number" is a "number"...)
  91. // 2)
  92. isNaN('1') // false
  93. // 🤔 the string '1' is not-"not a number"... so it's a number??
  94. // 3)
  95. isNaN('one') // true
  96. // 🤔 'one' is NaN but `NaN === 'one'` is false...
  97. ```
  98. With is-what the way we treat NaN makes a little bit more sense:
  99. ```js
  100. import { isNumber, isNaNValue } from 'is-what'
  101. // 1)
  102. isNumber(NaN) // false!
  103. // let's not treat NaN as a number
  104. // 2)
  105. isNaNValue('1') // false
  106. // if it's not NaN, it's not NaN!!
  107. // 3)
  108. isNaNValue('one') // false
  109. // if it's not NaN, it's not NaN!!
  110. isNaNValue(NaN) // true
  111. ```
  112. ### isPlainObject vs isAnyObject
  113. Checking for a JavaScript object can be really difficult. In JavaScript you can create classes that will behave just like JavaScript objects but might have completely different prototypes. With is-what I went for this classification:
  114. - `isPlainObject` will only return `true` on plain JavaScript objects and not on classes or others
  115. - `isAnyObject` will be more loose and return `true` on regular objects, classes, etc.
  116. ```js
  117. // define a plain object
  118. const plainObject = { hello: 'I am a good old object.' }
  119. // define a special object
  120. class SpecialObject {
  121. constructor(somethingSpecial) {
  122. this.speciality = somethingSpecial
  123. }
  124. }
  125. const specialObject = new SpecialObject('I am a special object! I am a class instance!!!')
  126. // check the plain object
  127. isPlainObject(plainObject) // returns true
  128. isAnyObject(plainObject) // returns true
  129. getType(plainObject) // returns 'Object'
  130. // check the special object
  131. isPlainObject(specialObject) // returns false !!!!!!!!!
  132. isAnyObject(specialObject) // returns true
  133. getType(specialObject) // returns 'Object'
  134. ```
  135. > Please note that `isPlainObject` will only return `true` for normal plain JavaScript objects.
  136. ### Getting and checking for specific types
  137. You can check for specific types with `getType` and `isType`:
  138. ```js
  139. import { getType, isType } from 'is-what'
  140. getType('') // returns 'String'
  141. // pass a Type as second param:
  142. isType('', String) // returns true
  143. ```
  144. If you just want to make sure your object _inherits_ from a particular class or
  145. `toStringTag` value, you can use `isInstanceOf()` like this:
  146. ```js
  147. import { isInstanceOf } from 'is-what'
  148. isInstanceOf(new XMLHttpRequest(), 'EventTarget')
  149. // returns true
  150. isInstanceOf(globalThis, ReadableStream)
  151. // returns false
  152. ```
  153. ## TypeScript
  154. is-what makes TypeScript know the type during if statements. This means that a check returns the type of the payload for TypeScript users.
  155. ```ts
  156. function isNumber(payload: unknown): payload is number {
  157. // return boolean
  158. }
  159. // As you can see above, all functions return a boolean for JavaScript, but pass the payload type to TypeScript.
  160. // usage example:
  161. function fn(payload: string | number): number {
  162. if (isNumber(payload)) {
  163. // ↑ TypeScript already knows payload is a number here!
  164. return payload
  165. }
  166. return 0
  167. }
  168. ```
  169. `isPlainObject` and `isAnyObject` with TypeScript will declare the payload to be an object type with any props:
  170. ```ts
  171. function isPlainObject(payload: unknown): payload is { [key: string]: unknown }
  172. function isAnyObject(payload: unknown): payload is { [key: string]: unknown }
  173. // The reason to return `{[key: string]: unknown}` is to be able to do
  174. if (isPlainObject(payload) && payload.id) return payload.id
  175. // if isPlainObject() would return `payload is object` then it would give an error at `payload.id`
  176. ```
  177. ### isObjectLike
  178. If you want more control over what kind of interface/type is casted when checking for objects.
  179. To cast to a specific type while checking for `isAnyObject`, can use `isObjectLike<T>`:
  180. ```ts
  181. import { isObjectLike } from 'is-what'
  182. const payload = { name: 'Mesqueeb' } // current type: `{ name: string }`
  183. // Without casting:
  184. if (isAnyObject(payload)) {
  185. // in here `payload` is casted to: `Record<string | number | symbol, unknown>`
  186. // WE LOOSE THE TYPE!
  187. }
  188. // With casting:
  189. // you can pass a specific type for TS that will be casted when the function returns
  190. if (isObjectLike<{ name: string }>(payload)) {
  191. // in here `payload` is casted to: `{ name: string }`
  192. }
  193. ```
  194. Please note: this library will not actually check the shape of the object, you need to do that yourself.
  195. `isObjectLike<T>` works like this under the hood:
  196. ```ts
  197. function isObjectLike<T extends object>(payload: unknown): payload is T {
  198. return isAnyObject(payload)
  199. }
  200. ```
  201. ## Meet the family (more tiny utils with TS support)
  202. - [is-what 🙉](https://github.com/mesqueeb/is-what)
  203. - [is-where 🙈](https://github.com/mesqueeb/is-where)
  204. - [merge-anything 🥡](https://github.com/mesqueeb/merge-anything)
  205. - [check-anything 👁](https://github.com/mesqueeb/check-anything)
  206. - [remove-anything ✂️](https://github.com/mesqueeb/remove-anything)
  207. - [getorset-anything 🐊](https://github.com/mesqueeb/getorset-anything)
  208. - [map-anything 🗺](https://github.com/mesqueeb/map-anything)
  209. - [filter-anything ⚔️](https://github.com/mesqueeb/filter-anything)
  210. - [copy-anything 🎭](https://github.com/mesqueeb/copy-anything)
  211. - [case-anything 🐫](https://github.com/mesqueeb/case-anything)
  212. - [flatten-anything 🏏](https://github.com/mesqueeb/flatten-anything)
  213. - [nestify-anything 🧅](https://github.com/mesqueeb/nestify-anything)
  214. ## Source code
  215. It's litterally just these functions:
  216. ```js
  217. function getType(payload) {
  218. return Object.prototype.toString.call(payload).slice(8, -1)
  219. }
  220. function isUndefined(payload) {
  221. return getType(payload) === 'Undefined'
  222. }
  223. function isString(payload) {
  224. return getType(payload) === 'String'
  225. }
  226. function isAnyObject(payload) {
  227. return getType(payload) === 'Object'
  228. }
  229. // etc...
  230. ```
  231. See the full source code [here](https://github.com/mesqueeb/is-what/blob/main/src/index.ts).