├── .babelrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── dist ├── config.js ├── connect.js ├── pure.js ├── pure2.js └── state.js ├── index.js ├── lib ├── config.js ├── connect.js ├── pure.js ├── pure2.js └── state.js ├── package.json └── test └── index.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | stage: 0 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug* 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.1" 4 | - "4.0" 5 | - "iojs" 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2.2.3 (2015-12-23) 2 | 3 | - update dataton dep version 4 | 5 | 2.2.2 (2015-12-23) 6 | 7 | - add change log 8 | - add travis ci support 9 | 10 | 2.2.0 (2015-12-14) 11 | 12 | - [feature] update pure render decorator 13 | 14 | 2.1.2 (2015-12-12) 15 | 16 | - [feature] add pure render decorator 17 | 18 | 2.1.1 (2015-12-05) 19 | 20 | - [feature] logging with component name 21 | 22 | 2.1.0 (2015-12-05) 23 | 24 | - [bugfix] prevent forceUpdate on an unmounted component 25 | 26 | 2.0.1 (2015-12-02) 27 | 28 | - [feature] log no-update message 29 | 30 | 2.0.0 (2015-11-03) 31 | 32 | - enhance test 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [DEPRECATED] This project is deprecated in favor of [the @noflux project](https://github.com/nofluxjs/noflux) 2 | 3 | * A migration document can be found [here](https://noflux.js.org/en/advanced/migration.html). 4 | 5 | * 迁移文档请见[这里](https://noflux.js.org/zh/advanced/migration.html)。 6 | 7 | --- 8 | 9 | [![Build Status](https://travis-ci.org/ssnau/noflux.svg?branch=master)](https://travis-ci.org/ssnau/noflux) 10 | 11 | noflux 12 | -------- 13 | 14 | a simple top down data flow implementation. 15 | 16 | 17 | install 18 | ---- 19 | 20 | ``` 21 | npm install noflux 22 | ``` 23 | 24 | usage 25 | ----- 26 | 27 | ``` 28 | import {connect, state} from "noflux"; 29 | import React from "react"; 30 | 31 | state.load({ 32 | name: 'jack' 33 | }); 34 | 35 | @connect 36 | export default class extends React.Component { 37 | render() { 38 | return ( 39 |
40 | state.set('name', e.target.value)} /> 41 |

hello, my name is {state.get('name')}

42 |
43 | ) 44 | } 45 | } 46 | ``` 47 | 48 | The page will be refresh once the state changes. 49 | 50 | 51 | API 52 | ----- 53 | 54 | ### connect 55 | 56 | a function works as decrocation to bind a React class with `state`. 57 | `state` will emit `change` event after its modification and the instance 58 | of the class will be re-rendered. 59 | 60 | ### pure 61 | 62 | a function works as decrocation to make a React class as a pure render component even though you pass cursors 63 | as props. 64 | 65 | ### state 66 | 67 | A instance of [dataton](http://npmjs.com/package/dataton) which implemented Copy-On-Write technique. You can: 68 | 69 | - `load`: load data into state. 70 | - `set`: set specific key-value for state. 71 | - `get`: get the correspond value for the provided key. 72 | - `cursor`: get the `cursor` from the provided path. 73 | 74 | 75 | 76 | License 77 | ---- 78 | 79 | MIT 80 | 81 | -------------------------------------------------------------------------------- /dist/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var win = typeof window !== 'undefined' ? window : { __noflux_env: 'production' }; 4 | module.exports = { 5 | muteConsole: false, 6 | isDev: function isDev() { 7 | return win.__noflux_env !== 'production'; 8 | } 9 | }; -------------------------------------------------------------------------------- /dist/connect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | exports['default'] = connect; 7 | 8 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 9 | 10 | var _state = require('./state'); 11 | 12 | var _state2 = _interopRequireDefault(_state); 13 | 14 | var _config = require("./config"); 15 | 16 | var allowConsole = function allowConsole(_) { 17 | return (0, _config.isDev)() && !_config.muteConsole && typeof console !== 'undefined'; 18 | }; 19 | 20 | function noop() {} 21 | 22 | /** 23 | * connect的作用在于,将给定的react class和指定的datasource连接。 24 | * 当state上发出'change'事件时,react实例对象能触发. 25 | */ 26 | 27 | function connect(clazz, data) { 28 | var datasource = data || _state2['default']; 29 | var on = function on(eventname, callback) { 30 | datasource.on(eventname, callback); 31 | return { 32 | remove: function remove() { 33 | datasource.removeListener(eventname, callback); 34 | } 35 | }; 36 | }; 37 | 38 | var origMidMount = clazz.prototype.componentDidMount || noop; 39 | var name = clazz.name; 40 | var handler, mHandler; 41 | clazz.prototype.componentDidMount = function () { 42 | var _this = this; 43 | 44 | handler = on('change', function () { 45 | /*eslint-disable no-console */ 46 | allowConsole() && console.time && console.time(name + '渲染耗时'); 47 | if (_this.__isUnmounted) return; // prevent forceUpdate an unmounted component 48 | _this.forceUpdate(function () { 49 | allowConsole() && console.timeEnd && console.timeEnd(name + '渲染耗时'); 50 | }); 51 | /*eslint-enable */ 52 | }); 53 | mHandler = on('message', function (data) { 54 | if (/no-update/.test(data.type)) { 55 | allowConsole() && console.log('调用了state的update方法但是未能触发页面更新,原因是传入值和已有值相同。 数据更新路径为' + data.path.join('/') + ", 传入值为" + JSON.stringify(data.value, null, 2)); 56 | } 57 | }); 58 | return origMidMount.call(this); 59 | }; 60 | var origWillUmount = clazz.prototype.componentWillUnmount || noop; 61 | clazz.prototype.componentWillUnmount = function () { 62 | handler && handler.remove(); 63 | mHandler && mHandler.remove(); 64 | this.__isUnmounted = true; 65 | return origWillUmount.call(this); 66 | }; 67 | 68 | return clazz; 69 | } 70 | 71 | module.exports = exports['default']; -------------------------------------------------------------------------------- /dist/pure.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 4 | 5 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 6 | 7 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 8 | 9 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 10 | 11 | function each(obj, fn) { 12 | Object.keys(obj).forEach(function (key) { 13 | fn(key, obj[key]); 14 | }); 15 | } 16 | 17 | function noop() {} 18 | 19 | function isCursor(val) { 20 | return typeof val === 'function' && val.get && val.update; 21 | } 22 | 23 | function shallowCompare(instance, nextProps, nextState) { 24 | return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState); 25 | } 26 | 27 | /** 28 | * Performs equality by iterating through keys on an object and returning 29 | * false when any key has values which are not strictly equal between 30 | * objA and objB. Returns true when the values of all keys are strictly equal. 31 | * 32 | * @return {boolean} 33 | */ 34 | function shallowEqual(objA, objB) { 35 | if (objA === objB) { 36 | return true; 37 | } 38 | 39 | if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { 40 | return false; 41 | } 42 | 43 | var keysA = Object.keys(objA); 44 | var keysB = Object.keys(objB); 45 | 46 | if (keysA.length !== keysB.length) { 47 | return false; 48 | } 49 | 50 | // Test for A's keys different from B. 51 | var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB); 52 | for (var i = 0; i < keysA.length; i++) { 53 | if (!bHasOwnProperty(keysA[i]) || !equal(objA[keysA[i]], objB[keysA[i]])) { 54 | return false; 55 | } 56 | } 57 | 58 | return true; 59 | } 60 | 61 | function equal(a, b) { 62 | if (isCursor(a) && isCursor(b)) return a() === b(); 63 | return a === b; 64 | } 65 | 66 | function scu(nextProps, nextState) { 67 | return shallowCompare(this, nextProps, nextState); 68 | } 69 | 70 | function pure(Clazz) { 71 | var React = require('react'); 72 | Clazz.prototype.shouldComponentUpdate = scu; 73 | 74 | // 包装一层 75 | 76 | var Wrapper = (function (_React$Component) { 77 | _inherits(Wrapper, _React$Component); 78 | 79 | function Wrapper() { 80 | _classCallCheck(this, Wrapper); 81 | 82 | _get(Object.getPrototypeOf(Wrapper.prototype), 'constructor', this).apply(this, arguments); 83 | } 84 | 85 | _createClass(Wrapper, [{ 86 | key: 'render', 87 | value: function render() { 88 | var props = {}; 89 | each(this.props, function (key, val) { 90 | props[key] = val; 91 | if (isCursor(val)) props['_cursor_val_' + key] = val(); 92 | }); 93 | return React.createElement(Clazz, props); 94 | } 95 | }]); 96 | 97 | return Wrapper; 98 | })(React.Component); 99 | 100 | return Wrapper; 101 | } 102 | 103 | module.exports = pure; -------------------------------------------------------------------------------- /dist/pure2.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var PROP = "__pure_props"; 4 | var NEXTPROP = "__pure_next_props"; 5 | 6 | function each(obj, fn) { 7 | Object.keys(obj).forEach(function (key) { 8 | fn(key, obj[key]); 9 | }); 10 | } 11 | 12 | function noop() {} 13 | 14 | function isCursor(val) { 15 | return typeof val === 'function' && val.get && val.update; 16 | } 17 | 18 | /** 19 | * Performs equality by iterating through keys on an object and returning 20 | * false when any key has values which are not strictly equal between 21 | * objA and objB. Returns true when the values of all keys are strictly equal. 22 | * 23 | * @return {boolean} 24 | */ 25 | function shallowEqual(objA, objB) { 26 | if (objA === objB) return true; 27 | 28 | if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { 29 | return false; 30 | } 31 | 32 | var keysA = Object.keys(objA); 33 | var keysB = Object.keys(objB); 34 | 35 | if (keysA.length !== keysB.length) return false; 36 | 37 | // Test for A's keys different from B. 38 | var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB); 39 | for (var i = 0; i < keysA.length; i++) { 40 | if (!bHasOwnProperty(keysA[i]) || !equal(objA[keysA[i]], objB[keysA[i]])) { 41 | return false; 42 | } 43 | } 44 | 45 | return true; 46 | } 47 | 48 | function equal(a, b) { 49 | if (isCursor(a) && isCursor(b)) return a() === b(); 50 | return a === b; 51 | } 52 | 53 | function assignTo(provider, receiver) { 54 | each(provider, function (key, val) { 55 | receiver[key] = val; 56 | if (isCursor(val)) receiver['_cursor_val_' + key] = val(); 57 | }); 58 | } 59 | 60 | function pure(Clazz) { 61 | // no instrumentation for server-side. 62 | if (typeof window === 'undefined') return Clazz; 63 | 64 | var rewrites = { 65 | 'shouldComponentUpdate': function pureShouldComponentUpdate(nextProps, nextState) { 66 | this[NEXTPROP] = {}; 67 | assignTo(nextProps, this[NEXTPROP]); 68 | return !shallowEqual(this[PROP] || {}, this[NEXTPROP]) || !shallowEqual(this.state, nextState); 69 | }, 70 | // no way to guarantee `componentWillReceiveProps` will be called. 71 | // if the underlying value of cursor changed but cursor stay unchanged, 72 | // will this function be called? 73 | /* 74 | 'componentWillReceiveProps': function pureComponentWillReceiveProps(props) { 75 | }, 76 | */ 77 | 'componentDidUpdate': function pureComponentDidUpdate() { 78 | this[PROP] = this[NEXTPROP]; 79 | }, 80 | 'componentWillMount': function pureComponentWillMount() { 81 | this[PROP] = {}; 82 | assignTo(this.props, this[PROP]); // sorry, `this.props` is readonly.. 83 | } 84 | }; 85 | 86 | var proto = Clazz.prototype; 87 | each(rewrites, function (key, fn) { 88 | var originalFn = proto[key] || noop; 89 | // for performance reason, no `arguments` 90 | proto[key] = function (a, b, c) { 91 | var val = originalFn.call(this, a, b, c); 92 | if (val === true || val === false) return val && fn.call(this, a, b, c); 93 | return fn.call(this, a, b); 94 | }; 95 | }); 96 | 97 | return Clazz; 98 | } 99 | 100 | module.exports = pure; -------------------------------------------------------------------------------- /dist/state.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8 | 9 | var _dataton = require('dataton'); 10 | 11 | var _dataton2 = _interopRequireDefault(_dataton); 12 | 13 | // load initial state 14 | var initialState = typeof window !== 'undefined' ? window._appState : {}; 15 | 16 | var state = new _dataton2['default'](initialState); 17 | exports['default'] = state; 18 | 19 | if (typeof window !== 'undefined') { 20 | window._state = state; // for debug 21 | } 22 | module.exports = exports['default']; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var _connect = require('./dist/connect'); 2 | var state = require('./dist/state'); 3 | var pure = require('./dist/pure2'); 4 | function connect(data) { 5 | // 如果传入的是Component Class,则直接connect它 6 | if (data && data.prototype && data.prototype.render) return _connect(data, state); 7 | return function(clazz) { 8 | return _connect(clazz, data); 9 | } 10 | } 11 | module.exports = { 12 | // decorators 13 | Connect: connect, 14 | connect: connect, 15 | pure: pure, 16 | state: state, 17 | State: require('dataton') 18 | }; 19 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | var win = typeof window !== 'undefined' ? window : {__noflux_env: 'production'}; 2 | module.exports = { 3 | muteConsole: false, 4 | isDev: () => win.__noflux_env !== 'production' 5 | }; 6 | -------------------------------------------------------------------------------- /lib/connect.js: -------------------------------------------------------------------------------- 1 | import state from './state'; 2 | import {isDev, muteConsole} from "./config"; 3 | 4 | const allowConsole = _ => isDev() && !muteConsole && typeof console !== 'undefined'; 5 | 6 | function noop(){} 7 | 8 | /** 9 | * connect的作用在于,将给定的react class和指定的datasource连接。 10 | * 当state上发出'change'事件时,react实例对象能触发. 11 | */ 12 | export default function connect(clazz, data) { 13 | var datasource = data || state; 14 | var on = function(eventname, callback) { 15 | datasource.on(eventname, callback); 16 | return { 17 | remove() { 18 | datasource.removeListener(eventname, callback); 19 | } 20 | } 21 | }; 22 | 23 | var origMidMount = clazz.prototype.componentDidMount || noop; 24 | var name = clazz.name; 25 | var handler, mHandler; 26 | clazz.prototype.componentDidMount = function() { 27 | handler = on('change', () => { 28 | /*eslint-disable no-console */ 29 | allowConsole() && console.time && console.time(name + '渲染耗时'); 30 | if (this.__isUnmounted) return; // prevent forceUpdate an unmounted component 31 | this.forceUpdate(() => { 32 | allowConsole() && console.timeEnd && console.timeEnd(name + '渲染耗时'); 33 | }); 34 | /*eslint-enable */ 35 | }); 36 | mHandler = on('message', function (data) { 37 | if (/no-update/.test(data.type)) { 38 | allowConsole() && console.log('调用了state的update方法但是未能触发页面更新,原因是传入值和已有值相同。 数据更新路径为' + data.path.join('/') + ", 传入值为" + JSON.stringify(data.value, null, 2)); 39 | } 40 | }); 41 | return origMidMount.call(this); 42 | }; 43 | var origWillUmount = clazz.prototype.componentWillUnmount || noop; 44 | clazz.prototype.componentWillUnmount = function () { 45 | handler && handler.remove(); 46 | mHandler && mHandler.remove(); 47 | this.__isUnmounted = true; 48 | return origWillUmount.call(this); 49 | }; 50 | 51 | return clazz; 52 | } 53 | -------------------------------------------------------------------------------- /lib/pure.js: -------------------------------------------------------------------------------- 1 | function each(obj, fn) { 2 | Object.keys(obj).forEach(function(key) { 3 | fn(key, obj[key]); 4 | }); 5 | } 6 | 7 | function noop(){} 8 | 9 | function isCursor(val) { 10 | return typeof val === 'function' && val.get && val.update; 11 | } 12 | 13 | function shallowCompare(instance, nextProps, nextState) { 14 | return ( 15 | !shallowEqual(instance.props, nextProps) || 16 | !shallowEqual(instance.state, nextState) 17 | ); 18 | } 19 | 20 | /** 21 | * Performs equality by iterating through keys on an object and returning 22 | * false when any key has values which are not strictly equal between 23 | * objA and objB. Returns true when the values of all keys are strictly equal. 24 | * 25 | * @return {boolean} 26 | */ 27 | function shallowEqual(objA, objB) { 28 | if (objA === objB) { 29 | return true; 30 | } 31 | 32 | 33 | if (typeof objA !== 'object' || objA === null || 34 | typeof objB !== 'object' || objB === null) { 35 | return false; 36 | } 37 | 38 | var keysA = Object.keys(objA); 39 | var keysB = Object.keys(objB); 40 | 41 | if (keysA.length !== keysB.length) { 42 | return false; 43 | } 44 | 45 | // Test for A's keys different from B. 46 | var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB); 47 | for (var i = 0; i < keysA.length; i++) { 48 | if (!bHasOwnProperty(keysA[i]) || !equal(objA[keysA[i]], objB[keysA[i]])) { 49 | return false; 50 | } 51 | } 52 | 53 | return true; 54 | } 55 | 56 | function equal(a, b) { 57 | if (isCursor(a) && isCursor(b)) return a() === b(); 58 | return a === b; 59 | } 60 | 61 | function scu(nextProps, nextState) { 62 | return shallowCompare(this, nextProps, nextState); 63 | } 64 | 65 | function pure(Clazz) { 66 | var React = require('react'); 67 | Clazz.prototype.shouldComponentUpdate = scu; 68 | 69 | // 包装一层 70 | class Wrapper extends React.Component { 71 | render() { 72 | var props = {}; 73 | each(this.props, function (key, val) { 74 | props[key] = val; 75 | if (isCursor(val)) props['_cursor_val_' + key] = val(); 76 | }); 77 | return React.createElement(Clazz, props); 78 | } 79 | } 80 | return Wrapper; 81 | } 82 | 83 | module.exports = pure; 84 | -------------------------------------------------------------------------------- /lib/pure2.js: -------------------------------------------------------------------------------- 1 | const PROP = "__pure_props"; 2 | const NEXTPROP = "__pure_next_props"; 3 | 4 | function each(obj, fn) { 5 | Object.keys(obj).forEach(function(key) { 6 | fn(key, obj[key]); 7 | }); 8 | } 9 | 10 | function noop(){} 11 | 12 | function isCursor(val) { 13 | return typeof val === 'function' && val.get && val.update; 14 | } 15 | 16 | /** 17 | * Performs equality by iterating through keys on an object and returning 18 | * false when any key has values which are not strictly equal between 19 | * objA and objB. Returns true when the values of all keys are strictly equal. 20 | * 21 | * @return {boolean} 22 | */ 23 | function shallowEqual(objA, objB) { 24 | if (objA === objB) return true; 25 | 26 | if (typeof objA !== 'object' || objA === null || 27 | typeof objB !== 'object' || objB === null) { 28 | return false; 29 | } 30 | 31 | var keysA = Object.keys(objA); 32 | var keysB = Object.keys(objB); 33 | 34 | if (keysA.length !== keysB.length) return false; 35 | 36 | // Test for A's keys different from B. 37 | var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB); 38 | for (var i = 0; i < keysA.length; i++) { 39 | if (!bHasOwnProperty(keysA[i]) || !equal(objA[keysA[i]], objB[keysA[i]])) { 40 | return false; 41 | } 42 | } 43 | 44 | return true; 45 | } 46 | 47 | function equal(a, b) { 48 | if (isCursor(a) && isCursor(b)) return a() === b(); 49 | return a === b; 50 | } 51 | 52 | function assignTo(provider, receiver) { 53 | each(provider, function (key, val) { 54 | receiver[key] = val; 55 | if (isCursor(val)) receiver['_cursor_val_' + key] = val(); 56 | }); 57 | } 58 | 59 | function pure(Clazz) { 60 | // no instrumentation for server-side. 61 | if (typeof window === 'undefined') return Clazz; 62 | 63 | const rewrites = { 64 | 'shouldComponentUpdate': function pureShouldComponentUpdate(nextProps, nextState) { 65 | this[NEXTPROP] = {}; 66 | assignTo(nextProps, this[NEXTPROP]); 67 | return ( 68 | !shallowEqual(this[PROP] || {}, this[NEXTPROP]) || 69 | !shallowEqual(this.state, nextState) 70 | ); 71 | }, 72 | // no way to guarantee `componentWillReceiveProps` will be called. 73 | // if the underlying value of cursor changed but cursor stay unchanged, 74 | // will this function be called? 75 | /* 76 | 'componentWillReceiveProps': function pureComponentWillReceiveProps(props) { 77 | }, 78 | */ 79 | 'componentDidUpdate': function pureComponentDidUpdate() { 80 | this[PROP] = this[NEXTPROP]; 81 | }, 82 | 'componentWillMount': function pureComponentWillMount() { 83 | this[PROP] = {}; 84 | assignTo(this.props, this[PROP]); // sorry, `this.props` is readonly.. 85 | } 86 | }; 87 | 88 | const proto = Clazz.prototype; 89 | each(rewrites, function (key, fn) { 90 | var originalFn = proto[key] || noop; 91 | // for performance reason, no `arguments` 92 | proto[key] = function (a, b, c) { 93 | var val = originalFn.call(this, a, b, c); 94 | if (val === true || val === false) return val && fn.call(this, a, b, c); 95 | return fn.call(this, a, b); 96 | }; 97 | }); 98 | 99 | return Clazz; 100 | } 101 | 102 | module.exports = pure; 103 | -------------------------------------------------------------------------------- /lib/state.js: -------------------------------------------------------------------------------- 1 | import State from 'dataton'; 2 | 3 | // load initial state 4 | const initialState = typeof window !== 'undefined' ? window._appState : {}; 5 | 6 | const state = new State(initialState); 7 | export default state; 8 | 9 | if (typeof window !== 'undefined') { 10 | window._state = state; // for debug 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "noflux", 3 | "version": "2.2.3", 4 | "description": "a simple top down data flow implementation", 5 | "main": "index.js", 6 | "scripts": { 7 | "prepublish": "npm run build", 8 | "test": "mocha --compilers js:babel/register", 9 | "coverage": "mocha --compilers js:babel/register -R html-cov > coverage.html", 10 | "coverage-lcov": "mocha --compilers js:babel/register -R mocha-lcov-reporter > lcov.info", 11 | "coverage-json": "mocha --compilers js:babel/register -R json-cov > coverage.json", 12 | "build": "babel lib --out-dir dist && rm -rf dist/__tests__" 13 | }, 14 | "browserify": { 15 | "transform": [ 16 | "envify" 17 | ] 18 | }, 19 | "keywords": [ 20 | "flux", 21 | "noflux" 22 | ], 23 | "author": "liuxijin", 24 | "license": "MIT", 25 | "devDependencies": { 26 | "babel": "^5.4.7", 27 | "blanket": "^1.1.7", 28 | "expect.js": "^0.3.1", 29 | "jsdom": "^5.4.3", 30 | "mocha": "^2.2.5", 31 | "mocha-lcov-reporter": "0.0.2", 32 | "react": "^0.14.7", 33 | "react-addons-test-utils": "^0.14.3", 34 | "sinon": "^1.14.1" 35 | }, 36 | "dependencies": { 37 | "dataton": "^1.6.4", 38 | "envify": "^3.0.0" 39 | }, 40 | "directories": { 41 | "test": "test" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "git+https://github.com/ssnau/noflux.git" 46 | }, 47 | "bugs": { 48 | "url": "https://github.com/ssnau/noflux/issues" 49 | }, 50 | "homepage": "https://github.com/ssnau/noflux#readme" 51 | } 52 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | var jsdom = require('jsdom'); 2 | global.document = jsdom.jsdom(''); 3 | global.window = document.defaultView; 4 | global.navigator = window.navigator = {}; 5 | global.DEBUG = false; 6 | global.navigator.userAgent = 'NodeJs JsDom'; 7 | global.navigator.appVersion = ''; 8 | 9 | window.SYNC_TIMEOUT = true; 10 | 11 | /* 12 | require('blanket')({ 13 | pattern: 'dist' 14 | }); 15 | */ 16 | 17 | var expect = require('expect.js'); 18 | var sinon = require('sinon'); 19 | var assert = require('assert'); 20 | require('../dist/config').muteConsole = true; // mute console first 21 | var noflux = require('../'); 22 | var state = noflux.state; 23 | var React = require('react'); 24 | // mute console 25 | 26 | var TestUtils = require('react-addons-test-utils'); 27 | 28 | describe('decorate', function () { 29 | 30 | it('Connect decorate should make the component fluxify', function () { 31 | var Connect = noflux.Connect; 32 | var state = noflux.state; 33 | var nameCursor = state.cursor('name'); 34 | nameCursor.update('jack'); 35 | 36 | @Connect 37 | class App extends React.Component { 38 | render() { 39 | return

; 40 | } 41 | } 42 | var component = TestUtils.renderIntoDocument(); 43 | var node = TestUtils.findRenderedDOMComponentWithTag(component, 'h1') 44 | assert.equal(node.id, 'jack'); 45 | state.cursor('name').update('john'); // will make the component rerender 46 | assert.equal(node.id, 'john'); 47 | }); 48 | 49 | it('you can connect to any datasource you want', function () { 50 | 51 | var Connect = noflux.Connect; 52 | var state = new noflux.State(); 53 | var gstate = noflux.state; 54 | state.load({}); 55 | var nameCursor = state.cursor('name'); 56 | nameCursor.update('jack'); 57 | 58 | @Connect(state) 59 | class App extends React.Component { 60 | render() { 61 | return

; 62 | } 63 | } 64 | var component = TestUtils.renderIntoDocument(); 65 | var node = TestUtils.findRenderedDOMComponentWithTag(component, 'h1') 66 | assert.equal(node.id, 'jack'); 67 | state.cursor('name').update('john'); // will make the component rerender 68 | gstate.cursor('name').update('ppp'); 69 | assert.equal(node.id, 'john'); 70 | }); 71 | 72 | it('pure render only redraw when cursor value changes', function () { 73 | var pure = noflux.pure; 74 | var connect = noflux.connect; 75 | var state = new noflux.State(); 76 | state.load({name: 'jack'}); 77 | 78 | var itemRenderCount = 0; 79 | var appRenderCount = 0; 80 | 81 | @pure 82 | class Item extends React.Component { 83 | render() { 84 | itemRenderCount++; 85 | return

hello

86 | } 87 | } 88 | 89 | @connect(state) 90 | class App extends React.Component { 91 | render() { 92 | appRenderCount++; 93 | return 94 | } 95 | } 96 | 97 | var component = TestUtils.renderIntoDocument(); 98 | assert.equal(appRenderCount, 1); 99 | assert.equal(itemRenderCount, 1); 100 | 101 | state.set('___', 100); // a dumb property to force redraw 102 | assert.equal(appRenderCount, 2); 103 | assert.equal(itemRenderCount, 1); // item would not redraw 104 | 105 | state.set('name', 'johnson'); 106 | assert.equal(appRenderCount, 3); 107 | assert.equal(itemRenderCount, 2); // item would not redraw 108 | 109 | state.set('name', 'joseph'); 110 | assert.equal(appRenderCount, 4); 111 | assert.equal(itemRenderCount, 3); // item would not redraw 112 | 113 | state.set('___', '332'); 114 | assert.equal(appRenderCount, 5); 115 | assert.equal(itemRenderCount, 3); // item would not redraw 116 | }); 117 | }); 118 | --------------------------------------------------------------------------------