UNPKG

omniscient

Version:

A library providing an abstraction for React components for passing the same data structure through the entire component flow using cursors and immutable data structures.

320 lines (285 loc) 9.77 kB
'use strict'; var isEqual = require('lodash.isequal'); /** * Directly fetch `shouldComponentUpdate` mixin to use outside of Omniscient. * You can do this if you don't want to use Omniscients syntactic sugar. * * @param {Object} nextProps Next props. Can be objects of cursors, values or immutable structures * @param {Object} nextState Next state. Can be objects of values or immutable structures * * @property {Function} isCursor Get default isCursor * @property {Function} isEqualState Get default isEqualState * @property {Function} isEqualProps Get default isEqualProps * @property {Function} isEqualCursor Get default isEqualCursor * @property {Function} isEqualImmutable Get default isEqualImmutable * @property {Function} isImmutable Get default isImmutable * @property {Function} isIgnorable Get default isIgnorable * @property {Function} debug Get default debug * * @module shouldComponentUpdate * @returns {Component} * @api public */ module.exports = factory(); /** * Create a “local” instance of the shouldComponentUpdate with overriden defaults. * * ### Options * ```js * { * isCursor: function (cursor), // check if is props * isEqualCursor: function (oneCursor, otherCursor), // check cursor * isEqualImmutable: function (oneImmutableStructure, otherImmutableStructure), // check immutable structures * isEqualState: function (currentState, nextState), // check state * isImmutable: function (currentState, nextState), // check if object is immutable * isEqualProps: function (currentProps, nextProps), // check props * isIgnorable: function (propertyValue, propertyKey), // check if property item is ignorable * unCursor: function (cursor) // convert from cursor to object * } * ``` * * @param {Object} [Options] Options with defaults to override * * @module shouldComponentUpdate.withDefaults * @returns {Function} shouldComponentUpdate with overriden defaults * @api public */ module.exports.withDefaults = factory; function factory(methods) { var debug; methods = methods || {}; var _isCursor = methods.isCursor || isCursor, _isEqualCursor = methods.isEqualCursor || isEqualCursor, _isEqualImmutable = methods.isEqualImmutable || isEqualImmutable, _isEqualState = methods.isEqualState || isEqualState, _isEqualProps = methods.isEqualProps || isEqualProps, _isImmutable = methods.isImmutable || isImmutable, _isIgnorable = methods.isIgnorable || isIgnorable, _unCursor = methods.unCursor || unCursor; var isNotIgnorable = not(or(_isIgnorable, isChildren)); shouldComponentUpdate.isCursor = _isCursor; shouldComponentUpdate.isEqualState = _isEqualState; shouldComponentUpdate.isEqualProps = _isEqualProps; shouldComponentUpdate.isEqualCursor = _isEqualCursor; shouldComponentUpdate.isEqualImmutable = _isEqualImmutable; shouldComponentUpdate.isImmutable = _isImmutable; shouldComponentUpdate.debug = debugFn; return shouldComponentUpdate; function shouldComponentUpdate(nextProps, nextState) { if (nextProps === this.props && nextState === this.state) { if (debug) debug.call(this, 'shouldComponentUpdate => false (equal input)'); return false; } if (!_isEqualState(this.state, nextState)) { if (debug) debug.call(this, 'shouldComponentUpdate => true (state has changed)'); return true; } var filteredNextProps = filter(nextProps, isNotIgnorable), filteredCurrentProps = filter(this.props, isNotIgnorable); if (!_isEqualProps(filteredCurrentProps, filteredNextProps)) { if (debug) debug.call(this, 'shouldComponentUpdate => true (props have changed)'); return true; } if (debug) debug.call(this, 'shouldComponentUpdate => false'); return false; } /** * Predicate to check if state is equal. Checks in the tree for immutable structures * and if it is, check by reference. Does not support cursors. * * Override through `shouldComponentUpdate.withDefaults`. * * @param {Object} value * @param {Object} other * * @module shouldComponentUpdate.isEqualState * @returns {Boolean} * @api public */ function isEqualState(value, other) { return isEqual(value, other, function(current, next) { if (current === next) return true; return compare(current, next, _isImmutable, _isEqualImmutable); }); } /** * Predicate to check if props are equal. Checks in the tree for cursors and immutable structures * and if it is, check by reference. * * Override through `shouldComponentUpdate.withDefaults`. * * @param {Object} value * @param {Object} other * * @module shouldComponentUpdate.isEqualProps * @returns {Boolean} * @api public */ function isEqualProps(value, other) { if (value === other) return true; var cursorsEqual = compare(value, other, _isCursor, _isEqualCursor); if (cursorsEqual !== void 0) return cursorsEqual; var immutableEqual = compare(value, other, _isImmutable, _isEqualImmutable); if (immutableEqual !== void 0) return immutableEqual; return isEqual(value, other, function(current, next) { if (current === next) return true; var cursorsEqual = compare(current, next, _isCursor, _isEqualCursor); if (cursorsEqual !== void 0) return cursorsEqual; return compare(current, next, _isImmutable, _isEqualImmutable); }); } /** * Predicate to check if cursors are equal through reference checks. Uses `unCursor`. * Override through `shouldComponentUpdate.withDefaults` to support different cursor * implementations. * * @param {Cursor} a * @param {Cursor} b * * @module shouldComponentUpdate.isEqualCursor * @returns {Boolean} * @api public */ function isEqualCursor(a, b) { return _unCursor(a) === _unCursor(b); } function debugFn(pattern, logFn) { if (typeof pattern === 'function') { logFn = pattern; pattern = void 0; } var logger = logFn; if (!logger && console.debug) { logger = console.debug.bind(console); } if (!logger && console.info) { logger = console.info.bind(console); } var regex = new RegExp(pattern || '.*'); debug = function(str) { var element = this._reactInternalFiber ? this._reactInternalFiber : this._reactInternalInstance ? this._reactInternalInstance._currentElement : this._currentElement; var key = element && element.key ? ' key=' + element.key : ''; var name = this.constructor.displayName; if (!key && !name) { name = 'Unknown'; } var tag = name + key; if (regex.test(tag)) logger('<' + tag + '>: ' + str); }; return debug; } } // Comparator used internally by isEqual implementation. Returns undefined // if we should do recursive isEqual. function compare(current, next, typeCheck, equalCheck) { var isCurrent = typeCheck(current); var isNext = typeCheck(next); if (isCurrent && isNext) { return equalCheck(current, next); } if (isCurrent || isNext) { return false; } return void 0; } /** * Predicate to check if immutable structures are equal through reference checks. * Override through `shouldComponentUpdate.withDefaults` to customize behaviour. * * @param {Immutable} a * @param {Immutable} b * * @module shouldComponentUpdate.isEqualImmutable * @returns {Boolean} * @api public */ function isEqualImmutable(a, b) { return a === b; } /** * Predicate to check if a potential is an immutable structure or not. * Override through `shouldComponentUpdate.withDefaults` to support different cursor * implementations. * * @param {maybeImmutable} value to check if it is immutable. * * @module shouldComponentUpdate.isImmutable * @returns {Boolean} * @api public */ var IS_ITERABLE_SENTINEL = '@@__IMMUTABLE_ITERABLE__@@'; function isImmutable(maybeImmutable) { return !!(maybeImmutable && maybeImmutable[IS_ITERABLE_SENTINEL]); } /** * Transforming function to take in cursor and return a non-cursor. * Override through `shouldComponentUpdate.withDefaults` to support different cursor * implementations. * * @param {cursor} cursor to transform * * @module shouldComponentUpdate.unCursor * @returns {Object|Number|String|Boolean} * @api public */ function unCursor(cursor) { return !isCursor(cursor) ? cursor : cursor.deref(); } /** * Predicate to check if `potential` is Immutable cursor or not (defaults to duck testing * Immutable.js cursors). Can override through `.withDefaults()`. * * @param {potential} potential to check if is cursor * * @module shouldComponentUpdate.isCursor * @returns {Boolean} * @api public */ function isCursor(potential) { return !!(potential && typeof potential.deref === 'function'); } function not(fn) { return function() { return !fn.apply(fn, arguments); }; } function filter(obj, predicate) { return Object.keys(obj).reduce(function(acc, key) { if (predicate(obj[key], key)) { acc[key] = obj[key]; } return acc; }, {}); } /** * Predicate to check if a property on props should be ignored or not. * For now this defaults to ignore if property key is `statics`, but that * is deprecated behaviour, and will be removed by the next major release. * * Override through `shouldComponentUpdate.withDefaults`. * * @param {Object} value * @param {String} key * * @module shouldComponentUpdate.isIgnorable * @returns {Boolean} * @api public */ function isIgnorable(_, key) { return false; } function isChildren(_, key) { return key === 'children'; } function or(fn1, fn2) { return function() { return fn1.apply(null, arguments) || fn2.apply(null, arguments); }; }