在教程开端先说些题外话,我喜欢在学习一门新技术或读过一本书后,写一篇教程或总结,既能帮助消化,也能加深印象和发现本身未注意的细节,写的过程其实仍然是一个学习的过程。有个记录的话,在将来须要用到相关知识时,也方便本身查阅。css
React既不是一个MVC框架,也不是一个模板引擎,而是Facebook在2013年推出的一个专一于视图层,用来构建用户界面的JavaScript库。它推崇组件式应用开发,而组件(component)是一段独立的、可重用的、用于完成某个功能的代码,包含了HTML、CSS和JavaScript三部份内容。React为了保持灵活性,只实现了核心功能,提供了少许的API,一些DOM方法都被剥离到了react-dom.js中。这么作虽然轻巧,但有时候要完成特定的业务场景,仍是须要与其余库结合,例如Redux、Flux等。React不只让函数式编程越发流行,还引入了JSX语法(能把HTML嵌入进JavaScript中)和Virtual DOM技术,大大提高了更新页面时的性能。在React中,每一个组件的呈现和行为都由特定的数据所决定,而数据的流动都是单向的,即单向数据流。在编写React时,推荐使用ES6语法,官方的文档也使用了ES6语法,所以,在学习React以前,建议对ES6有所了解,避免没必要要的困惑。html
下面是一段较为完整的React代码,本文大部分的示例代码来源于此,阅读下面的代码能够对React有一个感性的认识。node
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; class Search extends Component { //static defaultProps = { // url: "http://jane.com" //}; constructor(props) { super(props); this.state = { txt: "请输入关键字" }; } componentWillMount() { console.log("component will mount"); } componentDidMount() { console.log("component did mount"); this.refs.txt.addEventListener("blur", (e) => { this.getValue(e); }); console.log(this.refs) } handle(keyword, e) { console.log(keyword); console.log(this); console.log(this.select.value); } getValue(e) { console.log(e.target.value); } refresh(e) { this.setState({ type: e.target.value }); } render() { console.log("render"); let {type} = this.state; console.log(type); return ( <div> {this.props.children} <select value={type} onChange={this.refresh.bind(this)}> <option value="1">标题</option> <option value="2">内容</option> </select> <select defaultValue={2} ref={(select) => this.select = select}> <option value="1">标题</option> <option value="2">内容</option> </select> <input placeholder={this.state.txt} ref="txt" defaultValue="教程" style={{marginLeft:10, textAlign:"center"}}/> <button className="btn" data-url={this.props.url} onClick={this.handle.bind(this, "REACT")}>{"<搜索>"}</button> </div> ); } } Search.defaultProps = { url: "http://jane.com" }; ReactDOM.render( <Search url="http://www.pwstrick.com"> <h1>React扫盲教程</h1> </Search>, document.getElementById("container") );
JSX是对JavaScript语法的一种扩展,它看起来像HTML,一样拥有清晰的DOM树状结构和元素属性,以下代码所示。但与HTML不一样的是,为了不自动插入分号时出现问题,在最外层得用圆括号包裹,而且必须用一个元素包裹(例以下面的<div>元素)其它元素,全部的元素还必须得闭合。react
(<div>
<input placeholder={this.state.txt} />
<button className="btn">{"<搜索>"}</button>
</div>)
1)元素git
JSX中的元素分为两种:DOM元素和组件元素(也叫React元素),DOM元素就是HTML文档映射的节点,首字母要小写;而组件元素的首字母要大写。不管是DOM元素仍是组件元素,最终都会经过React.createElement()方法转换成JSON对象,以下所示,JSON对象是简化过的。github
//React.createElement()方法 React.createElement("div", null, [ React.createElement("input", { placeholder: `${this.state.txt}` }, null), React.createElement("button", { className: "btn" }, "<搜索>") ]); //简化过的JSON对象 { type: "div", props: { children: [ { type: "input", props: { placeholder: `${this.state.txt}` } }, { type: "button", props: { className: "btn", children: "<搜索>" } } ] } }
因为JSX中的元素可以被编译成对象,所以还能够把它们应用到条件、循环等语句中,或者做为一个值应用到变量和参数上。算法
2)属性npm
JSX中的属性要用驼峰的方式表示,例如maxlength要改为maxLength、readonly要改为readOnly。还有两个比较特殊的属性:class和for,因为这两个是JavaScript中的关键字,所以要更名,前者改为className,后者改为htmlFor。编程
JSX中的属性值不只能够是字符串,还能够是表达式。若是是表达式,那么就要用花括号(“{}”)包裹。而在JSX中,任何位置均可以使用表达式。浏览器
有一点要注意,为了防止XSS的攻击,React会把全部要显示到DOM中的字符串进行转义,例如“<搜索>”转义成“<搜索>”。
3)Virtual DOM
众所周知,DOM操做会引发重绘和重排,而这是很是消耗性能的。React能把元素转换成对象,也就是说能够用对象来表示DOM树,而这个存在于内存中的对象正是Virtual DOM。Virtual DOM至关于一个缓存,当数据更新后,React会从新计算Virtual DOM,再与上一次的Virtual DOM经过diff算法作比对(以下图所示),最后只在页面中更新修改过的DOM。因为大部分的操做都在内存中进行,所以性能将会有很大的提高。
组件的构建方式有3种:React.createClass()、ES6的类和函数。当用ES6的类来构建时,全部的组件都继承自抽象基础类React.Component,该抽象类声明了state、props、defaultProps和displayName等属性,定义了render()、setState()和forceUpdate()等方法。注意,在组件的构造函数constructor()中要调用super()函数,用于初始化this和执行抽象类的构造函数。
import React, { Component } from 'react'; class Search extends Component { constructor (props) { super(props); } render() { return (); } }
组件中的render()方法是必须的,它会返回一个元素、数字或字符串等各类值。render()是一个纯函数,即输出(返回值)只依赖输入(参数),而且执行过程当中没有反作用(不改变外部状态)。
组件之间能够相互嵌套,而它们的数据流是自顶向下流动的(以下图所示),即父组件将数据传给子组件。此处传递的数据就是组件的配置参数,由props属性控制,而组件的内部状态保存在state属性中。
1)props
若是一个组件要作到可复用,那么它应该是可配置的。为此,React提供了props属性,它的使用以下所示。
class Search extends Component { render() { return ( <div> <button className="btn" data-url={this.props.url}>{"<搜索>"}</button> </div> ); } } <Search url="http://www.pwstrick.com" />
先给Search组件定义一个名为url的属性,而后在组件内部,能够经过引用props属性来获取url的值。有一点要注意,props是只读属性,所以在组件内部没法修改它。
React为组件提供了默认的配置,能够调用它的静态属性defaultProps。总共有两种写法实现默认配置,以下代码所示,其中写法一用到了ES6中的static关键字。
//写法一 class Search extends Component { static defaultProps = { url: "http://jane.com" }; } //写法二 Search.defaultProps = { url: "http://jane.com" }; <Search />
此时,即便组件不定义url属性,在组件内部仍是会有值。
props还有一个特殊的属性:children,它的值是组件内的子元素,以下代码所示,children属性的值为“<h1>React扫盲教程</h1>”。
class Search extends Component { render() { return ( <div> {this.props.children} </div> ); } } <Search> <h1>React扫盲教程</h1> </Search>
2)state
组件的呈现会随着内部状态和外部配置而改变,一般会在组件的构造函数中初始化须要的内部状态,以下代码所示,为文本框添加默认提示。
class Search extends Component { constructor (props) { super(props); this.state = { txt: "请输入关键字" }; } }
React还提供了setState()方法,用于更新组件的状态。注意,不要经过直接为state赋值的方式来更新状态,由于setState()方法在更新状态后,还会调用render()方法,从新渲染组件。此外,React为了提高性能,会把屡次setState()调用合并成一次,像下面这样写打印出的txt属性的值仍然是前一次的值,所以状态更新是异步的。
this.setState({ txt: "React" }); console.log(this.state.txt); //"请输入关键字"
3)生命周期
组件的生命周期(life cycle)可简单的分为4个阶段:初始化(Initialization)、挂载(Mounting)、更新(Updation)和卸载(Unmounting),具体以下图所示。每一个阶段都会对应几个方法,其中包含will的方法会在某个方法以前被调用,而包含did的方法会在某个方法以后被调用。
一、在初始化阶段,会设置props、state等属性。
二、在挂载阶段,两个挂载方法将以组件的render()为分界点。
三、更新阶段发生在传递props或执行setState()的时候。
四、当一个组件被移除时,就会调用componentWillUnmount()方法。
当组件在页面中输出时,在控制台将依次输出“will mount”、“render”和“did mount”。
class Search extends Component { componentWillMount() { console.log("will mount"); } componentDidMount() { console.log("did mount"); } render() { console.log("render"); } }
1)ReactDOM
若是要把组件添加到真实的DOM中,那么就须要使用ReactDOM中的render()方法,以下代码所示,其实在前面已经调用过几回这个方法了。
ReactDOM.render( <Search />, document.getElementById("container") );
此方法可接收三个参数,第一个是要渲染(即添加)的元素,第二个是容器元素(即添加的位置),第三个是可选的回调函数,会在渲染或更新以后执行。
ReactDOM还提供了另外两个方法:unmountComponentAtNode()和findDOMNode(),具体可参考官方文档。
2)事件
React实现了一种合成事件(SyntheticEvent),合成事件只有冒泡传播,而且它的注册方式、事件对象和事件处理程序中的this对象都与原生事件不一样。
一、合成事件会经过设置元素的属性来注册事件,但与原生事件不一样的是,属性的命名要用驼峰的写法而不是所有小写,而且属性值能够是任意类型而再也不仅是字符串,以下代码所示。React已经封装好了一系列的事件类型(原生事件类型的一个子集),而且已经处理好它们的兼容性,提供的事件类型能够参考官网。
class Search extends Component { handle(e) { console.log("click"); } render() { return ( <div> <button onClick={this.handle}>搜索</button> </div> ); } }
二、合成事件中的事件对象(event object)是一个基于W3C标准的SyntheticEvent对象的实例,它不但与原生的事件对象拥有相同的属性和方法(例如cancelable、preventDefault()、stopPropagation()等),还完美解决了兼容性问题。
三、React的事件处理程序中的this对象默认是undefined,由于注册的事件都是以普通函数的方式调用的。若是要让this指向当前组件,那么能够用bind()方法或ES6的箭头函数。
class Search extends Component { //bind()方法 handle1(e) { console.log(this); } //箭头函数 handle2 = (e) => { console.log(this); }; render() { return ( <div> <button onClick={this.handle1.bind(this)}>搜索</button> <button onClick={this.handle2}>搜索</button> </div> ); } }
四、在向事件处理程序传递参数时,要把事件对象放在最后,以下代码所示。
class Search extends Component { handle(keyword, e) { console.log(keyword); console.log(this); } render() { return ( <div> <button onClick={this.handle1.bind(this, "REACT")}>搜索</button> </div> ); } }
五、若是要为组件中某个元素注册原生事件,那么能够利用元素的ref属性和组件的refs对象实现。例如实现一个文本框在失去焦点时,打印输出它的值,以下代码所示。注意,原生事件的注册要在componentDidMount()方法内执行。
class Search extends Component { componentDidMount() { this.refs.txt.addEventListener("blur", (e) => { this.getValue(e); }); } getValue(e) { console.log(e.target.value); } render() { return ( <div> <input placeholder={this.state.txt} ref="txt" /> </div> ); } }
在上面的代码中,ref属性的值被设为了“txt”,此时,在refs对象中就会出现一个名为“txt”的属性,关于这个它们的具体用法能够参考官网。
3)表单
HTML中的表单元素(例如<input>、<select>和<radio>等)在React都有相应的组件实现,React还把表单中的组件分为受控和非受控。
受控组件(controlled component)的状态由React组件控制,它的每一个状态的改变都会有一个与之对应的事件处理程序,而且在程序内部会调用setState()方法更新状态。React推荐使用受控组件,下面是一个受控组件,注意,选择框(<select>元素)中的value属性表示选中项。
class Search extends Component { refresh(e) { this.setState({ type: e.target.value }); } render() { let {type} = this.state; return ( <div> <select value={type} onChange={this.refresh.bind(this)}> <option value="1">标题</option> <option value="2">内容</option> </select> </div> ); } }
非受控组件(uncontrolled component)的状态不受React组件控制,也不用为每一个状态编写对应的事件处理程序,但能够经过元素的ref属性获取它的值,非受控组件的写法更像是传统的DOM操做。在使用非受控组件时,若是要为其设置默认值,可使用属性defaultValue或defaultChecked,具体以下所示。
class Search extends Component { handle(e) { console.log(this.select.value); } render() { return ( <div> <select defaultValue={2} ref={(select) => this.select = select}> <option value="1">标题</option> <option value="2">内容</option> </select> <button onClick={this.handle.bind(this)}>搜索</button> </div> ); } }
4)样式
在React问世的初期,因为它推崇组件模式,所以会要求HTML、CSS和JavaScript混合在一块儿,这与过去的关注点分离正好相反。React已将HTML用JSX封装,而对CSS的封装,则抛出了CSS in JS的解决方案,即用JavaScript写CSS。
在React中的元素都包含className和style属性,前者可设置CSS类,后者可定义内联样式。style的属性值是一个对象,其属性就是CSS属性,但属性名要用驼峰的方式命名,例如margin-left改为marginLeft,具体以下所示。
class Search extends Component { render() { return ( <div> <input style={{marginLeft:10, textAlign:"center"}}/> </div> ); } }
注意,属性名不会自动补全浏览器前缀,而且React会自动给须要单位的数字加上px。在MDN上给出了CSS属性用JavaScript命名的对应关系,可在此处参考。
因为React处理CSS的功能并不强大,所以市面上出现了不少与CSS in JS相关第三方类库,例如classnames、polished.js等,有外国网友还专门搜集了40多种相关的类库。
虽然这种方式能让组件更方便的模块化,但同时也完全抛弃了CSS,既不能使用CSS的特性(例如选择器、媒体查询等),也没法再用CSS预处理器(例如SASS、LESS等)。为了解决上述问题,又有人提出了CSS Modules。
若是要在React中制做动画,官方推荐使用React Transition Group和React Motion。不过,你也可使用普通的动画库(例如animejs),只要在DOM渲染好之后调用便可。
1)跨级通讯
React数据流动是单向的,组件之间通讯最多见的方式是父组件经过props向子组件传递信息,但这种方式只能逐级传递,若是要跨级通讯(即父组件与孙子组件通讯),那么能够利用状态提高实现,但这样的话,代码会显得很不优雅而且很臃肿。好在React包含一个Context特性,能够知足刚刚的需求,不过官方不建议大量使用该特性,由于它不但会增长组件之间的耦合性,还会让应用变得混乱不堪,下图演示了两种数据传递的过程。在理解Context特性后,能更合理的使用状态管理容器Redux。
当一个组件设置了Context后,它的子组件就能直接访问Context中的内容,Context至关于一个全局变量,但做用域仅限于它的子组件中。总共有两种Context的实现方式,都基于生产者消费者模式。首先来看第一种,具体代码以下所示。
import PropTypes from 'prop-types'; class Grandpa extends Component { getChildContext() { return { name: "strick" }; } render() { return (<Son />); } } Grandpa.childContextTypes = { name: PropTypes.string }; class Son extends Component { render() { return (<Grandson />); } } class Grandson extends Component { render() { let { name } = this.context; return (<div>爷爷叫{name}</div>); } } Grandson.contextTypes = { name: PropTypes.string };
在上面的代码中,建立了三个组件,Grandpa是最上层的父组件(生产者),Son是中间的子组件,Grandson是最下层的孙子组件(消费者)。首先在Grandpa中,声明了一个静态属性childContextTypes和一个getChildContext()方法,这两个是必须的,不然没法实现数据传递。其中childContextTypes是一个对象,它的属性名就是要传递的变量名,而属性值则经过PropTypes指明了该变量的数据类型,getChildContext()方法返回的对象就是要传递的一组变量和它们的值。而后在Son中渲染Grandson组件。最后为Grandson声明一个静态属性contextTypes,一样是个对象,而且属性名和属性值与childContextTypes中的相同。
第二种方式是在React 16.3的版本中引入的,比起第一种方式,写法更加简洁,而且Context的生产者和消费者都以组件的方式实现,以下所示。
let NameContext = React.createContext({ name }); class Grandpa extends Component { render() { return ( <NameContext.Provider value={{name: "strick"}}> <Son /> </NameContext.Provider> ); } } class Son extends Component { render() { return (<Grandson />); } } class Grandson extends Component { render() { return ( <NameContext.Consumer> {(context) => ( <div>爷爷叫{context.name}</div> )} </NameContext.Consumer> ); } }
上面的代码依然建立了三个组件,名字也和第一种方式中的相同。除了中间组件Son以外,另外两个组件的内容发生了变化。首先,经过React.createContext()方法建立一个Context对象,此对象包含两个组件:Provider和Consumer,前者是生产者,后者是消费者。而后在Grandpa的render()方法中设置Provider组件的value属性,此属性至关于getChildContext()方法。最后在Grandson组件中调用Context对象,注意,Consumer组件的子节点只能是一个函数。
2)高阶组件
高阶组件(higher-order component,简称HOC)不是一个真的组件,而是一个函数,它的参数中包含组件,其返回值是一个功能加强的新组件。高阶组件是一个没有反作用的纯函数,它遵循了装饰者模式的设计思想,不会修改传递进来的原组件,而是对其进行包装和拓展,不只加强了组件的复用性和灵活性,还保持了组件的易用性。下面演示了高阶组件是如何控制props和state的。
class Button extends Component { render() { return ( <div> <button>{ this.props.txt }</button> </div> ); } } //高阶组件 function HOC(Mine) { class Wrapped extends Component { constructor() { super(); this.state = { txt: "提交" }; } render() { return <Mine {...this.state} />; } } return Wrapped; } let Wrapped = HOC(Button);
高阶组件HOC()的函数体中建立了一个名为Wrapped的组件,在它的构造函数中初始化了state状态。而后在其render()方法中使用了{...this.state},这是JSX的一种语法,在state对象前添加扩展运算符,可把它解构成组件的一组属性。最后在Button组件中调用传递进来的属性。
高阶组件还有迁移重复代码、劫持render()方法和引用refs等功能。
就先整理这些了,若有错误,欢迎指正,后面还会陆续加入漏掉的知识点。
最后,我想说下,其实本身也是一个React初学者,经过这样的梳理后,对React有了更为深入的理解,在后续的学习中能容易的吸取新的知识点。
源码下载:
https://github.com/pwstrick/react
参考资料: