一、React简介

React 是 Facebook 推出的一个用来构建用户界面的 JavaScript 库。主要用于构建UI,很多人使用 React 作为 MVC 架构的 V 层,它起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源。

React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。有以下几个特点:

  • 简单:仅仅只要表达出你的应用程序在任一个时间点应该长的样子,然后当底层的数据变了,React 会自动处理所有用户界面的更新。

  • 声明式设计:React采用声明范式,可以轻松描述应用。数据变化后,React 概念上与点击”刷新”按钮类似,但仅会更新变化的部分。

  • 高效:React通过对DOM的模拟,最大限度地减少与DOM的交互。

  • 灵活:React可以与已知的库或框架很好地配合。

  • JSX:JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。

  • 组件:通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。

  • 单向响应的数据流: React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。

二、API

1、顶层API

JSX 是 Facebook 团队提出的一个语法方案,可以在 JavaScript 的代码直接中使用 。

  • React.createClass
ReactClass createClass(object specification)

创建一个组件类,并作出定义。

  • React.createElement
ReactElement createElement( 
	string / ReactClass type, 
	[object props], 
	[children ...] 
)

创建并返回一个新的指定类型的 ReactElement。type 参数可以是一个 html 标签名字字符串(例如,”div”、”span”,等等),或者是 ReactClass 通过 React.createClass 创建的)。

  • React.createFactory
factoryFunction createFactory( string / ReactClass type )

返回一个生成指定类型 ReactElements 的函数。

  • ReactDOM.render
ReactComponent render(
	ReactElement element, 
	DOMElement container, 
	[function callback] 
)

渲染一个 ReactElement 到 DOM 中,放在 container 指定的 DOM 元素下,返回一个到该组件的引用。

如果 ReactElement 之前就被渲染到了 container 中,该函数将会更新此 ReactElement,仅改变需要改变的 DOM 节点以展示最新的 React 组件。

如果提供了可选的回调函数,则该函数将会在组件渲染或者更新之后调用。

  • React.unmountComponentAtNode
boolean unmountComponentAtNode(DOMElement container)

从 DOM 中移除已经挂载的 React 组件,清除相应的事件处理器和 state。如果在 container 内没有组件挂载,这个函数将什么都不做。如果组件成功移除,则返回 true;如果没有组件被移除,则返回 false。

  • React.isValidElement
boolean isValidElement(* object)

判断对象是否是一个 ReactElement。

  • React.DOM

React.DOM 运用 React.createElement 为 DOM 组件提供了方便的包装。该方式仅在未使用 JSX 的时候适用。例如:

React.DOM.div(null, 'Hello World!')
  • React.PropTypes

React.PropTypes 包含了能与组件 propTypes 对象共用的类型,用于验证传入组件的 props。

  • React.Children

    React.Children 为处理 this.props.children 这个封闭的数据结构提供了有用的工具。

    • React.Children.map

        object React.Children.map(object children, function fn [, object context])
      

      在每一个直接子级(包含在 children 参数中的)上调用 fn 函数,此函数中的 this 指向 上下文。如果 children是一个内嵌的对象或者数组,它将被遍历:不会传入容器对象到 fn 中。如果 children 参数是 null 或者 undefined,那么返回 null 或者 undefined 而不是一个空对象。

    • React.Children.forEach

        React.Children.forEach(object children, function fn [, object context])
      

      类似于 React.Children.map(),但是不返回对象。

    • React.Children.count

        number React.Children.count(object children)
      

      返回 children 当中的组件总数,和传递给 map 或者 forEach 的回调函数的调用次数一致。

    • React.Children.only

        object React.Children.only(object children)
      

      返回 children 中仅有的子级,否则抛出异常。(适用于只有一个child的情况)

2、组件API

  • setState
setState(object nextState[, function callback])

合并 nextState 和当前 state。这是在事件处理函数中和请求回调函数中触发 UI 更新的主要方法。另外,也支持可选的回调函数,该函数在 setState 执行完毕并且组件重新渲染完成之后调用。

注意:绝对不要直接改变 this.state,因为在之后调用 setState() 可能会替换掉你做的更改,把 this.state 当做不可变的。setState() 将总是触发一次重绘,除非在 shouldComponentUpdate() 中实现了条件渲染逻辑。
  • replaceState
replaceState(object nextState[, function callback])

类似于 setState(),但是删除之前所有已存在的 state 键,这些键都不在 nextState 中。

  • forceUpdate
forceUpdate([function callback])

调用 forceUpdate() 将会导致 render() 方法在相应的组件上被调用,并且子级组件也会调用自己的 render()。如果 render() 方法从 this.props 或者 this.state 之外的地方读取数据,你需要通过调用 forceUpdate() 告诉 React 什么时候需要再次运行 render()。如果直接改变了 this.state,也需要调用 forceUpdate()。

通常情况下,应该尽量避免所有使用 forceUpdate() 的情况,在 render() 中仅从 this.props 和 this.state 中读取数据。这会使应用大大简化,并且更加高效。

  • isMounted
bool isMounted()

如果组件渲染到了 DOM 中,isMounted() 返回 true。可以使用该方法保证 setState() 和 forceUpdate() 在异步场景下的调用不会出错。

三、React组件

1、简单组件

创建一个简单的HelloMessage组件:

var HelloMessage = React.createClass({ 
	render: function() { 
		return <h1>Hello World</h1>; 
	} 
}); 
ReactDOM.render( <HelloMessage />, document.getElementById('example') );

2、复合组件

可以通过创建多个组件来合成一个组件,即把组件的不同功能点进行分离:

var LinkButton = React.createClass({
	render: function(){
		return (
			<button className="link">按钮</button>
		);
	}
});
var MenuButton = React.createClass({
	render: function(){
	return (
			<button className="menu">菜单按钮</button>
		);
	}
});
var ToolBar = React.createClass({
	render: function(){
		return (
			<div id="toolBar" className="toolbar">
				<LinkButton/>
				<MenuButton/>
			</div>
		);
	}
});
ReactDOM.render(<ToolBar/>, document.querySelector("#tool"));

四、React State

React 把组件看成是一个状态机(State Machines),通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。

React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。React 来决定如何最高效地更新 DOM。

常用的通知 React 数据变化的方法是调用 setState(data, callback)。这个方法会合并(merge) data 到 this.state,并重新渲染组件。渲染完成后,调用可选的 callback 回调。大部分情况下不需要提供 callback,因为 React 会负责把界面更新到最新状态。

  • 哪些组件应该有state?

大部分组件的工作应该是从 props 里取数据并渲染出来。但是,有时需要对用户输入、服务器请求或者时间变化等作出响应,这时才需要使用 State。

尝试把尽可能多的组件无状态化。 这样做能隔离 state,把它放到最合理的地方,也能减少冗余并,同时易于解释程序运作过程。

常用的模式是创建多个只负责渲染数据的无状态(stateless)组件,在它们的上层创建一个有状态(stateful)组件并把它的状态通过 props 传给子级。这个有状态的组件封装了所有用户的交互逻辑,而这些无状态组件则负责声明式地渲染数据。

  • 哪些应该作为state?

State 应该包括那些可能被组件的事件处理器改变并触发用户界面更新的数据。当创建一个状态化的组件时, state 应该仅包括能表示用户界面状态所需的最少数据,在 render() 里再根据 state 来计算你需要的其它数据。因为如果在 state 里添加冗余数据或计算所得数据,需要你经常手动保持数据同步,不能让 React 来帮你处理。

  • 哪些不该作为state?

    • 计算所得数据:把所有的计算都放到 render() 里更容易保证用户界面和数据的一致性。

    • React 组件:在 render() 里使用当前 props 和 state 来创建它。

    • 基于 props 的重复数据:尽可能使用 props 来作为惟一数据来源。只有当未来的 props 可能会变化,需要知道它以前值的时候需要把 props 保存到state。

当创建一个状态化的组件时,只在state中存入表示它状态的最少数据,在 render() 里再根据 state 来计算你需要的其它数据。因为如果在 state 里添加冗余数据或计算所得数据,需要你经常手动保持数据同步,不能让 React 来帮你处理。

var LikeButton = React.createClass({
	getInitialState: function() {
		return {liked: false};//默认state
	},
	render: function() {
		return (
			<p>You {this.state.liked ? "like" : "don't like"} this.</p>
		);
	}
});
ReactDOM.render(<LikeButton />,  document.getElementById('example'));

五、React Props

state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。

var HelloMessage = React.createClass({
	getDefaultProps: function() {
		return {name: "Lucy"};//默认props
	},
	render: function() {
		return <h1>Hello {this.props.name}</h1>;
	}
});
ReactDOM.render(<HelloMessage name="Jack"/>, document.getElementById('example'));

1、Props验证

随着应用不断变大,保证组件被正确使用变得非常有用。React.PropTypes 提供很多验证器 (validator) 来验证传入数据的有效性。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。

2、验证器说明

  • 可以被渲染的对象 numbers, strings, elements 或 array
optionalNode: React.PropTypes.node 
  • 可以声明 prop 为指定的 JS 基本数据类型,默认情况,这些数据是可选的
optionalArray: React.PropTypes.array
optionalBool: React.PropTypes.bool
optionalFunc: React.PropTypes.func
optionalNumber: React.PropTypes.number
optionalObject: React.PropTypes.object
optionalString: React.PropTypes.string
  • React 元素
optionalElement: React.PropTypes.element
  • 用instanceof 操作符声明 prop 为类的实例
optionalMessage: React.PropTypes.instanceOf(Message)
  • 用 enum 来限制 prop 只接受指定的值
optionalEnum: React.PropTypes.oneOf(['News', 'Photos'])
  • 可以是多个对象类型中的一个
optionalUnion: React.PropTypes.oneOfType([ 
	React.PropTypes.string, 
	React.PropTypes.number,
	React.PropTypes.instanceOf(Message) 
])
  • 指定类型组成的数组
optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number)
  • 指定类型的属性构成的对象
optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number)
  • 特定 shape 参数的对象
optionalObjectWithShape: React.PropTypes.shape({ 
	color: React.PropTypes.string, 
	fontSize: React.PropTypes.number 
})
  • 任意类型加上 isRequired 来使 prop 不可空
requiredFunc: React.PropTypes.func.isRequired
  • 不可空的任意类型
requiredAny: React.PropTypes.any.isRequired
  • 自定义验证器。如果验证失败需要返回一个 Error 对象。
customProp: function(props, propName, componentName) { 
	if (!/matchme/.test(props[propName])) { 
		return new Error('Validation failed!'); 
	}
}

样例:

var title = "Hello React!";
var MyTitle = React.createClass({
	propTypes: {
		title: React.PropTypes.string.isRequired,
	},
	render: function() {
		return <h1> {this.props.title} </h1>;
	}
});
ReactDOM.render(<MyTitle title={title} />, document.getElementById('example'));
ReactDOM.render(<MyTitle />, document.getElementById('example'));

没有传title属性时,控制台会输出:

Warning: Failed propType: Required prop `title` was not specified in `MyTitle`.

六、React事件

React 里只需把事件处理器(event handler)以骆峰命名(camelCased)形式当作组件的 props 传入即可,就像使用普通 HTML 那样。React 内部创建一套合成事件系统来使所有事件在 IE8 和以上浏览器表现一致。也就是说,React 知道如何冒泡和捕获事件,而且你的事件处理器接收到的 events 参数与 W3C 规范 一致,无论你使用哪种浏览器。

var LikeButton = React.createClass({
	getInitialState: function() {
		return {liked: false};
	},
	handleClick: function(){
		this.setState({liked: !this.state.liked});
	},
	render: function() {
		return (
			<p onClick={this.handleClick} >You {this.state.liked ? "like" : "don't like"} this. Click to toggle.</p>
		);
	}
});
ReactDOM.render(<LikeButton />,  document.getElementById('test'));

七、React组件生命周期

React组件的生命周期主要有三种:挂载(mount)、更新(update)、卸载(unmount);

1、生命周期方法:

  • componentWillMount

在渲染前调用,在客户端也在服务端。

  • componentDidMount

在第一次渲染后调用,只在客户端。 React 组件的数据可以通过在此方法中使用 Ajax 来获取,当从服务端获取数据后可以将数据存储在 state 中,再用 this.setState 方法重新渲染 UI。

  • componentWillReceiveProps

在组件接收到一个新的prop时被调用。这个方法在初始化时不会被调用。

  • shouldComponentUpdate

返回一个布尔值。在组件接收到新的props或者state时被调用,在初始化时或者使用forceUpdate时不被调用。

  • componentWillUpdate

在组件接收到新的props或者state但还没有render时被调用,在初始化时不会被调用。

  • componentDidUpdate

在组件完成更新后立即调用,在初始化时不会被调用。

  • componentWillUnmount

在组件从 DOM 中移除的时候立刻被调用。当使用异步加载数据时,在组件卸载前使用此事件来取消未完成的请求。

2、样例

var MyComponent = React.createClass({
	getInitialState: function(){
		console.log('getInitialState');
		return {count: 0};
	},
	getDefaultProps: function(){
		console.log('getDefaultProps');
		return {message: 'Hello!'};
	},
	componentWillMount: function(){
		console.log('comopnentWillMount');
	},
	componentDidMount: function(){
		console.log('componentDidMount');
	},
	componentWillReceiveProps: function(){
		console.log('componentWillReceiveProps');
	},
	componentWillUpdate: function(){
		console.log('componentWillUpdate');
	},
	componentDidUpdate: function(){
		console.log('componentDidUpdate');
	},
	componentWillUnmount: function(){
		console.log('componentWillUnmount');
	},
	shouldComponentUpdate: function(){
		console.log('shouldComponentUpdate');
		return true;
	},
	handleCount: function(){
		this.setState({count: this.state.count + 1});
	},
	unmountComponent: function(){
		ReactDOM.unmountComponentAtNode(document.querySelector('#example'));
	},
	render: function(){
		console.log('render');
		return (
			<div>
				<p>State: {this.state.count} Prop: {this.props.message} </p>
				<button onClick={this.handleCount}>count</button>
				<button onClick={this.unmountComponent}>unmount</button>
			</div>
		);
	}
});

var MyContainer = React.createClass({
	getInitialState: function(){
		return {count: 0};
	},
	passOn: function(){
		this.setState({count: this.state.count + 1});
	},
	render: function(){
		return (
			<div>
				<MyComponent message={"message" + this.state.count}/>
				<button onClick={this.passOn}>传递props</button>
			</div>
		);
	}
});

ReactDOM.render(<MyContainer/>, document.querySelector('#example'));
  • 首次渲染:

getDefaultProps —- getInitialState —- comopnentWillMount —- render —- componentDidMount

  • 更新state:

shouldComponentUpdate —- componentWillUpdate —- render —- componentDidUpdate

  • 更新prop: componentWillReceiveProps —- shouldComponentUpdate —- componentWillUpdate —- render —- componentDidUpdate

  • 卸载组件:

componentWillUnmount

八、React refs

1、使用场景

在组件的render方法返回UI结构后,想要调用从render返回的组件实例的方法。例如:在<input/>元素的值更新为一个空字符串后使它聚焦。

  • ref 的字符串属性
var App = React.createClass({
	getInitialState: function() {
		return {userInput: ''};
	},
	handleChange: function(e) {
		this.setState({userInput: e.target.value});
	},
	clearAndFocusInput: function() {
		this.setState({userInput: ''}, function(){
			this.refs.myInput.focus();// We wish to focus the <input /> now!
		}); // Clear the input	
	},
	render: function() {
		return (
			<div>
				<div onClick={this.clearAndFocusInput}>Click to Focus and Reset</div>
				<input ref="myInput" value={this.state.userInput}	onChange={this.handleChange}/>
			</div>
		);
	}
});
ReactDOM.render(<App/>, document.querySelector('#test-refs'));
  • ref 的回调属性

ref 属性可以是一个回调函数,而不是一个字符串名字。这个回调函数在组件安装后立即执行。被引用的组件作为一个参数传递,且回调函数可以立即使用这个组件,或保存供以后使用(或实现这两种行为)。

<input ref={ function(component){ ReactDOM.findDOMNode(component).focus();} } />
//component.focus()  	
//ReactDOM.findDOMNode(component) === component
  • 小结

调用一个特定子实例的方法, refs 是一个很好的方式,而通过流动式接收的 props 和 state 的方式可能是不方便的。然而,对于你的应用程序中的流动数据来说,refs 应该不是你的首选抽象特性。

九、React表单组件

诸如 <input><textarea><option> 这样的表单组件不同于其他组件,因为他们可以通过用户交互发生变化。这些组件提供的界面使响应用户交互的表单数据处理更加容易。

1、交互属性:

  • 表单组件支持几个受用户交互影响的属性:

value:用于 <input><textarea> 组件。

checked:用于类型为 checkbox 或者 radio 的 <input> 组件。

selected:用于 <option> 组件。

在 HTML 中,<textarea> 的值通过子节点设置;在 React 中则应该使用 value 代替。表单组件可以通过 onChange 回调函数来监听组件变化。

  • 当用户做出以下交互时,onChange 执行并通过浏览器做出响应:

<input> 或 <textarea> 的 value 发生变化时。

<input> 的 checked 状态改变时。

<option> 的 selected 状态改变时。

和所有 DOM 事件一样,所有的 HTML 原生组件都支持 onChange 属性,而且可以用来监听冒泡的 change 事件。

2、受限组件

设置了 value 的 <input> 是一个受限组件。 对于受限的 <input>,渲染出来的 HTML 元素始终保持 value 属性的值。

render: function(){return (<form><input type="text" value="hello"/></form>);}

上面的代码将渲染出一个值为 Hello! 的 input 元素。用户在渲染出来的元素里输入任何值都不起作用,因为 React 已经赋值为 Hello!。同时在控制台会有警告(Warning)。

如果想响应更新用户输入的值,就得使用 onChange 事件:

var MyForm = React.createClass({
	getInitialState: function(){
		return {value: 'Hello!'};
	},
	handleChange: function(event){
		this.setState({value: event.target.value});
	},
	render: function(){
		return (
			<form>
				<input type="text" value={this.state.value} onChange={this.handleChange}/>
			</form>
		);
	}
});
ReactDOM.render(<MyForm/>, document.querySelector('#myform'));

3、不受限组件

没有设置 value(或者设为 null) 的 <input> 组件是一个不受限组件。对于不受限的 <input> 组件,渲染出来的元素直接反应用户输入。

render: function(){
	return (<form><input type="text" /></form>);
}

上面的代码将渲染出一个空值的输入框,用户输入将立即反应到元素上。和受限元素一样,使用 onChange 事件可以监听值的变化。

使用 defaultValue 属性可以给组件设置一个非空的初始值。

var MyForm = React.createClass({
	getInitialState: function(){
		return {value: 'Hello!'};
	}, 	render: function(){
		return (
			<form>
				<input type="text" defaultValue={this.state.value}/>
			</form>
		);
	}
});
ReactDOM.render(<MyForm/>, document.querySelector('#myform'));

同样地, 类型为 radio、checkbox 的<input> 支持 defaultChecked 属性, <select> 支持 defaultValue 属性。

十、React插件

React.addons 是为了构建 React 应用而放置的一些有用工具的地方。 使用插件的时候需要用 react-with-addons.js 替换 react.js。

ReactLink是一种简单表达React双向绑定的方式。

在React里面,数据流是一个方向的:从拥有者到子节点(单项绑定)。然而,有很多应用需要你读取一些数据,然后传回给你的程序(双向绑定)。例如,在开发表单的时候,当你接收到用户输入时,你将会频繁地想更新某些React state。

在React中,你可以通过监听一个”change”事件来实现这个功能,从你的数据源(通常是DOM)读取,然后在你某个组件上调用setState()。

使用ReactLink可以实现双向绑定 – 隐式地强制使DOM里面的数据总是和某些React state保持一致 。

注意:

ReactLink仅仅是一个onChange/setState()模式的简单包装和约定。它不会从根本上改变数据在你的React应用中如何流动。

var WithLink = React.createClass({
    mixins: [React.addons.LinkedStateMixin],
    getInitialState: function() {
        return {message: 'Hello!'};
    },
    render: function() {
        return (
            <div>
                <p>Message: {this.state.message}</p>
                <input type="text" valueLink={this.linkState('message')} />
            </div>
        );
    }
});
ReactDOM.render(<WithLink/>, document.querySelector('#withLink'));

LinkedStateMixin给React组件添加一个叫做linkState()的方法。linkState()返回一个ReactLink对象,包含React state当前的值和一个用来改变它的回调函数,ReactLink对象也可以在树中作为props被向上传递或者向下传递。

<input type="checkbox" checkedLink={this.linkState('hobby')}/>

使用上述写法在控制台会有警告:
Warning: valueLink prop on input is deprecated; set value and onChange instead .

onChange方式如下:

var NoLink = React.createClass({    
	getInitialState: function() {
        return {message: 'Hello!'};
    },
    handleChange: function(event) {
        this.setState({message: event.target.value});
    },
    render: function() {
        return (
            <div>
                <p>Message: {this.state.message}</p>
                <input type="text" value={this.state.message} onChange={this.handleChange} />
            </div>
        );
    }
});
ReactDOM.render(<NoLink/>, document.querySelector('#noLink'));

2、动画 - CSSTransitionGroup

CSSTransitionGroup插件可以简单地实现基本的CSS动画和过渡。

3、性能分析工具 - Perf

ReactPerf 是一个分析工具,通过使用钩子函数查看应用总体的性能概览。

  • 通用API

    • Perf.start()Perf.stop()

      开始/停止检测。React的中间操作被记录下来,用于下面的分析。花费一段微不足道的时间的操作会被忽略。

    • Perf.printInclusive(measurement)

      打印花费的总时间。如果不传递任何参数,默认打印最后的所有检测记录。

    • Perf.printExclusive(measurement)

      “独占(Exclusive)”时间不包含挂载组件的时间:处理props,getInitialState,调用componentWillMount和componentDidMount等。

    • Perf.printWasted(measurement)

      “浪费”的时间被花在根本没有渲染任何东西的组件上,例如界面渲染后保持不变,没有操作DOM元素。

    • Perf.printOperations(measurement)

      “浪费”的时间被花在根本没有渲染任何东西的组件上,例如界面渲染后保持不变,没有操作DOM元素。

十一、测试框架Jest

Jest是一个JavaScript测试框架,被Facebook用来测试所有的JavaScript代码包括React应用。

1、安装与使用

  • 执行npm install --save-dev jest安装Jest

  • 创建__tests__目录

  • 在package.json中添加:”scripts”: { “test”: “jest” }

  • 运行:npm test

2、样例

  • 目录结构

  • sum.js
module.exports = function(a, b){
	return a + b;
}
  • sum-test.js
test('adds 1 + 2 to equal 3', function(){
	var sum = require('../js/sum');
	expect(sum(1, 2)).toBe(3);
});
  • 运行npm test

参考资料

React教程

极客学院React