一、初识React
A Javascript Library For Building User Interfaces
React 是一个 UI 库,具体说 React 是做 UI 组件的 JavaScript 库,也就是用 JS 写前端UI。
React是用来构建随着时间顺序数据在不断变化的大规模应用程序。
每次的数据变化,页面也将自动更新,且只更新变化的那一部分。将显示数据变得简单。React不会去操作真正的DOM,用一种更快的内置仿造的DOM来操作差异,为你计算出效率更高的DOM改变。
React实现了一套完整的事件合成机制,能够保持事件冒泡的一致性,跨浏览器执行。
基于JSX脚本开发,通过提供的转换器可转换为JS。
React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。React 来决定如何最高效地更新 DOM。
二、开发环境配置
对于react,我们只关注两点。第一个就是,JSX的支持,另外一个就是,ES6的支持。
好消息是业界领先的 ES6 编译工具 Babel 随着作者被 Facebook 招入麾下,已经内置了对 JSX 的支持,我们只需要配置 Babel 一个编译工具就可以了,配合 webpack 非常方便。
安装 Webpack:npm install -g webpack(前提是已经安装好了node,利用node的npm安装)
Webpack 使用一个名为 webpack.config.js
的配置文件,要编译 JSX,先安装对应的 loader: npm install babel-loader --save-dev
假设我们在当前工程目录有一个入口文件 entry.js
,React 组件放置在一个 components/
目录下,组件被 entry.js
引用,要使用 entry.js
,我们把这个文件指定输出到 dist/bundle.js
,Webpack 配置如下:
var path = require('path');
module.exports = { entry: './entry.js', output: { path: path.join(__dirname, '/dist'), filename: 'bundle.js' }, resolve: { extensions: ['', '.js', '.jsx'] }, module: { loaders: [ { test: /\.js|jsx$/, loaders: ['babel'] } ] } }
resolve
指定可以被 import
的文件后缀。比如 Hello.jsx
这样的文件就可以直接用 import Hello from 'Hello'
引用。
loaders
指定 babel-loader 编译后缀名为 .js
或者 .jsx
的文件,这样你就可以在这两种类型的文件中自由使用 JSX 和 ES6 了。
监听编译: webpack -d --watch
更多关于 Webpack 的介绍 webpack-howto
三、第一个Hello World组件
var Hello = React.createClass({
render: function(){
return <div>Hello {this.props.name} </div>
}
})
ReactDOM.render(<Hello name='World' />,
document.getElementById('container'))
页面将显示Hello World
四、JSX
JSX是可选的,无需在react中必须使用。JSX类似于HTML的XML格式,可以通过函数调用来生成一个模版,将类XML的语法转换成原生的JS。
i> 使用JSX:
var Hello = React.createClass({
render : function() {
return <div>{this.props.name}</div>
}
})
ReactDOM.render({<Hello name='John' /> , root})
ii> 不使用JSX
var Hello = React.createClass({
render : function() {
return React.createElement(
'div',
null,
'Hello',
this.props.name
)
}
})
ReactDOM.render(React.createElement(Hello,{ name: 'John'},root))
可以通过React.createElement()来创建一个树,参数是1)标签名2)属性对象3)子节点
可以通过 {变量名} 来将变量的值作为属性值
JSX熟悉扩散 JSX Spread Attributes
可以用通过 {...obj} 来批量设置一个对象的键值对到组件的属性,注意顺序,因为熟悉可以被覆盖
var Hello = React.createClass({
render: function(){
return <div>name:{this.props.name},age:{this.props.age}</div>
}
})
var user = {'name': 'John','age': 13}
ReactDOM.render(<Hello {...user} name='Andy' /> , document.body)
页面将显示name:Andy,age:13,Andy覆盖了John
组件(标签)需要闭合。
组件的render函数只能返回一个根节点,如果包含多个子组件,需要使用div或者span或者其他组件包裹。
五、组件
1)组件生命周期
一个简单的例子(使用ES6语法)
import {Component}, React from 'react'
import {render} from 'react-dom'
class LikeButton extends Component {
constructor(props) {
super(props)
this.state({like: false})
}
handleClick() {
this.setState(like: !this.state.like)
}
render() {
let text = this.state.like ? 'like' : 'unlike'
return (
<div>
<button onClick = {this.handleClick.bind(this)}>
{text}
</button>
</div>
)
}
}
render(<LinkeButton /> , root)
组件的生命周期主要有三个部分:
i>挂载:组件被注入DOM
ii>更新:组件被重新渲染来决定DOM是否应该被更新
iii>卸载:组件从DOM中移除
每一个部分都有对应的生命周期函数
i>挂载:
componentWillMount()
只会在挂载之前调用一次,在 render
之前调用,你可以在这个方法里面调用 setState
改变状态,并且不会导致额外调用一次 render
componentDidMount()
只会在挂载完成之后调用一次,在 render
之后调用,从这里开始可以通过ReactDOM.findDOMNode(this)
获取到组件的 DOM 节点。
ii>更新:
这些方法不会在首次 render
组件的周期调用
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
componentDidUpdate()
iii>卸载:
componentWillUnmount()
其它:
getInitialState()
初始化 this.state
的值,只在组件挂载之前调用一次。
如果是使用 ES6 的语法,你也可以在构造函数中初始化状态,比如:
class Counter extends Component { constructor(props) { super(props); this.state = { count: props.initialCount }; } render() { // ... } }
getDefaultProps()
只在组件创建时调用一次并缓存返回的对象(即在 React.createClass
之后就会调用)。因为这个方法在实例初始化之前调用,所以在这个方法里面不能依赖 this
获取到这个组件的实例。在组件挂载之后,这个方法缓存的结果会用来保证访问 this.props
的属性时,当这个属性没有在父组件中传入(在这个组件的 JSX 属性里设置),也总是有值的。
如果是使用 ES6 语法,可以直接定义 defaultProps
这个类属性来替代,这样能更直观的知道 default props 是预先定义好的对象值:
Counter.defaultProps = { initialCount: 0 };
render()
必须要添加的方法。组装生成这个组件的 HTML 结构(使用原生 HTML 标签或者子组件),也可以返回 null
或者 false
,这时候 ReactDOM.findDOMNode(this)
会返回 null
。
2) 事件处理
从上一个生命周期的例子可得,可以看到 React 里面绑定事件的方式和在 HTML 中绑定事件类似,使用驼峰式命名指定要绑定的 onClick
属性为组件定义的一个方法 {this.handleClick.bind(this)}。
注意要显式调用 bind(this)
将事件函数上下文绑定要组件实例上,这也是 React 推崇的原则:没有黑科技,尽量使用显式的容易理解的 JavaScript 代码。
React 实现了一个“合成事件”层(synthetic event system),这个事件模型保证了和 W3C 标准保持一致,所以不用担心有什么诡异的用法,并且这个事件层消除了 IE 与 W3C 标准实现之间的兼容问题。
“合成事件”还提供了额外的好处:
“合成事件”会以事件委托(event delegation)的方式绑定到组件最上层,并且在组件卸载(unmount)的时候自动销毁绑定的事件。
3)DOM操作
在使用react时,我们不需要关心DOM怎样去更新UI,只是不断的更新state值,来渲染页面。但有时候,需要我们取到真实的DOM节点。
当组件加载到页面上之后(mounted),你都可以通过 react-dom
提供的 findDOMNode()
方法拿到组件对应的 DOM 元素。
componentDidMound() {
const el = findDOMNode(this);
}
findDOMNode()
不能用在无状态组件上。
另外一种方式就是通过在要引用的 DOM 元素上面设置一个 ref
属性指定一个名称,然后通过 this.refs.name
来访问对应的 DOM 元素。
如果 ref
是设置在原生 HTML 元素上,它拿到的就是 DOM 元素,如果设置在自定义组件上,它拿到的就是组件实例,这时候就需要通过 findDOMNode
来拿到组件的 DOM 元素。
4)组件件通信
父子组件间通信
这种情况下很简单,就是通过 props
属性传递,在父组件给子组件设置 props
,然后子组件就可以通过 props
访问到父组件的数据/方法,这样就搭建起了父子组件间通信的桥梁。
import React, { Component } from 'react'
import { render } from 'react-dom'
class GroceryList extends Component {
handleClick(i) {
console.log('You clicked: ' + this.props.items[i])
}
render() {
return (
<div>
{this.props.items.map((item, i) => {
return (
<div onClick={this.handleClick.bind(this, i)} key={i}>{item}</div>
)
})}
</div>
)
}
}
render(
<GroceryList items={['Apple', 'Banana', 'Cranberry']} />, mountNode
)
div
可以看作一个子组件,指定它的 onClick
事件调用父组件的方法。