React的思想
React 的三个特点:
- 单向数据流(而不是双向数据绑定)
- 只关心MVC 中的View,所以组织大规模应用还需要 flux 之类的框架
- Virtual DOM,对DOM的一层抽象
React 的特点就是简单,Simple。所以它只关心View,而整个MVC的架构需要借助其他框架实现。
因为没有双向数据绑定,所以React的组件状态非常容易保持,只要确定了 state 和 props ,同一个类创建出来的实例应该都是一样的(虽然这不是强制的)。
不过同时也导致使用 React 不像 Angular 那么方便,需要自己去监听各种事件来更新自己的State。
如果说 Angular 开创了一个双向数据绑定的时代,解决了数据和视图同步的问题,那么 React 可以说是开创了一个 Virtual DOM的时代,解决了跨平台UI的问题。
Virtual DOM 可以说完全是为了跨平台考虑,可以把它看做一个UI 接口,虽然是一层简单的DOM抽象,但是确实做到了从直接操作DOM 到 操作抽象的Virtual DOM。从操作实现类到操作接口,是质变。Virtual DOM 既可以编译成HTML,也完全可以编译成 Java 和 OC,并且写法没有太多区别。
之前写过一个React系列博客,感兴趣的可以翻翻我之前的博客。 今天再看的时候发现 React 已经更新到了 0.14
,并且把 ReactDOM 独立出来了。
JSX 和 Virtual DOM
JSX 虽然在 React 体系中并不重要(官方列出的三大特点根本没他什么事,而且React 完全可以不用JSX来编写),但是确实让写模板方便了很多,至少可以不用再拼接字符串了。
JSX 就是一个简单的编译器,可以把 JSX 语法的模板编译成对应的 React 代码。而JSX语法就是直接在JS中写HTML。
比如:
var app = <Nav color="blue"><Profile>click</Profile></Nav>;
实际上被编译成了:
var app = React.createElement( Nav,{color:"blue"},React.createElement(Profile,null,"click") );
React.createElement 实际创建出来的就是 Virtual DOM,在浏览器环境下最终会编译成HTML代码。因为 直接用 React.createElement 来写一个模板实在是太麻烦了,所以才有了 JSX 会自动帮我们生成这些代码。
仔细看上面的代码会发现,JSX编译的时候其实就是把一个元素里面包裹的所有孩子都作为 createElement 的第 3个参数开始传进去了,比如只有一个孩子就是第三个参数,如果有三个,就分别是第 3 4 5个参数。
JSX 中可以通过 大括号来嵌入JS代码,比如这样:
var person = <Person name={window.isLoggedIn ? window.name : ''} />;
然后他会被编译成这样:
var person = React.createElement( Person,{name: window.isLoggedIn ? window.name : ''} );
JSX 中的JS代码必须是表达式
。所以你如果直接写一个 if 语句肯定会报错,因为if语句不是表达式。
实际上,想在JSX中写if语句还真的没有直接的办法,官方给出了一个解决方法是把if逻辑写在JSX外面,参见:https://facebook.github.io/react/tips/if-else-in-JSX.html
React 组件和生命周期
直接看一个官方的例子:
var LikeButton = React.createClass({ getInitialState: function() { return {liked: false}; },handleClick: function(event) { this.setState({liked: !this.state.liked}); },render: function() { var text = this.state.liked ? 'like' : 'haven\'t liked'; return ( <p onClick={this.handleClick}> You {text} this. Click to toggle. </p> ); } }); ReactDOM.render( <LikeButton />,document.getElementById('example') );
这是一个“赞”按钮。 React.createClass 就是创建了一个类,然后通过 ReactDOM.render 就可以把这个类实例化(或者在另一个组件中实例化也行)。
上面的代码创建了一个类 LikeButton,然后把它实例化。 所以React 组件的生命周期其实分为两部分,一部分是类的生命周期,一部分是实例的生命周期。
类的生命周期比较简单,在创建类的时候只会调用一个方法,就是 getDefaultProps
,来获取一个默认的 props ,并且缓存起来,然后每一个实例如果没有给他指定 props 都会用这个。
实例的生命周期就比较复杂,主要分为三个阶段:
实例化阶段
getInitialState: 获取 this.state 的默认值
componentWillMount: 在render之前调用此方法,在render之前需要做的事情就在这里处理
render: 渲染并返回一个虚拟DOM
componentDidMount: 在render之后,react会使用render返回的虚拟DOM来创建真实DOM,完成之后调用此方法。
其中有几个点需要注意:
1,this.state 只存储原始数据,不要存储计算后的数据
比如 this.state.time = 1433245642536,那么就不要再存一个 this.state.timeString = ‘2015-06-02 19:47:22’ 因为这个是由 time 计算出来的,其实他不是一种state,他只是 this.state.time 的一种展示方式而已。
这个应该放在render中计算出来:
time: {this.formatTime(this.state.time)}
2,componentWillMount 用来处理render之前的逻辑,不要在render中处理业务逻辑。
render就是一个模板的作用,他只处理和展示相关的逻辑,比如格式化时间这样的,如果有业务逻辑,那么要放在 componentWillMount 中执行。
所以render中一定不会出现改变 state 之类的操作。
3,render返回的是虚拟DOM
所谓虚拟DOM,其实就是 React.DOM.div 之类的实例,他就是一个JS对象。render方法完成之后,真实的DOM并不存在。
4,componentDidMount 中处理和真实DOM相关的逻辑
这时候真实的DOM已经渲染出来,可以通过 this.getDOMNode() 方法来使用了。典型的场景就是可以在这里调用jquery插件。
更新阶段
当组件实例化完成,就进入了存在期,这时候一般会响应用户操作和父组件的更新来更新视图。
componentWillRecieveProps: 父组件或者通过组件的实例调用 setProps 改变当前组件的 props 时调用。
shouldComponentUpdate: 是否需要更新,慎用
componentWillUpdate: 调用 render方之前
render:
componentDidUpdate: 真实DOM已经完成更新。
销毁阶段
componentWillUnmount
这里处理一些解绑事件之类的逻辑。
虽然说了这么多,其实我们最常用的还是实例化阶段里面的几个函数。
注意一个小细节就是 React 也是更新了 state 之后会自动更新视图,不过他是通过 setter 方式实现的,也就是你不能直接修改 state ,必须通过 this.setState
或者 this.replaceState
来修改,不然 React 是无法知道 state
已经被更新了的。
组织多个组件
React 中通过组合而不是继承来组织应用。一个应用中从一个组件启动,并在这个组件中创建其他组件,所有的组件最终形成一颗树状结构。
我们依然有父组件和子组件的称呼,不过他们不是继承关系,而是父组件创建了子组件的关系。
这样就涉及到一个重要的问题就是:父子组件的通信问题。
在不借助其他框架的情况下,React 中父子组件是这么通信的:
也就是说,有父子关系的组件其实完全通过 props
属性来通信。那么没有父子关系的怎么办? 官方建议通过 全局的事件系统来通信。
mixins
上面说过 React 中的组件不是继承的,而只是谁创建了谁的关系,那么我们一些公共的逻辑应该怎么处理呢。公共的逻辑应该通过 mixins 来实现。
mixin,可以非常简单的理解,他就是把 一个 mixin 对象上的方法都混合到了另一个组件上,和 $.extend 方法做的事情类似。
不过,react对mixin做了一些特殊处理。
在mixin中写的生命周期相关的回调都会被合并,也就是他们都会执行,而不会互相覆盖掉。
比如 你在mixin中可以定义 componentDidMount 来初始化组件,他不会覆盖掉使用这个mixin的组件。实际执行的时候,会先执行 mixin 的 componentDidMount ,最后执行组件的 componentDidMount 方法。
需要注意的是,因为mixin的作用是抽离公共功能,不存在渲染dom的需要,所以它没有render方法。如果你定义了render方法,那么他会和组件的render方法冲突而报错。
同样,mixin不应该污染state,所以他也没有 setState 方法。mixin应该只提供接口(即方法),不应该提供任何属性。mixin内部的属性最好是通过闭包的形式作为私有变量存在。就像下面这样:
var Timer = function() { var t = 1;//私有属性 return {//xxxx} }
最好不要放到this上,避免污染。
当然,如果你真的在this上写了一些变量,那么react也会进行mixin,因为react并不会区分你的属性到底是不是函数。
MVC 架构 flux 和 reflux
因为 React 其实只是 View,所以如果组织大型的应用的话因为缺少数据层会非常难以解耦。 React 官方出了一个框架叫 flux。严格说 flux 是一个MVC的规范,而不是一个框架,基于 flux 规范完全可以自己去实现而不需要用 flux 库。
有了flux 之后我们就可以把 View 和 Model 完全分开了。View 就是 React 组件,而 Model 是 flux 中的 store。
盗一张flux官方的流程图:
图中的 View 就是我们 React 组件,它存有store的实例,并且从store中取出数据来创建视图,当需要更新数据的时候,他会触发一个 action。
Store 就是一个存储数据的对象,它会在 dispatch上注册自己感兴趣的事件。
Dispatcher 是flux提供的一个全局的事件分发器,而 action 其实就是自定义的事件而已。
当 发生某些事件的时候,比如用户点击了一个按钮,这个时候View 并不会直接去修改 store ,而是触发一个 action。这个Action 通过 dispatch 进行分发,store已经对自己感兴趣的事件注册过,所以他可以收到这个事件,并更新数据,最终反映到视图上的更新。
所以说白了 flux 只是定义了一个规范而已,它的库特别小,因为其实他就提供了一个全局 Dispatcher 的默认实现。至于 store 要怎么写完全是自己决定的,只要能存数据并且在Dispatcher上注册监听就行了。
另外还有一个类似的叫 reflux ,后序有空了仔细看看个
react native
待续