前言

我之前喜欢玩一款游戏:全民飞机大战,而且有点痴迷其中,如果你想站在游戏的第一阶梯,便需要不断的练技术练装备,但是腾讯的游戏一般而言是有点恶心的,他会不断的出新飞机、新装备、新宠物,所以,很多时候你一个飞机以及装备还没满级,新的装备就又出来了,并且一定是更强!

于是很多人便直接抛弃当前的飞机与装备,追求更好的,这个时候如果是人民币玩家或者骨灰级大神玩家的话,基本可以很快站在世界的顶端,一者是装备好,一者是技术好,但是我不愿意投入太多钱,也不愿意投入过多精力,于是在一套极品装备满级后会积累资源,因为一代之间变化不会太大,到第二代甚至第三代才开始换飞机换装备,也基本处于了第一阶梯,一直到一次游戏大更新,直接导致我当前的飞机与装备完全二逼了,我当时一时脑热投入了所有资源去刷新的极品装备,最后闹的血本无归,于是便删除了该游戏,一年时间付诸东流!!!

再回过头来看最近两年前端的变化,单是一个前端工程化工具就变了几次,而且新出来的总是叫嚷着要替换之前的,grunt->gulp->webpack->es6

再看前端框架的一些产量:backbone、angularJS、react、canJS、vueJS......

真有点乱花渐欲迷人眼的意思,似乎前端技术也开始想要坑前端玩家,因为人家会了新技能,你就落后了,于是很多技术沉淀已经足够的大神便直接在团队使用某一技术,带领团队组员深入了解了该技术的好,并大势宣传新技术。

很多人在这种情况下就中招了!他们可能会抛弃现有技术栈,直接跟风新的技术,在现有装备都没满级的情况下又去刷新装备,如果哪天一次游戏玩法大更新,大神依旧一套极品装备在那风骚,而炮灰仓库中积累着一箩筐低等级的极品装备,却没有可用的,不可谓不悲哀!

一门技术从入门到精通,是需要时间的,在有限的时间中要学习那么多的新技术,还得落地到实际工作中,而每一次新技术的落地都是对曾经架构的否定与推翻,这个成本不可谓不高,对一些创业团队甚至是致命的。工作中也没那么多时间让你折腾新东西,所以一定是了解清楚了一门再去学习其它的,不要半途而废也不要盲目选型。

我最近回顾了这几年所学,可以说技术栈没有怎么更新,但是我对我所习的每一个技术基本上进入了深入的了解:

① 在MVVM还不太火的时候使用了MVC框架一直到最近,对为什么要使用这种模式,这种模式的好处有了比较深入的了解,并且已经碰到了更复杂的业务逻辑

② 当一个页面过于复杂时(比如1000多行代码的订单填写页),我能通过几年沉淀,将之拆分为多个业务组件模块,保持主控制器的业务清晰,代码量维护在500行之内,并且各子模块业务也清晰,根据model进行通信

③ 使用Grunt完成前端工程化,从构建项目,到打包压缩项目,到优化项目,总的来说无往不利

④ ......

就编程方法,思维习惯,解决问题的方法来说,与两年前有了很大的变化,而且感觉很难提高了。于是我认识到,就现有的装备下,可能已经玩到极限了,可能到了跟风的时候了,而时下热门的ReactJS似乎是一个很好的切入点,React一端代码多端运行的噱头也足够。

初识ReactJS

我最初接触ReactJS的时候,最火的好像是angular,React Native也没有出现,看了他的demo,对其局部刷新的实现很感兴趣。结果,翻看源码一看洋洋洒洒一万多行代码,于是马上便退却了。却不想现在火成了这般模样,身边学习的人多,用于生产的少,我想幕后必然有黑手在推动!也可以预测的是,1,2年后会有更好的框架会取代他,可能是原团队的自我推翻,也有可能是Google公司又新出了什么框架,毕竟前端最近几年才开始真正富客户端,还有很长的路要走。当然,这不是我们关心的重点,我们这里的重点是Hello world。

ReactJS的Hello World是这样写的:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <script src="build/react.js" type="text/javascript"></script>
  5. <script src="build/JSXTransformer.js" type="text/javascript"></script>
  6. </head>
  7. <body>
  8. <div id="example">
  9. </div>
  10. <script type="text/jsx">
  11. React.render(
  12. <h1>Hello, world!</h1>,
  13. document.getElementById('example')
  14. );
  15. </script>
  16. </body>
  17. </html>
  1. <div id="example"><h1 data-reactid=".0">Hello, world!</h1></div>

React一来就搞了一个标新立异的地方:jsx(js扩展),说实话,这种做法真的有点大手笔,最初的这种声明式标签写法,在我脑中基本可以追溯到5年前的.net控件了,比如gridview与datalist组件。

在text/jsx中的代码最初不会被浏览器理会,他会被react的JSXTransformer编译为常规的JS,然后浏览器才能解析。这里与html模板会转换为js函数是一个道理,我们有一种优化方案是模板预编译,即:

在打包时候便将模板转换为js函数,免去在线解析的过程,react当然也可以这样做,这里如果要解析的话,会是这个样子:

  1. React.render(
  2. React.createElement("h1", null, "Hello, world!"),
  3. document.getElementById('example')
  4. );

因为render中的代码可以很复杂,render中的代码写法就是一种语法糖,帮助我们更好的写表现层代码:render方法中可以写html与js混杂的代码:

  1. var data = [1,2,3];
  2. React.render(
  3. <h1>Hello, {data.toString(',')}!</h1>,
  4. document.getElementById('example')
  5. );
  1. var data = [1,2,3];
  2. React.render(
  3. <h1>{
  4. data.map(function(v, i) {
  5. return <div>{i}-{v}</div>
  6. })
  7. }</h1>,
  8. document.getElementById('example')
  9. );

所以,react提供了很多类JS的语法,JSXTransformer相当于一个语言解释器,而解析逻辑长达10000多行代码,这个可不是一般屌丝可以碰的,react从这里便走出了不平常的路,而他这样做的意义是什么,我们还不知道。

标签化View

react提供了一个方法,将代码组装成一个组件,然后像HTML标签一样插入网页:

  1. var Pili = React.createClass({
  2. render: function() {
  3. return <h1>Hello World!</h1>;
  4. }
  5. });
  6.  
  7. React.render(
  8. <Pili />,
  9. document.getElementById('example')
  10. );

所谓,声明试编程,便是将需要的属性写到标签上,以一个文本框为例:

  1. <input type="text" data-type="num" data-max="100" data-min="0" data-remove=true />

我们想要输入的是数字,有数字限制,而且在移动端输入的时候,右边会有一个X按钮清除文本,这个便是我们期望的声明式标签。

react中,标签需要和原始的类发生通信,比如属性的读取是这样的:

  1. var Pili = React.createClass({
  2. render: function() {
  3. return <h1>Hello {this.props.name}!</h1>;
  4. }
  5. });
  6.  
  7. React.render(
  8. <Pili name='霹雳布袋戏'/>,
  9. document.getElementById('example')
  10. );
  11.  
  12. //Hello 霹雳布袋戏!

上文中Pili便是一个组件,标签使用法便是一个实例,声明式写法最终也会被编译成一般的js方法,这个不是我们现在关注的重点。

  1. 由于classfor为关键字,需要使用classNamehtmlFor替换

通过this.props对象可以获取组件的属性,其中一个例外为children,他表示组件的所有节点:

  1. var Pili = React.createClass({
  2. render: function() {
  3. return (
  4. <div>
  5. {
  6. this.props.children.map(function (child) {
  7. return <div>{child}</div>
  8. })
  9. }
  10. </div>
  11. );
  12. }
  13. });
  14.  
  15. React.render(
  16. <Pili name='霹雳布袋戏'>
  17. <span>素还真</span>
  18. <span>叶小钗</span>
  19. </Pili>
  20. ,
  21. document.getElementById('example')
  22. );
  1. <div id="Div1">
  2. <div data-reactid=".0">
  3. <div data-reactid=".0.0">
  4. <span data-reactid=".0.0.0">素还真</span></div>
  5. <div data-reactid=".0.1">
  6. <span data-reactid=".0.1.0">叶小钗</span></div>
  7. </div>
  8. </div>

PS:return的语法与js语法不太一样,不能随便加分号

如果想限制某一个属性必须是某一类型的话,便需要设置PropTypes属性:

  1. var Pili = React.createClass({
  2. propType: {
  3. //name必须有,并且必须是字符串
  4. name: React.PropTypes.string.isRequired
  5. },
  6. render: function() {
  7. return <h1>Hello {this.props.name}!</h1>;
  8. }
  9. });

如果想设置属性的默认值,则需要:

  1. var Pili = React.createClass({
  2. propType: {
  3. //name必须有,并且必须是字符串
  4. name: React.PropTypes.string.isRequired
  5. },
  6. getDefaultProps : function () {
  7. return {
  8. title : '布袋戏'
  9. };
  10. },
  11. render: function() {
  12. return <h1>Hello {this.props.name}!</h1>;
  13. }
  14. });

我们仍然需要dom交互,我们有时也需要获取真实的dom节点,这个时候需要这么做:

  1. var MyComponent = React.createClass({
  2. handleClick: function() {
  3. React.findDOMNode(this.refs.myTextInput).focus();
  4. },
  5. render: function() {
  6. return (
  7. <div>
  8. <input type="text" ref="myTextInput" />
  9. <input type="button" value="Focus the text input" onClick={this.handleClick} />
  10. </div>
  11. );
  12. }
  13. });

事件触发的时候通过ref属性获取当前dom元素,然后可进行操作,我们这里看看返回的dom是什么:

  1. <input type="text" data-reactid=".0.0">

看来是真实的dom结构被返回了,另外一个比较关键的事情,便是这里的dom事件支持,需要细读文档:http://facebook.github.io/react/docs/events.html#supported-events

表单元素,属于用户与组件的交互,内容不能由props获取,这个时候一般有状态机获取,所谓状态机,便是会经常变化的属性。

  1. var Input = React.createClass({
  2. getInitialState: function() {
  3. return {value: 'Hello!'};
  4. },
  5. handleChange: function(event) {
  6. this.setState({value: event.target.value});
  7. },
  8. render: function () {
  9. var value = this.state.value;
  10. return (
  11. <div>
  12. <input type="text" value={value} onChange={this.handleChange} />
  13. <p>{value}</p>
  14. </div>
  15. );
  16. }
  17. });
  18.  
  19. React.render(<Input/>, document.body);

组件有其生命周期,每个阶段会触发相关事件可被用户捕捉使用:

  1. Mounting:已插入真实 DOM
  2. Updating:正在被重新渲染
  3. Unmounting:已移出真实 DOM

一般来说,我们会为一个状态发生前后绑定事件,react也是如此:

  1. componentWillMount()
  2. componentDidMount()
  3. componentWillUpdate(object nextProps, object nextState)
  4. componentDidUpdate(object prevProps, object prevState)
  5. componentWillUnmount()
  6. 此外,React 还提供两种特殊状态的处理函数。
  7. componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
  8. shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用

根据之前的经验,会监控组件的生命周期的操作的时候,往往都是比较高阶的应用了,我们这里暂时不予关注。

好了,之前的例子多半来源于阮一峰老师的教程,我们这里来一个简单的验收,便实现上述只能输入数字的文本框:

  1. var NumText = React.createClass({
  2. getInitialState: function() {
  3. return {value: 50};
  4. },
  5. propTypes: {
  6. value: React.PropTypes.number
  7. },
  8. handleChange: function (e) {
  9. var v = parseInt(e.target.value);
  10. if(v > this.props.max || v < this.props.min ) return;
  11. if(isNaN(v)) v = '';
  12. this.setState({value: v});
  13. },
  14. render: function () {
  15. return (
  16. <input type="text" value={this.state.value} onChange={this.handleChange} />
  17. );
  18. }
  19. });
  20.  
  21. React.render(
  22. <NumText min="0" max="100" />,
  23. document.body
  24. );

通过以上学习,我们对React有了一个初步认识,现在我们进入其todolist,看看其是如何实现的

此段参考:阮一峰老师的入门教程,http://www.ruanyifeng.com/blog/2015/03/react.html

TodoMVC

入口文件

TodoMVC为MVC框架经典的demo,难度适中,而又可以展示MVC的思想,我们来看看React此处的入口代码:

  1. <!doctype html>
  2. <html lang="en" data-framework="react">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>React • TodoMVC</title>
  6. <link rel="stylesheet" href="node_modules/todomvc-common/base.css">
  7. <link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
  8. </head>
  9. <body>
  10. <section class="todoapp">
  11. </section>
  12. <script src="node_modules/react/dist/react-with-addons.js"></script>
  13. <script src="node_modules/react/dist/JSXTransformer.js"></script>
  14. <script src="node_modules/director/build/director.js"></script>
  15. <script src="js/utils.js"></script>
  16. <script src="js/todoModel.js"></script>
  17.  
  18. <script type="text/jsx" src="js/todoItem.jsx"></script>
  19. <script type="text/jsx" src="js/footer.jsx"></script>
  20. <script type="text/jsx" src="js/app.jsx"></script>
  21. </body>
  22. </html>

页面很干净,除了react基本js与其模板解析文件外,还多了一个director.js,因为react本身不提供路由功能,所以路由的工作便需要插件,director便是路由插件,这个不是我们今天学习的重点,然后是两个js文件:

  1. var app = app || {};
  2.  
  3. (function () {
  4. 'use strict';
  5.  
  6. app.Utils = {
  7. uuid: function () {
  8. /*jshint bitwise:false */
  9. var i, random;
  10. var uuid = '';
  11.  
  12. for (i = 0; i < 32; i++) {
  13. random = Math.random() * 16 | 0;
  14. if (i === 8 || i === 12 || i === 16 || i === 20) {
  15. uuid += '-';
  16. }
  17. uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
  18. .toString(16);
  19. }
  20.  
  21. return uuid;
  22. },
  23.  
  24. pluralize: function (count, word) {
  25. return count === 1 ? word : word + 's';
  26. },
  27.  
  28. store: function (namespace, data) {
  29. if (data) {
  30. return localStorage.setItem(namespace, JSON.stringify(data));
  31. }
  32.  
  33. var store = localStorage.getItem(namespace);
  34. return (store && JSON.parse(store)) || [];
  35. },
  36.  
  37. extend: function () {
  38. var newObj = {};
  39. for (var i = 0; i < arguments.length; i++) {
  40. var obj = arguments[i];
  41. for (var key in obj) {
  42. if (obj.hasOwnProperty(key)) {
  43. newObj[key] = obj[key];
  44. }
  45. }
  46. }
  47. return newObj;
  48. }
  49. };
  50. })();

utils

  1. var app = app || {};
  2.  
  3. (function () {
  4. 'use strict';
  5.  
  6. var Utils = app.Utils;
  7. // Generic "model" object. You can use whatever
  8. // framework you want. For this application it
  9. // may not even be worth separating this logic
  10. // out, but we do this to demonstrate one way to
  11. // separate out parts of your application.
  12. app.TodoModel = function (key) {
  13. this.key = key;
  14. this.todos = Utils.store(key);
  15. this.onChanges = [];
  16. };
  17.  
  18. app.TodoModel.prototype.subscribe = function (onChange) {
  19. this.onChanges.push(onChange);
  20. };
  21.  
  22. app.TodoModel.prototype.inform = function () {
  23. Utils.store(this.key, this.todos);
  24. this.onChanges.forEach(function (cb) { cb(); });
  25. };
  26.  
  27. app.TodoModel.prototype.addTodo = function (title) {
  28. this.todos = this.todos.concat({
  29. id: Utils.uuid(),
  30. title: title,
  31. completed: false
  32. });
  33.  
  34. this.inform();
  35. };
  36.  
  37. app.TodoModel.prototype.toggleAll = function (checked) {
  38. // Note: it's usually better to use immutable data structures since they're
  39. // easier to reason about and React works very well with them. That's why
  40. // we use map() and filter() everywhere instead of mutating the array or
  41. // todo items themselves.
  42. this.todos = this.todos.map(function (todo) {
  43. return Utils.extend({}, todo, { completed: checked });
  44. });
  45.  
  46. this.inform();
  47. };
  48.  
  49. app.TodoModel.prototype.toggle = function (todoToToggle) {
  50. this.todos = this.todos.map(function (todo) {
  51. return todo !== todoToToggle ?
  52. todo :
  53. Utils.extend({}, todo, { completed: !todo.completed });
  54. });
  55.  
  56. this.inform();
  57. };
  58.  
  59. app.TodoModel.prototype.destroy = function (todo) {
  60. this.todos = this.todos.filter(function (candidate) {
  61. return candidate !== todo;
  62. });
  63.  
  64. this.inform();
  65. };
  66.  
  67. app.TodoModel.prototype.save = function (todoToSave, text) {
  68. this.todos = this.todos.map(function (todo) {
  69. return todo !== todoToSave ? todo : Utils.extend({}, todo, { title: text });
  70. });
  71.  
  72. this.inform();
  73. };
  74.  
  75. app.TodoModel.prototype.clearCompleted = function () {
  76. this.todos = this.todos.filter(function (todo) {
  77. return !todo.completed;
  78. });
  79.  
  80. this.inform();
  81. };
  82.  
  83. })();

utils为简单的工具类,不予理睬;无论什么时候数据层一定是MVC的重点,这里稍微给予一点关注:

① model层实现了一个简单的事件订阅通知系统

② 从类实现来说,他仅有三个属性,key(存储与localstorage的命名空间),todos(真实的数据对象),changes(事件集合)

③ 与backbone的model不同,backbone的数据操作占了其实现大部分篇幅,backbone的TodoMVC会完整定义Model的增删差改依次触发的事件,所以Model定义结束,程序就有了完整的脉络,而我们看react这里有点“弱化”数据处理的感觉

④ 总的来说,整个Model的方法皆在操作todos数据,subscribe用于注册事件,每次操作皆会通知changes函数响应,并且存储到localstorage,从重构的角度来说inform其实只应该完成通知的工作,存储的事情不应该做,但是这与我们今天所学没有什么管理,不予理睬,接下来我们进入View层的代码。

组件化编程

React号称组件化编程,我们从标签化、声明式编程的角度来一起看看他第一个View TodoItem的实现:

  1. var app = app || {};
  2.  
  3. (function () {
  4. 'use strict';
  5.  
  6. var ESCAPE_KEY = 27;
  7. var ENTER_KEY = 13;
  8.  
  9. app.TodoItem = React.createClass({
  10. handleSubmit: function (event) {
  11. var val = this.state.editText.trim();
  12. if (val) {
  13. this.props.onSave(val);
  14. this.setState({editText: val});
  15. } else {
  16. this.props.onDestroy();
  17. }
  18. },
  19.  
  20. handleEdit: function () {
  21. this.props.onEdit();
  22. this.setState({editText: this.props.todo.title});
  23. },
  24.  
  25. handleKeyDown: function (event) {
  26. if (event.which === ESCAPE_KEY) {
  27. this.setState({editText: this.props.todo.title});
  28. this.props.onCancel(event);
  29. } else if (event.which === ENTER_KEY) {
  30. this.handleSubmit(event);
  31. }
  32. },
  33.  
  34. handleChange: function (event) {
  35. this.setState({editText: event.target.value});
  36. },
  37.  
  38. getInitialState: function () {
  39. return {editText: this.props.todo.title};
  40. },
  41.  
  42. /**
  43. * This is a completely optional performance enhancement that you can
  44. * implement on any React component. If you were to delete this method
  45. * the app would still work correctly (and still be very performant!), we
  46. * just use it as an example of how little code it takes to get an order
  47. * of magnitude performance improvement.
  48. */
  49. shouldComponentUpdate: function (nextProps, nextState) {
  50. return (
  51. nextProps.todo !== this.props.todo ||
  52. nextProps.editing !== this.props.editing ||
  53. nextState.editText !== this.state.editText
  54. );
  55. },
  56.  
  57. /**
  58. * Safely manipulate the DOM after updating the state when invoking
  59. * `this.props.onEdit()` in the `handleEdit` method above.
  60. * For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate
  61. * and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate
  62. */
  63. componentDidUpdate: function (prevProps) {
  64. if (!prevProps.editing && this.props.editing) {
  65. var node = React.findDOMNode(this.refs.editField);
  66. node.focus();
  67. node.setSelectionRange(node.value.length, node.value.length);
  68. }
  69. },
  70.  
  71. render: function () {
  72. return (
  73. <li className={React.addons.classSet({
  74. completed: this.props.todo.completed,
  75. editing: this.props.editing
  76. })}>
  77. <div className="view">
  78. <input
  79. className="toggle"
  80. type="checkbox"
  81. checked={this.props.todo.completed}
  82. onChange={this.props.onToggle}
  83. />
  84. <label onDoubleClick={this.handleEdit}>
  85. {this.props.todo.title}
  86. </label>
  87. <button className="destroy" onClick={this.props.onDestroy} />
  88. </div>
  89. <input
  90. ref="editField"
  91. className="edit"
  92. value={this.state.editText}
  93. onBlur={this.handleSubmit}
  94. onChange={this.handleChange}
  95. onKeyDown={this.handleKeyDown}
  96. />
  97. </li>
  98. );
  99. }
  100. });
  101. })();

TodoItem

根据我们之前的知识,这里是创建了一个自定义标签,而标签返回的内容是:

  1. render: function () {
  2. return (
  3. <li className={React.addons.classSet({
  4. completed: this.props.todo.completed,
  5. editing: this.props.editing
  6. })}>
  7. <div className="view">
  8. <input
  9. className="toggle"
  10. type="checkbox"
  11. checked={this.props.todo.completed}
  12. onChange={this.props.onToggle}
  13. />
  14. <label onDoubleClick={this.handleEdit}>
  15. {this.props.todo.title}
  16. </label>
  17. <button className="destroy" onClick={this.props.onDestroy} />
  18. </div>
  19. <input
  20. ref="editField"
  21. className="edit"
  22. value={this.state.editText}
  23. onBlur={this.handleSubmit}
  24. onChange={this.handleChange}
  25. onKeyDown={this.handleKeyDown}
  26. />
  27. </li>
  28. );
  29. }

要展示这个View需要依赖其属性与状态:

  1. getInitialState: function () {
  2. return {editText: this.props.todo.title};
  3. },

这里没有属性的描写,而他本身也仅仅是标签组件,更多的信息我们需要去看调用方,该组件显示的是body部分,TodoMVC还有footer部分的操作工具条,这里的实现便比较简单了:

  1. var app = app || {};
  2.  
  3. (function () {
  4. 'use strict';
  5.  
  6. app.TodoFooter = React.createClass({
  7. render: function () {
  8. var activeTodoWord = app.Utils.pluralize(this.props.count, 'item');
  9. var clearButton = null;
  10.  
  11. if (this.props.completedCount > 0) {
  12. clearButton = (
  13. <button
  14. className="clear-completed"
  15. onClick={this.props.onClearCompleted}>
  16. Clear completed
  17. </button>
  18. );
  19. }
  20.  
  21. // React idiom for shortcutting to `classSet` since it'll be used often
  22. var cx = React.addons.classSet;
  23. var nowShowing = this.props.nowShowing;
  24. return (
  25. <footer className="footer">
  26. <span className="todo-count">
  27. <strong>{this.props.count}</strong> {activeTodoWord} left
  28. </span>
  29. <ul className="filters">
  30. <li>
  31. <a
  32. href="#/"
  33. className={cx({selected: nowShowing === app.ALL_TODOS})}>
  34. All
  35. </a>
  36. </li>
  37. {' '}
  38. <li>
  39. <a
  40. href="#/active"
  41. className={cx({selected: nowShowing === app.ACTIVE_TODOS})}>
  42. Active
  43. </a>
  44. </li>
  45. {' '}
  46. <li>
  47. <a
  48. href="#/completed"
  49. className={cx({selected: nowShowing === app.COMPLETED_TODOS})}>
  50. Completed
  51. </a>
  52. </li>
  53. </ul>
  54. {clearButton}
  55. </footer>
  56. );
  57. }
  58. });
  59. })();

TodoFooter

我们现在将关注点放在其所有标签的调用方,app.jsx(TodoApp),因为我没看见这个TodoMVC的控制器在哪,也就是我没有看见控制逻辑的js文件在哪,所以控制流程的代码只能在这里了:

  1. var app = app || {};
  2.  
  3. (function () {
  4. 'use strict';
  5.  
  6. app.ALL_TODOS = 'all';
  7. app.ACTIVE_TODOS = 'active';
  8. app.COMPLETED_TODOS = 'completed';
  9. var TodoFooter = app.TodoFooter;
  10. var TodoItem = app.TodoItem;
  11.  
  12. var ENTER_KEY = 13;
  13.  
  14. var TodoApp = React.createClass({
  15. getInitialState: function () {
  16. return {
  17. nowShowing: app.ALL_TODOS,
  18. editing: null
  19. };
  20. },
  21.  
  22. componentDidMount: function () {
  23. var setState = this.setState;
  24. var router = Router({
  25. '/': setState.bind(this, {nowShowing: app.ALL_TODOS}),
  26. '/active': setState.bind(this, {nowShowing: app.ACTIVE_TODOS}),
  27. '/completed': setState.bind(this, {nowShowing: app.COMPLETED_TODOS})
  28. });
  29. router.init('/');
  30. },
  31.  
  32. handleNewTodoKeyDown: function (event) {
  33. if (event.keyCode !== ENTER_KEY) {
  34. return;
  35. }
  36.  
  37. event.preventDefault();
  38.  
  39. var val = React.findDOMNode(this.refs.newField).value.trim();
  40.  
  41. if (val) {
  42. this.props.model.addTodo(val);
  43. React.findDOMNode(this.refs.newField).value = '';
  44. }
  45. },
  46.  
  47. toggleAll: function (event) {
  48. var checked = event.target.checked;
  49. this.props.model.toggleAll(checked);
  50. },
  51.  
  52. toggle: function (todoToToggle) {
  53. this.props.model.toggle(todoToToggle);
  54. },
  55.  
  56. destroy: function (todo) {
  57. this.props.model.destroy(todo);
  58. },
  59.  
  60. edit: function (todo) {
  61. this.setState({editing: todo.id});
  62. },
  63.  
  64. save: function (todoToSave, text) {
  65. this.props.model.save(todoToSave, text);
  66. this.setState({editing: null});
  67. },
  68.  
  69. cancel: function () {
  70. this.setState({editing: null});
  71. },
  72.  
  73. clearCompleted: function () {
  74. this.props.model.clearCompleted();
  75. },
  76.  
  77. render: function () {
  78. var footer;
  79. var main;
  80. var todos = this.props.model.todos;
  81.  
  82. var shownTodos = todos.filter(function (todo) {
  83. switch (this.state.nowShowing) {
  84. case app.ACTIVE_TODOS:
  85. return !todo.completed;
  86. case app.COMPLETED_TODOS:
  87. return todo.completed;
  88. default:
  89. return true;
  90. }
  91. }, this);
  92.  
  93. var todoItems = shownTodos.map(function (todo) {
  94. return (
  95. <TodoItem
  96. key={todo.id}
  97. todo={todo}
  98. onToggle={this.toggle.bind(this, todo)}
  99. onDestroy={this.destroy.bind(this, todo)}
  100. onEdit={this.edit.bind(this, todo)}
  101. editing={this.state.editing === todo.id}
  102. onSave={this.save.bind(this, todo)}
  103. onCancel={this.cancel}
  104. />
  105. );
  106. }, this);
  107.  
  108. var activeTodoCount = todos.reduce(function (accum, todo) {
  109. return todo.completed ? accum : accum + 1;
  110. }, 0);
  111.  
  112. var completedCount = todos.length - activeTodoCount;
  113.  
  114. if (activeTodoCount || completedCount) {
  115. footer =
  116. <TodoFooter
  117. count={activeTodoCount}
  118. completedCount={completedCount}
  119. nowShowing={this.state.nowShowing}
  120. onClearCompleted={this.clearCompleted}
  121. />;
  122. }
  123.  
  124. if (todos.length) {
  125. main = (
  126. <section className="main">
  127. <input
  128. className="toggle-all"
  129. type="checkbox"
  130. onChange={this.toggleAll}
  131. checked={activeTodoCount === 0}
  132. />
  133. <ul className="todo-list">
  134. {todoItems}
  135. </ul>
  136. </section>
  137. );
  138. }
  139.  
  140. return (
  141. <div>
  142. <header className="header">
  143. <h1>todos</h1>
  144. <input
  145. ref="newField"
  146. className="new-todo"
  147. placeholder="What needs to be done?"
  148. onKeyDown={this.handleNewTodoKeyDown}
  149. autoFocus={true}
  150. />
  151. </header>
  152. {main}
  153. {footer}
  154. </div>
  155. );
  156. }
  157. });
  158.  
  159. var model = new app.TodoModel('react-todos');
  160.  
  161. function render() {
  162. React.render(
  163. <TodoApp model={model}/>,
  164. document.getElementsByClassName('todoapp')[0]
  165. );
  166. }
  167.  
  168. model.subscribe(render);
  169. render();
  170. })();

TodoAPP

这里同样是创建了一个标签,然后最后一段代码有所不同:

  1. var model = new app.TodoModel('react-todos');
  2.  
  3. function render() {
  4. React.render(
  5. <TodoApp model={model}/>,
  6. document.getElementsByClassName('todoapp')[0]
  7. );
  8. }
  9.  
  10. model.subscribe(render);
  11. render();

① 这里创建了一个Model的实例,我们知道创建的时候,todos便由localstorage获取了数据(如果有的话)

② 这里了定义了一个方法,以todoapp为容器,装载标签

③ 为model订阅render方法,意思是每次model有变化都将重新渲染页面,这里的代码比较关键,按照代码所示,每次数据变化都应该执行render方法,如果list数量比较多的话,每次接重新渲染岂不是浪费性能,但真实使用过程中,可以看到React竟然是局部刷新的,他这个机制非常牛逼啊!

④ 最后执行了render方法,开始了TodoApp标签的渲染,我们这里再将TodoApp的渲染逻辑贴出来

  1. render: function () {
  2. var footer;
  3. var main;
  4. var todos = this.props.model.todos;
  5.  
  6. var shownTodos = todos.filter(function (todo) {
  7. switch (this.state.nowShowing) {
  8. case app.ACTIVE_TODOS:
  9. return !todo.completed;
  10. case app.COMPLETED_TODOS:
  11. return todo.completed;
  12. default:
  13. return true;
  14. }
  15. }, this);
  16.  
  17. var todoItems = shownTodos.map(function (todo) {
  18. return (
  19. <TodoItem
  20. key={todo.id}
  21. todo={todo}
  22. onToggle={this.toggle.bind(this, todo)}
  23. onDestroy={this.destroy.bind(this, todo)}
  24. onEdit={this.edit.bind(this, todo)}
  25. editing={this.state.editing === todo.id}
  26. onSave={this.save.bind(this, todo)}
  27. onCancel={this.cancel}
  28. />
  29. );
  30. }, this);
  31.  
  32. var activeTodoCount = todos.reduce(function (accum, todo) {
  33. return todo.completed ? accum : accum + 1;
  34. }, 0);
  35.  
  36. var completedCount = todos.length - activeTodoCount;
  37.  
  38. if (activeTodoCount || completedCount) {
  39. footer =
  40. <TodoFooter
  41. count={activeTodoCount}
  42. completedCount={completedCount}
  43. nowShowing={this.state.nowShowing}
  44. onClearCompleted={this.clearCompleted}
  45. />;
  46. }
  47.  
  48. if (todos.length) {
  49. main = (
  50. <section className="main">
  51. <input
  52. className="toggle-all"
  53. type="checkbox"
  54. onChange={this.toggleAll}
  55. checked={activeTodoCount === 0}
  56. />
  57. <ul className="todo-list">
  58. {todoItems}
  59. </ul>
  60. </section>
  61. );
  62. }
  63.  
  64. return (
  65. <div>
  66. <header className="header">
  67. <h1>todos</h1>
  68. <input
  69. ref="newField"
  70. className="new-todo"
  71. placeholder="What needs to be done?"
  72. onKeyDown={this.handleNewTodoKeyDown}
  73. autoFocus={true}
  74. />
  75. </header>
  76. {main}
  77. {footer}
  78. </div>
  79. );
  80. }

说句实话,这段代码不知为什么有一些令人感到难受......

① 他首先获取了注入的model实例,获取其所需的数据todos,注入点在:

  1. <TodoApp model={model}/>

② 然后他由自身状态机,获取真实要显示的项目,其实这里如果不考虑路由的变化,完全显示即可

  1. getInitialState: function () {
  2. return {
  3. nowShowing: app.ALL_TODOS,
  4. editing: null
  5. };
  6. },

③ 数据获取成功后,便使用该数据组装为一个个独立的TodoItem标签:

  1. var todoItems = shownTodos.map(function (todo) {
  2. return (
  3. <TodoItem
  4. key={todo.id}
  5. todo={todo}
  6. onToggle={this.toggle.bind(this, todo)}
  7. onDestroy={this.destroy.bind(this, todo)}
  8. onEdit={this.edit.bind(this, todo)}
  9. editing={this.state.editing === todo.id}
  10. onSave={this.save.bind(this, todo)}
  11. onCancel={this.cancel}
  12. />
  13. );
  14. }, this);

标签具有很多事件,这里要注意一下各个事件这里事件绑定与控制器上绑定有何不同

④ 然后其做了一些工作处理底部工具条或者头部全部选中的工作

⑤ 最后开始渲染整个标签:

  1. return (
  2. <div>
  3. <header className="header">
  4. <h1>todos</h1>
  5. <input
  6. ref="newField"
  7. className="new-todo"
  8. placeholder="What needs to be done?"
  9. onKeyDown={this.handleNewTodoKeyDown}
  10. autoFocus={true}
  11. />
  12. </header>
  13. {main}
  14. {footer}
  15. </div>
  16. );

该标签事实上为3个模块组成的了:header部分、body部分、footer部分,模块与模块之间的通信依赖便是model数据了,因为这里最终的渲染皆在app的render处,而render处渲染所有标签全部共同依赖于一个model,就算这里依赖于多个model,只要是统一在render处做展示即可。

流程分析

我们前面理清了整个脉络,接下来我们理一理几个关键脉络:

① 新增

TodoApp为其头部input标签绑定了一个onKeyDown事件,事件代理到了handleNewTodoKeyDown:

  1. handleNewTodoKeyDown: function (event) {
  2. if (event.keyCode !== ENTER_KEY) {
  3. return;
  4. }
  5.  
  6. event.preventDefault();
  7.  
  8. var val = React.findDOMNode(this.refs.newField).value.trim();
  9.  
  10. if (val) {
  11. this.props.model.addTodo(val);
  12. React.findDOMNode(this.refs.newField).value = '';
  13. }
  14. },

因为用户输入的数据不能由属性或者状态值获取,这里使用了dom操作的方法获取输入数据,这里的钩子是ref,事件触发了model新增一条记录,并且将文本框置为空,现在我们进入model新增的逻辑:

  1. app.TodoModel.prototype.addTodo = function (title) {
  2. this.todos = this.todos.concat({
  3. id: Utils.uuid(),
  4. title: title,
  5. completed: false
  6. });
  7.  
  8. this.inform();
  9. };

model以最简的方式构造了一个数据对象,改变了todos的值,然后通知model发生了变化,而我们都知道informa程序干了两件事:

  1. app.TodoModel.prototype.inform = function () {
  2. Utils.store(this.key, this.todos);
  3. this.onChanges.forEach(function (cb) { cb(); });
  4. };

存储localstorage、触发订阅model变化的回调,也就是:

  1. function render() {
  2. React.render(
  3. <TodoApp model={model}/>,
  4. document.getElementsByClassName('todoapp')[0]
  5. );
  6. }
  7.  
  8. model.subscribe(render);

于是整个标签可耻的重新渲染了,我们再来看看编辑是怎么回事:

② 编辑

这个编辑便与TodoApp没有什么关系了:

  1. <label onDoubleClick={this.handleEdit}>
  2. {this.props.todo.title}
  3. </label>

当双击标签项时,触发了代理的处理程序:

  1. handleEdit: function () {
  2. this.props.onEdit();
  3. this.setState({editText: this.props.todo.title});
  4. },

这里他做了两个事情:

onEdit,为父标签注入的方法,他这里执行函数作用域是指向this.props的,所以外层定义时指定了作用域:

  1. return (
  2. <TodoItem
  3. key={todo.id}
  4. todo={todo}
  5. onToggle={this.toggle.bind(this, todo)}
  6. onDestroy={this.destroy.bind(this, todo)}
  7. onEdit={this.edit.bind(this, todo)}
  8. editing={this.state.editing === todo.id}
  9. onSave={this.save.bind(this, todo)}
  10. onCancel={this.cancel}
  11. />
  12. );

其次,他改变了自身状态机,而状态机或者属性的变化皆会引起标签重新渲染,然后当触发keydown事件后,完成的逻辑便与上面一致了

思考

经过之前的学习,我们对React有了一个大概的了解,是时候搬出React设计的初衷了:

  1. Just the ui
  2. virtual dom
  3. data flow

后面两个概念还没强烈的感触,这里仅仅对Just the ui有一些认识,似乎React仅仅提供了MVC中View的实现,但是这个View又强大到可以抛弃C了,可以看到上述代码控制器被无限的弱化了,而我觉得React其实真实想提供的可能是一种开发方式的思路,React便是如何帮你实现这种思路的方案:

  1. 模块化编程、组件化编程、标签化编程,可能是React真正想表达的思想

我们在组织负责业务逻辑时,也会分模块、分UI,但是我们一般是采用控制器调用组件的方式使用,React这里不同的一点是使用标签分模块,孰优孰劣要真实开发过生产项目的朋友才能认识,真实的应用路由的功能必不可少,应该有不少插件会主动抱大腿,但使用灵活性仍然得项目实践验证。

react本身很干净,不包括模块加载的机制,真正发布生产前需要通过webpack打包处理,但是对于复杂项目来说,按需加载是必不可少的,这块不知道如何

而我的关注点仍然落在了样式上,之前做组件或者做页面时,有一个优化方案,是将对应的样式作为一个View的依赖项加载,一个View保持最小的html&css&js量加载,而react对样式与动画一块的支持如何,也需要生产验证;复杂的项目开发,Model的设计一定是至关重要的,也许借鉴Backbone Model的实现+React的View处理,会是一个不错的选择

最后,因为现在没有生产项目能让我使用React试水,过多的话基本就是意淫了,根据我之前MVC的使用经验,感觉灵活性上估计React仍然有一段路要走,但是其模块化编程的思路倒是对我的项目有莫大的指导作用,对于这门技术的深入,经过今天的学习,我打算再观望一下,不知道angularJS怎么样,我也许该对这门MVVM的框架展开调研

初探React,将我们的View标签化的更多相关文章

  1. [RN] React Native 实现 多选标签

    React Native 实现 多选标签 效果如下: 实现代码: import React, {Component} from 'react'; import {Button, StyleSheet, ...

  2. 让资源管理器变得像Chrome一样标签化

    让资源管理器变得像Chrome一样标签化 前段时间WIn10开发者预览版发布了更新通知,其中一个主要特性就是给资源管理器添加了标签化的功能. 习惯了各种浏览器便捷的标签化管理,早就想要这个实用的功能了 ...

  3. 音频标签化3:igor-8m项目的训练、评估与测试

    上一节介绍了youtube-8m项目,这个项目以youtube-8m dataset(简称8m-dataset)样本集为基础,进行训练.评估与测试.youtube-8m设计用于视频特征样本,但实际也适 ...

  4. 音频标签化2:youtube-8m项目的训练、评估与测试

    之前小程介绍了使用机器学习的办法来解决"音频标签化"的问题,并且提到了训练样本audioset跟youtube-8m的dataset,而训练模型上也提到了youtube-8m的模型 ...

  5. 音频标签化1:audioset与训练模型 | 音频特征样本

    随着机器学习的发展,很多"历史遗留"问题有了新的解决方案.这些遗留问题中,有一个是音频标签化,即如何智能地给一段音频打上标签的问题,标签包括"吉他"." ...

  6. vscode react自动补全html标签

    第一步:点击上图左下角设置,找到Settings,搜索includeLanguages 第二步:如上图点击图中红色区域,settings.json 第三部:把代码加入,如上图红色选择区域. " ...

  7. What is the difference between Reactjs and Rxjs?--React is the V (View) in MVC (Model/View/Controller).

    This is really different, React is view library; and Rxjs is reactive programming library for javasc ...

  8. [RN] React Native 下实现底部标签(支持滑动切换)

    上一篇文章 [RN] React Native 下实现底部标签(不支持滑动切换) 总结了不支持滑动切换的方法,此篇文章总结出 支持滑动 的方法 准备工作之类的,跟上文类似,大家可点击上文查看相关内容. ...

  9. 初探react

    知道这个框架有一段时间了,可是项目中没有用到,也懒得整理,最近两天比较清闲,又想起了它.好了,废话不多说了,都是干货. react是个什么框架? 为什么用react? react 的原理 react有 ...

随机推荐

  1. TCP/IP之TCP_NODELAY与TCP_CORK

    TCP/IP之Nagle算法与40ms延迟提到了Nagle 算法.这样虽然提高了网络吞吐量,但是实时性却降低了,在一些交互性很强的应用程序来说是不允许的,使用TCP_NODELAY选项可以禁止Nagl ...

  2. Linux简单指令操作

    Linux CentOS运维中,常用的操作和命令记录下: 1.DNS设置 在Linux服务器上,当我们ping出现这个错误时:ping: unknown host,很大可能是系统的DNS没有设置或者设 ...

  3. Spring Boot -- 配置切换指南

    一般在一个项目中,总是会有好多个环境.比如: 开发环境 -> 测试环境 -> 预发布环境 -> 生产环境 每个环境上的配置文件总是不一样的,甚至开发环境中每个开发者的环境可能也会有一 ...

  4. 简单事件机制Java实现

    一个很简单方便的事件处理方法. 使用效果 事件发布者: //定义事件 public static EventTrans<String> AuthFailed = new EventTran ...

  5. ABP框架 - 功能管理

    文档目录 本节内容: 简介 关于 IFeatureValueStore 功能类型 Boolean 功能 Value 功能 定义功能 基本功能属性 其它功能属性 功能层次 检查功能 使用Requires ...

  6. string length() 方法注意点

    突然意识到string length() 是跟文件的字符编码相关的 测试了下, 果然如此: 对于常见字, 结果是一样的, System.out.println("T中国123".l ...

  7. 如何权衡自己的angular水准

    angular是现在常用的一个前端MVVM框架,感受下下面的问题权衡下自己的水准吧. 1. angular的数据绑定采用什么机制?详述原理2. 两个平级界面块a和b,如果a中触发一个事件,有哪些方式能 ...

  8. Windows下PowerShell监控Keepalived

    一.背景 某数据库服务器为CentOS,想要监控Keepalived的VIP是否有问题,通过邮件进行报警,但这台机器不能上外网,现在只能在Windows下通过PowerShell来完成发邮件预警. 二 ...

  9. CSS系列:CSS常用样式

    1. 通用样式 Base.css * { margin:; padding:; } body { width: 1000px; margin: 0 auto; font-size: 12px; fon ...

  10. Jquery 选择器注意的问题--记录(五)

    1. $("p.intro")-> 所有 class="intro" 的 <p> 元素 $("div#intro .head&quo ...