UDN-企业互联网技术人气社区

板块导航

浏览  : 464
回复  : 4

[React] React学习之围棋记谱本制作(三)状态管理

[复制链接]
独领风骚的头像 楼主
本帖最后由 独领风骚 于 2016-12-21 15:28 编辑

  接上篇

  React是状态改变引起组件外观界面的变化。刚学时,很自然想到组件间通信、控制组件状态的方法:获取某组件的状态,修改,进而控制组件外观。可上网一查,好像套路不是这样的。研究了一个上午,搞清楚了其中的门道。正统的方法是:维护一个全局状态,通过发布/定阅机制,组件注册感兴趣的状态变化监听器(回调函数),状态变化时通知监听器,监听器修改组件的状态,然后引发界面变化。

  原理弄明白了,准备开始弄。

  可是,笔记本电脑发生了一件很诡异的事件:chrome怎么也打不开开发者工具,热键、右键、菜单都不行。没有console输出的错误的提示,找不到错误代码的位置,只能靠猜,代码很快就写好了,调试、除错花了一个上午的时间也没有搞定。

  下午卸载、重新安装chrome,开发者工具好了,很快就把代码调好了。

  一个状态管理器、几个组件,代码、逻辑、流程很简单,画个示意图。
1.png
  一、状态管理器

  状态管理器维护组件的全局状态,使用EventEmitter模块注册事件监听器、激活事件。事件管理可以使用node.js带的EventEmitter模块,也可以自己定义一个简单的订阅/发布管理器。
  1. /**
  2. * 用于GO的状态管理。管理所有组件的状态,所有组件订阅事件,同步状态
  3. */

  4. var Events = require('events');

  5. class GoStateManager {
  6.     constructor() {
  7.         this.current = {
  8.             index:1,//当前步数
  9.             black:false,//是否是黑子
  10.             place:false,//是否是布局摆子,如果是,不改变当前步数,布局时摆的棋子上面不显示数字
  11.         };
  12.         this.eventEmitter = new Events.EventEmitter();
  13.     };

  14.     //订阅状态变化事件
  15.     subCurrentChange(listener) {
  16.         this.eventEmitter.addListener('currentChange',listener);
  17.     }

  18.     //状态变化事件发生,通知监听器
  19.     pubCurrentChange(){
  20.         this.eventEmitter.emit("currentChange",this.current);
  21.     }

  22.     //下一步
  23.     next(){
  24.         if(this.current.place==true){
  25.         }
  26.         else{
  27.             this.current.index++;
  28.             this.current.black=!this.current.black;
  29.             this.pubCurrentChange();
  30.         }
  31.     }

  32.     //设置布局状态
  33.     setPlace(bPlace){
  34.         this.current.place=bPlace;
  35.         this.pubCurrentChange();
  36.     }

  37.     //重新开始
  38.     restart(){
  39.         this.current.index=1;
  40.         this.current.black=true;
  41.         this.current.place=false;
  42.         this.pubCurrentChange();
  43.     }

  44. }

  45. module.exports = new GoStateManager();
复制代码

  二、当前步展示组件及测试按钮组件
  1. var React = require('react');
  2. var ReactDOM = require('react-dom');
  3. var StateManager = require('../../store/main/GoStateManager');

  4. //当前步状态指示器,可以指标当前步数、落子方、是否处理布局状态等信息
  5. class CurrentLabel extends React.Component{
  6.     constructor(props){
  7.         super(props);
  8.         //使用全局的状态作为初始状态
  9.         this.state={
  10.             index:StateManager.current.index,
  11.             black:StateManager.current.black,
  12.             place:StateManager.current.place,
  13.         };
  14.         //设置currentChange函数的this
  15.         this.currentChange=this.currentChange.bind(this);
  16.         //注册事件监听器
  17.         StateManager.subCurrentChange(this.currentChange);
  18.     }

  19.     //状态改变事件监听器,调整组件的状态
  20.     currentChange(current){
  21.         this.setState({
  22.             index:current.index,
  23.             black:current.black,
  24.             place:current.place,
  25.         });
  26.     }

  27.     render(){
  28.         return <span>
  29.             <strong>当前步数:</strong>{this.state.index}
  30.             <strong> 落子方:</strong>{this.state.black==true?'黑方':'白方'}
  31.             <strong> 是否是布局状态:</strong>{this.state.place==true?'布局':'行棋'}
  32.         </span>;
  33.     }
  34. }

  35. //状态指标器测试按钮:下一步
  36. class CurrentTestBtn_Next extends React.Component{
  37.     constructor(props ){
  38.         super(props);
  39.         this.state={}
  40.         this.clickHandle = this.clickHandle.bind(this);
  41.     }

  42.     clickHandle(){
  43.         //点击事件,通知状态管理器,下一步
  44.         StateManager.next();
  45.     }

  46.     render(){
  47.         return <button className="btn btn-primary" onClick={this.clickHandle}>下一步</button>;
  48.     }
  49. }

  50. //状态指标器测试按钮:切换布局状态
  51. class CurrentTestBtn_Place extends React.Component{
  52.     constructor(props ){
  53.         super(props);
  54.         this.state={}
  55.         this.clickHandle = this.clickHandle.bind(this);
  56.     }

  57.     clickHandle(){
  58.         StateManager.setPlace(!StateManager.current.place);
  59.     }

  60.     render(){
  61.         return <button className="btn btn-primary" onClick={this.clickHandle}>切换布局状态</button>;
  62.     }
  63. }

  64. //状态指标器测试按钮:切换布局状态
  65. class CurrentTestBtn_Restart extends React.Component{
  66.     constructor(props ){
  67.         super(props);
  68.         this.state={}
  69.         this.clickHandle = this.clickHandle.bind(this);
  70.     }

  71.     clickHandle(){
  72.         //点击事件,重新开始
  73.         StateManager.restart();
  74.     }

  75.     render(){
  76.         return <button className="btn btn-primary" onClick={this.clickHandle}>重新开始</button>;
  77. }
  78. }


  79. ReactDOM.render(
  80.   <div>
  81.     <CurrentLabel /><br/>
  82.     <CurrentTestBtn_Next />&nbsp;
  83.     <CurrentTestBtn_Place />&nbsp;
  84.     <CurrentTestBtn_Restart />
  85.   </div>,
  86.   document.getElementById('main-container')
  87. );
复制代码

  三、执行效果

  状态指示组件及三个测试按钮。当点击按钮时,状态指示组件的内容会发生相应的变化。
1.png
  四、自己定义的发布/订阅实现的简单类,上面代码运行不需要这个类
  1. /**
  2. * 自定义的发布定阅实现类
  3. */
  4. class MyEventEmitter {
  5.     constructor() {
  6.         this.handlers = { };
  7.     };

  8.     //增加监听
  9.     addListener(eventName,handler) {
  10.         var self = this;
  11.         if (!(eventName in self.handlers)){
  12.             self.handlers[eventName] = [];
  13.         }
  14.         self.handlers[eventName].push(handler);
  15.         return self;
  16.     }

  17.     //删除对应的监听
  18.     removeListener(eventName,handler) {
  19.         var self = this;
  20.         if (!(eventName in self.handlers)){
  21.             return;
  22.         }
  23.         var idx=-1;
  24.         for(var i=0; i<self.handlers[eventName].length; i++){
  25.             if(self.handlers[eventName][i]==handler){
  26.                 idx = i;
  27.                 break;
  28.             }
  29.         }
  30.         if(idx!=-1){
  31.             self.handlers[eventName].splice(idx,1);
  32.         }
  33.         return self;
  34.     }

  35.     emit(eventName){
  36.         var self = this;
  37.         var args = Array.prototype.slice.call(arguments,1);
  38.         if (!(eventName in self.handlers)){
  39.             return;
  40.         }
  41.         var idx = self.handlers[eventName].length;
  42.         while(idx--){
  43.             self.handlers[eventName][idx].apply(self,args);
  44.         }
  45.         return self;
  46.     }

  47. }

  48. module.exports = new MyEventEmitter();
复制代码

  下篇

相关帖子

发表于 2017-1-5 10:26:59 来自手机 | 显示全部楼层
鄙视楼下的顶帖没我快,哈哈
使用道具 举报

回复

发表于 2017-1-5 10:26:59 | 显示全部楼层
回帖支持下楼主,请眼熟我,我叫“不帅“
使用道具 举报

回复

发表于 2017-1-5 10:27:00 | 显示全部楼层
貌似看过类似的文章恩,排版更清晰点就更好了
使用道具 举报

回复

发表于 2017-1-5 10:27:02 | 显示全部楼层
JavaScript依赖于浏览器本身,与操作环境无关,只要计算机能运行浏览器,并支持JavaScript的浏览器,就可正确执行,从而实现了“编写一次,走遍天下”的梦想。
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部