【JavaScript】ReactJS基础
初探React,将我们的View标签化
前言
我之前喜欢玩一款游戏:全民飞机大战,而且有点痴迷其中,如果你想站在游戏的第一阶梯,便需要不断的练技术练装备,但是腾讯的游戏一般而言是有点恶心的,他会不断的出新飞机、新装备、新宠物,所以,很多时候你一个飞机以及装备还没满级,新的装备就又出来了,并且一定是更强!
于是很多人便直接抛弃当前的飞机与装备,追求更好的,这个时候如果是人民币玩家或者骨灰级大神玩家的话,基本可以很快站在世界的顶端,一者是装备好,一者是技术好,但是我不愿意投入太多钱,也不愿意投入过多精力,于是在一套极品装备满级后会积累资源,因为一代之间变化不会太大,到第二代甚至第三代才开始换飞机换装备,也基本处于了第一阶梯,一直到一次游戏大更新,直接导致我当前的飞机与装备完全二逼了,我当时一时脑热投入了所有资源去刷新的极品装备,最后闹的血本无归,于是便删除了该游戏,一年时间付诸东流!!!
再回过头来看最近两年前端的变化,单是一个前端工程化工具就变了几次,而且新出来的总是叫嚷着要替换之前的,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>

- <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 );

所谓,声明试编程,便是将需要的属性写到标签上,以一个文本框为例:
- <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方法,这个不是我们现在关注的重点。
- 由于class与for为关键字,需要使用className与htmlFor替换
通过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是什么:
- <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);

组件有其生命周期,每个阶段会触发相关事件可被用户捕捉使用:
- Mounting:已插入真实 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真实 DOM
一般来说,我们会为一个状态发生前后绑定事件,react也是如此:

- componentWillMount()
- componentDidMount()
- componentWillUpdate(object nextProps, object nextState)
- componentDidUpdate(object prevProps, object prevState)
- componentWillUnmount()
- 此外,React 还提供两种特殊状态的处理函数。
- componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
- 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
根据我们之前的知识,这里是创建了一个自定义标签,而标签返回的内容是:

- render: function () {
- return (
- <li className={React.addons.classSet({
- completed: this.props.todo.completed,
- editing: this.props.editing
- })}>
- <div className="view">
- <input
- className="toggle"
- type="checkbox"
- checked={this.props.todo.completed}
- onChange={this.props.onToggle}
- />
- <label onDoubleClick={this.handleEdit}>
- {this.props.todo.title}
- </label>
- <button className="destroy" onClick={this.props.onDestroy} />
- </div>
- <input
- ref="editField"
- className="edit"
- value={this.state.editText}
- onBlur={this.handleSubmit}
- onChange={this.handleChange}
- onKeyDown={this.handleKeyDown}
- />
- </li>
- );
- }

要展示这个View需要依赖其属性与状态:
- getInitialState: function () {
- return {editText: this.props.todo.title};
- },
这里没有属性的描写,而他本身也仅仅是标签组件,更多的信息我们需要去看调用方,该组件显示的是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,注入点在:
- <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设计的初衷了:
- Just the ui
- virtual dom
- data flow
后面两个概念还没强烈的感触,这里仅仅对Just the ui有一些认识,似乎React仅仅提供了MVC中View的实现,但是这个View又强大到可以抛弃C了,可以看到上述代码控制器被无限的弱化了,而我觉得React其实真实想提供的可能是一种开发方式的思路,React便是如何帮你实现这种思路的方案:
- 模块化编程、组件化编程、标签化编程,可能是React真正想表达的思想
我们在组织负责业务逻辑时,也会分模块、分UI,但是我们一般是采用控制器调用组件的方式使用,React这里不同的一点是使用标签分模块,孰优孰劣要真实开发过生产项目的朋友才能认识,真实的应用路由的功能必不可少,应该有不少插件会主动抱大腿,但使用灵活性仍然得项目实践验证。
react本身很干净,不包括模块加载的机制,真正发布生产前需要通过webpack打包处理,但是对于复杂项目来说,按需加载是必不可少的,这块不知道如何
而我的关注点仍然落在了样式上,之前做组件或者做页面时,有一个优化方案,是将对应的样式作为一个View的依赖项加载,一个View保持最小的html&css&js量加载,而react对样式与动画一块的支持如何,也需要生产验证;复杂的项目开发,Model的设计一定是至关重要的,也许借鉴Backbone Model的实现+React的View处理,会是一个不错的选择
最后,因为现在没有生产项目能让我使用React试水,过多的话基本就是意淫了,根据我之前MVC的使用经验,感觉灵活性上估计React仍然有一段路要走,但是其模块化编程的思路倒是对我的项目有莫大的指导作用,对于这门技术的深入,经过今天的学习,我打算再观望一下,不知道angularJS怎么样,我也许该对这门MVVM的框架展开调研
参考资料:
http://www.cnblogs.com/yexiaochai/p/4853398.html
http://stackoverflow.com/questions/17585787/whats-data-reactid-attribute-in-html
【JavaScript】ReactJS基础的更多相关文章
- ReactJS基础(续)
前边的ReactJS基础,我们可以了解到,对于React,可以说是万物皆组件 React的组件应该具有 可组合(Composeable)可重用(Reusable)可维护(Maintainable)的特 ...
- JavaScript RegExp 基础详谈
前言: 正则对于一个码农来说是最基础的了,而且在博客园中,发表关于讲解正则表达式的技术文章,更是数不胜数,各有各的优点,但是就是这种很基础的东西,如果我们不去真正仔细研究.学习.掌握,而是抱着需要的时 ...
- JavaScript学习基础部分
JavaScript学习基础 一.简介 1.JavaScript 是因特网上最流行的脚本语言,并且可在所有主要的浏览器中运行,比方说 Internet Explorer. Mozilla.Firefo ...
- JavaScript入门基础
JavaScript基本语法 1.运算符 运算符就是完成操作的一系列符号,它有七类: 赋值运算符(=,+=,-=,*=,/=,%=,<<=,>>=,|=,&=).算术运 ...
- JavaScript 语言基础知识点总结(思维导图)
JavaScript 数组 JavaScript 函数基础 Javascript 运算符 JavaScript 流程控制 JavaScript 正则表达式 JavaScript 字符串函数 JavaS ...
- JavaScript语言基础知识点图示(转)
一位牛人归纳的JavaScript 语言基础知识点图示. 1.JavaScript 数据类型 2.JavaScript 变量 3.Javascript 运算符 4.JavaScript 数组 5.Ja ...
- JavaScript 语言基础知识点总结
网上找到的一份JavaScript 语言基础知识点总结,还不错,挺全面的. (来自:http://t.cn/zjbXMmi @刘巍峰 分享 )
- Javascript语法基础
Javascript语法基础 一.基本数据类型 JavaScript中支持数字.字符串和布尔值三种基本数据类型: 1.数字 数字型是JavaScript中的基本数据类型.在JavaScript ...
- JavaScript 函数基础
1. JavaScript 函数基础 1. 定义方法 2. 函数的调用方法 3. 函数方法 apply : 将函数作为数组的方法来调用 将参数以数组形式传递给该方法 call : 将函数作为对象的 ...
- JavaScript编程:javaScript核心基础语法
1.javaScript核心基础语法: javaScript技术体系包含了5个内容: 1.核心语言定义: 2.原生对象和雷子对象: 3.浏览器对象 ...
随机推荐
- 使用GitLab进行落地项目的管理,并且自动更新、重启、回滚
Gitlab 清空项目历史commit,节省空间 http://blog.csdn.net/dounine/article/details/77840416?locationNum=6&f ...
- PHP必用代码片段
在编写代码的时候有个神奇的工具总是好的!下面这里收集了 50+ PHP 代码片段,可以帮助你开发 PHP 项目. 这些 PHP 片段对于 PHP 初学者也非常有帮助,非常容易学习,让我们开始学习吧- ...
- AC日记——Dynamic Ranking 洛谷 P2617
Dynamic Ranking 思路: 可持久化树状数组: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 1 ...
- Java实现POI读取Excel文件,兼容后缀名xls和xlsx
1.引入所需的jar包: maven管理项目的话直接添加以下坐标即可: <!-- https://mvnrepository.com/artifact/org.apache.poi/poi -- ...
- CodeForces 723F st-Spanning Tree
$dfs$,构造. 类似于$k$度限制生成树的想法,可以将$s$和$t$先从图中删去,将剩下的部分求连通块,每个连通块内部很容易构造生成树,每个连通块缩成一个点来处理. 连通块分三种: $1$.只与$ ...
- AndroidManifest.xml文件详解(uses-feature)
http://blog.csdn.net/think_soft/article/details/7596796 语法(SYNTAX): <uses-featureandroid:name=&qu ...
- 差分【bzoj3043】IncDec Sequence
Description 给定一个长度为n的数列{a1,a2...an},每次可以选择一个区间[l,r],使这个区间内的数都加一或者都减一. 问至少需要多少次操作才能使数列中的所有数都一样,并求出在保证 ...
- bytes2HexString
public static String bytes2HexString(byte[] b) { String r = ""; for (int i = 0; i < b.l ...
- Xamarin.iOS真机测试报错
Xamarin.iOS真机测试报错 错误信息:The MinimumOSVersion inside Info.plist does not include the device version( ...
- 【POJ 2409】 Let it Bead(置换、burnside引理)
Let it Bead "Let it Bead" company is located upstairs at 700 Cannery Row in Monterey, CA. ...