React入门笔记(一):简介和JSX
React入门笔记(二):组件和AJAX
React入门笔记(三):表单、事件、Ref属性
React中的Diff算法——Christopher Chedeau(原文翻译)
一、简介
React是一个用于构建用户界面的JavaScript库,可以认为是MVC中的V(视图),由于React的代码逻辑简单,越来越多的人开始使用。
React的优点如下:
- 声明式设计:采用声明范式,可以轻松描述应用。
- 高效:通过对DOM的模拟,最大限度地减少与DOM的交互。
- 灵活:可以与已知的库或框架很好地结合使用。
- JSX:JSX是JavaScript语法的扩展,React并不一定使用JSX,但是建议使用。
- 组件:通过React构建组件,使代码复用性更高。
- 单向相应的数据流:实现了单向响应的数据流,从而减少重复代码,所以比传统数据绑定更简单。
React库以及一些常用的依赖库:
- react.js:必需的核心代码
- react-dom.js:dom渲染
- react-dom-fiber.js:虚拟调用栈,用于任务调度
- react-dom-server.js:dom服务端渲染
- react-with-addons.js:扩展功能
- browser.js:Babel转换器的核心代码,用于将ES6代码转为ES5代码,也是jsx的编译依赖库,在react 0.14后被使用
- jsxtransformer.js:jsx的编译依赖库,用于React 0.14之前
- …
二、JSX
JSX是一个看起来很像XML的JavaScript语法扩展,其优点是:
- 执行更快,因为在编译为JavaScript代码后进行了优化;
- 类型安全的,在编译过程就能发现错误;
- 使用JSX编写模板更加简单快速;
JSX看起来类似HTML,元素的自定义属性需要使用data-前缀,以下是一个简单示例。ReactDOM.render
是React的最基本方法,用于将模板转为HTML语言,并插入指定的DOM节点。
// ===== DEMO 01
// id为'example'的元素作为这一小段html片段的container
// 代码中嵌套多个HTML标签,需要使用一个标签元素包裹它,
// 所以此处最外层的div是必须的!!
ReactDOM.render(
<div>
<h1>Hello</h1>
<h2>world!</h2>
<p data-myattribute="attr-name">
React is easy to learn!</p>
</div>
,
document.getELementById('example');
);
JSX位置
JSX语法与JavaScript语法并不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel"
。
JSX可以像普通JavaScript代码一样直接写到html文件中,用
<script type="text/babel"></script>
包裹代码片。React JSX代码可以放在一个独立文件上,比如创建一个
helloworld.js
文件,代码如DEMO 01
,然后在HTML文件中引入该JS文件:// ===== DEMO 02 <body> <div id="example"></div> <script type="text/babel" src="helloworld.js"></script> </body>
开启花括号大法
JSX允许HTML和JavaScript的混写,JSX的基本语法规则,就是遇到HTML标签(以<
开头)就用HTML规则解析,遇到代码块(以{
开头),就用JavaScript规则解析。所以在html代码中插入一些JavaScript代码块时,就通通用花括号括起来,以下详细介绍:
JavaScript表达式
在JSX中使用JavaScript表达式可以直接写在花括号{}
中,但是在JSX中不能使用if else
表达式,但可以使用conditional(三元运算) 表达式来替代,如{i==1?'True':'False'}
。样式
React推荐使用内联样式,可以使用camelCase语法来设置内联样式(这样是否会很占用名字空间?)。React会在指定元素数字后自动添加px
,以下为一个简单示例:// ===== DEMO 03 var myStyle = { fontSize: 100, color: 'FF0000' }; ReactDOM.render( <h1 style={myStyle}>Hello world!</h1>, document.getElementById('example') );
注释
标签内注释要写在花括号中,举栗子如下:// ===== DEMO 04 ReactDOM.render( /*标签外注释……*/ <div> <h1>Hello world!</h1> {/*标签内注释……*/} </div>, document.getElementById('example') );
数组
JSX允许在模板中插入数组,数组会自动展开所有内容,相当于数组中的所有元素拼接在一起,举栗子如下:// ==== DEMO 05 var arr = [ <h1>Hello</h1>, <h2>world!</h2>, ]; ReactDOM.render( <div>{arr}</div>, document.getELementById('example') ); // Hello // world!
React元素 vs React组件
React元素(React element)
React中最小基本单位,可以用JSX语法简单的创建一个React元素:const element = <div className="element">React element</div>
React元素不是真实的DOM元素而只是js的普通对象(plain objects),所以也无法直接调用DOM原生API,只有这个元素被渲染完成后,才能通过选择器获取对应的DOM元素。按照React有限状态机的设计思想,应当用状态和属性来表述组件,要尽量避免DOM操作,即便要进行DOM操作,也应当使用React提供的接口
ref
和getDOMNode()
。除了使用JSX语法,我们还可以使用React.creatElement()
和React.cloneElement()
来构建React元素,这两个函数的参数如下所示。// ==== DEMO 06 React.createElement( type, // 标签名,如`div`,`span`,React组件等 [props], // 传入属性 [...children] // 该参数及之后都是组件的子组件 ) React.cloneElement( element, // React元素,新添加的元素会并入原有属性 [props], [...children] )
React组件(React component)
有三种构建组件的方法。React.createClass()
、ES6 class
和无状态函数。- React.createClass():最早、兼容性最好的方法。在0.14版本前官方指定的组件写法。
- ES6 class:目前官方推荐的使用方法,使用了ES6标准语法来构建,实际仍然是调用了
React.createClass()
来实现,ES6 class
的生命周期和自动绑定方式与React.createClass()
略有不同。 无状态函数:使用函数构建的无状态组件,传入
props
和context
两个参数,没有state
,除了render()
,没有其他生命周期方法。// ==== DEMO 07 // React.createClass() var Greeting = React.createClass({ render: function(){ return <h1>Hello {this.props.name}</h1>; } }); // ES6 class class Greeting extends React.Component{ render(){ return <h1>Hello {this.props.name}</h1>; } } // 无状态函数 function Greeting(props){ return <h1>Hello {this.props.name}</h1>; }
React.createClass()
和ES6 class
构建的组件的数据结构是类,无状态组件数据结构是函数,但是在React被视为是一样的。
元素与组件的区别
组件由元素构成,元素数据结构是普通对象,而组件数据结构是类或纯函数。
元素就是一个用于描述出现在页面中的 DOM 节点或者 React 组件的纯对象。元素可以在自己的属性中包含其它元素。创建一个元素的成本很低,一旦元素被创建之后,就不再发生变化。元素并不是一个真正的实例,而是一种用来告诉 React 你希望哪些东西显示在页面中的方式。 一个描述组件的元素也属于元素的范畴,就像一个描述 DOM 节点的元素一样,它们可以互相嵌套混搭使用。
React 组件可以用好几种方式声明,可以是一个包含 render() 方法的类,也可以是一个简单的函数,不管怎么样,它都是以属性作为输入,返回元素树作为输出。函数式组件没有实例,类组件才有。
除此之外,还有几点区别要注意:this.props.children
:this.props
对象的属性与组件的属性一一对应,但是有一个例外,就是this.props.children
属性。它表示==组件的所有子节点==。在JSX中,被元素嵌套的元素会以属性children的方式传入该元素的组件。当仅嵌套一个元素时,children是一个React元素,当嵌套多个元素时,children是一个React元素的数组(可以使用React.Children.map来遍历)。可以直接把children写入JSX中,但如果需要传入新属性,就要用到React.cloneElement()
来构建新的元素。// map方法对于嵌套一个元素,多个元素或者没有嵌套都适用 render() { var child = this.props.children return ( {/*代码中嵌套多个HTML标签,需要使用一个标签元素包裹它, 所以此处最外层的div是必须的!!*/} <div> {React.Children.map(this.props.children, function(child){ return child; })} </div> ); } // 仅仅适用于只嵌套一个React元素 render() { var child = this.props.children return <div>{ React.cloneElement(child, {tip: 'right way'}) }</div> }
这里需要注意,
this.props.children
的值有三种可能:如果当前组件没有子节点,它就是undefined
;如果有一个子节点,数据类型是object
;如果有多个子节点,数据类型就是array
。所以,处理 this.props.children 的时候要小心。React提供一个工具方法React.Children
来处理this.props.children
。我们可以用React.Children.map
来遍历子节点,而不用担心this.props.children
的数据类型是undefined
还是object
。用户组件:有时组件可以让用户以属性的方式传入自定义的组件,来提升组件的灵活性。这个属性传入的就应该是React元素,而非React组件。使用React元素可以让用户传入自定义组件的同时,为组件添加属性。同样,可以使用
React.cloneElement()
为自定义组件添加更多属性,或替换子元素。<MyComponent tick={ <UserComponent tip="OK"/> }/>
在JSX中的渲染
React可以渲染React元素或React组件。要渲染HTML标签,只需要在JSX里使用小写字母开头的标签名。var myDivELement = <div className="foo" />; ReactDOM.render(myDivElement, document.getELementById('example'));
要渲染React组件,只需创建一个大写字母开头的本地变量。
var MyComponent = React.createClass({/*...*/}); var myElement = <MyComponent someProperty={true} />; ReactDOM.render(myElement, document.getElementById('example'));
React的JSX使用大小写的约定来区分本地组件的类和HTML标签。
三、感想与注意点
- 由于JSX就是JavaScript,一些标识符像class和for不建议作为XML属性名。作为替代,React DOM使用className和htmlFor来做对应的属性。
- React通过元素来描述组件的DOM节点或者子组件,从而达到切分组件的目的,降低组件的复杂度,并且使得组件更加易于解耦。组件实际上是元素树的封装,这也是React能够进行局部渲染的前提。
四、参考
[1] React教程
[2] React元素和组件的区别
[3] React 组件、元素以及实例
[4] React 入门实例教程