概念

Reflux是根据React的flux创建的单向数据流类库。
Reflux的单向数据流模式主要由actions和stores组成。例如,当组件list新增item时,会调用actions的某个方法(如addItem(data)),并将新的数据当参数传递进去,通过事件机制,数据会传递到stroes中,stores可以向服务器发起请求,并更新数据数据库。数据更新成功后,还是通过事件机制传递的组件list当中,并更新ui。整个过程的对接是通过事件驱动的。就像这样:

╔═════════╗       ╔════════╗       ╔═════════════════╗
║ Actions ║──────>║ Stores ║──────>║ View Components ║
╚═════════╝       ╚════════╝       ╚═════════════════╝
     ^                                      │
     └──────────────────────────────────────┘

代码看起来像这样的:

var TodoActions = Reflux.createActions([
    'addItem'
]);

var TodoStore = Reflux.createStore({
    items: [1, 2],
    listenables: [TodoActions],
    onAddItem: function (model) {
        $.post('/server/add', {data: model}, function (data) {
            this.items.unshift(data);
            this.trigger(this.items);
        });
    }
});


var TodoComponent = React.createClass({
    mixins: [Reflux.listenTo(TodoStore, 'onStatusChange')],
    getInitialState: function () {
        return {list: []};
    },
    onStatusChange: function () {
        this.setState({list: TodoStore.items});
    },
    render: function () {
        return (
            <div>
                {this.state.list.map(function (item) {
                    return <p>{item}</p>
                })}
            </div>
        )
    }
});


React.render(<TodoComponent />, document.getElementById('container'));

同React Flux比较

相同点

  • 有actions
  • 有stores
  • 单向数据流

不同点

  • 通过内部拓展actions的行为,移除了单例的dispatcher
  • stores可以监听actions的行为,无需进行冗杂的switch判断
  • stores可以相互监听,可以进行进一步的数据聚合操作,类似于,map/reduce
  • waitFor被连续和平行的数据流所替代

创建Action

var statusUpdate = Reflux.createAction(options);

返回值是一个函数,调用这个函数就会触发相应的事件,在store中监听这个函数,并作相应的处理

var addItem = Reflux.createAction();

var TodoStore = Reflux.createStore({
    init: function () {
        this.listenTo(addItem, 'addItem');
    },
    addItem: function (model) {
        console.log(model);
    }
});

addItem({name: 'xxx'});

创建多个Action

var TodoActions = Reflux.createActions([
    'addItem',
    'deleteItem'
]);

store监听actions的行为:

var TodoActions = Reflux.createActions([
    'addItem',
    'deleteItem'
]);

var TodoStore = Reflux.createStore({
    init: function () {
        this.listenTo(TodoActions.addItem, 'addItem');
        this.listenTo(TodoActions.deleteItem, 'deleteItem');
    },
    addItem: function (model) {
       console.log(model)
    },
    deleteItem:function(model){
        console.log(model);
    }
});

TodoActions.addItem({name:'xxx'});
TodoActions.deleteItem({name:'yyy'});

异步Action

真实的应用场景中,几乎所有的操作都会向后端请求,而这些操作都是异步的,Reflux也提供了相应的Promise接口

var getAll = Reflux.createAction({asyncResult:true});

例如获取全部数据:

var getAll = Reflux.createAction({asyncResult: true});

var TodoStore = Reflux.createStore({
    init: function () {
        this.listenTo(getAll, 'getAll');
    },
    getAll: function (model) {
        $.get('/all', function (data) {
            if (data) {
                getAll.completed(data);
            } else {
                getAll.failed(data);
            }

        });
    }
});

getAll({name: 'xxx'})
    .then(function (data) {
        console.log(data);
    })
    .catch(function (err) {
        throw err;
    });

Action hooks

Reflux为每个action都提供了两个hook方法

  • preEmit(params),action emit之前调用,参数是action传递过来的,返回值会传递给shouldEmit
  • shouldEmit(params) action emit之前调用,参数默认是action传递,如果preEmit有返回值,则是preEmit返回值,返回值决定是否emit

情景一:

var addItem = Reflux.createAction({
    preEmit: function (params) {
        console.log('preEmit:' + params);           
    },
    shouldEmit: function (params) {
        console.log('shouldEmit:' + params);           
    }
});

var TodoStore = Reflux.createStore({
    init: function () {
        this.listenTo(addItem, 'addItem');
    },
    addItem: function (params) {
        console.log('addItem:' + params);
    }
});

addItem('xxx');

控制台打印
$ preEmit:xxx
$ shouldEmit:xxx

情景二:

var addItem = Reflux.createAction({
    preEmit: function (params) {
        console.log('preEmit:' + params);
        return 324;
    },
    shouldEmit: function (params) {
        console.log('shouldEmit:' + params);
        return true;
    }
});

var TodoStore = Reflux.createStore({
    init: function () {
        this.listenTo(addItem, 'addItem');
    },
    addItem: function (params) {
        console.log('addItem:' + params);
    }
});

addItem('xxx');

控制台打印
$ preEmit:xxx
$ shouldEmit:324
$ addItem:324

注意几个返回值和参数的关系

Action Methods

当需要给所有的action添加公用方法时,可以这么干:

Reflux.ActionMethods.print = function (str) {
    console.log(str);
};

var addItem = Reflux.createAction();

var TodoStore = Reflux.createStore({
    init: function () {
        this.listenTo(addItem, 'addItem');
    },
    addItem: function (params) {
        console.log('addItem:' + params);
    }
});

addItem.print('xxx');

trigger、triggerAsync和triggerPromise

直接调用addItem()实际上是调用trigger或者triggerAsync或者triggerPromise,它们区别在于

var addItem = Reflux.createAction(); addItem();                 #默认调用triggerAsync,相当于addItem.triggerAsync()
var addItem = Reflux.createAction({sync:true});addItem();       #默认调用trigger,相当于addItem.trigger()
var addItem = Reflux.createAction({asyncResult:true});addItem();#默认调用triggerPromise,相当于addItem.triggerPromise()

trigger和triggerAsync区别在于:

triggerAsync = setTimeout(function () {
    trigger()
}, 0);

trigger和triggerPromise区别在于,triggerPromise的返回值是promise

创建Store

Store可以响应Action的行为,并同服务器交互。

监听单个Action

在init方法中添加监听处理

var addItem = Reflux.createAction();

var TodoStore = Reflux.createStore({
    init: function () {
        this.listenTo(addItem, 'addItem');
    },
    addItem: function (model) {
        console.log(model);
    }
});

addItem({name: 'xxx'});

监听多个Action

作死写法

var TodoActions = Reflux.createActions([
    'addItem',
    'deleteItem'
]);

var TodoStore = Reflux.createStore({
    init: function () {
        this.listenTo(TodoActions.addItem, 'addItem');
        this.listenTo(TodoActions.deleteItem, 'deleteItem');
    },
    addItem: function (model) {
        console.log(model);
    },
    deleteItem: function (model) {
        console.log(model);
    }
});

TodoActions.addItem({name: 'xxx'});
TodoActions.deleteItem({name: 'yyy'});

两个action的时候在init里写了两遍监听处理方法,如果有十个甚至多个的话,写起来就像这样的:

var TodoActions = Reflux.createActions([
    'item1',
    'item2',
    'item3',
    'item4',
    'item5',
    'item6',
    'item7',
    'item8',
    'item9',
    'item10'
]);

var TodoStore = Reflux.createStore({
    init: function () {
        this.listenTo(TodoActions.item1, 'item1');
        this.listenTo(TodoActions.item2, 'item2');
        this.listenTo(TodoActions.item3, 'item3');
        this.listenTo(TodoActions.item4, 'item4');
        this.listenTo(TodoActions.item5, 'item5');
        this.listenTo(TodoActions.item6, 'item6');
        this.listenTo(TodoActions.item7, 'item7');
        this.listenTo(TodoActions.item8, 'item8');
        this.listenTo(TodoActions.item9, 'item9');
        this.listenTo(TodoActions.item10, 'item10');

    },
    item1: function (model) {
        console.log(model);
    },
    item2: function (model) {
        console.log(model);
    }
});

TodoActions.item1({name: 'xxx'});
TodoActions.item2({name: 'yyy'});

listenToMany

还好Reflux给我们提供了listenToMany方法,避免重复劳动:

var TodoActions = Reflux.createActions([
    'item1',
    'item2',
    'item3',
    'item4',
    'item5',
    'item6',
    'item7',
    'item8',
    'item9',
    'item10'
]);

var TodoStore = Reflux.createStore({
    init: function () {
        this.listenToMany(TodoActions);
    },
    onItem1: function (model) {
        console.log(model);
    },
    onItem2: function (model) {
        console.log(model);
    }
});

TodoActions.item1({name: 'xxx'});
TodoActions.item2({name: 'yyy'});

处理方法只需让action的标识首字母大写并加上on就可以了。

标识如果首字母大写就会识别不了,例如将上面的item1改成Itme1。这坑爹的!

listenables

var TodoActions = Reflux.createActions([
    'item1',
    'item2',
    'item3',
    'item4',
    'item5',
    'item6',
    'item7',
    'item8',
    'item9',
    'item10'
]);

var TodoStore = Reflux.createStore({
    listenables: [TodoActions],
    onItem1: function (model) {
        console.log(model);
    },
    onItem2: function (model) {
        console.log(model);
    }
});

TodoActions.item1({name: 'xxx'});
TodoActions.item2({name: 'yyy'});

一般我们写真实应用的时候都应该采用这种写法!!!

Store Methods

拓展Store的公用方法有两种方式。

方式一

Reflux.StoreMethods.print = function (str) {
    console.log(str);
};

var addItem = Reflux.createAction();

var TodoStore = Reflux.createStore({
    init: function () {
        this.listenTo(addItem, 'addItem');
    },
    addItem: function (model) {
        console.log(model);
    }
});

TodoStore.print('rrr');

方式二

var Mixins = {
    print: function (str) {
        console.log(str);
    }
}

var addItem = Reflux.createAction();

var TodoStore = Reflux.createStore({
    mixins: [Mixins],
    init: function () {
        this.listenTo(addItem, 'addItem');
    },
    addItem: function (model) {
        console.log(model);
    }
});

TodoStore.print('rrr');

同组件结合

前面说了,Action、Store和组件这三者是通过事件机制响应变化的,构建组件的时候首先需要监听Store的状态。
先定义Action和Store

var TodoActions = Reflux.createActions([
    'getAll'
]);

var TodoStore = Reflux.createStore({
    items: [1,2,3],
    listenables: [TodoActions],
    onGetAll: function () {
        this.trigger(this.items);
    }
});

基本

var TodoComponent = React.createClass({
    getInitialState: function () {
        return {list: []};
    },
    onStatusChange: function (list) {
        this.setState({list: list});
    },
    componentDidMount: function () {
        this.unsubscribe = TodoStore.listen(this.onStatusChange);
        TodoActions.getAll();
    },
    componentWillUnmount: function () {
        this.unsubscribe();
    },
    render: function () {
        return (
            <div>
                {this.state.list.map(function (item) {
                    return <p>{item}</p>
                })}
            </div>
        )
    }
});    
React.render(<TodoComponent />, document.getElementById('container'));

这里有两点需要注意:

  • 当组件的生命周期结束时需要解除对Store的监听
  • 当Store调用trigger时,才会执行onStatusChange函数,所以每次Store更新时,需要手动调用trigger函数

Mixins

var TodoComponent = React.createClass({
    mixins: [Reflux.ListenerMixin],
    getInitialState: function () {
        return {list: []};
    },
    onStatusChange: function (list) {
        this.setState({list: list});
    },
    componentDidMount: function () {
        this.unsubscribe = TodoStore.listen(this.onStatusChange);
        TodoActions.getAll();
    },
    render: function () {
        return (
            <div>
                {this.state.list.map(function (item) {
                    return <p>{item}</p>
                })}
            </div>
        )
    }
});
React.render(<TodoComponent />, document.getElementById('container'));

Reflux.listenTo

var TodoComponent = React.createClass({
    mixins: [Reflux.listenTo(TodoStore,'onStatusChange')],
    getInitialState: function () {
        return {list: []};
    },
    onStatusChange: function (list) {
        this.setState({list: list});
    },
    componentDidMount: function () {
        TodoActions.getAll();
    },
    render: function () {
        return (
            <div>
                {this.state.list.map(function (item) {
                    return <p>{item}</p>
                })}
            </div>
        )
    }
});
React.render(<TodoComponent />, document.getElementById('container'));

Reflux.connect

var TodoComponent = React.createClass({
    mixins: [Reflux.connect(TodoStore,'list')],
    getInitialState: function () {
        return {list: []};
    },
    componentDidMount: function () {
        TodoActions.getAll();
    },
    render: function () {
        return (
            <div>
                {this.state.list.map(function (item) {
                    return <p>{item}</p>
                })}
            </div>
        )
    }
});
React.render(<TodoComponent />, document.getElementById('container'));

数据会自动更新到state的list当中。

Reflux.connectFilter

var TodoComponent = React.createClass({
    mixins: [Reflux.connectFilter(TodoStore, 'list', function (list) {
        return list.filter(function (item) {
            return item > 1;
        });
    })],
    getInitialState: function () {
        return {list: []};
    },
    componentDidMount: function () {
        TodoActions.getAll();
    },
    render: function () {
        return (
            <div>
                {this.state.list.map(function (item) {
                    return <p>{item}</p>
                })}
            </div>
        )
    }
});
React.render(<TodoComponent />, document.getElementById('container'));

对数据加了一层过滤器。

以上便Component同Store交互的内容,大家可以根据实际情况选择不同的写法。

小结

我这人喜欢拿代码来表述思想。

var TodoActions = Reflux.createActions([
    'getAll',
    'addItem',
    'deleteItem',
    'updateItem'
]);

var TodoStore = Reflux.createStore({
    items: [1, 2, 3],
    listenables: [TodoActions],
    onGetAll: function () {
        $.get('/all', function (data) {
            this.items = data;
            this.trigger(this.items);
        }.bind(this));
    },
    onAddItem: function (model) {
        $.post('/add', model, function (data) {
            this.items.unshift(data);
            this.trigger(this.items);
        }.bind(this));
    },
    onDeleteItem: function (model, index) {
        $.post('/delete', model, function (data) {
            this.items.splice(index, 1);
            this.trigger(this.items);
        }.bind(this));
    },
    onUpdateItem: function (model, index) {
        $.post('/update', model, function (data) {
            this.items[index] = data;
            this.trigger(this.items);
        }.bind(this));
    }
});


var TodoComponent = React.createClass({
    mixins: [Reflux.connect(TodoStore, 'list')],
    getInitialState: function () {
        return {list: []};
    },
    componentDidMount: function () {
        TodoActions.getAll();
    },   
    render: function () {
        return (
            <div>
                {this.state.list.map(function(item){
                    return <TodoItem data={item}/>
                })}
            </div>
        )
    }
});

var TodoItem = React.createClass({
    componentDidMount: function () {
        TodoActions.getAll();
    },
    handleAdd: function (model) {
        TodoActions.addItem(model);
    },
    handleDelete: function (model,index) {
        TodoActions.deleteItem(model,index);
    },
    handleUpdate: function (model) {
        TodoActions.updateItem(model);
    },
    render: function () {
        var item=this.props.data;
        return (
            <div>
                <p>{item.name}</p>
                <p>{item.email}</p>
                <p>/*操作按钮*/</p>
            </div>
        )
    }
});
React.render(<TodoComponent />, document.getElementById('container'));

实际情况远比这复杂,只是提供一个思路供大家参考。

代码链接

github

参考

Reflux

你可能感兴趣的文章

12 条评论
xuweijian · 3月30日

文章前部分写的还较细致连贯,后面就看不下去了,只贴代码,无助于理解

+1 回复

钻钻毛儿 · 2015年06月07日

赞一个!

回复

YeatsZhang · 2015年07月10日

把我想写一直没写的文章写了,很赞~ 现在准备试试redux

回复

callmez · 2015年07月15日

这个 action要怎么拆? 和store放一起? 放一起的话....外部再require这个action 就不对了吧...

难道全部放一个文件里?

回复

naitnix · 2015年08月06日

请问,store之间的互相监听是怎么实现的?没见到说明啊,在使用中遇到了一个问题,就是两个兄弟组件,a组件的state发生了变化,进行了重新渲染,但是b组件也要有对应的重新渲染,这在reflux中怎么实现呢?我想到的是a组件调用父组件的store,然后在trigger,达到更新b组件的目的,但是这样的话,b组件的key必须保持与a组件一致。如果保持一致的话,b组件的重新渲染还有其他可以触发的条件,这又会跟现在的情况冲突

回复

iFatzhan · 2015年08月12日

你好,请问一下,不知道对于影响一种Store的多个Action的异步调用结果要怎么处理呢?比如在你的小结的代码里,如果TodoActions(getAll、addItem等)四个Action如果都是异步的(要请求ServerAPI),那这四个异步的Action都有可能产生成功或失败的结果。以前在View中直接调用API然后then, catch处理结果就行。但用reflux我一直没想明白如何处理比较好。虽然reflux提供了异步的Action(默认有completed, failed的child Action),但是在Store里监听所有这些Action的Completed和Failed似乎也不好,因为Store并不关心Failed的状态,只关心Completed后的数据。而且Store的trigger只是告诉外部“我的数据变化了”,但是对于Failed的情况,Store的数据可能是没有变化的,trigger没有意义。

回复

___L_e_e · 2015年08月12日

Holly Hight 我都看懂了 楼主写得真棒

回复

小俞 作者 · 2015年08月12日

后端可以统一返回这样的数据{code:0,data:{}},trigger的时候可以trigger这个结构,然后根据code的不同进行相应处理,比如code不正常,可以显示“请求失败,重新刷新”。点击刷新的时候,可以再次请求。
var TodoActions = Reflux.createActions([
'getAll'
]);
var TodoStore = Reflux.createStore({
listenables: [TodoActions],
onGetAll: function () {
$.get('/all', function (response) {
this.trigger(response);
}.bind(this));
}
});
var TodoComponent = React.createClass({
mixins: [Reflux.connect(TodoStore, 'response')],
getInitialState: function () {
return {response: {code:0,data:{}}};
},
componentDidMount: function () {
TodoActions.getAll();
},
handleRefresh:function(){
TodoActions.getAll();
},
render: function () {
if(this.state.response.code===0){
return

正常显示
    }else{
        return <div>请求失败,<button onClick={this.handleRefresh}>重新刷新</button></div>
   }
}

});

以上代码纯手打,没测试过哦。我一直都是自己在搞些自己的项目,没有线上实际项目的检验,解决方法应该是多样的,记住一个原则即可,相同的代码尽量不要写两遍。

回复

小俞 作者 · 2015年08月12日

我会把数据丢在父组件上,子组件A和B只负责渲染。

回复

iFatzhan · 2015年08月12日

谢谢。我是把api call放到Action的,然后Store去监听成功和失败的Action。我也是每个请求有response返回,response.isSuccess()之类的判断是否成功。在Demo里都是很理想的,但在实际项目中会遇到很多不理想的情况。比如TodoAction的addItem、deleteItem、updateItem,成功了Store更新自己然后trigger通知是很正常,但是失败了的话,外部可能要确切地知道到底是哪个Action失败了。比如addItem失败,那Store要trigger('addItem', status),deleteItem失败了,Store.trigger('deleteItem', status)。但这就违背了Store的初衷:在数据发生改变时通知监听者。而这两个trigger其实是通知监听者Action的执行结果,它本身的数据没有变化。
或者更加明显的例子是,Todo有个需求要通过无副作用的api call检查某个item的状态,再执行下一步的操作,而这个api call无论成功或失败,都不会对Store产生影响,所以就没有必要封装成Action和Store来处理这个API的数据,ControllerView自己直接调用Api就可以消化掉。

你说得对的,相同的代码不要写两遍,之前在Flux中如何处理各种异步ApiCall的状态问题思考了几天,最后还是从实际出发,需要用Store处理失败的分支就用Store,不需要的就在ControllerView里直接action.catch(doSomething)(Reflux里Action也可以返回Promise),甚至在ControllerView里直接调用Api(像上面提到的只用于状态判断的例子)

回复

麦芽糖 · 2015年10月03日

写的很好,一口气读完不费力。 而且思路一步一步进去。 赞一个。

回复

一罐可乐 · 2016年03月01日

文章的最后提供了很多的写法,我觉得可以再更新下ES6的写法,哈哈

回复

载入中...
小俞 小俞

7.8k 声望

发布于专栏

学习笔记

自我学习总结

118 人关注

系列文章