├── .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 | [](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 |
--------------------------------------------------------------------------------