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.
245 lines (192 loc) • 6.14 kB
JavaScript
var React = require('react'),
deepEqual = require('deep-equal');
module.exports = component;
module.exports.shouldComponentUpdate = shouldComponentUpdate;
module.exports.isEqualState = function isEqualState () { return deepEqual.apply(this, arguments); };
module.exports.isEqualCursor = function isEqualCursor (a, b) { return unCursor(a) === unCursor(b); };
module.exports.isCursor = isCursor;
var debug;
module.exports.debug = function (pattern) {
var regex = new RegExp(pattern || '.*');
debug = function (str) {
var key = this._currentElement && this._currentElement.key ? ' key=' + this._currentElement.key : '';
var name = this.constructor.displayName;
var tag = name + key;
if ((key || name) && regex.test(tag)) console.debug('<' + tag + '>: ' + str);
};
}
function component (displayName, mixins, render) {
var options = createDefaultArguments(displayName, mixins, render);
var Component = React.createClass({
displayName: options.displayName,
mixins: options.mixins,
render: function render () {
if (debug) debug.call(this, 'render');
return options.render.call(this, this.props, this.props.statics);
}
});
var create = function (key, props) {
var children = toArray(arguments).filter(React.isValidElement);
if (typeof key === 'object') {
props = key;
key = void 0;
}
if (!props) {
props = { };
}
if (isCursor(props)) {
props = { cursor: props };
}
if (key) {
props.key = key;
}
if (!!children.length) {
props.children = children;
}
return React.createElement(Component, props);
};
create.jsx = Component;
return create;
};
function shouldComponentUpdate (nextProps, nextState) {
var isEqualState = module.exports.isEqualState;
var isNotIgnorable = not(or(isStatics, isChildren));
var nextCursors = filterKeyValue(guaranteeObject(nextProps), isNotIgnorable),
currentCursors = filterKeyValue(guaranteeObject(this.props), isNotIgnorable);
var nextCursorsKeys = Object.keys(nextCursors),
currentCursorsKeys = Object.keys(currentCursors);
if (currentCursorsKeys.length !== nextCursorsKeys.length) {
if (debug) debug.call(this, 'shouldComponentUpdate => true (number of cursors differ)');
return true;
}
if (hasDifferentKeys(currentCursorsKeys, currentCursors, nextCursors)) {
if (debug) debug.call(this, 'shouldComponentUpdate => true (cursors have different keys)');
return true;
}
if (hasChangedCursors(currentCursors, nextCursors)) {
if (debug) debug.call(this, 'shouldComponentUpdate => true (cursors have changed)');
return true;
}
if (!isEqualState(this.state, nextState)) {
if (debug) debug.call(this, 'shouldComponentUpdate => true (state has changed)');
return true;
}
if (hasChangedProperties(currentCursors, nextCursors)) {
if (debug) debug.call(this, 'shouldComponentUpdate => true (properties have changed)');
return true;
}
if (debug) debug.call(this, 'shouldComponentUpdate => false');
return false;
}
function guaranteeObject (prop) {
if (!prop) {
return {};
}
if (isCursor(prop)) {
return { _dummy_key: prop };
}
if (typeof prop !== 'object') {
return { _dummy_key: prop };
}
return prop;
}
function hasDifferentKeys (currentCursorsKeys, currentCursors, nextCursors) {
return !currentCursorsKeys.every(function existsInBoth (key) {
return typeof currentCursors[key] !== 'undefined' && typeof nextCursors[key] !== 'undefined';
});
}
function hasChangedCursors (current, next) {
current = filterKeyValue(current, isCursor);
next = filterKeyValue(next, isCursor);
var isEqualCursor = module.exports.isEqualCursor;
for (var key in current)
if (!isEqualCursor(current[key], next[key]))
return true;
return false;
}
function hasChangedProperties (current, next) {
current = filterKeyValue(current, not(isCursor));
next = filterKeyValue(next, not(isCursor));
for (var key in current)
if (!deepEqual(current[key], next[key]))
return true;
return false;
}
function createDefaultArguments (displayName, mixins, render) {
// (render)
if (typeof displayName === 'function') {
render = displayName;
mixins = [];
displayName = void 0;
}
// (mixins, render)
if (typeof displayName === 'object' && typeof mixins === 'function') {
render = mixins;
mixins = displayName;
displayName = void 0;
}
// (displayName, render)
if (typeof displayName === 'string' && typeof mixins === 'function') {
render = mixins;
mixins = [];
}
// Else (displayName, mixins, render)
if (!Array.isArray(mixins)) {
mixins = [mixins];
}
if (!hasShouldComponentUpdate(mixins)) {
var ShouldComponentUpdate = {
shouldComponentUpdate: module.exports.shouldComponentUpdate
};
mixins = [ShouldComponentUpdate].concat(mixins);
}
return {
displayName: displayName,
mixins: mixins,
render: render
};
}
function hasShouldComponentUpdate (mixins) {
return !!mixins.filter(function (mixin) {
return !!mixin.shouldComponentUpdate;
}).length;
}
function isCursor (potential) {
return potential &&
((typeof potential.deref === 'function') || (typeof potential.__deref === 'function'));
}
function unCursor(cursor) {
if (!isCursor(cursor)) {
return cursor;
}
if (typeof cursor.deref === 'function') {
return cursor.deref();
}
return cursor.__deref();
}
function filterKeyValue (object, predicate) {
var key, filtered = {};
for (key in object)
if (predicate(object[key], key))
filtered[key] = object[key];
return filtered;
}
function not (fn) {
return function () {
return !fn.apply(fn, arguments);
};
}
function isStatics (val, key) {
return key === 'statics';
}
function isChildren (val, key) {
return key === 'children';
}
function or (fn1, fn2) {
return function () {
return fn1.apply(null, arguments) || fn2.apply(null, arguments);
};
}
function toArray (args) {
return Array.prototype.slice.call(args);
}